From 89e7cb30ae555da3184f7cade065a21696145ae2 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Fri, 10 Sep 2021 12:58:41 -0700 Subject: [PATCH 1/7] add multi_a Iter keys in `MultiA` Fix witness generation for `MultiA` --- src/miniscript/astelem.rs | 50 +++++++++++++++++++++++++++-- src/miniscript/context.rs | 15 ++++++++- src/miniscript/decode.rs | 21 ++++++++++++ src/miniscript/iter.rs | 18 +++++++---- src/miniscript/lex.rs | 13 ++++++++ src/miniscript/mod.rs | 32 ++++++++++++++++++ src/miniscript/satisfy.rs | 42 ++++++++++++++++++++++++ src/miniscript/types/extra_props.rs | 31 ++++++++++++++++-- src/miniscript/types/mod.rs | 22 ++++++++++--- src/policy/mod.rs | 2 +- 10 files changed, 229 insertions(+), 17 deletions(-) diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 019fdd36f..75aa5be0e 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -119,7 +119,9 @@ impl Terminal { && c.real_for_each_key(pred) } Terminal::Thresh(_, ref subs) => subs.iter().all(|sub| sub.real_for_each_key(pred)), - Terminal::Multi(_, ref keys) => keys.iter().all(|key| pred(ForEach::Key(key))), + Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => { + keys.iter().all(|key| pred(ForEach::Key(key))) + } } } pub(super) fn real_translate_pk( @@ -209,6 +211,10 @@ impl Terminal { let keys: Result, _> = keys.iter().map(&mut *translatefpk).collect(); Terminal::Multi(k, keys?) } + Terminal::MultiA(k, ref keys) => { + let keys: Result, _> = keys.iter().map(&mut *translatefpk).collect(); + Terminal::MultiA(k, keys?) + } }; Ok(frag) } @@ -312,6 +318,13 @@ impl fmt::Debug for Terminal { } f.write_str(")") } + Terminal::MultiA(k, ref keys) => { + write!(f, "multi_a({}", k)?; + for k in keys { + write!(f, ",{}", k)?; + } + f.write_str(")") + } _ => unreachable!(), } } @@ -368,6 +381,13 @@ impl fmt::Display for Terminal { } f.write_str(")") } + Terminal::MultiA(k, ref keys) => { + write!(f, "multi_a({}", k)?; + for k in keys { + write!(f, ",{}", k)?; + } + f.write_str(")") + } // wrappers _ => { if let Some((ch, sub)) = self.wrap_char() { @@ -540,7 +560,7 @@ where Ok(Terminal::Thresh(k, subs?)) } - ("multi", n) => { + ("multi", n) | ("multi_a", n) => { if n == 0 { return Err(errstr("no arguments given")); } @@ -554,7 +574,12 @@ where .map(|sub| expression::terminal(sub, Pk::from_str)) .collect(); - pks.map(|pks| Terminal::Multi(k, pks)) + if frag_name == "multi" { + pks.map(|pks| Terminal::Multi(k, pks)) + } else { + // must be multi_a + pks.map(|pks| Terminal::MultiA(k, pks)) + } } _ => Err(Error::Unexpected(format!( "{}({} args) while parsing Miniscript", @@ -745,6 +770,19 @@ impl Terminal { .push_int(keys.len() as i64) .push_opcode(opcodes::all::OP_CHECKMULTISIG) } + Terminal::MultiA(k, ref keys) => { + debug_assert!(Ctx::is_tap()); + // keys must be atleast len 1 here, guaranteed by typing rules + builder = builder.push_ms_key::<_, Ctx>(&keys[0]); + builder = builder.push_opcode(opcodes::all::OP_CHECKSIG); + for pk in keys.iter().skip(1) { + builder = builder.push_ms_key::<_, Ctx>(pk); + builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD); + } + builder + .push_int(k as i64) + .push_opcode(opcodes::all::OP_NUMEQUAL) + } } } @@ -799,6 +837,12 @@ impl Terminal { + script_num_size(pks.len()) + pks.iter().map(|pk| Ctx::pk_len(pk)).sum::() } + Terminal::MultiA(k, ref pks) => { + script_num_size(k) + + 1 // NUMEQUAL + + pks.iter().map(|pk| Ctx::pk_len(pk)).sum::() // n keys + + pks.len() // n times CHECKSIGADD + } } } } diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index 3f55dbc2b..0ea029793 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -70,6 +70,8 @@ pub enum ScriptContextError { StackSizeLimitExceeded { actual: usize, limit: usize }, /// More than 20 keys in a Multi fragment CheckMultiSigLimitExceeded, + /// MultiA is only allowed in post tapscript + MultiANotAllowed, } impl fmt::Display for ScriptContextError { @@ -141,6 +143,9 @@ impl fmt::Display for ScriptContextError { "CHECkMULTISIG ('multi()' descriptor) only supports up to 20 pubkeys" ) } + ScriptContextError::MultiANotAllowed => { + write!(f, "Multi a(CHECKSIGADD) only allowed post tapscript") + } } } } @@ -339,9 +344,11 @@ impl ScriptContext for Legacy { return Err(ScriptContextError::CheckMultiSigLimitExceeded); } } + Terminal::MultiA(..) => { + return Err(ScriptContextError::MultiANotAllowed); + } _ => {} } - Ok(()) } @@ -437,6 +444,9 @@ impl ScriptContext for Segwitv0 { } Ok(()) } + Terminal::MultiA(..) => { + return Err(ScriptContextError::MultiANotAllowed); + } _ => Ok(()), } } @@ -628,6 +638,9 @@ impl ScriptContext for BareCtx { if ms.ext.pk_cost > MAX_SCRIPT_SIZE { return Err(ScriptContextError::MaxWitnessScriptSizeExceeded); } + if let Terminal::MultiA(..) = ms.node { + return Err(ScriptContextError::MultiANotAllowed); + } Ok(()) } diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 260dab395..1182f9eea 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -187,6 +187,8 @@ pub enum Terminal { Thresh(usize, Vec>>), /// k ()* n CHECKMULTISIG Multi(usize, Vec), + /// CHECKSIG ( CHECKSIGADD)*(n-1) k NUMEQUAL + MultiA(usize, Vec), } macro_rules! match_token { @@ -487,6 +489,25 @@ pub fn parse( keys.reverse(); term.reduce0(Terminal::Multi(k as usize, keys))?; }, + // MultiA + Tk::NumEqual, Tk::Num(k) => { + let mut keys = Vec::with_capacity(k as usize); // atleast k capacity + while tokens.peek() == Some(&Tk::CheckSigAdd) { + match_token!( + tokens, + Tk::CheckSigAdd, Tk::Bytes32(pk) => keys.push(::from_slice(pk) + .map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?), + ); + } + // Last key must be with a CheckSig + match_token!( + tokens, + Tk::CheckSig, Tk::Bytes32(pk) => keys.push(::from_slice(pk) + .map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?), + ); + keys.reverse(); + term.reduce0(Terminal::MultiA(k as usize, keys))?; + }, ); } Some(NonTerm::MaybeAndV) => { diff --git a/src/miniscript/iter.rs b/src/miniscript/iter.rs index 36c4b69ef..b8675aa17 100644 --- a/src/miniscript/iter.rs +++ b/src/miniscript/iter.rs @@ -121,7 +121,7 @@ impl Miniscript { pub fn get_leaf_pk(&self) -> Vec { match self.node { Terminal::PkK(ref key) => vec![key.clone()], - Terminal::Multi(_, ref keys) => keys.clone(), + Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys.clone(), _ => vec![], } } @@ -139,7 +139,9 @@ impl Miniscript { match self.node { Terminal::PkH(ref hash) => vec![hash.clone()], Terminal::PkK(ref key) => vec![key.to_pubkeyhash()], - Terminal::Multi(_, ref keys) => keys.iter().map(Pk::to_pubkeyhash).collect(), + Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => { + keys.iter().map(Pk::to_pubkeyhash).collect() + } _ => vec![], } } @@ -155,7 +157,7 @@ impl Miniscript { match self.node { Terminal::PkH(ref hash) => vec![PkPkh::HashedPubkey(hash.clone())], Terminal::PkK(ref key) => vec![PkPkh::PlainPubkey(key.clone())], - Terminal::Multi(_, ref keys) => keys + Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys .into_iter() .map(|key| PkPkh::PlainPubkey(key.clone())) .collect(), @@ -170,7 +172,9 @@ impl Miniscript { pub fn get_nth_pk(&self, n: usize) -> Option { match (&self.node, n) { (&Terminal::PkK(ref key), 0) => Some(key.clone()), - (&Terminal::Multi(_, ref keys), _) => keys.get(n).cloned(), + (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { + keys.get(n).cloned() + } _ => None, } } @@ -186,7 +190,9 @@ impl Miniscript { match (&self.node, n) { (&Terminal::PkH(ref hash), 0) => Some(hash.clone()), (&Terminal::PkK(ref key), 0) => Some(key.to_pubkeyhash()), - (&Terminal::Multi(_, ref keys), _) => keys.get(n).map(Pk::to_pubkeyhash), + (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { + keys.get(n).map(Pk::to_pubkeyhash) + } _ => None, } } @@ -199,7 +205,7 @@ impl Miniscript { match (&self.node, n) { (&Terminal::PkH(ref hash), 0) => Some(PkPkh::HashedPubkey(hash.clone())), (&Terminal::PkK(ref key), 0) => Some(PkPkh::PlainPubkey(key.clone())), - (&Terminal::Multi(_, ref keys), _) => { + (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { keys.get(n).map(|key| PkPkh::PlainPubkey(key.clone())) } _ => None, diff --git a/src/miniscript/lex.rs b/src/miniscript/lex.rs index 7341125ae..8927f9844 100644 --- a/src/miniscript/lex.rs +++ b/src/miniscript/lex.rs @@ -31,7 +31,9 @@ pub enum Token<'s> { BoolOr, Add, Equal, + NumEqual, CheckSig, + CheckSigAdd, CheckMultiSig, CheckSequenceVerify, CheckLockTimeVerify, @@ -129,6 +131,13 @@ pub fn lex<'s>(script: &'s script::Script) -> Result>, Error> { ret.push(Token::Equal); ret.push(Token::Verify); } + script::Instruction::Op(opcodes::all::OP_NUMEQUAL) => { + ret.push(Token::NumEqual); + } + script::Instruction::Op(opcodes::all::OP_NUMEQUALVERIFY) => { + ret.push(Token::NumEqual); + ret.push(Token::Verify); + } script::Instruction::Op(opcodes::all::OP_CHECKSIG) => { ret.push(Token::CheckSig); } @@ -136,6 +145,10 @@ pub fn lex<'s>(script: &'s script::Script) -> Result>, Error> { ret.push(Token::CheckSig); ret.push(Token::Verify); } + // Change once the opcode name is updated + script::Instruction::Op(opcodes::all::OP_CHECKSIGADD) => { + ret.push(Token::CheckSigAdd); + } script::Instruction::Op(opcodes::all::OP_CHECKMULTISIG) => { ret.push(Token::CheckMultiSig); } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 538703683..08a032075 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -448,6 +448,7 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext); #[cfg(test)] mod tests { + use super::{Miniscript, ScriptContext}; use super::{Segwitv0, Tap}; use hex_script; @@ -458,10 +459,12 @@ mod tests { use {DummyKey, DummyKeyHash, MiniscriptKey, TranslatePk, TranslatePk1}; use bitcoin::hashes::{hash160, sha256, Hash}; + use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::{self, secp256k1}; use std::str; use std::str::FromStr; use std::sync::Arc; + use TranslatePk2; type Segwitv0Script = Miniscript; type Tapscript = Miniscript; @@ -1000,4 +1003,33 @@ mod tests { )) .unwrap(); } + + #[test] + fn multi_a_tests() { + // Test from string tests + type Segwitv0Ms = Miniscript; + type TapMs = Miniscript; + let segwit_multi_a_ms = Segwitv0Ms::from_str_insane("multi_a(1,A,B,C)"); + assert_eq!( + segwit_multi_a_ms.unwrap_err().to_string(), + "Multi a(CHECKSIGADD) only allowed post tapscript" + ); + let tap_multi_a_ms = TapMs::from_str_insane("multi_a(1,A,B,C)").unwrap(); + assert_eq!(tap_multi_a_ms.to_string(), "multi_a(1,A,B,C)"); + + // Test encode/decode and translation tests + let tap_ms = tap_multi_a_ms.translate_pk2_infallible(|_| { + XOnlyPublicKey::from_str( + "e948a0bbf8b15ee47cf0851afbce8835b5f06d3003b8e7ed6104e82a1d41d6f8", + ) + .unwrap() + }); + // script rtt test + assert_eq!( + Miniscript::::parse_insane(&tap_ms.encode()).unwrap(), + tap_ms + ); + assert_eq!(tap_ms.script_size(), 104); + assert_eq!(tap_ms.encode().len(), tap_ms.script_size()); + } } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 502082ebb..ef13461e9 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -947,6 +947,44 @@ impl Satisfaction { } } } + Terminal::MultiA(k, ref keys) => { + // Collect all available signatures + let mut sig_count = 0; + let mut sigs = vec![vec![vec![]]; keys.len()]; + for (i, pk) in keys.iter().rev().enumerate() { + match Witness::signature(stfr, pk) { + Witness::Stack(sig) => { + sigs[i] = sig; + sig_count += 1; + // This a privacy issue, we are only selecting the first available + // sigs. Incase pk at pos 1 is not selected, we know we did not have access to it + // bitcoin core also implements the same logic for MULTISIG, so I am not bothering + // permuting the sigs for now + if sig_count == k { + break; + } + } + Witness::Impossible => {} + Witness::Unavailable => unreachable!( + "Signature satisfaction without witness must be impossible" + ), + } + } + + if sig_count < k { + Satisfaction { + stack: Witness::Impossible, + has_sig: false, + } + } else { + Satisfaction { + stack: sigs.into_iter().fold(Witness::empty(), |acc, sig| { + Witness::combine(acc, Witness::Stack(sig)) + }), + has_sig: true, + } + } + } } } @@ -1064,6 +1102,10 @@ impl Satisfaction { stack: Witness::Stack(vec![vec![]; k + 1]), has_sig: false, }, + Terminal::MultiA(_, ref pks) => Satisfaction { + stack: Witness::Stack(vec![vec![]; pks.len()]), + has_sig: false, + }, } } diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 238a4e366..735c429f3 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -234,6 +234,29 @@ impl Property for ExtData { } } + fn from_multi_a(k: usize, n: usize) -> Self { + let num_cost = match (k > 16, n > 16) { + (true, true) => 4, + (false, true) => 3, + (true, false) => 3, + (false, false) => 2, + }; + ExtData { + pk_cost: num_cost + 33 * n /*pks*/ + (n-1) /*checksigadds*/ + 1, + has_free_verify: true, + ops_count_static: 1, // We don't care about opcounts in tapscript + ops_count_sat: Some(n + 1), + ops_count_nsat: Some(n + 1), + stack_elem_count_sat: Some(n), + stack_elem_count_dissat: Some(n), + max_sat_size: Some(((n - k) + 64 * k, (n - k) + 64 * k)), + max_dissat_size: Some((n, n)), + timelock_info: TimeLockInfo::default(), + exec_stack_elem_count_sat: Some(2), // the two nums before num equal verify + exec_stack_elem_count_dissat: Some(2), + } + } + fn from_hash() -> Self { //never called directly unreachable!() @@ -990,7 +1013,7 @@ impl Property for ExtData { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k()), Terminal::PkH(..) => Ok(Self::from_pk_h()), - Terminal::Multi(k, ref pks) => { + Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -1003,7 +1026,11 @@ impl Property for ExtData { error: ErrorKind::OverThreshold(k, pks.len()), }); } - Ok(Self::from_multi(k, pks.len())) + match *fragment { + Terminal::Multi(..) => Ok(Self::from_multi(k, pks.len())), + Terminal::MultiA(..) => Ok(Self::from_multi_a(k, pks.len())), + _ => unreachable!(), + } } Terminal::After(t) => { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index d3be67ba9..33b246481 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -269,6 +269,12 @@ pub trait Property: Sized { /// Type property of a `Multi` fragment fn from_multi(k: usize, n: usize) -> Self; + /// Type property of a `MultiA` fragment + fn from_multi_a(k: usize, n: usize) -> Self { + // default impl same as multi + Self::from_multi(k, n) + } + /// Type property of a hash fragment fn from_hash() -> Self; @@ -410,7 +416,7 @@ pub trait Property: Sized { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k()), Terminal::PkH(..) => Ok(Self::from_pk_h()), - Terminal::Multi(k, ref pks) => { + Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -423,7 +429,11 @@ pub trait Property: Sized { error: ErrorKind::OverThreshold(k, pks.len()), }); } - Ok(Self::from_multi(k, pks.len())) + match *fragment { + Terminal::Multi(..) => Ok(Self::from_multi(k, pks.len())), + Terminal::MultiA(..) => Ok(Self::from_multi_a(k, pks.len())), + _ => unreachable!(), + } } Terminal::After(t) => { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The @@ -789,7 +799,7 @@ impl Property for Type { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k()), Terminal::PkH(..) => Ok(Self::from_pk_h()), - Terminal::Multi(k, ref pks) => { + Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -802,7 +812,11 @@ impl Property for Type { error: ErrorKind::OverThreshold(k, pks.len()), }); } - Ok(Self::from_multi(k, pks.len())) + match *fragment { + Terminal::Multi(..) => Ok(Self::from_multi(k, pks.len())), + Terminal::MultiA(..) => Ok(Self::from_multi_a(k, pks.len())), + _ => unreachable!(), + } } Terminal::After(t) => { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The diff --git a/src/policy/mod.rs b/src/policy/mod.rs index b03d15666..ac36846de 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -161,7 +161,7 @@ impl Liftable for Terminal { subs.into_iter().map(|s| s.node.lift()).collect(); Semantic::Threshold(k, semantic_subs?) } - Terminal::Multi(k, ref keys) => Semantic::Threshold( + Terminal::Multi(k, ref keys) | Terminal::MultiA(k, ref keys) => Semantic::Threshold( k, keys.into_iter() .map(|k| Semantic::KeyHash(k.to_pubkeyhash())) From e26d4e6bb5ac118b00669e0f9fc1b5898a959f3f Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 11 Oct 2021 01:02:38 -0700 Subject: [PATCH 2/7] Use bitcoin::EcdsaSig from rust-bitcoin --- examples/sign_multisig.rs | 10 ++--- examples/verify_tx.rs | 10 ++--- src/descriptor/bare.rs | 5 +-- src/descriptor/mod.rs | 26 +++++++++--- src/descriptor/segwitv0.rs | 5 +-- src/interpreter/mod.rs | 57 +++++++++++++------------ src/interpreter/stack.rs | 8 ++-- src/lib.rs | 2 +- src/miniscript/satisfy.rs | 87 +++++++++++++++++--------------------- src/policy/compiler.rs | 17 ++++---- src/psbt/mod.rs | 32 +++++--------- 11 files changed, 128 insertions(+), 131 deletions(-) diff --git a/examples/sign_multisig.rs b/examples/sign_multisig.rs index 80f881d81..16cdd137c 100644 --- a/examples/sign_multisig.rs +++ b/examples/sign_multisig.rs @@ -61,10 +61,10 @@ fn main() { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]).expect("key 3"), ]; - let bitcoin_sig = ( + let bitcoin_sig = bitcoin::EcdsaSig { // copied at random off the blockchain; this is not actually a valid // signature for this transaction; Miniscript does not verify - secp256k1::ecdsa::Signature::from_str( + sig: secp256k1::ecdsa::Signature::from_str( "3045\ 0221\ 00f7c3648c390d87578cd79c8016940aa8e3511c4104cb78daa8fb8e429375efc1\ @@ -72,8 +72,8 @@ fn main() { 531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177", ) .unwrap(), - bitcoin::EcdsaSigHashType::All, - ); + hash_ty: bitcoin::EcdsaSigHashType::All, + }; let descriptor_str = format!( "wsh(multi(2,{},{},{}))", @@ -112,7 +112,7 @@ fn main() { // Attempt to satisfy at age 0, height 0 let original_txin = tx.input[0].clone(); - let mut sigs = HashMap::::new(); + let mut sigs = HashMap::::new(); // Doesn't work with no signatures assert!(my_descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); diff --git a/examples/verify_tx.rs b/examples/verify_tx.rs index b8fe41ea6..1ecb20c6c 100644 --- a/examples/verify_tx.rs +++ b/examples/verify_tx.rs @@ -138,8 +138,8 @@ fn main() { .expect("Can only fail in sighash single when corresponding output is not present"); // Restrict to sighash_all just to demonstrate how to add additional filters // `&_` needed here because of https://github.com/rust-lang/rust/issues/79187 - let vfyfn = move |pk: &_, bitcoinsig: miniscript::BitcoinSig| { - bitcoinsig.1 == bitcoin::EcdsaSigHashType::All && vfyfn(pk, bitcoinsig) + let vfyfn = move |pk: &_, bitcoinsig: miniscript::bitcoin::EcdsaSig| { + bitcoinsig.hash_ty == bitcoin::EcdsaSigHashType::All && vfyfn(pk, bitcoinsig) }; println!("\nExample two"); @@ -165,9 +165,9 @@ fn main() { ) .unwrap(); - let iter = interpreter.iter(|pk, (sig, sighashtype)| { - sighashtype == bitcoin::EcdsaSigHashType::All - && secp.verify_ecdsa(&message, &sig, &pk.key).is_ok() + let iter = interpreter.iter(|pk, ecdsa_sig| { + ecdsa_sig.hash_ty == bitcoin::EcdsaSigHashType::All + && secp.verify_ecdsa(&message, &ecdsa_sig.sig, &pk.key).is_ok() }); println!("\nExample three"); for elem in iter { diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index c3d0a1270..153fee15d 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -335,9 +335,8 @@ impl DescriptorTrait for Pkh { Pk: ToPublicKey, S: Satisfier, { - if let Some(sig) = satisfier.lookup_sig(&self.pk) { - let mut sig_vec = sig.0.serialize_der().to_vec(); - sig_vec.push(sig.1.as_u32() as u8); + if let Some(sig) = satisfier.lookup_ecdsa_sig(&self.pk) { + let sig_vec = sig.to_vec(); let script_sig = script::Builder::new() .push_slice(&sig_vec[..]) .push_key(&self.pk.to_public_key()) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 7fde5a0b4..eb74eef2b 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -655,13 +655,12 @@ mod tests { use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::{hash160, sha256}; use bitcoin::util::bip32; - use bitcoin::{self, secp256k1, PublicKey}; + use bitcoin::{self, secp256k1, EcdsaSigHashType, PublicKey}; use descriptor::key::Wildcard; use descriptor::{ DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePub, DescriptorXKey, }; use hex_script; - use miniscript::satisfy::BitcoinSig; use std::cmp; use std::collections::HashMap; use std::str::FromStr; @@ -950,9 +949,12 @@ mod tests { } impl Satisfier for SimpleSat { - fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option { + fn lookup_ecdsa_sig(&self, pk: &bitcoin::PublicKey) -> Option { if *pk == self.pk { - Some((self.sig, bitcoin::EcdsaSigHashType::All)) + Some(bitcoin::EcdsaSig { + sig: self.sig, + hash_ty: bitcoin::EcdsaSigHashType::All, + }) } else { None } @@ -1161,8 +1163,20 @@ mod tests { let satisfier = { let mut satisfier = HashMap::with_capacity(2); - satisfier.insert(a, (sig_a.clone(), ::bitcoin::EcdsaSigHashType::All)); - satisfier.insert(b, (sig_b.clone(), ::bitcoin::EcdsaSigHashType::All)); + satisfier.insert( + a, + bitcoin::EcdsaSig { + sig: sig_a, + hash_ty: EcdsaSigHashType::All, + }, + ); + satisfier.insert( + b, + bitcoin::EcdsaSig { + sig: sig_b, + hash_ty: EcdsaSigHashType::All, + }, + ); satisfier }; diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index 722b12e44..be656cda1 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -436,9 +436,8 @@ impl DescriptorTrait for Wpkh { Pk: ToPublicKey, S: Satisfier, { - if let Some(sig) = satisfier.lookup_sig(&self.pk) { - let mut sig_vec = sig.0.serialize_der().to_vec(); - sig_vec.push(sig.1.as_u32() as u8); + if let Some(sig) = satisfier.lookup_ecdsa_sig(&self.pk) { + let sig_vec = sig.to_vec(); let script_sig = Script::new(); let witness = vec![sig_vec, self.pk.to_public_key().to_bytes()]; Ok((witness, script_sig)) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 4e6dcc6e6..aab9f8f41 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -27,7 +27,7 @@ use miniscript::context::NoChecks; use miniscript::ScriptContext; use Miniscript; use Terminal; -use {BitcoinSig, Descriptor, ToPublicKey}; +use {Descriptor, ToPublicKey}; mod error; mod inner; @@ -82,7 +82,7 @@ impl<'txin> Interpreter<'txin> { /// /// Running the iterator through will consume the internal stack of the /// `Iterpreter`, and it should not be used again after this. - pub fn iter<'iter, F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool>( + pub fn iter<'iter, F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool>( &'iter mut self, verify_sig: F, ) -> Iter<'txin, 'iter, F> { @@ -190,7 +190,7 @@ impl<'txin> Interpreter<'txin> { unsigned_tx: &'a bitcoin::Transaction, input_idx: usize, amount: u64, - ) -> Result bool + 'a, Error> { + ) -> Result bool + 'a, Error> { // Precompute all sighash types because the borrowck doesn't like us // pulling self into the closure let sighashes = [ @@ -232,19 +232,21 @@ impl<'txin> Interpreter<'txin> { )?, ]; - Ok(move |pk: &bitcoin::PublicKey, (sig, sighash_type)| { - // This is an awkward way to do this lookup, but it lets us do exhaustiveness - // checking in case future rust-bitcoin versions add new sighash types - let sighash = match sighash_type { - bitcoin::EcdsaSigHashType::All => sighashes[0], - bitcoin::EcdsaSigHashType::None => sighashes[1], - bitcoin::EcdsaSigHashType::Single => sighashes[2], - bitcoin::EcdsaSigHashType::AllPlusAnyoneCanPay => sighashes[3], - bitcoin::EcdsaSigHashType::NonePlusAnyoneCanPay => sighashes[4], - bitcoin::EcdsaSigHashType::SinglePlusAnyoneCanPay => sighashes[5], - }; - secp.verify_ecdsa(&sighash, &sig, &pk.key).is_ok() - }) + Ok( + move |pk: &bitcoin::PublicKey, ecdsa_sig: bitcoin::EcdsaSig| { + // This is an awkward way to do this lookup, but it lets us do exhaustiveness + // checking in case future rust-bitcoin versions add new sighash types + let sighash = match ecdsa_sig.hash_ty { + bitcoin::EcdsaSigHashType::All => sighashes[0], + bitcoin::EcdsaSigHashType::None => sighashes[1], + bitcoin::EcdsaSigHashType::Single => sighashes[2], + bitcoin::EcdsaSigHashType::AllPlusAnyoneCanPay => sighashes[3], + bitcoin::EcdsaSigHashType::NonePlusAnyoneCanPay => sighashes[4], + bitcoin::EcdsaSigHashType::SinglePlusAnyoneCanPay => sighashes[5], + }; + secp.verify_ecdsa(&sighash, &ecdsa_sig.sig, &pk.key).is_ok() + }, + ) } } @@ -327,7 +329,7 @@ struct NodeEvaluationState<'intp> { /// /// In case the script is actually dissatisfied, this may return several values /// before ultimately returning an error. -pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool> { +pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool> { verify_sig: F, public_key: Option<&'intp bitcoin::PublicKey>, state: Vec>, @@ -341,7 +343,7 @@ pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, BitcoinSig) - impl<'intp, 'txin: 'intp, F> Iterator for Iter<'intp, 'txin, F> where NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { type Item = Result, Error>; @@ -362,7 +364,7 @@ where impl<'intp, 'txin: 'intp, F> Iter<'intp, 'txin, F> where NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { /// Helper function to push a NodeEvaluationState on state stack fn push_evaluation_state( @@ -770,14 +772,15 @@ fn verify_sersig<'txin, F>( sigser: &[u8], ) -> Result where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { if let Some((sighash_byte, sig)) = sigser.split_last() { - let sighashtype = bitcoin::EcdsaSigHashType::from_u32_standard(*sighash_byte as u32) + let hash_ty = bitcoin::EcdsaSigHashType::from_u32_standard(*sighash_byte as u32) .map_err(|_| Error::NonStandardSigHash([sig, &[*sighash_byte]].concat().to_vec()))?; let sig = secp256k1::ecdsa::Signature::from_der(sig)?; - if verify_sig(pk, (sig, sighashtype)) { - Ok(sig) + let ecdsa_sig = bitcoin::EcdsaSig { sig, hash_ty }; + if verify_sig(pk, ecdsa_sig) { + Ok(ecdsa_sig.sig) } else { Err(Error::InvalidSignature(*pk)) } @@ -794,7 +797,6 @@ mod tests { use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; use miniscript::context::NoChecks; - use BitcoinSig; use Miniscript; use MiniscriptKey; use ToPublicKey; @@ -839,8 +841,9 @@ mod tests { #[test] fn sat_constraints() { let (pks, der_sigs, secp_sigs, sighash, secp) = setup_keys_sigs(10); - let vfyfn_ = - |pk: &bitcoin::PublicKey, (sig, _)| secp.verify_ecdsa(&sighash, &sig, &pk.key).is_ok(); + let vfyfn_ = |pk: &bitcoin::PublicKey, ecdsa_sig: bitcoin::EcdsaSig| { + secp.verify_ecdsa(&sighash, &ecdsa_sig.sig, &pk.key).is_ok() + }; fn from_stack<'txin, 'elem, F>( verify_fn: F, @@ -848,7 +851,7 @@ mod tests { ms: &'elem Miniscript, ) -> Iter<'elem, 'txin, F> where - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { Iter { verify_sig: verify_fn, diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index ff07c8300..88b198ae8 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -18,7 +18,7 @@ use bitcoin; use bitcoin::blockdata::{opcodes, script}; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use {BitcoinSig, ToPublicKey}; +use ToPublicKey; use super::{verify_sersig, Error, HashLockType, SatisfiedConstraint}; @@ -132,7 +132,7 @@ impl<'txin> Stack<'txin> { pk: &'intp bitcoin::PublicKey, ) -> Option, Error>> where - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { if let Some(sigser) = self.pop() { match sigser { @@ -171,7 +171,7 @@ impl<'txin> Stack<'txin> { pkh: &'intp hash160::Hash, ) -> Option, Error>> where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { if let Some(Element::Push(pk)) = self.pop() { let pk_hash = hash160::Hash::hash(pk); @@ -367,7 +367,7 @@ impl<'txin> Stack<'txin> { pk: &'intp bitcoin::PublicKey, ) -> Option, Error>> where - F: FnOnce(&bitcoin::PublicKey, BitcoinSig) -> bool, + F: FnOnce(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { if let Some(witness_sig) = self.pop() { if let Element::Push(sigser) = witness_sig { diff --git a/src/lib.rs b/src/lib.rs index eb2f16d2c..828cfedc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,7 @@ pub use descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait}; pub use interpreter::Interpreter; pub use miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, Tap}; pub use miniscript::decode::Terminal; -pub use miniscript::satisfy::{BitcoinSig, Preimage32, Satisfier}; +pub use miniscript::satisfy::{Preimage32, Satisfier}; pub use miniscript::Miniscript; ///Public key trait which can be converted to Hash type diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index ef13461e9..3a9c5e23c 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -22,8 +22,8 @@ use std::collections::HashMap; use std::sync::Arc; use std::{cmp, i64, mem}; +use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; -use bitcoin::{self, secp256k1}; use {MiniscriptKey, ToPublicKey}; use miniscript::limits::{ @@ -34,28 +34,15 @@ use Miniscript; use ScriptContext; use Terminal; -/// Type alias for a signature/hashtype pair -pub type BitcoinSig = (secp256k1::ecdsa::Signature, bitcoin::EcdsaSigHashType); /// Type alias for 32 byte Preimage. pub type Preimage32 = [u8; 32]; - -/// Helper function to create BitcoinSig from Rawsig -/// Useful for downstream when implementing Satisfier. -/// Returns underlying secp if the Signature is not of correct format -pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result { - let (flag, sig) = rawsig.split_last().unwrap(); - let flag = bitcoin::EcdsaSigHashType::from_u32_standard(*flag as u32) - .map_err(|_| ::interpreter::Error::NonStandardSigHash([sig, &[*flag]].concat().to_vec()))?; - let sig = secp256k1::ecdsa::Signature::from_der(sig)?; - Ok((sig, flag)) -} /// Trait describing a lookup table for signatures, hash preimages, etc. /// Every method has a default implementation that simply returns `None` /// on every query. Users are expected to override the methods that they /// have data for. pub trait Satisfier { - /// Given a public key, look up a signature with that key - fn lookup_sig(&self, _: &Pk) -> Option { + /// Given a public key, look up an ECDSA signature with that key + fn lookup_ecdsa_sig(&self, _: &Pk) -> Option { None } @@ -64,11 +51,14 @@ pub trait Satisfier { None } - /// Given a keyhash, look up the signature and the associated key + /// Given a keyhash, look up the EC signature and the associated key /// Even if signatures for public key Hashes are not available, the users /// can use this map to provide pkh -> pk mapping which can be useful /// for dissatisfying pkh. - fn lookup_pkh_sig(&self, _: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_ecdsa_sig( + &self, + _: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { None } @@ -146,17 +136,17 @@ impl Satisfier for After { } } -impl Satisfier for HashMap { - fn lookup_sig(&self, key: &Pk) -> Option { +impl Satisfier for HashMap { + fn lookup_ecdsa_sig(&self, key: &Pk) -> Option { self.get(key).map(|x| *x) } } -impl Satisfier for HashMap +impl Satisfier for HashMap where Pk: MiniscriptKey + ToPublicKey, { - fn lookup_sig(&self, key: &Pk) -> Option { + fn lookup_ecdsa_sig(&self, key: &Pk) -> Option { self.get(&key.to_pubkeyhash()).map(|x| x.1) } @@ -164,23 +154,29 @@ where self.get(pk_hash).map(|x| x.0.clone()) } - fn lookup_pkh_sig(&self, pk_hash: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_ecdsa_sig( + &self, + pk_hash: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { self.get(pk_hash) .map(|&(ref pk, sig)| (pk.to_public_key(), sig)) } } impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &'a S { - fn lookup_sig(&self, p: &Pk) -> Option { - (**self).lookup_sig(p) + fn lookup_ecdsa_sig(&self, p: &Pk) -> Option { + (**self).lookup_ecdsa_sig(p) } fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { (**self).lookup_pkh_pk(pkh) } - fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { - (**self).lookup_pkh_sig(pkh) + fn lookup_pkh_ecdsa_sig( + &self, + pkh: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { + (**self).lookup_pkh_ecdsa_sig(pkh) } fn lookup_sha256(&self, h: sha256::Hash) -> Option { @@ -209,16 +205,19 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' } impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &'a mut S { - fn lookup_sig(&self, p: &Pk) -> Option { - (**self).lookup_sig(p) + fn lookup_ecdsa_sig(&self, p: &Pk) -> Option { + (**self).lookup_ecdsa_sig(p) } fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { (**self).lookup_pkh_pk(pkh) } - fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { - (**self).lookup_pkh_sig(pkh) + fn lookup_pkh_ecdsa_sig( + &self, + pkh: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { + (**self).lookup_pkh_ecdsa_sig(pkh) } fn lookup_sha256(&self, h: sha256::Hash) -> Option { @@ -254,23 +253,23 @@ macro_rules! impl_tuple_satisfier { Pk: MiniscriptKey + ToPublicKey, $($ty: Satisfier< Pk>,)* { - fn lookup_sig(&self, key: &Pk) -> Option { + fn lookup_ecdsa_sig(&self, key: &Pk) -> Option { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_sig(key) { + if let Some(result) = $ty.lookup_ecdsa_sig(key) { return Some(result); } )* None } - fn lookup_pkh_sig( + fn lookup_pkh_ecdsa_sig( &self, key_hash: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_pkh_sig(key_hash) { + if let Some(result) = $ty.lookup_pkh_ecdsa_sig(key_hash) { return Some(result); } )* @@ -402,12 +401,8 @@ impl Ord for Witness { impl Witness { /// Turn a signature into (part of) a satisfaction fn signature>(sat: S, pk: &Pk) -> Self { - match sat.lookup_sig(pk) { - Some((sig, hashtype)) => { - let mut ret = sig.serialize_der().to_vec(); - ret.push(hashtype.as_u32() as u8); - Witness::Stack(vec![ret]) - } + match sat.lookup_ecdsa_sig(pk) { + Some(sig) => Witness::Stack(vec![sig.to_vec()]), // Signatures cannot be forged None => Witness::Impossible, } @@ -425,12 +420,8 @@ impl Witness { /// Turn a key/signature pair related to a pkh into (part of) a satisfaction fn pkh_signature>(sat: S, pkh: &Pk::Hash) -> Self { - match sat.lookup_pkh_sig(pkh) { - Some((pk, (sig, hashtype))) => { - let mut ret = sig.serialize_der().to_vec(); - ret.push(hashtype.as_u32() as u8); - Witness::Stack(vec![ret.to_vec(), pk.to_public_key().to_bytes()]) - } + match sat.lookup_pkh_ecdsa_sig(pkh) { + Some((pk, sig)) => Witness::Stack(vec![sig.to_vec(), pk.to_public_key().to_bytes()]), None => Witness::Impossible, } } diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 5039109ef..e07ae2f4e 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -1161,7 +1161,7 @@ where mod tests { use super::*; use bitcoin::blockdata::{opcodes, script}; - use bitcoin::{self, hashes, secp256k1, EcdsaSigHashType}; + use bitcoin::{self, hashes, secp256k1}; use std::collections::HashMap; use std::str::FromStr; use std::string::String; @@ -1169,7 +1169,6 @@ mod tests { use miniscript::{satisfy, Legacy, Segwitv0}; use policy::Liftable; use script_num_size; - use BitcoinSig; type SPolicy = Concrete; type BPolicy = Concrete; @@ -1364,14 +1363,16 @@ mod tests { assert_eq!(abs.n_keys(), 5); assert_eq!(abs.minimum_n_keys(), Some(3)); - let bitcoinsig = (sig, EcdsaSigHashType::All); - let mut sigvec = sig.serialize_der().to_vec(); - sigvec.push(1); // sighash all + let bitcoinsig = bitcoin::EcdsaSig { + sig, + hash_ty: bitcoin::EcdsaSigHashType::All, + }; + let sigvec = bitcoinsig.to_vec(); - let no_sat = HashMap::::new(); - let mut left_sat = HashMap::::new(); + let no_sat = HashMap::::new(); + let mut left_sat = HashMap::::new(); let mut right_sat = - HashMap::::new(); + HashMap::::new(); for i in 0..5 { left_sat.insert(keys[i], bitcoinsig); diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 094f04778..fcf64b539 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -30,9 +30,9 @@ use bitcoin::Script; use interpreter; use miniscript::limits::SEQUENCE_LOCKTIME_DISABLE_FLAG; -use miniscript::satisfy::{bitcoinsig_from_rawsig, After, Older}; +use miniscript::satisfy::{After, Older}; +use Preimage32; use Satisfier; -use {BitcoinSig, Preimage32}; use {MiniscriptKey, ToPublicKey}; mod finalizer; @@ -224,33 +224,23 @@ impl<'psbt> PsbtInputSatisfier<'psbt> { } impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfier<'psbt> { - fn lookup_sig(&self, pk: &Pk) -> Option { - if let Some(rawsig) = self.psbt.inputs[self.index] + fn lookup_ecdsa_sig(&self, pk: &Pk) -> Option { + self.psbt.inputs[self.index] .partial_sigs .get(&pk.to_public_key()) - { - // We have already previously checked that all signatures have the - // correct sighash flag. - bitcoinsig_from_rawsig(&rawsig.to_vec()).ok() - } else { - None - } + .map(|sig| *sig) } - fn lookup_pkh_sig(&self, pkh: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { - if let Some((pk, sig)) = self.psbt.inputs[self.index] + fn lookup_pkh_ecdsa_sig( + &self, + pkh: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { + self.psbt.inputs[self.index] .partial_sigs .iter() .filter(|&(pubkey, _sig)| pubkey.to_pubkeyhash() == Pk::hash_to_hash160(pkh)) .next() - { - // If the mapping is incorrect, return None - bitcoinsig_from_rawsig(&sig.to_vec()) - .ok() - .map(|bitcoinsig| (*pk, bitcoinsig)) - } else { - None - } + .map(|(pk, sig)| (*pk, *sig)) } fn check_after(&self, n: u32) -> bool { From 2c32c0396c8fbffb6021228f6b68f9a041902efb Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 21 Oct 2021 03:59:20 -0700 Subject: [PATCH 3/7] Use bitcoin::SchnorrSig type from rust-bitcoin --- src/interpreter/error.rs | 9 ++++ src/miniscript/satisfy.rs | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index 0cf29a60f..982439608 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -37,6 +37,8 @@ pub enum Error { IncorrectWScriptHash, /// MultiSig missing at least `1` witness elements out of `k + 1` required InsufficientSignaturesMultiSig, + /// Invalid Sighash type + InvalidSchnorrSigHashType(Vec), /// Signature failed to verify InvalidSignature(bitcoin::PublicKey), /// Last byte of this signature isn't a standard sighash type @@ -138,6 +140,13 @@ impl fmt::Display for Error { } Error::IncorrectWScriptHash => f.write_str("witness script did not match scriptpubkey"), Error::InsufficientSignaturesMultiSig => f.write_str("Insufficient signatures for CMS"), + Error::InvalidSchnorrSigHashType(ref sig) => { + write!( + f, + "Invalid sighash type for schnorr signature '{}'", + sig.to_hex() + ) + } Error::InvalidSignature(pk) => write!(f, "bad signature with pk {}", pk), Error::NonStandardSigHash(ref sig) => { write!( diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 3a9c5e23c..7129c3f9b 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -46,6 +46,11 @@ pub trait Satisfier { None } + /// Given a public key, look up an schnorr signature with that key + fn lookup_schnorr_sig(&self, _: &Pk) -> Option { + None + } + /// Given a `Pkh`, lookup corresponding `Pk` fn lookup_pkh_pk(&self, _: &Pk::Hash) -> Option { None @@ -62,6 +67,17 @@ pub trait Satisfier { None } + /// Given a keyhash, look up the schnorr signature and the associated key + /// Even if signatures for public key Hashes are not available, the users + /// can use this map to provide pkh -> pk mapping which can be useful + /// for dissatisfying pkh. + fn lookup_pkh_schnorr_sig( + &self, + _: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + None + } + /// Given a SHA256 hash, look up its preimage fn lookup_sha256(&self, _: sha256::Hash) -> Option { None @@ -142,6 +158,12 @@ impl Satisfier for HashMap Satisfier for HashMap { + fn lookup_schnorr_sig(&self, key: &Pk) -> Option { + self.get(key).map(|x| *x) + } +} + impl Satisfier for HashMap where Pk: MiniscriptKey + ToPublicKey, @@ -163,11 +185,36 @@ where } } +impl Satisfier for HashMap +where + Pk: MiniscriptKey + ToPublicKey, +{ + fn lookup_schnorr_sig(&self, key: &Pk) -> Option { + self.get(&key.to_pubkeyhash()).map(|x| x.1) + } + + fn lookup_pkh_pk(&self, pk_hash: &Pk::Hash) -> Option { + self.get(pk_hash).map(|x| x.0.clone()) + } + + fn lookup_pkh_schnorr_sig( + &self, + pk_hash: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + self.get(pk_hash) + .map(|&(ref pk, sig)| (pk.to_public_key(), sig)) + } +} + impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &'a S { fn lookup_ecdsa_sig(&self, p: &Pk) -> Option { (**self).lookup_ecdsa_sig(p) } + fn lookup_schnorr_sig(&self, p: &Pk) -> Option { + (**self).lookup_schnorr_sig(p) + } + fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { (**self).lookup_pkh_pk(pkh) } @@ -179,6 +226,13 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_ecdsa_sig(pkh) } + fn lookup_pkh_schnorr_sig( + &self, + pkh: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + (**self).lookup_pkh_schnorr_sig(pkh) + } + fn lookup_sha256(&self, h: sha256::Hash) -> Option { (**self).lookup_sha256(h) } @@ -209,6 +263,10 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_ecdsa_sig(p) } + fn lookup_schnorr_sig(&self, p: &Pk) -> Option { + (**self).lookup_schnorr_sig(p) + } + fn lookup_pkh_pk(&self, pkh: &Pk::Hash) -> Option { (**self).lookup_pkh_pk(pkh) } @@ -220,6 +278,13 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_pkh_ecdsa_sig(pkh) } + fn lookup_pkh_schnorr_sig( + &self, + pkh: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + (**self).lookup_pkh_schnorr_sig(pkh) + } + fn lookup_sha256(&self, h: sha256::Hash) -> Option { (**self).lookup_sha256(h) } @@ -263,6 +328,16 @@ macro_rules! impl_tuple_satisfier { None } + fn lookup_schnorr_sig(&self, key: &Pk) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_schnorr_sig(key) { + return Some(result); + } + )* + None + } + fn lookup_pkh_ecdsa_sig( &self, key_hash: &Pk::Hash, @@ -276,6 +351,19 @@ macro_rules! impl_tuple_satisfier { None } + fn lookup_pkh_schnorr_sig( + &self, + key_hash: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_pkh_schnorr_sig(key_hash) { + return Some(result); + } + )* + None + } + fn lookup_pkh_pk( &self, key_hash: &Pk::Hash, From 1a7e779408325f5c25e11ba5af1549a5b098f959 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 21 Oct 2021 04:02:54 -0700 Subject: [PATCH 4/7] Change satisfier to support xonly sigs --- src/miniscript/satisfy.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 7129c3f9b..58305be2e 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -488,11 +488,19 @@ impl Ord for Witness { impl Witness { /// Turn a signature into (part of) a satisfaction - fn signature>(sat: S, pk: &Pk) -> Self { - match sat.lookup_ecdsa_sig(pk) { - Some(sig) => Witness::Stack(vec![sig.to_vec()]), - // Signatures cannot be forged - None => Witness::Impossible, + fn signature, Ctx: ScriptContext>(sat: S, pk: &Pk) -> Self { + if Ctx::is_tap() { + match sat.lookup_schnorr_sig(pk) { + Some(sig) => Witness::Stack(vec![sig.to_vec()]), + // Signatures cannot be forged + None => Witness::Impossible, + } + } else { + match sat.lookup_ecdsa_sig(pk) { + Some(sig) => Witness::Stack(vec![sig.to_vec()]), + // Signatures cannot be forged + None => Witness::Impossible, + } } } @@ -828,7 +836,7 @@ impl Satisfaction { { match *term { Terminal::PkK(ref pk) => Satisfaction { - stack: Witness::signature(stfr, pk), + stack: Witness::signature::<_, _, Ctx>(stfr, pk), has_sig: true, }, Terminal::PkH(ref pkh) => Satisfaction { @@ -989,7 +997,7 @@ impl Satisfaction { let mut sig_count = 0; let mut sigs = Vec::with_capacity(k); for pk in keys { - match Witness::signature(stfr, pk) { + match Witness::signature::<_, _, Ctx>(stfr, pk) { Witness::Stack(sig) => { sigs.push(sig); sig_count += 1; @@ -1031,7 +1039,7 @@ impl Satisfaction { let mut sig_count = 0; let mut sigs = vec![vec![vec![]]; keys.len()]; for (i, pk) in keys.iter().rev().enumerate() { - match Witness::signature(stfr, pk) { + match Witness::signature::<_, _, Ctx>(stfr, pk) { Witness::Stack(sig) => { sigs[i] = sig; sig_count += 1; From f6b2536247fc45ae09df1be4f98937a3c556b338 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 21 Oct 2021 04:34:42 -0700 Subject: [PATCH 5/7] Add satisfaction tests --- src/miniscript/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 08a032075..541087fb7 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -449,6 +449,8 @@ serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext); #[cfg(test)] mod tests { + use {Satisfier, ToPublicKey}; + use super::{Miniscript, ScriptContext}; use super::{Segwitv0, Tap}; use hex_script; @@ -1031,5 +1033,24 @@ mod tests { ); assert_eq!(tap_ms.script_size(), 104); assert_eq!(tap_ms.encode().len(), tap_ms.script_size()); + + // Test satisfaction code + struct SimpleSatisfier(secp256k1::schnorr::Signature); + + // a simple satisfier that always outputs the same signature + impl Satisfier for SimpleSatisfier { + fn lookup_schnorr_sig(&self, _pk: &Pk) -> Option { + Some(bitcoin::SchnorrSig { + sig: self.0, + hash_ty: bitcoin::SchnorrSigHashType::Default, + }) + } + } + + let schnorr_sig = secp256k1::schnorr::Signature::from_str("84526253c27c7aef56c7b71a5cd25bebb66dddda437826defc5b2568bde81f0784526253c27c7aef56c7b71a5cd25bebb66dddda437826defc5b2568bde81f07").unwrap(); + let s = SimpleSatisfier(schnorr_sig); + + let wit = tap_ms.satisfy(s).unwrap(); + assert_eq!(wit, vec![schnorr_sig.as_ref().to_vec(), vec![], vec![]]); } } From e70e6a3cfe0fd8a4b6aa4968124ec48003410e24 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Thu, 21 Oct 2021 15:33:10 -0700 Subject: [PATCH 6/7] Fix fuzzer crash while allocating keys for multi_a --- .../roundtrip_miniscript_script.rs | 27 +++++++++++++++++++ fuzz/fuzz_targets/roundtrip_miniscript_str.rs | 27 +++++++++++++++++++ src/lib.rs | 5 ++++ src/miniscript/decode.rs | 5 ++++ 4 files changed, 64 insertions(+) diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs index 31efd7204..67c087479 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_script.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_script.rs @@ -35,3 +35,30 @@ fn main() { }); } } + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash3() { + let mut a = Vec::new(); + extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); + super::do_test(&a); + } +} diff --git a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs index 431f1d93a..eeb077ae0 100644 --- a/fuzz/fuzz_targets/roundtrip_miniscript_str.rs +++ b/fuzz/fuzz_targets/roundtrip_miniscript_str.rs @@ -38,3 +38,30 @@ fn main() { }); } } + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); + super::do_test(&a); + } +} diff --git a/src/lib.rs b/src/lib.rs index 828cfedc0..272908c43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -479,6 +479,8 @@ pub enum Error { AddrError(bitcoin::util::address::Error), /// A `CHECKMULTISIG` opcode was preceded by a number > 20 CmsTooManyKeys(u32), + /// A tapscript multi_a cannot support more than MAX_BLOCK_WEIGHT/32 keys + MultiATooManyKeys(u32), /// Encountered unprintable character in descriptor Unprintable(u8), /// expected character while parsing descriptor; didn't find one @@ -671,6 +673,9 @@ impl fmt::Display for Error { Error::PubKeyCtxError(ref pk, ref ctx) => { write!(f, "Pubkey error: {} under {} scriptcontext", pk, ctx) } + Error::MultiATooManyKeys(k) => { + write!(f, "MultiA too many keys {}", k) + } } } } diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 1182f9eea..7b7046eee 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -17,6 +17,7 @@ //! Functionality to parse a Bitcoin Script into a `Miniscript` //! +use bitcoin::blockdata::constants::MAX_BLOCK_WEIGHT; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use std::marker::PhantomData; use std::{error, fmt}; @@ -491,6 +492,10 @@ pub fn parse( }, // MultiA Tk::NumEqual, Tk::Num(k) => { + // Check size before allocating keys + if k > MAX_BLOCK_WEIGHT/32 { + return Err(Error::MultiATooManyKeys(MAX_BLOCK_WEIGHT/32)) + } let mut keys = Vec::with_capacity(k as usize); // atleast k capacity while tokens.peek() == Some(&Tk::CheckSigAdd) { match_token!( From 42e4c50689ee96fc0a806f02c88834f24d78fd59 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Tue, 11 Jan 2022 15:23:28 +0530 Subject: [PATCH 7/7] Cleanup context code with SigType enum NoChecks context needs to know how to serialize the given public key or where to lookup for it's signature (schnorr or ecdsa). The simplest way to convey this information is to separate out NoChecks for ecdsa and schnorr. When using NoChecksEcdsa the associated type bitcoin::PublicKey or when using NoChecksSchnorr, the associated type XOnlyPublicKey should take care of it. --- src/interpreter/inner.rs | 14 +-- src/interpreter/mod.rs | 14 +-- src/miniscript/astelem.rs | 5 +- src/miniscript/context.rs | 197 ++++++++++++++++++++++++++++++++++---- src/miniscript/satisfy.rs | 11 +-- src/util.rs | 8 +- 6 files changed, 206 insertions(+), 43 deletions(-) diff --git a/src/interpreter/inner.rs b/src/interpreter/inner.rs index 43f6ea3dc..2af4d0802 100644 --- a/src/interpreter/inner.rs +++ b/src/interpreter/inner.rs @@ -17,7 +17,7 @@ use bitcoin::blockdata::witness::Witness; use bitcoin::hashes::{hash160, sha256, Hash}; use super::{stack, Error, Stack}; -use miniscript::context::NoChecks; +use miniscript::context::NoChecksEcdsa; use {Miniscript, MiniscriptKey}; /// Attempts to parse a slice as a Bitcoin public key, checking compressedness @@ -48,7 +48,7 @@ fn pk_from_stackelem<'a>( fn script_from_stackelem<'a>( elem: &stack::Element<'a>, -) -> Result, Error> { +) -> Result, Error> { match *elem { stack::Element::Push(sl) => { Miniscript::::parse_insane(&bitcoin::Script::from(sl.to_owned())) @@ -86,7 +86,7 @@ pub enum Inner { /// pay-to-pkhash or pay-to-witness-pkhash) PublicKey(bitcoin::PublicKey, PubkeyType), /// The script being evaluated is an actual script - Script(Miniscript, ScriptType), + Script(Miniscript, ScriptType), } // The `Script` returned by this method is always generated/cloned ... when @@ -598,7 +598,7 @@ mod tests { fn script_bare() { let preimage = b"12345678----____12345678----____"; let hash = hash160::Hash::hash(&preimage[..]); - let miniscript: ::Miniscript = + let miniscript: ::Miniscript = ::Miniscript::from_str_insane(&format!("hash160({})", hash)).unwrap(); let spk = miniscript.encode(); @@ -625,7 +625,7 @@ mod tests { fn script_sh() { let preimage = b"12345678----____12345678----____"; let hash = hash160::Hash::hash(&preimage[..]); - let miniscript: ::Miniscript = + let miniscript: ::Miniscript = ::Miniscript::from_str_insane(&format!("hash160({})", hash)).unwrap(); let redeem_script = miniscript.encode(); @@ -663,7 +663,7 @@ mod tests { fn script_wsh() { let preimage = b"12345678----____12345678----____"; let hash = hash160::Hash::hash(&preimage[..]); - let miniscript: ::Miniscript = + let miniscript: ::Miniscript = ::Miniscript::from_str_insane(&format!("hash160({})", hash)).unwrap(); let witness_script = miniscript.encode(); @@ -701,7 +701,7 @@ mod tests { fn script_sh_wsh() { let preimage = b"12345678----____12345678----____"; let hash = hash160::Hash::hash(&preimage[..]); - let miniscript: ::Miniscript = + let miniscript: ::Miniscript = ::Miniscript::from_str_insane(&format!("hash160({})", hash)).unwrap(); let witness_script = miniscript.encode(); diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index aab9f8f41..b8bdc29ea 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -23,7 +23,7 @@ use bitcoin::blockdata::witness::Witness; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::util::sighash; use bitcoin::{self, secp256k1}; -use miniscript::context::NoChecks; +use miniscript::context::NoChecksEcdsa; use miniscript::ScriptContext; use Miniscript; use Terminal; @@ -311,7 +311,7 @@ pub enum SatisfiedConstraint<'intp, 'txin> { ///depending on evaluation of the children. struct NodeEvaluationState<'intp> { ///The node which is being evaluated - node: &'intp Miniscript, + node: &'intp Miniscript, ///number of children evaluated n_evaluated: usize, ///number of children satisfied @@ -342,7 +342,7 @@ pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, bitcoin::Ecds ///Iterator for Iter impl<'intp, 'txin: 'intp, F> Iterator for Iter<'intp, 'txin, F> where - NoChecks: ScriptContext, + NoChecksEcdsa: ScriptContext, F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { type Item = Result, Error>; @@ -363,13 +363,13 @@ where impl<'intp, 'txin: 'intp, F> Iter<'intp, 'txin, F> where - NoChecks: ScriptContext, + NoChecksEcdsa: ScriptContext, F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { /// Helper function to push a NodeEvaluationState on state stack fn push_evaluation_state( &mut self, - node: &'intp Miniscript, + node: &'intp Miniscript, n_evaluated: usize, n_satisfied: usize, ) -> () { @@ -796,7 +796,7 @@ mod tests { use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; - use miniscript::context::NoChecks; + use miniscript::context::NoChecksEcdsa; use Miniscript; use MiniscriptKey; use ToPublicKey; @@ -848,7 +848,7 @@ mod tests { fn from_stack<'txin, 'elem, F>( verify_fn: F, stack: &'elem mut Stack<'txin>, - ms: &'elem Miniscript, + ms: &'elem Miniscript, ) -> Iter<'elem, 'txin, F> where F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 75aa5be0e..6c3728dd4 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -29,6 +29,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use errstr; use expression; +use miniscript::context::SigType; use miniscript::types::{self, Property}; use miniscript::ScriptContext; use script_num_size; @@ -761,7 +762,7 @@ impl Terminal { .push_opcode(opcodes::all::OP_EQUAL) } Terminal::Multi(k, ref keys) => { - debug_assert!(!Ctx::is_tap()); + debug_assert!(Ctx::sig_type() == SigType::Ecdsa); builder = builder.push_int(k as i64); for pk in keys { builder = builder.push_key(&pk.to_public_key()); @@ -771,7 +772,7 @@ impl Terminal { .push_opcode(opcodes::all::OP_CHECKMULTISIG) } Terminal::MultiA(k, ref keys) => { - debug_assert!(Ctx::is_tap()); + debug_assert!(Ctx::sig_type() == SigType::Schnorr); // keys must be atleast len 1 here, guaranteed by typing rules builder = builder.push_ms_key::<_, Ctx>(&keys[0]); builder = builder.push_opcode(opcodes::all::OP_CHECKSIG); diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index 0ea029793..c718303d9 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -74,6 +74,14 @@ pub enum ScriptContextError { MultiANotAllowed, } +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum SigType { + /// Ecdsa signature + Ecdsa, + /// Schnorr Signature + Schnorr, +} + impl fmt::Display for ScriptContextError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -285,12 +293,10 @@ where Self::other_top_level_checks(ms) } - /// Reverse lookup to store whether the context is tapscript. - /// pk(33-byte key) is a valid under both tapscript context and segwitv0 context - /// We need to context decide whether the serialize pk to 33 byte or 32 bytes. - fn is_tap() -> bool { - return false; - } + /// The type of signature required for satisfaction + // We need to context decide whether the serialize pk to 33 byte or 32 bytes. + // And to decide which type of signatures to look for during satisfaction + fn sig_type() -> SigType; /// Get the len of public key when serialized based on context /// Note that this includes the serialization prefix. Returns @@ -395,6 +401,10 @@ impl ScriptContext for Legacy { fn name_str() -> &'static str { "Legacy/p2sh" } + + fn sig_type() -> SigType { + SigType::Ecdsa + } } /// Segwitv0 ScriptContext @@ -503,6 +513,10 @@ impl ScriptContext for Segwitv0 { fn name_str() -> &'static str { "Segwitv0" } + + fn sig_type() -> SigType { + SigType::Ecdsa + } } /// Tap ScriptContext @@ -600,8 +614,8 @@ impl ScriptContext for Tap { ms.ext.max_sat_size.map(|x| x.0) } - fn is_tap() -> bool { - true + fn sig_type() -> SigType { + SigType::Schnorr } fn pk_len(_pk: &Pk) -> usize { @@ -684,16 +698,20 @@ impl ScriptContext for BareCtx { fn name_str() -> &'static str { "BareCtx" } + + fn sig_type() -> SigType { + SigType::Ecdsa + } } -/// "No Checks" Context +/// "No Checks Ecdsa" Context /// /// Used by the "satisified constraints" iterator, which is intended to read /// scripts off of the blockchain without doing any sanity checks on them. -/// This context should not be used unless you know what you are doing. +/// This context should *NOT* be used unless you know what you are doing. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum NoChecks {} -impl ScriptContext for NoChecks { +pub enum NoChecksEcdsa {} +impl ScriptContext for NoChecksEcdsa { // todo: When adding support for interpreter, we need a enum with all supported keys here type Key = bitcoin::PublicKey; fn check_terminal_non_malleable( @@ -727,22 +745,166 @@ impl ScriptContext for NoChecks { } fn max_satisfaction_size(_ms: &Miniscript) -> Option { - panic!("Tried to compute a satisfaction size bound on a no-checks miniscript") + panic!("Tried to compute a satisfaction size bound on a no-checks ecdsa miniscript") + } + + fn pk_len(_pk: &Pk) -> usize { + panic!("Tried to compute a pk len bound on a no-checks ecdsa miniscript") + } + + fn name_str() -> &'static str { + // Internally used code + "NochecksEcdsa" + } + + fn check_witness(_witness: &[Vec]) -> Result<(), ScriptContextError> { + // Only really need to do this for segwitv0 and legacy + // Bare is already restrcited by standardness rules + // and would reach these limits. + Ok(()) + } + + fn check_global_validity( + ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Self::check_global_consensus_validity(ms)?; + Self::check_global_policy_validity(ms)?; + Ok(()) + } + + fn check_local_validity( + ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Self::check_global_consensus_validity(ms)?; + Self::check_global_policy_validity(ms)?; + Self::check_local_consensus_validity(ms)?; + Self::check_local_policy_validity(ms)?; + Ok(()) + } + + fn top_level_type_check(ms: &Miniscript) -> Result<(), Error> { + if ms.ty.corr.base != types::Base::B { + return Err(Error::NonTopLevel(format!("{:?}", ms))); + } + Ok(()) + } + + fn other_top_level_checks(_ms: &Miniscript) -> Result<(), Error> { + Ok(()) + } + + fn top_level_checks(ms: &Miniscript) -> Result<(), Error> { + Self::top_level_type_check(ms)?; + Self::other_top_level_checks(ms) + } + + fn sig_type() -> SigType { + SigType::Ecdsa + } +} + +/// "No Checks Schnorr" Context +/// +/// Used by the "satisified constraints" iterator, which is intended to read +/// scripts off of the blockchain without doing any sanity checks on them. +/// This context should *NOT* be used unless you know what you are doing. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum NoChecksSchnorr {} +impl ScriptContext for NoChecksSchnorr { + // todo: When adding support for interpreter, we need a enum with all supported keys here + type Key = bitcoin::secp256k1::XOnlyPublicKey; + fn check_terminal_non_malleable( + _frag: &Terminal, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn check_global_policy_validity( + _ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn check_global_consensus_validity( + _ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn check_local_policy_validity( + _ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn check_local_consensus_validity( + _ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Ok(()) + } + + fn max_satisfaction_size(_ms: &Miniscript) -> Option { + panic!("Tried to compute a satisfaction size bound on a no-checks schnorr miniscript") } fn pk_len(_pk: &Pk) -> usize { - panic!("Tried to compute a pk len bound on a no-checks miniscript") + panic!("Tried to compute a pk len bound on a no-checks schnorr miniscript") } fn name_str() -> &'static str { // Internally used code - "Nochecks" + "NochecksSchnorr" + } + + fn check_witness(_witness: &[Vec]) -> Result<(), ScriptContextError> { + // Only really need to do this for segwitv0 and legacy + // Bare is already restrcited by standardness rules + // and would reach these limits. + Ok(()) + } + + fn check_global_validity( + ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Self::check_global_consensus_validity(ms)?; + Self::check_global_policy_validity(ms)?; + Ok(()) + } + + fn check_local_validity( + ms: &Miniscript, + ) -> Result<(), ScriptContextError> { + Self::check_global_consensus_validity(ms)?; + Self::check_global_policy_validity(ms)?; + Self::check_local_consensus_validity(ms)?; + Self::check_local_policy_validity(ms)?; + Ok(()) + } + + fn top_level_type_check(ms: &Miniscript) -> Result<(), Error> { + if ms.ty.corr.base != types::Base::B { + return Err(Error::NonTopLevel(format!("{:?}", ms))); + } + Ok(()) + } + + fn other_top_level_checks(_ms: &Miniscript) -> Result<(), Error> { + Ok(()) + } + + fn top_level_checks(ms: &Miniscript) -> Result<(), Error> { + Self::top_level_type_check(ms)?; + Self::other_top_level_checks(ms) + } + + fn sig_type() -> SigType { + SigType::Schnorr } } /// Private Mod to prevent downstream from implementing this public trait mod private { - use super::{BareCtx, Legacy, NoChecks, Segwitv0, Tap}; + use super::{BareCtx, Legacy, NoChecksEcdsa, NoChecksSchnorr, Segwitv0, Tap}; pub trait Sealed {} @@ -751,5 +913,6 @@ mod private { impl Sealed for Legacy {} impl Sealed for Segwitv0 {} impl Sealed for Tap {} - impl Sealed for NoChecks {} + impl Sealed for NoChecksEcdsa {} + impl Sealed for NoChecksSchnorr {} } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 58305be2e..e430414ab 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -489,18 +489,17 @@ impl Ord for Witness { impl Witness { /// Turn a signature into (part of) a satisfaction fn signature, Ctx: ScriptContext>(sat: S, pk: &Pk) -> Self { - if Ctx::is_tap() { - match sat.lookup_schnorr_sig(pk) { + match Ctx::sig_type() { + super::context::SigType::Ecdsa => match sat.lookup_ecdsa_sig(pk) { Some(sig) => Witness::Stack(vec![sig.to_vec()]), // Signatures cannot be forged None => Witness::Impossible, - } - } else { - match sat.lookup_ecdsa_sig(pk) { + }, + super::context::SigType::Schnorr => match sat.lookup_schnorr_sig(pk) { Some(sig) => Witness::Stack(vec![sig.to_vec()]), // Signatures cannot be forged None => Witness::Impossible, - } + }, } } diff --git a/src/util.rs b/src/util.rs index bec3f52f8..16feeeaa3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use bitcoin; use bitcoin::blockdata::script; use bitcoin::Script; +use miniscript::context; use {ScriptContext, ToPublicKey}; pub(crate) fn varint_len(n: usize) -> usize { @@ -39,10 +40,9 @@ impl MsKeyBuilder for script::Builder { Pk: ToPublicKey, Ctx: ScriptContext, { - if Ctx::is_tap() { - self.push_slice(&key.to_x_only_pubkey().serialize()) - } else { - self.push_key(&key.to_public_key()) + match Ctx::sig_type() { + context::SigType::Ecdsa => self.push_key(&key.to_public_key()), + context::SigType::Schnorr => self.push_slice(&key.to_x_only_pubkey().serialize()), } } }