Skip to content

Commit f1220a2

Browse files
committed
Merge #255: Add TapCtx
96cb2ec Add comment to decoding x-only keys (sanket1729) 498bad2 Address review feedback (sanket1729) 954df35 Check stack execution height in tapscript execution (sanket1729) 95ab99d Add height check to miniscript types (sanket1729) 3eb63d5 Add parse/parse_insane for schnorr keys (sanket1729) 888cd62 Add support for parsing Tapscript Miniscripts (sanket1729) 9a5384d Add TapCtx (sanket1729) 4da439f Fix warnings (sanket1729) Pull request description: This is still a draft WIP for taproot PR. There are several API changes and multiple design decisions, and I am open to other possible design opinions. In particular, feedback about changes to `ToPublicKey` and the `ScriptContext` trait is appreciated. This does **not** include support for P2TR descriptors, multi_a fragment in miniscript. Still todo: - [ ] Track height during execution to make sure we don't exceed 1000 elements. This was not a concern previously because it was impossible to hit the limit without exceeding 201 opcodes. But with taproot, this is now possible. See #198 . - [ ] Implement support for multi_a fragment in Taproot. Depends on support for new opcodes from rust-bitcoin. We can use NOPs meanwhile as this is not a hard blocker. - [ ] Implement taproot descriptor. This would require some design considerations about the tree representation. - [ ] Integrate taproot with interpreter and compiler. The interpreter is probably straightforward but carrying around `bitcoin:::PublicKey` and `schnorr::Publickey` would be tricky. - [ ] Integrate with descriptorPublicKey so that descriptor wallets like bdk can actually use it. ACKs for top commit: apoelstra: ACK 96cb2ec Tree-SHA512: 261173c3a6f8c980d204fce1f868dcd17c4219fd12a8e6e197b503efb47ae9b9371d7a9208c43008f1c21db55ffc3f68e6d7fb8badbf2bf397b2970292f8ffe4
2 parents 8ad083a + 96cb2ec commit f1220a2

File tree

21 files changed

+731
-114
lines changed

21 files changed

+731
-114
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Taproot updates
2+
- Changed the ToPublicKey trait to support x-only keys.
13
# 6.0.1 - Aug 5, 2021
24

35
- The `lift` method on a Miniscript node was fixed. It would previously mix up

fuzz/fuzz_targets/roundtrip_miniscript_script.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ fn do_test(data: &[u8]) {
88
// Try round-tripping as a script
99
let script = script::Script::from(data.to_owned());
1010

11-
if let Ok(pt) = Miniscript::<_, Segwitv0>::parse(&script) {
11+
if let Ok(pt) = Miniscript::<miniscript::bitcoin::PublicKey, Segwitv0>::parse(&script) {
1212
let output = pt.encode();
1313
assert_eq!(pt.script_size(), output.len());
1414
assert_eq!(output, script);

src/descriptor/bare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ impl<Pk: MiniscriptKey> DescriptorTrait<Pk> for Pkh<Pk> {
339339
}
340340

341341
fn max_satisfaction_weight(&self) -> Result<usize, Error> {
342-
Ok(4 * (1 + 73 + self.pk.serialized_len()))
342+
Ok(4 * (1 + 73 + BareCtx::pk_len(&self.pk)))
343343
}
344344

345345
fn script_code(&self) -> Script

src/descriptor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ mod tests {
921921
struct SimpleSat {
922922
sig: secp256k1::Signature,
923923
pk: bitcoin::PublicKey,
924-
};
924+
}
925925

926926
impl Satisfier<bitcoin::PublicKey> for SimpleSat {
927927
fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {

src/descriptor/segwitv0.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,9 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
293293
pub fn new(pk: Pk) -> Result<Self, Error> {
294294
// do the top-level checks
295295
if pk.is_uncompressed() {
296-
Err(Error::ContextError(ScriptContextError::CompressedOnly))
296+
Err(Error::ContextError(ScriptContextError::CompressedOnly(
297+
pk.to_string(),
298+
)))
297299
} else {
298300
Ok(Self { pk: pk })
299301
}
@@ -376,7 +378,9 @@ where
376378
impl<Pk: MiniscriptKey> DescriptorTrait<Pk> for Wpkh<Pk> {
377379
fn sanity_check(&self) -> Result<(), Error> {
378380
if self.pk.is_uncompressed() {
379-
Err(Error::ContextError(ScriptContextError::CompressedOnly))
381+
Err(Error::ContextError(ScriptContextError::CompressedOnly(
382+
self.pk.to_string(),
383+
)))
380384
} else {
381385
Ok(())
382386
}
@@ -430,7 +434,7 @@ impl<Pk: MiniscriptKey> DescriptorTrait<Pk> for Wpkh<Pk> {
430434
}
431435

432436
fn max_satisfaction_weight(&self) -> Result<usize, Error> {
433-
Ok(4 + 1 + 73 + self.pk.serialized_len())
437+
Ok(4 + 1 + 73 + Segwitv0::pk_len(&self.pk))
434438
}
435439

436440
fn script_code(&self) -> Script

src/descriptor/sortedmulti.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
178178
script_num_size(self.k)
179179
+ 1
180180
+ script_num_size(self.pks.len())
181-
+ self.pks.iter().map(|pk| pk.serialized_len()).sum::<usize>()
181+
+ self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
182182
}
183183

184184
/// Maximum number of witness elements used to satisfy the Miniscript

src/interpreter/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl From<::Error> for Error {
103103
}
104104

105105
impl error::Error for Error {
106-
fn cause(&self) -> Option<&error::Error> {
106+
fn cause(&self) -> Option<&dyn error::Error> {
107107
match *self {
108108
Error::Secp(ref err) => Some(err),
109109
ref x => Some(x),

src/interpreter/inner.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ fn script_from_stackelem<'a>(
5050
) -> Result<Miniscript<bitcoin::PublicKey, NoChecks>, Error> {
5151
match *elem {
5252
stack::Element::Push(sl) => {
53-
Miniscript::parse_insane(&bitcoin::Script::from(sl.to_owned())).map_err(Error::from)
53+
Miniscript::<bitcoin::PublicKey, _>::parse_insane(&bitcoin::Script::from(sl.to_owned()))
54+
.map_err(Error::from)
5455
}
5556
stack::Element::Satisfied => Miniscript::from_ast(::Terminal::True).map_err(Error::from),
5657
stack::Element::Dissatisfied => {
@@ -270,7 +271,7 @@ pub fn from_txdata<'txin>(
270271
// ** bare script **
271272
} else {
272273
if wit_stack.is_empty() {
273-
let miniscript = Miniscript::parse_insane(spk)?;
274+
let miniscript = Miniscript::<bitcoin::PublicKey, _>::parse_insane(spk)?;
274275
Ok((
275276
Inner::Script(miniscript, ScriptType::Bare),
276277
ssig_stack,

src/interpreter/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ mod tests {
847847
height: 1002,
848848
has_errored: false,
849849
}
850-
};
850+
}
851851

852852
let pk = ms_str!("c:pk_k({})", pks[0]);
853853
let pkh = ms_str!("c:pk_h({})", pks[1].to_pubkeyhash());

src/lib.rs

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
//! ```
8888
//!
8989
//!
90-
#![allow(bare_trait_objects)]
9190
#![cfg_attr(all(test, feature = "unstable"), feature(test))]
9291
// Coding conventions
9392
#![deny(unsafe_code)]
@@ -125,7 +124,7 @@ use bitcoin::hashes::{hash160, sha256, Hash};
125124

126125
pub use descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
127126
pub use interpreter::Interpreter;
128-
pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0};
127+
pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, Tap};
129128
pub use miniscript::decode::Terminal;
130129
pub use miniscript::satisfy::{BitcoinSig, Preimage32, Satisfier};
131130
pub use miniscript::Miniscript;
@@ -142,16 +141,6 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha
142141

143142
/// Converts an object to PublicHash
144143
fn to_pubkeyhash(&self) -> Self::Hash;
145-
146-
/// Computes the size of a public key when serialized in a script,
147-
/// including the length bytes
148-
fn serialized_len(&self) -> usize {
149-
if self.is_uncompressed() {
150-
66
151-
} else {
152-
34
153-
}
154-
}
155144
}
156145

157146
impl MiniscriptKey for bitcoin::PublicKey {
@@ -170,6 +159,14 @@ impl MiniscriptKey for bitcoin::PublicKey {
170159
}
171160
}
172161

162+
impl MiniscriptKey for bitcoin::schnorr::PublicKey {
163+
type Hash = hash160::Hash;
164+
165+
fn to_pubkeyhash(&self) -> Self::Hash {
166+
hash160::Hash::hash(&self.serialize())
167+
}
168+
}
169+
173170
impl MiniscriptKey for String {
174171
type Hash = String;
175172

@@ -183,6 +180,12 @@ pub trait ToPublicKey: MiniscriptKey {
183180
/// Converts an object to a public key
184181
fn to_public_key(&self) -> bitcoin::PublicKey;
185182

183+
/// Convert an object to x-only pubkey
184+
fn to_x_only_pubkey(&self) -> bitcoin::schnorr::PublicKey {
185+
let pk = self.to_public_key();
186+
bitcoin::schnorr::PublicKey::from(pk.key)
187+
}
188+
186189
/// Converts a hashed version of the public key to a `hash160` hash.
187190
///
188191
/// This method must be consistent with `to_public_key`, in the sense
@@ -202,6 +205,25 @@ impl ToPublicKey for bitcoin::PublicKey {
202205
}
203206
}
204207

208+
impl ToPublicKey for bitcoin::schnorr::PublicKey {
209+
fn to_public_key(&self) -> bitcoin::PublicKey {
210+
// This code should never be used.
211+
// But is implemented for completeness
212+
let mut data: Vec<u8> = vec![0x02];
213+
data.extend(self.serialize().iter());
214+
bitcoin::PublicKey::from_slice(&data)
215+
.expect("Failed to construct 33 Publickey from 0x02 appended x-only key")
216+
}
217+
218+
fn to_x_only_pubkey(&self) -> bitcoin::schnorr::PublicKey {
219+
*self
220+
}
221+
222+
fn hash_to_hash160(hash: &hash160::Hash) -> hash160::Hash {
223+
*hash
224+
}
225+
}
226+
205227
/// Dummy key which de/serializes to the empty string; useful sometimes for testing
206228
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
207229
pub struct DummyKey;
@@ -448,7 +470,7 @@ pub enum Error {
448470
InvalidOpcode(opcodes::All),
449471
/// Some opcode occurred followed by `OP_VERIFY` when it had
450472
/// a `VERIFY` version that should have been used instead
451-
NonMinimalVerify(miniscript::lex::Token),
473+
NonMinimalVerify(String),
452474
/// Push was illegal in some context
453475
InvalidPush(Vec<u8>),
454476
/// rust-bitcoin script error
@@ -517,6 +539,8 @@ pub enum Error {
517539
ImpossibleSatisfaction,
518540
/// Bare descriptors don't have any addresses
519541
BareDescriptorAddr,
542+
/// PubKey invalid under current context
543+
PubKeyCtxError(miniscript::decode::KeyParseError, &'static str),
520544
}
521545

522546
#[doc(hidden)]
@@ -563,7 +587,7 @@ fn errstr(s: &str) -> Error {
563587
}
564588

565589
impl error::Error for Error {
566-
fn cause(&self) -> Option<&error::Error> {
590+
fn cause(&self) -> Option<&dyn error::Error> {
567591
match *self {
568592
Error::BadPubkey(ref e) => Some(e),
569593
_ => None,
@@ -580,7 +604,7 @@ impl fmt::Display for Error {
580604
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
581605
match *self {
582606
Error::InvalidOpcode(op) => write!(f, "invalid opcode {}", op),
583-
Error::NonMinimalVerify(tok) => write!(f, "{} VERIFY", tok),
607+
Error::NonMinimalVerify(ref tok) => write!(f, "{} VERIFY", tok),
584608
Error::InvalidPush(ref push) => write!(f, "invalid push {:?}", push), // TODO hexify this
585609
Error::Script(ref e) => fmt::Display::fmt(e, f),
586610
Error::CmsTooManyKeys(n) => write!(f, "checkmultisig with {} keys", n),
@@ -634,6 +658,9 @@ impl fmt::Display for Error {
634658
Error::AnalysisError(ref e) => e.fmt(f),
635659
Error::ImpossibleSatisfaction => write!(f, "Impossible to satisfy Miniscript"),
636660
Error::BareDescriptorAddr => write!(f, "Bare descriptors don't have address"),
661+
Error::PubKeyCtxError(ref pk, ref ctx) => {
662+
write!(f, "Pubkey error: {} under {} scriptcontext", pk, ctx)
663+
}
637664
}
638665
}
639666
}

src/miniscript/astelem.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use expression;
3232
use miniscript::types::{self, Property};
3333
use miniscript::ScriptContext;
3434
use script_num_size;
35+
36+
use util::MsKeyBuilder;
3537
use {Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey, TranslatePk};
3638

3739
impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
@@ -628,7 +630,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
628630
Pk: ToPublicKey,
629631
{
630632
match *self {
631-
Terminal::PkK(ref pk) => builder.push_key(&pk.to_public_key()),
633+
Terminal::PkK(ref pk) => builder.push_ms_key::<_, Ctx>(pk),
632634
Terminal::PkH(ref hash) => builder
633635
.push_opcode(opcodes::all::OP_DUP)
634636
.push_opcode(opcodes::all::OP_HASH160)
@@ -734,6 +736,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
734736
.push_opcode(opcodes::all::OP_EQUAL)
735737
}
736738
Terminal::Multi(k, ref keys) => {
739+
debug_assert!(!Ctx::is_tap());
737740
builder = builder.push_int(k as i64);
738741
for pk in keys {
739742
builder = builder.push_key(&pk.to_public_key());
@@ -754,7 +757,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
754757
/// will handle the segwit/non-segwit technicalities for you.
755758
pub fn script_size(&self) -> usize {
756759
match *self {
757-
Terminal::PkK(ref pk) => pk.serialized_len(),
760+
Terminal::PkK(ref pk) => Ctx::pk_len(pk),
758761
Terminal::PkH(..) => 24,
759762
Terminal::After(n) => script_num_size(n as usize) + 1,
760763
Terminal::Older(n) => script_num_size(n as usize) + 1,
@@ -794,7 +797,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
794797
script_num_size(k)
795798
+ 1
796799
+ script_num_size(pks.len())
797-
+ pks.iter().map(|pk| pk.serialized_len()).sum::<usize>()
800+
+ pks.iter().map(|pk| Ctx::pk_len(pk)).sum::<usize>()
798801
}
799802
}
800803
}

0 commit comments

Comments
 (0)