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/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/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/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/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 4e6dcc6e6..b8bdc29ea 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -23,11 +23,11 @@ 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; -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() + }, + ) } } @@ -309,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 @@ -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>, @@ -340,8 +342,8 @@ pub struct Iter<'intp, 'txin: 'intp, F: FnMut(&bitcoin::PublicKey, BitcoinSig) - ///Iterator for Iter impl<'intp, 'txin: 'intp, F> Iterator for Iter<'intp, 'txin, F> where - NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + NoChecksEcdsa: ScriptContext, + F: FnMut(&bitcoin::PublicKey, bitcoin::EcdsaSig) -> bool, { type Item = Result, Error>; @@ -361,13 +363,13 @@ where impl<'intp, 'txin: 'intp, F> Iter<'intp, 'txin, F> where - NoChecks: ScriptContext, - F: FnMut(&bitcoin::PublicKey, BitcoinSig) -> bool, + 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, ) -> () { @@ -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)) } @@ -793,8 +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 BitcoinSig; + use miniscript::context::NoChecksEcdsa; use Miniscript; use MiniscriptKey; use ToPublicKey; @@ -839,16 +841,17 @@ 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, stack: &'elem mut Stack<'txin>, - ms: &'elem Miniscript, + 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..272908c43 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 @@ -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/astelem.rs b/src/miniscript/astelem.rs index 019fdd36f..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; @@ -119,7 +120,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 +212,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 +319,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 +382,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 +561,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 +575,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", @@ -736,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()); @@ -745,6 +771,19 @@ impl Terminal { .push_int(keys.len() as i64) .push_opcode(opcodes::all::OP_CHECKMULTISIG) } + Terminal::MultiA(k, ref keys) => { + 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); + 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 +838,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..c718303d9 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -70,6 +70,16 @@ 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, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum SigType { + /// Ecdsa signature + Ecdsa, + /// Schnorr Signature + Schnorr, } impl fmt::Display for ScriptContextError { @@ -141,6 +151,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") + } } } } @@ -280,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 @@ -339,9 +350,11 @@ impl ScriptContext for Legacy { return Err(ScriptContextError::CheckMultiSigLimitExceeded); } } + Terminal::MultiA(..) => { + return Err(ScriptContextError::MultiANotAllowed); + } _ => {} } - Ok(()) } @@ -388,6 +401,10 @@ impl ScriptContext for Legacy { fn name_str() -> &'static str { "Legacy/p2sh" } + + fn sig_type() -> SigType { + SigType::Ecdsa + } } /// Segwitv0 ScriptContext @@ -437,6 +454,9 @@ impl ScriptContext for Segwitv0 { } Ok(()) } + Terminal::MultiA(..) => { + return Err(ScriptContextError::MultiANotAllowed); + } _ => Ok(()), } } @@ -493,6 +513,10 @@ impl ScriptContext for Segwitv0 { fn name_str() -> &'static str { "Segwitv0" } + + fn sig_type() -> SigType { + SigType::Ecdsa + } } /// Tap ScriptContext @@ -590,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 { @@ -628,6 +652,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(()) } @@ -671,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( @@ -714,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 miniscript") + panic!("Tried to compute a pk len bound on a no-checks ecdsa miniscript") } fn name_str() -> &'static str { // Internally used code - "Nochecks" + "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 schnorr miniscript") + } + + fn name_str() -> &'static str { + // Internally used code + "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 {} @@ -738,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/decode.rs b/src/miniscript/decode.rs index 260dab395..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}; @@ -187,6 +188,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 +490,29 @@ pub fn parse( keys.reverse(); term.reduce0(Terminal::Multi(k as usize, keys))?; }, + // 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!( + 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..541087fb7 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -448,6 +448,9 @@ 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; @@ -458,10 +461,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 +1005,52 @@ 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()); + + // 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![]]); + } } diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 502082ebb..e430414ab 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,20 @@ 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 + } + + /// Given a public key, look up an schnorr signature with that key + fn lookup_schnorr_sig(&self, _: &Pk) -> Option { None } @@ -64,11 +56,25 @@ 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_ecdsa_sig( + &self, + _: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { + 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_sig(&self, _: &Pk::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + fn lookup_pkh_schnorr_sig( + &self, + _: &Pk::Hash, + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { None } @@ -146,17 +152,44 @@ 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 { + fn lookup_schnorr_sig(&self, key: &Pk) -> Option { + self.get(key).map(|x| *x) + } +} + +impl Satisfier for HashMap +where + Pk: MiniscriptKey + ToPublicKey, +{ + fn lookup_ecdsa_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_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 Satisfier for HashMap where Pk: MiniscriptKey + ToPublicKey, { - fn lookup_sig(&self, key: &Pk) -> Option { + fn lookup_schnorr_sig(&self, key: &Pk) -> Option { self.get(&key.to_pubkeyhash()).map(|x| x.1) } @@ -164,23 +197,40 @@ 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_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_sig(&self, p: &Pk) -> Option { - (**self).lookup_sig(p) + 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) } - 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_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 { @@ -209,16 +259,30 @@ 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_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) } - 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_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 { @@ -254,23 +318,46 @@ 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_ecdsa_sig(key) { + return Some(result); + } + )* + 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, + ) -> Option<(bitcoin::PublicKey, bitcoin::EcdsaSig)> { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_sig(key) { + if let Some(result) = $ty.lookup_pkh_ecdsa_sig(key_hash) { return Some(result); } )* None } - fn lookup_pkh_sig( + fn lookup_pkh_schnorr_sig( &self, key_hash: &Pk::Hash, - ) -> Option<(bitcoin::PublicKey, BitcoinSig)> { + ) -> Option<(bitcoin::PublicKey, bitcoin::SchnorrSig)> { let &($(ref $ty,)*) = self; $( - if let Some(result) = $ty.lookup_pkh_sig(key_hash) { + if let Some(result) = $ty.lookup_pkh_schnorr_sig(key_hash) { return Some(result); } )* @@ -401,15 +488,18 @@ 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]) - } - // Signatures cannot be forged - None => Witness::Impossible, + fn signature, Ctx: ScriptContext>(sat: S, pk: &Pk) -> Self { + 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, + }, + 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, + }, } } @@ -425,12 +515,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, } } @@ -749,7 +835,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 { @@ -910,7 +996,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; @@ -947,6 +1033,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::<_, _, Ctx>(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 +1188,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/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/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())) 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 { 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()), } } }