diff --git a/Cargo.toml b/Cargo.toml index b3340376b..edcef2219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [features] default = ["std"] -std = ["bitcoin/std", "bitcoin/secp-recovery"] +std = ["bitcoin/std", "bitcoin/secp-recovery", "secp256k1-zkp"] no-std = ["hashbrown", "bitcoin/no-std"] compiler = [] trace = [] @@ -24,6 +24,7 @@ rand = ["bitcoin/rand"] bitcoin = { version = "0.28.1", default-features = false } serde = { version = "1.0", optional = true } hashbrown = { version = "0.11", optional = true } +secp256k1-zkp = { git = "https://github.com/sanket1729/rust-secp256k1-zkp", branch = "pr29", optional = true} [dev-dependencies] bitcoind = {version = "0.26.1", features=["22_0"]} diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 2b02dc165..0ad79ae4e 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -34,6 +34,7 @@ use bitcoin::{self, secp256k1, Address, Network, Script, TxIn}; use sync::Arc; use self::checksum::verify_checksum; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::{Legacy, Miniscript, Segwitv0}; use crate::prelude::*; use crate::{ @@ -180,7 +181,7 @@ impl Descriptor { // roundabout way to constuct `c:pk_k(pk)` let ms: Miniscript = Miniscript::from_ast(miniscript::decode::Terminal::Check(Arc::new( - Miniscript::from_ast(miniscript::decode::Terminal::PkK(pk)) + Miniscript::from_ast(miniscript::decode::Terminal::PkK(KeyExpr::SingleKey(pk))) .expect("Type check cannot fail"), ))) .expect("Type check cannot fail"); diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 2c2c4b917..23392a412 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -593,6 +593,9 @@ where Terminal::PkK(ref pk) => { debug_assert_eq!(node_state.n_evaluated, 0); debug_assert_eq!(node_state.n_satisfied, 0); + let pk = pk + .single_key() + .expect("Musig keys cannot be parsed from Script"); let res = self.stack.evaluate_pk(&mut self.verify_sig, *pk); if res.is_some() { return res; @@ -868,10 +871,11 @@ where // evaluate each key with as a pk // note that evaluate_pk will error on non-empty incorrect sigs // push 1 on satisfied sigs and push 0 on empty sigs - match self - .stack - .evaluate_pk(&mut self.verify_sig, subs[node_state.n_evaluated]) - { + let pkk = subs[node_state.n_evaluated] + .single_key() + .expect("Musig keys cannot be parsed from Script"); + let res = self.stack.evaluate_pk(&mut self.verify_sig, *pkk); + match res { Some(Ok(x)) => { self.push_evaluation_state( node_state.node, diff --git a/src/lib.rs b/src/lib.rs index dd0888624..52847d4fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,9 @@ extern crate alloc; #[cfg(not(feature = "std"))] extern crate hashbrown; +#[cfg(feature = "std")] +extern crate secp256k1_zkp; + #[cfg(any(feature = "std", test))] extern crate core; @@ -775,6 +778,8 @@ pub enum Error { TrNoScriptCode, /// No explicit script for Tr descriptors TrNoExplicitScript, + /// Parsing error for single key + SingleKeyParseError, } // https://github.com/sipa/miniscript/pull/5 for discussion on this number @@ -848,6 +853,7 @@ impl fmt::Display for Error { Error::TaprootSpendInfoUnavialable => write!(f, "Taproot Spend Info not computed."), Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"), Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"), + Error::SingleKeyParseError => f.write_str("not able to parse the single key"), } } } @@ -888,6 +894,7 @@ impl error::Error for Error { | BareDescriptorAddr | TaprootSpendInfoUnavialable | TrNoScriptCode + | SingleKeyParseError | TrNoExplicitScript => None, Script(e) => Some(e), AddrError(e) => Some(e), diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index b21756430..daabb5801 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -26,6 +26,7 @@ use bitcoin::blockdata::{opcodes, script}; use sync::Arc; use crate::miniscript::context::SigType; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::types::{self, Property}; use crate::miniscript::ScriptContext; use crate::prelude::*; @@ -80,7 +81,7 @@ impl Terminal { Pk::RawPkHash: 'a, { match *self { - Terminal::PkK(ref p) => pred(p), + Terminal::PkK(ref p) => p.for_each_key(pred), Terminal::PkH(ref p) => pred(p), Terminal::RawPkH(..) | Terminal::After(..) @@ -112,9 +113,8 @@ 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) | Terminal::MultiA(_, ref keys) => { - keys.iter().all(|key| pred(key)) - } + Terminal::Multi(_, ref keys) => keys.iter().all(|key| pred(key)), + Terminal::MultiA(_, ref keys) => keys.iter().all(|key| key.for_each_key(&mut *pred)), } } @@ -125,7 +125,7 @@ impl Terminal { T: Translator, { let frag: Terminal = match *self { - Terminal::PkK(ref p) => Terminal::PkK(t.pk(p)?), + Terminal::PkK(ref p) => Terminal::PkK(p.translate_pk(t)?), Terminal::PkH(ref p) => Terminal::PkH(t.pk(p)?), Terminal::RawPkH(ref p) => Terminal::RawPkH(t.pkh(p)?), Terminal::After(n) => Terminal::After(n), @@ -186,7 +186,8 @@ impl Terminal { Terminal::Multi(k, keys?) } Terminal::MultiA(k, ref keys) => { - let keys: Result, _> = keys.iter().map(|k| t.pk(k)).collect(); + let keys: Result>, _> = + keys.iter().map(|k| k.translate_pk(t)).collect(); Terminal::MultiA(k, keys?) } }; @@ -455,7 +456,7 @@ impl_from_tree!( } let mut unwrapped = match (frag_name, top.args.len()) { ("pk_k", 1) => { - expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK)) + expression::unary(top, Terminal::PkK) } ("pk_h", 1) => expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)), ("after", 1) => expression::terminal(&top.args[0], |x| { @@ -522,15 +523,18 @@ impl_from_tree!( return Err(errstr("higher threshold than there were keys in multi")); } - let pks: Result, _> = top.args[1..] - .iter() - .map(|sub| expression::terminal(sub, Pk::from_str)) - .collect(); - if frag_name == "multi" { + let pks: Result, _> = top.args[1..] + .iter() + .map(|sub| expression::terminal(sub, Pk::from_str)) + .collect(); pks.map(|pks| Terminal::Multi(k, pks)) } else { // must be multi_a + let pks: Result>, _> = top.args[1..] + .iter() + .map(|sub| KeyExpr::::from_tree(sub)) + .collect(); pks.map(|pks| Terminal::MultiA(k, pks)) } } @@ -734,7 +738,7 @@ impl Terminal { 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_ms_key::<_, Ctx>(&pk); builder = builder.push_opcode(opcodes::all::OP_CHECKSIGADD); } builder @@ -753,7 +757,7 @@ impl Terminal { /// will handle the segwit/non-segwit technicalities for you. pub fn script_size(&self) -> usize { match *self { - Terminal::PkK(ref pk) => Ctx::pk_len(pk), + Terminal::PkK(ref pk) => Ctx::key_expr_len(pk), Terminal::PkH(..) | Terminal::RawPkH(..) => 24, Terminal::After(n) => script_num_size(n as usize) + 1, Terminal::Older(n) => script_num_size(n as usize) + 1, @@ -798,7 +802,7 @@ impl Terminal { Terminal::MultiA(k, ref pks) => { script_num_size(k) + 1 // NUMEQUAL - + pks.iter().map(|pk| Ctx::pk_len(pk)).sum::() // n keys + + pks.iter().map(|pk| Ctx::key_expr_len(pk)).sum::() // n keys + pks.len() // n times CHECKSIGADD } } diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index d838ee4ba..37bc750dd 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -26,6 +26,7 @@ use crate::miniscript::limits::{ MAX_SCRIPT_SIZE, MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS, }; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::types; use crate::prelude::*; use crate::util::witness_to_scriptsig; @@ -77,6 +78,8 @@ pub enum ScriptContextError { CheckMultiSigLimitExceeded, /// MultiA is only allowed in post tapscript MultiANotAllowed, + /// Musig is only allowed in tapscript and taproot descriptors + MusigNotAllowed(String), } #[cfg(feature = "std")] @@ -100,6 +103,7 @@ impl error::Error for ScriptContextError { | TaprootMultiDisabled | StackSizeLimitExceeded { .. } | CheckMultiSigLimitExceeded + | MusigNotAllowed(_) | MultiANotAllowed => None, } } @@ -180,6 +184,9 @@ impl fmt::Display for ScriptContextError { ScriptContextError::MultiANotAllowed => { write!(f, "Multi a(CHECKSIGADD) only allowed post tapscript") } + ScriptContextError::MusigNotAllowed(ref err) => { + write!(f, "Musig is only allowed in tapscript : err {}", err) + } } } } @@ -334,6 +341,13 @@ where /// 34 for Segwitv0, 33 for Tap fn pk_len(pk: &Pk) -> usize; + /// Get the len of the keyexpr + fn key_expr_len(pk: &KeyExpr) -> usize { + match pk { + KeyExpr::SingleKey(pk) => Self::pk_len(pk), + KeyExpr::MuSig(_) => 33, + } + } /// Local helper function to display error messages with context fn name_str() -> &'static str; } @@ -384,12 +398,21 @@ impl ScriptContext for Legacy { } match ms.node { - Terminal::PkK(ref key) if key.is_x_only_key() => { - return Err(ScriptContextError::XOnlyKeysNotAllowed( - key.to_string(), - Self::name_str(), - )) - } + Terminal::PkK(ref key) => match key { + KeyExpr::::SingleKey(pk) => { + if pk.is_x_only_key() { + return Err(ScriptContextError::XOnlyKeysNotAllowed( + pk.to_string(), + Self::name_str(), + )); + } + } + KeyExpr::::MuSig(_) => { + return Err(ScriptContextError::MusigNotAllowed(String::from( + Self::name_str(), + ))) + } + }, Terminal::Multi(_k, ref pks) => { if pks.len() > MAX_PUBKEYS_PER_MULTISIG { return Err(ScriptContextError::CheckMultiSigLimitExceeded); @@ -490,17 +513,24 @@ impl ScriptContext for Segwitv0 { } match ms.node { - Terminal::PkK(ref pk) => { - if pk.is_uncompressed() { - return Err(ScriptContextError::CompressedOnly(pk.to_string())); - } else if pk.is_x_only_key() { - return Err(ScriptContextError::XOnlyKeysNotAllowed( - pk.to_string(), + Terminal::PkK(ref key) => match key { + KeyExpr::::SingleKey(pk) => { + if pk.is_uncompressed() { + return Err(ScriptContextError::CompressedOnly(pk.to_string())); + } else if pk.is_x_only_key() { + return Err(ScriptContextError::XOnlyKeysNotAllowed( + pk.to_string(), + Self::name_str(), + )); + } + Ok(()) + } + KeyExpr::::MuSig(_) => { + return Err(ScriptContextError::MusigNotAllowed(String::from( Self::name_str(), - )); + ))); } - Ok(()) - } + }, Terminal::Multi(_k, ref pks) => { if pks.len() > MAX_PUBKEYS_PER_MULTISIG { return Err(ScriptContextError::CheckMultiSigLimitExceeded); @@ -618,13 +648,24 @@ impl ScriptContext for Tap { } match ms.node { - Terminal::PkK(ref pk) => { - if pk.is_uncompressed() { - return Err(ScriptContextError::UncompressedKeysNotAllowed); + Terminal::PkK(ref key) => { + if key.iter().any(|pk| pk.is_uncompressed()) { + Err(ScriptContextError::UncompressedKeysNotAllowed) + } else { + Ok(()) } - Ok(()) } Terminal::Multi(..) => Err(ScriptContextError::TaprootMultiDisabled), + Terminal::MultiA(_, ref keys) => { + if keys + .iter() + .all(|keyexpr| keyexpr.iter().any(|pk| pk.is_uncompressed())) + { + Err(ScriptContextError::UncompressedKeysNotAllowed) + } else { + Ok(()) + } + } _ => Ok(()), } } @@ -712,11 +753,23 @@ impl ScriptContext for BareCtx { return Err(ScriptContextError::MaxWitnessScriptSizeExceeded); } match ms.node { - Terminal::PkK(ref key) if key.is_x_only_key() => { - return Err(ScriptContextError::XOnlyKeysNotAllowed( - key.to_string(), - Self::name_str(), - )) + Terminal::PkK(ref key) => { + match key { + KeyExpr::::SingleKey(pk) => { + if pk.is_x_only_key() { + return Err(ScriptContextError::XOnlyKeysNotAllowed( + key.to_string(), + Self::name_str(), + )); + } + } + KeyExpr::::MuSig(_) => { + return Err(ScriptContextError::MusigNotAllowed(String::from( + Self::name_str(), + ))) + } + } + Ok(()) } Terminal::Multi(_k, ref pks) => { if pks.len() > MAX_PUBKEYS_PER_MULTISIG { @@ -753,7 +806,13 @@ impl ScriptContext for BareCtx { match &ms.node { Terminal::Check(ref ms) => match &ms.node { Terminal::RawPkH(_pkh) => Ok(()), - Terminal::PkK(_pk) | Terminal::PkH(_pk) => Ok(()), + Terminal::PkH(_pk) => Ok(()), + Terminal::PkK(key) => match key { + KeyExpr::::SingleKey(_pk) => Ok(()), + KeyExpr::::MuSig(_) => Err(Error::ContextError( + ScriptContextError::MusigNotAllowed(String::from(Self::name_str())), + )), + }, _ => Err(Error::NonStandardBareScript), }, Terminal::Multi(_k, subs) if subs.len() <= 3 => Ok(()), diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index d7f72c1d8..f09f176f8 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -28,6 +28,7 @@ use sync::Arc; use crate::miniscript::lex::{Token as Tk, TokenIter}; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::types::extra_props::ExtData; use crate::miniscript::types::{Property, Type}; use crate::miniscript::ScriptContext; @@ -132,7 +133,7 @@ pub enum Terminal { False, // pubkey checks /// `` - PkK(Pk), + PkK(KeyExpr), /// `DUP HASH160 EQUALVERIFY` PkH(Pk), /// Only for parsing PkH for Script @@ -192,7 +193,7 @@ pub enum Terminal { /// k ()* n CHECKMULTISIG Multi(usize, Vec), /// CHECKSIG ( CHECKSIGADD)*(n-1) k NUMEQUAL - MultiA(usize, Vec), + MultiA(usize, Vec>), } macro_rules! match_token { @@ -300,12 +301,12 @@ pub fn parse( Tk::Bytes33(pk) => { let ret = Ctx::Key::from_slice(pk) .map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?; - term.reduce0(Terminal::PkK(ret))? + term.reduce0(Terminal::PkK(KeyExpr::SingleKey(ret)))? }, Tk::Bytes65(pk) => { let ret = Ctx::Key::from_slice(pk) .map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?; - term.reduce0(Terminal::PkK(ret))? + term.reduce0(Terminal::PkK(KeyExpr::SingleKey(ret)))? }, // Note this does not collide with hash32 because they always followed by equal // and would be parsed in different branch. If we get a naked Bytes32, it must be @@ -321,7 +322,7 @@ pub fn parse( // Finally for the first case, K being parsed as a solo expression is a Pk type Tk::Bytes32(pk) => { let ret = Ctx::Key::from_slice(pk).map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?; - term.reduce0(Terminal::PkK(ret))? + term.reduce0(Terminal::PkK(KeyExpr::SingleKey(ret)))? }, // checksig Tk::CheckSig => { @@ -494,15 +495,15 @@ pub fn parse( 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()))?), + Tk::CheckSigAdd, Tk::Bytes32(pk) => keys.push(KeyExpr::SingleKey(::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()))?), + Tk::CheckSig, Tk::Bytes32(pk) => keys.push(KeyExpr::SingleKey(::from_slice(pk) + .map_err(|e| Error::PubKeyCtxError(e, Ctx::name_str()))?)), ); keys.reverse(); term.reduce0(Terminal::MultiA(k as usize, keys))?; diff --git a/src/miniscript/iter.rs b/src/miniscript/iter.rs index 1df400902..5cd30a684 100644 --- a/src/miniscript/iter.rs +++ b/src/miniscript/iter.rs @@ -22,6 +22,7 @@ use sync::Arc; use super::decode::Terminal; use super::{Miniscript, MiniscriptKey, ScriptContext}; +use crate::miniscript::musig_key::KeyExprIter; use crate::prelude::*; /// Iterator-related extensions for [Miniscript] @@ -125,8 +126,16 @@ impl Miniscript { /// `miniscript.iter_pubkeys().collect()`. pub fn get_leapk(&self) -> Vec { match self.node { - Terminal::PkK(ref key) | Terminal::PkH(ref key) => vec![key.clone()], - Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys.clone(), + Terminal::PkK(ref key) => key.iter().map(|pk| pk.clone()).collect(), + Terminal::PkH(ref key) => vec![key.clone()], + Terminal::Multi(_, ref keys) => keys.clone(), + Terminal::MultiA(_, ref keys) => { + let mut res: Vec = Vec::::new(); + for key in keys { + res.extend(key.iter().cloned()); + } + res + } _ => vec![], } } @@ -143,9 +152,17 @@ impl Miniscript { pub fn get_leapkh(&self) -> Vec { match self.node { Terminal::RawPkH(ref hash) => vec![hash.clone()], - Terminal::PkK(ref key) | Terminal::PkH(ref key) => vec![key.to_pubkeyhash()], - Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => { - keys.iter().map(Pk::to_pubkeyhash).collect() + Terminal::PkH(ref key) => vec![key.to_pubkeyhash()], + Terminal::PkK(ref key) => key.iter().map(|pk| pk.to_pubkeyhash()).collect(), + Terminal::Multi(_, ref keys) => keys.iter().map(Pk::to_pubkeyhash).collect(), + Terminal::MultiA(_, ref keys) => { + let mut res: Vec = Vec::::new(); + for key in keys { + for pk in key.iter() { + res.push(pk.to_pubkeyhash()); + } + } + res } _ => vec![], } @@ -161,13 +178,26 @@ impl Miniscript { pub fn get_leapk_pkh(&self) -> Vec> { match self.node { Terminal::RawPkH(ref hash) => vec![PkPkh::HashedPubkey(hash.clone())], - Terminal::PkH(ref key) | Terminal::PkK(ref key) => { + Terminal::PkH(ref key) => { vec![PkPkh::PlainPubkey(key.clone())] } - Terminal::Multi(_, ref keys) | Terminal::MultiA(_, ref keys) => keys + Terminal::PkK(ref key) => key + .iter() + .map(|pk| PkPkh::PlainPubkey(pk.clone())) + .collect(), + Terminal::Multi(_, ref keys) => keys .iter() .map(|key| PkPkh::PlainPubkey(key.clone())) .collect(), + Terminal::MultiA(_, ref keys) => { + let mut res: Vec> = Vec::>::new(); + for key in keys { + for pk in key.iter() { + res.push(PkPkh::PlainPubkey(pk.clone())); + } + } + res + } _ => vec![], } } @@ -176,16 +206,20 @@ impl Miniscript { /// if any. Otherwise returns `Option::None`. /// /// NB: The function analyzes only single miniscript item and not any of its descendants in AST. + pub fn get_nth_pk(&self, n: usize) -> Option { match (&self.node, n) { - (&Terminal::PkK(ref key), 0) | (&Terminal::PkH(ref key), 0) => Some(key.clone()), - (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { - keys.get(n).cloned() + (&Terminal::PkH(ref key), 0) => Some(key.clone()), + (&Terminal::Multi(_, ref keys), _) => { + if n < keys.len() { + Some(keys[n].clone()) + } else { + None + } } _ => None, } } - /// Returns `Option::Some` with hash of n'th public key from the current miniscript item, /// if any. Otherwise returns `Option::None`. /// @@ -193,14 +227,17 @@ impl Miniscript { /// returns it cloned copy. /// /// NB: The function analyzes only single miniscript item and not any of its descendants in AST. + pub fn get_nth_pkh(&self, n: usize) -> Option { match (&self.node, n) { (&Terminal::RawPkH(ref hash), 0) => Some(hash.clone()), - (&Terminal::PkK(ref key), 0) | (&Terminal::PkH(ref key), 0) => { - Some(key.to_pubkeyhash()) - } - (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { - keys.get(n).map(Pk::to_pubkeyhash) + (&Terminal::PkH(ref key), 0) => Some(key.to_pubkeyhash()), + (&Terminal::Multi(_, ref keys), _) => { + if n < keys.len() { + Some(keys[n].to_pubkeyhash()) + } else { + None + } } _ => None, } @@ -210,20 +247,56 @@ impl Miniscript { /// if any. Otherwise returns `Option::None`. /// /// NB: The function analyzes only single miniscript item and not any of its descendants in AST. + pub fn get_nth_pk_pkh(&self, n: usize) -> Option> { match (&self.node, n) { (&Terminal::RawPkH(ref hash), 0) => Some(PkPkh::HashedPubkey(hash.clone())), - (&Terminal::PkH(ref key), 0) | (&Terminal::PkK(ref key), 0) => { - Some(PkPkh::PlainPubkey(key.clone())) - } - (&Terminal::Multi(_, ref keys), _) | (&Terminal::MultiA(_, ref keys), _) => { - keys.get(n).map(|key| PkPkh::PlainPubkey(key.clone())) + (&Terminal::PkH(ref key), 0) => Some(PkPkh::PlainPubkey(key.clone())), + (&Terminal::Multi(_, ref keys), _) => { + if n < keys.len() { + Some(PkPkh::PlainPubkey(keys[n].clone())) + } else { + None + } } _ => None, } } } +/// Parent iter for all the below iters +struct BaseIter<'a, Pk: MiniscriptKey, Ctx: ScriptContext> { + node_iter: Iter<'a, Pk, Ctx>, + curr_node: Option<&'a Miniscript>, + // If current fragment is PkK or MultiA, then this is the iterator over public keys + musig_iter: Option>, + // Helps in checking whether current fragment is MultiA or not, + // additionally provides the length of vec + multi_a_len: Option, + key_index: usize, +} +impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> BaseIter<'a, Pk, Ctx> { + fn goto_next_node(&mut self) -> () { + self.curr_node = self.node_iter.next(); + self.key_index = 0; + let mut multi_a_len = None; + self.musig_iter = match self.curr_node { + Some(script) => match script.node { + Terminal::PkK(ref pk) => { + multi_a_len = Some(1 as u32); + Some(pk.iter()) + } + Terminal::MultiA(_, ref keys) => { + multi_a_len = Some(keys.len() as u32); + Some(keys[0].iter()) + } + _ => None, + }, + None => None, + }; + self.multi_a_len = multi_a_len; + } +} /// Iterator for traversing all [Miniscript] miniscript AST references starting from some specific /// node which constructs the iterator via [Miniscript::iter] method. pub struct Iter<'a, Pk: MiniscriptKey, Ctx: ScriptContext> { @@ -285,43 +358,95 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for Iter<'a, Pk, Ctx> { curr } } - /// Iterator for traversing all [MiniscriptKey]'s in AST starting from some specific node which /// constructs the iterator via [Miniscript::iter_pk] method. pub struct PkIter<'a, Pk: MiniscriptKey, Ctx: ScriptContext> { - node_iter: Iter<'a, Pk, Ctx>, - curr_node: Option<&'a Miniscript>, - key_index: usize, + base_iter: BaseIter<'a, Pk, Ctx>, } impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> PkIter<'a, Pk, Ctx> { fn new(miniscript: &'a Miniscript) -> Self { let mut iter = Iter::new(miniscript); - PkIter { - curr_node: iter.next(), + let curr_node = iter.next(); + let mut multi_a_len = None; + let musig_iter = match curr_node { + Some(script) => match script.node { + Terminal::PkK(ref pk) => { + multi_a_len = Some(1 as u32); + Some(pk.iter()) + } + Terminal::MultiA(_, ref keys) => { + multi_a_len = Some(keys.len() as u32); + Some(keys[0].iter()) + } + _ => None, + }, + None => None, + }; + let bs_iter = BaseIter { + curr_node: curr_node, node_iter: iter, + musig_iter: musig_iter, key_index: 0, - } + multi_a_len: multi_a_len, + }; + PkIter { base_iter: bs_iter } } } impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkIter<'a, Pk, Ctx> { type Item = Pk; - fn next(&mut self) -> Option { loop { - match self.curr_node { + match self.base_iter.curr_node { None => break None, - Some(node) => match node.get_nth_pk(self.key_index) { - None => { - self.curr_node = self.node_iter.next(); - self.key_index = 0; - continue; + Some(script) => match &script.node { + Terminal::PkK(_) => { + // check if musig_iter has something + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(pk.clone()), + None => { + self.base_iter.goto_next_node(); + continue; + } + } } - Some(pk) => { - self.key_index += 1; - break Some(pk); + Terminal::MultiA(_, keys) => { + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(pk.clone()), + None => { + // When the current iterator has yielded all the keys + let vec_size = self.base_iter.multi_a_len.unwrap(); + self.base_iter.key_index += 1; + if (self.base_iter.key_index as u32) < vec_size { + // goto the next KeyExpr in the vector + self.base_iter.musig_iter = + Some(keys[self.base_iter.key_index].iter()); + match self.base_iter.musig_iter.as_mut().unwrap().next() { + None => { + self.base_iter.key_index += 1; + continue; + } + Some(pk) => break Some(pk.clone()), + } + } else { + // if we have exhausted all the KeyExpr + self.base_iter.goto_next_node(); + continue; + } + } + } } + _ => match script.get_nth_pk(self.base_iter.key_index) { + Some(pk) => { + self.base_iter.key_index += 1; + break Some(pk); + } + None => { + self.base_iter.goto_next_node(); + continue; + } + }, }, } } @@ -331,39 +456,92 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkIter<'a, Pk, Ctx> /// Iterator for traversing all [MiniscriptKey] hashes in AST starting from some specific node which /// constructs the iterator via [Miniscript::iter_pkh] method. pub struct PkhIter<'a, Pk: MiniscriptKey, Ctx: ScriptContext> { - node_iter: Iter<'a, Pk, Ctx>, - curr_node: Option<&'a Miniscript>, - key_index: usize, + base_iter: BaseIter<'a, Pk, Ctx>, } impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> PkhIter<'a, Pk, Ctx> { fn new(miniscript: &'a Miniscript) -> Self { let mut iter = Iter::new(miniscript); - PkhIter { - curr_node: iter.next(), + let curr_node = iter.next(); + let mut multi_a_len = None; + let musig_iter = match curr_node { + Some(script) => match script.node { + Terminal::PkK(ref pk) => { + multi_a_len = Some(1 as u32); + Some(pk.iter()) + } + Terminal::MultiA(_, ref keys) => { + multi_a_len = Some(keys.len() as u32); + Some(keys[0].iter()) + } + _ => None, + }, + None => None, + }; + let bs_iter = BaseIter { + curr_node: curr_node, node_iter: iter, + musig_iter: musig_iter, + multi_a_len: multi_a_len, key_index: 0, - } + }; + PkhIter { base_iter: bs_iter } } } impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkhIter<'a, Pk, Ctx> { type Item = Pk::RawPkHash; - fn next(&mut self) -> Option { loop { - match self.curr_node { + match self.base_iter.curr_node { None => break None, - Some(node) => match node.get_nth_pkh(self.key_index) { - None => { - self.curr_node = self.node_iter.next(); - self.key_index = 0; - continue; + Some(script) => match &script.node { + Terminal::PkK(_) => { + // check if musig_iter has something + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(pk.to_pubkeyhash()), + None => { + self.base_iter.goto_next_node(); + continue; + } + } } - Some(pk) => { - self.key_index += 1; - break Some(pk); + Terminal::MultiA(_, keys) => { + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(pk.to_pubkeyhash()), + None => { + // When the current iterator has yielded all the keys + let vec_size = self.base_iter.multi_a_len.unwrap(); + self.base_iter.key_index += 1; + if (self.base_iter.key_index as u32) < vec_size { + // goto the next KeyExpr in the vector + self.base_iter.musig_iter = + Some(keys[self.base_iter.key_index].iter()); + match self.base_iter.musig_iter.as_mut().unwrap().next() { + None => { + self.base_iter.key_index += 1; + continue; + } + Some(pk) => break Some(pk.to_pubkeyhash()), + } + } else { + // if we have exhausted all the KeyExpr + self.base_iter.goto_next_node(); + continue; + } + } + } } + _ => match script.get_nth_pkh(self.base_iter.key_index) { + Some(pk) => { + self.base_iter.key_index += 1; + break Some(pk); + } + None => { + self.base_iter.goto_next_node(); + continue; + } + }, }, } } @@ -393,19 +571,36 @@ impl> PkPkh { /// starting from some specific node which constructs the iterator via /// [Miniscript::iter_pk_pkh] method. pub struct PkPkhIter<'a, Pk: MiniscriptKey, Ctx: ScriptContext> { - node_iter: Iter<'a, Pk, Ctx>, - curr_node: Option<&'a Miniscript>, - key_index: usize, + base_iter: BaseIter<'a, Pk, Ctx>, } impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> PkPkhIter<'a, Pk, Ctx> { fn new(miniscript: &'a Miniscript) -> Self { let mut iter = Iter::new(miniscript); - PkPkhIter { - curr_node: iter.next(), + let curr_node = iter.next(); + let mut multi_a_len = None; + let musig_iter = match curr_node { + Some(script) => match script.node { + Terminal::PkK(ref pk) => { + multi_a_len = Some(1 as u32); + Some(pk.iter()) + } + Terminal::MultiA(_, ref keys) => { + multi_a_len = Some(keys.len() as u32); + Some(keys[0].iter()) + } + _ => None, + }, + None => None, + }; + let bs_iter = BaseIter { + curr_node: curr_node, node_iter: iter, key_index: 0, - } + musig_iter: musig_iter, + multi_a_len: multi_a_len, + }; + PkPkhIter { base_iter: bs_iter } } /// Returns a `Option`, listing all public keys found in AST starting from this @@ -438,18 +633,55 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkPkhIter<'a, Pk, C fn next(&mut self) -> Option { loop { - match self.curr_node { + match self.base_iter.curr_node { None => break None, - Some(node) => match node.get_nth_pk_pkh(self.key_index) { - None => { - self.curr_node = self.node_iter.next(); - self.key_index = 0; - continue; + Some(script) => match &script.node { + Terminal::PkK(_) => { + // check if musig_iter has something + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(PkPkh::PlainPubkey(pk.clone())), + None => { + self.base_iter.goto_next_node(); + continue; + } + } } - Some(pk) => { - self.key_index += 1; - break Some(pk); + Terminal::MultiA(_, keys) => { + match self.base_iter.musig_iter.as_mut().unwrap().next() { + Some(pk) => break Some(PkPkh::PlainPubkey(pk.clone())), + None => { + // When the current iterator has yielded all the keys + let vec_size = self.base_iter.multi_a_len.unwrap(); + self.base_iter.key_index += 1; + if (self.base_iter.key_index as u32) < vec_size { + // goto the next KeyExpr in the vector + self.base_iter.musig_iter = + Some(keys[self.base_iter.key_index].iter()); + match self.base_iter.musig_iter.as_mut().unwrap().next() { + None => { + self.base_iter.key_index += 1; + continue; + } + Some(pk) => break Some(PkPkh::PlainPubkey(pk.clone())), + } + } else { + // if we have exhausted all the KeyExpr + self.base_iter.goto_next_node(); + continue; + } + } + } } + _ => match script.get_nth_pk_pkh(self.base_iter.key_index) { + Some(pk) => { + self.base_iter.key_index += 1; + break Some(pk.clone()); + } + None => { + self.base_iter.goto_next_node(); + continue; + } + }, }, } } @@ -460,12 +692,16 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> Iterator for PkPkhIter<'a, Pk, C // dependent libraries for their own tasts based on Miniscript AST #[cfg(test)] pub mod test { + use core::str::FromStr; + use bitcoin; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; use bitcoin::secp256k1; - use super::{Miniscript, PkPkh}; - use crate::miniscript::context::Segwitv0; + use super::{Miniscript, PkIter, PkPkh}; + use crate::miniscript::context::{Segwitv0, Tap}; + type Segwitv0String = Miniscript; + type TapscriptString = Miniscript; pub type TestData = ( Miniscript, @@ -604,6 +840,51 @@ pub mod test { ] } + #[test] + fn test_musig_iter() { + // TEST: musig inside Pk in Tap context + let ms = TapscriptString::from_str("or_b(pk(A),s:pk(musig(B,C)))").unwrap(); + let mut pk_iter = PkIter::new(&ms); + let keys = vec!["A", "B", "C"]; + for key in keys { + assert_eq!(String::from(key), pk_iter.next().unwrap()); + } + + // TEST: complex musig in Tap context + let ms = + TapscriptString::from_str("or_b(pk(A),s:pk(musig(F,B,musig(C,musig(D,E)))))").unwrap(); + let mut pk_iter = PkIter::new(&ms); + let keys = vec!["A", "F", "B", "C", "D", "E"]; + for key in keys { + assert_eq!(String::from(key), pk_iter.next().unwrap()); + } + + // TEST: without musig in segwit context + let ms = Segwitv0String::from_str("or_b(pk(A),s:pk(B))").unwrap(); + let mut pk_iter = PkIter::new(&ms); + let keys = vec!["A", "B"]; + for key in keys { + assert_eq!(String::from(key), pk_iter.next().unwrap()); + } + + // TEST: musig inside multi_a in Tap context + let ms = TapscriptString::from_str("or_b(pk(A),a:multi_a(1,B,musig(C,D)))").unwrap(); + let mut pk_iter = PkIter::new(&ms); + let keys = vec!["A", "B", "C", "D"]; + for key in keys { + assert_eq!(String::from(key), pk_iter.next().unwrap()); + } + + // TEST: musig and normal key in Tap context + let ms = + TapscriptString::from_str("or_b(pk(musig(A1,A2)),a:multi_a(1,B,musig(C,musig(D,E))))") + .unwrap(); + let mut pk_iter = PkIter::new(&ms); + let keys = vec!["A1", "A2", "B", "C", "D", "E"]; + for key in keys { + assert_eq!(String::from(key), pk_iter.next().unwrap()); + } + } #[test] fn get_keys() { gen_testcases() diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 9d50d63d1..7dec34b7d 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -41,6 +41,7 @@ pub mod hash256; pub mod iter; pub mod lex; pub mod limits; +pub mod musig_key; pub mod satisfy; pub mod types; @@ -454,19 +455,134 @@ mod tests { use bitcoin::secp256k1::XOnlyPublicKey; use bitcoin::util::taproot::TapLeafHash; use bitcoin::{self, secp256k1}; + use secp256k1::{rand, KeyPair, Secp256k1}; use sync::Arc; use super::{Miniscript, ScriptContext, Segwitv0, Tap}; + use crate::miniscript::context::ScriptContextError; + use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::types::{self, ExtData, Property, Type}; use crate::miniscript::Terminal; use crate::policy::Liftable; use crate::prelude::*; use crate::test_utils::{StrKeyTranslator, StrXOnlyKeyTranslator}; - use crate::{hex_script, DummyKey, DummyKeyHash, Satisfier, ToPublicKey, TranslatePk}; + use crate::{ + hex_script, Descriptor, DummyKey, DummyKeyHash, Error, ForEachKey, Legacy, Satisfier, + ToPublicKey, TranslatePk, + }; type Segwitv0Script = Miniscript; + type Segwitv0String = Miniscript; + type TapscriptString = Miniscript; type Tapscript = Miniscript; + #[test] + fn musig_validity() { + // create a miniscript with segwit context and try to add a musig key + // expect to receive error + let ms = Segwitv0String::from_str("or_b(pk(A),s:pk(musig(B,C)))"); + assert_eq!( + Error::ContextError(ScriptContextError::MusigNotAllowed(String::from( + "Segwitv0" + ))), + ms.unwrap_err() + ); + let ms = Segwitv0String::from_str("or_b(pk(A),s:pk(musig(A,B,musig(C,musig(D,E)))))"); + assert_eq!( + Error::ContextError(ScriptContextError::MusigNotAllowed(String::from( + "Segwitv0" + ))), + ms.unwrap_err() + ); + // create a miniscript with tapscript context and have musig key inside it + // expect to get parsed correctly. + let ms = TapscriptString::from_str("or_b(pk(A),s:pk(musig(B,C)))"); + assert_eq!(ms.is_ok(), true); + + // create a P2SH descriptor with musig key inside and expect to receive an error + let desc = Descriptor::::from_str("sh(pk(musig(K2,K3)))"); + assert_eq!( + Error::ContextError(ScriptContextError::MusigNotAllowed(String::from( + "Legacy/p2sh" + ))), + desc.unwrap_err() + ); + + let desc = Descriptor::::from_str("sh(and_v(v:pk(A),pk(musig(B))))"); + assert_eq!( + Error::ContextError(ScriptContextError::MusigNotAllowed(String::from( + "Legacy/p2sh" + ))), + desc.unwrap_err() + ); + // create a Tr descriptor with musig key inside and expect to get parsed correctly + let desc = Descriptor::::from_str("tr(X,{pk(musig(X1)),multi_a(1,X2,X3)})"); + assert_eq!(desc.is_ok(), true); + + let desc = + Descriptor::::from_str("tr(pk(musig(E)),{pk(A),multi_a(1,B,musig(C,D))})"); + assert_eq!(desc.is_ok(), true); + + let desc = Descriptor::::from_str("tr(pk(musig(D)),pk(musig(A,B,musig(C))))"); + assert_eq!(desc.is_ok(), true); + } + + #[test] + fn check_script_size() { + type Segwitv0MS = Miniscript; + type TapMS = Miniscript; + type LegacyMS = Miniscript; + + let secp = Secp256k1::new(); + let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); + let comp_key = secp256k1::PublicKey::from_keypair(&key_pair); + let xonly = secp256k1::XOnlyPublicKey::from_keypair(&key_pair); + + // pk(compressed) in Segwitv0 context + let ms: Segwitv0MS = ms_str!("pk({})", &comp_key.to_string()); + assert_eq!(1 + 33 + 1, ms.script_size()); // PUSH_LEN key_size OP_CHECKSIG + + // pk(xonly) in Tap context + let ms: TapMS = ms_str!("pk({})", &xonly.to_string()); + assert_eq!(1 + 32 + 1, ms.script_size()); // PUSH_LEN key_size OP_CHECKSIG + + // pk(musig) in Tap context + let ms: TapMS = ms_str!("pk(musig({}))", &xonly.to_string()); + assert_eq!(1 + 32 + 1, ms.script_size()); // PUSH_LEN key_size OP_CHECKSIG + + // pk(uncompressed) in Legacy context + let pk = bitcoin::PublicKey::from_str( + "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133" + ).unwrap(); + let ms: LegacyMS = ms_str!("pk({})", &pk.to_string()); + assert_eq!(1 + 65 + 1, ms.script_size()); // PUSH_LEN key_size OP_CHECKSIG + } + + #[test] + fn test_for_each_key() { + // TEST: regular miniscript without musig + let normal_ms: Miniscript = + Miniscript::::from_str("and_v(v:pk(A),multi_a(2,B,C,D))").unwrap(); + assert_eq!(normal_ms.for_each_key(|pk| pk.len() == 1), true); + assert_eq!(normal_ms.for_each_key(|pk| pk.len() > 0), true); + + // TEST: musig inside pk + let musig_ms: Miniscript = + Miniscript::::from_str("or_b(pk(A),s:pk(musig(B,CC)))").unwrap(); + assert_eq!(musig_ms.for_each_key(|pk| pk.len() == 1), false); + assert_eq!(musig_ms.for_each_key(|pk| pk.len() > 0), true); + + // TEST: complex script containing musig inside inside pk + let musig_ms: Miniscript = Miniscript::::from_str( + "or_b(and_b(pk(musig(A,B)),s:pk(F)),a:multi_a(1,C,musig(musig(D,E))))", + ) + .unwrap(); + + assert_eq!(musig_ms.for_each_key(|pk| pk.len() == 1), true); + assert_eq!(musig_ms.for_each_key(|pk| pk.len() > 0), true); + assert_eq!(musig_ms.for_each_key(|pk| pk.starts_with("A")), false); + } + fn pubkeys(n: usize) -> Vec { let mut ret = Vec::with_capacity(n); let secp = secp256k1::Secp256k1::new(); @@ -488,6 +604,17 @@ mod tests { ret } + fn xonly_pubkeys(n: usize) -> Vec { + let mut ret = Vec::with_capacity(n); + let secp = secp256k1::Secp256k1::new(); + for _ in 0..n { + let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); + let xonly = XOnlyPublicKey::from_keypair(&key_pair); + ret.push(xonly) + } + ret + } + fn string_rtt( script: Miniscript, expected_debug: &str, @@ -644,7 +771,7 @@ mod tests { let pkk_ms: Miniscript = Miniscript { node: Terminal::Check(Arc::new(Miniscript { - node: Terminal::PkK(DummyKey), + node: Terminal::PkK(KeyExpr::SingleKey(DummyKey)), ty: Type::from_pk_k::(), ext: types::extra_props::ExtData::from_pk_k::(), phantom: PhantomData, @@ -682,7 +809,7 @@ mod tests { let pkk_ms: Segwitv0Script = Miniscript { node: Terminal::Check(Arc::new(Miniscript { - node: Terminal::PkK(pk), + node: Terminal::PkK(KeyExpr::SingleKey(pk)), ty: Type::from_pk_k::(), ext: types::extra_props::ExtData::from_pk_k::(), phantom: PhantomData, @@ -1033,6 +1160,185 @@ mod tests { .unwrap(); } + #[test] + fn musig_translate() { + type TapscriptString = Miniscript; + let ms = TapscriptString::from_str("or_b(pk(A),s:pk(musig(B,C)))").unwrap(); + + // TEST: musig inside pk + let tap_ms = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + "or_b(pk(8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa),s:pk(musig(ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2,9729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40)))", + tap_ms.to_string(), + ); + + // TEST: regular miniscript without musig + let ms = TapscriptString::from_str("and_v(v:pk(A),multi_a(2,B,C,D))").unwrap(); + let tap_ms = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + "and_v(v:pk(8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa),multi_a(2,ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2,9729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40,2564fe9b5beef82d3703a607253f31ef8ea1b365772df434226aee642651b3fa))", + tap_ms.to_string(), + ); + + // TEST: nested musig + let ms = + TapscriptString::from_str("or_b(pk(A),s:pk(musig(B,musig(C,musig(D,E)))))").unwrap(); + let tap_ms = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + "or_b(pk(8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa),s:pk(musig(ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2,musig(9729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40,musig(2564fe9b5beef82d3703a607253f31ef8ea1b365772df434226aee642651b3fa,89637f97580a796e050791ad5a2f27af1803645d95df021a3c2d82eb8c2ca7ff)))))", + tap_ms.to_string(), + ); + + // TEST: musig inside multi_a + let ms = TapscriptString::from_str("and_v(v:pk(A),multi_a(1,musig(B,C),D))").unwrap(); + let tap_ms = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + "and_v(v:pk(8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa),multi_a(1,musig(ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2,9729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40),2564fe9b5beef82d3703a607253f31ef8ea1b365772df434226aee642651b3fa))", + tap_ms.to_string(), + ); + + // TEST: complex script containing musig inside inside pk + let ms = TapscriptString::from_str( + "or_b(and_b(pk(musig(A,B)),s:pk(F)),a:multi_a(1,C,musig(musig(D,E))))", + ) + .unwrap(); + let tap_ms = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + "or_b(and_b(pk(musig(8c28a97bf8298bc0d23d8c749452a32e694b65e30a9472a3954ab30fe5324caa,ab1ac1872a38a2f196bed5a6047f0da2c8130fe8de49fc4d5dfb201f7611d8e2)),s:pk(71efa4e26a4179e112860b88fc98658a4bdbc59c7ab6d4f8057c35330c7a89ee)),a:multi_a(1,9729247032c0dfcf45b4841fcd72f6e9a2422631fc3466cf863e87154754dd40,musig(musig(2564fe9b5beef82d3703a607253f31ef8ea1b365772df434226aee642651b3fa,89637f97580a796e050791ad5a2f27af1803645d95df021a3c2d82eb8c2ca7ff))))", + tap_ms.to_string(), + ); + } + + #[test] + fn musig_encode_decode_tests() { + // TEST: regular miniscript without musig + let ms = Miniscript::::from_str("and_v(v:pk(A),multi_a(2,B,C,D))").unwrap(); + assert_eq!(ms.to_string(), "and_v(v:pk(A),multi_a(2,B,C,D))"); + let ms_with_keys = ms.translate_pk(&mut StrXOnlyKeyTranslator::new()).unwrap(); + assert_eq!( + Miniscript::::parse(&ms_with_keys.encode()).unwrap(), + ms_with_keys + ); + assert_eq!(ms_with_keys.encode().len(), ms_with_keys.script_size()); + + // TEST: musig inside pk + let ms = Miniscript::::from_str("or_b(pk(A),s:pk(musig(B,C)))").unwrap(); + assert_eq!(ms.to_string(), "or_b(pk(A),s:pk(musig(B,C)))"); + let keys = xonly_pubkeys(3); + let ms_with_keys: Miniscript = + ms_str!("or_b(pk({}),s:pk(musig({},{})))", keys[0], keys[1], keys[2]); + let musig_key = KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[1]), + KeyExpr::SingleKey(keys[2]), + ]); + let ms_post_encoding: Miniscript = + ms_str!("or_b(pk({}),s:pk({}))", keys[0], musig_key.key_agg(),); + assert_eq!( + Miniscript::::parse_insane(&ms_with_keys.encode()).unwrap(), + ms_post_encoding + ); + + // TEST: nested musig + let ms = + Miniscript::::from_str("or_b(pk(A),s:pk(musig(B,musig(C,musig(D,E)))))") + .unwrap(); + assert_eq!( + ms.to_string(), + "or_b(pk(A),s:pk(musig(B,musig(C,musig(D,E)))))" + ); + let keys = xonly_pubkeys(5); + let ms_with_keys: Miniscript = ms_str!( + "or_b(pk({}),s:pk(musig({},musig({},musig({},{})))))", + keys[0], + keys[1], + keys[2], + keys[3], + keys[4], + ); + let musig_key = KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[1]), + KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[2]), + KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[3]), + KeyExpr::SingleKey(keys[4]), + ]), + ]), + ]); + let ms_post_encoding: Miniscript = + ms_str!("or_b(pk({}),s:pk({}))", keys[0], musig_key.key_agg(),); + assert_eq!( + Miniscript::::parse_insane(&ms_with_keys.encode()).unwrap(), + ms_post_encoding + ); + + // TEST: musig inside multi_a + let ms = + Miniscript::::from_str("and_v(v:pk(A),multi_a(1,musig(B,C),D))").unwrap(); + assert_eq!(ms.to_string(), "and_v(v:pk(A),multi_a(1,musig(B,C),D))"); + let keys = xonly_pubkeys(4); + let ms_with_keys: Miniscript = ms_str!( + "and_v(v:pk({}),multi_a(1,musig({},{}),{}))", + keys[0], + keys[1], + keys[2], + keys[3], + ); + let musig_key = KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[1]), + KeyExpr::SingleKey(keys[2]), + ]); + let ms_post_encoding: Miniscript = ms_str!( + "and_v(v:pk({}),multi_a(1,{},{}))", + keys[0], + musig_key.key_agg(), + keys[3], + ); + assert_eq!( + Miniscript::::parse_insane(&ms_with_keys.encode()).unwrap(), + ms_post_encoding + ); + + // TEST: complex script containing musig inside inside pk + let ms = Miniscript::::from_str( + "or_b(and_b(pk(musig(A,B)),s:pk(C)),a:multi_a(1,D,musig(musig(E,F))))", + ) + .unwrap(); + assert_eq!( + ms.to_string(), + "or_b(and_b(pk(musig(A,B)),s:pk(C)),a:multi_a(1,D,musig(musig(E,F))))" + ); + let keys = xonly_pubkeys(6); + let ms_with_keys: Miniscript = ms_str!( + "or_b(and_b(pk(musig({},{})),s:pk({})),a:multi_a(1,{},musig(musig({},{}))))", + keys[0], + keys[1], + keys[2], + keys[3], + keys[4], + keys[5], + ); + let musig_key_1 = KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[0]), + KeyExpr::SingleKey(keys[1]), + ]); + let musig_key_2 = KeyExpr::MuSig(vec![KeyExpr::MuSig(vec![ + KeyExpr::SingleKey(keys[4]), + KeyExpr::SingleKey(keys[5]), + ])]); + let ms_post_encoding: Miniscript = ms_str!( + "or_b(and_b(pk({}),s:pk({})),a:multi_a(1,{},{}))", + musig_key_1.key_agg(), + keys[2], + keys[3], + musig_key_2.key_agg(), + ); + assert_eq!( + Miniscript::::parse_insane(&ms_with_keys.encode()).unwrap(), + ms_post_encoding + ); + } + #[test] fn multi_a_tests() { // Test from string tests diff --git a/src/miniscript/musig_key.rs b/src/miniscript/musig_key.rs new file mode 100644 index 000000000..3b2b6866a --- /dev/null +++ b/src/miniscript/musig_key.rs @@ -0,0 +1,270 @@ +//! Support for multi-signature keys +use core::fmt; +use core::str::FromStr; + +use bitcoin::secp256k1::Secp256k1; +#[cfg(feature = "std")] +use secp256k1_zkp::MusigKeyAggCache; +use bitcoin::secp256k1::VerifyOnly; + +use crate::expression::{FromTree, Tree}; +use crate::prelude::*; +use crate::{expression, Error, ForEachKey, MiniscriptKey, ToPublicKey, TranslatePk, Translator}; + +#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] +/// Enum for representing musig keys in miniscript +pub enum KeyExpr { + /// Single-key (e.g pk(a), here 'a' is a single key) + SingleKey(Pk), + + /// Collection of keys in used for musig-signature + MuSig(Vec>), +} + +impl FromTree for KeyExpr +where + ::Err: ToString, +{ + fn from_tree(tree: &Tree) -> Result, Error> { + if tree.name == "musig" { + let key_expr_vec = tree + .args + .iter() + .map(|subtree| KeyExpr::::from_tree(subtree)) + .collect::>, Error>>()?; + Ok(KeyExpr::MuSig(key_expr_vec)) + } else { + let single_key = expression::terminal(tree, Pk::from_str)?; + Ok(KeyExpr::SingleKey(single_key)) + } + } +} + +impl FromStr for KeyExpr +where + ::Err: ToString, +{ + type Err = Error; + fn from_str(s: &str) -> Result { + let (key_tree, _) = Tree::from_slice(s)?; + FromTree::from_tree(&key_tree) + } +} + +#[derive(Debug, Clone)] +/// Iterator for [`KeyExpr`] +pub struct KeyExprIter<'a, Pk: MiniscriptKey> { + stack: Vec<&'a KeyExpr>, +} + +impl<'a, Pk> Iterator for KeyExprIter<'a, Pk> +where + Pk: MiniscriptKey + 'a, +{ + type Item = &'a Pk; + + fn next(&mut self) -> Option { + while !self.stack.is_empty() { + let last = self.stack.pop().expect("Size checked above"); + match last { + KeyExpr::MuSig(key_vec) => { + // push the elements in reverse order + key_vec.iter().rev().for_each(|key| self.stack.push(key)); + } + KeyExpr::SingleKey(ref pk) => return Some(pk), + } + } + None + } +} + +impl fmt::Debug for KeyExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + KeyExpr::SingleKey(ref pk) => write!(f, "{:?}", pk), + KeyExpr::MuSig(ref my_vec) => { + write!(f, "musig(")?; + let len = my_vec.len(); + for (index, k) in my_vec.iter().enumerate() { + if index == len - 1 { + write!(f, "{:?}", k)?; + } else { + write!(f, "{:?}", k)?; + } + } + f.write_str(")") + } + } + } +} + +impl fmt::Display for KeyExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + KeyExpr::SingleKey(ref pk) => write!(f, "{}", pk), + KeyExpr::MuSig(ref my_vec) => { + write!(f, "musig(")?; + let len = my_vec.len(); + for (index, k) in my_vec.iter().enumerate() { + if index == len - 1 { + write!(f, "{}", k)?; + } else { + write!(f, "{},", k)?; + } + } + f.write_str(")") + } + } + } +} + +impl KeyExpr { + /// Iterate over all keys + pub fn iter(&self) -> KeyExprIter { + KeyExprIter { stack: vec![self] } + } +} + +impl KeyExpr { + /// Returns an XOnlyPublicKey from a KeyExpr + pub fn key_agg(&self) -> bitcoin::XOnlyPublicKey { + match self { + KeyExpr::::SingleKey(pk) => pk.to_x_only_pubkey(), + KeyExpr::::MuSig(_keys) => { + let secp = Secp256k1::verification_only(); + self.key_agg_helper(&secp) + } + } + } + + #[cfg(feature = "std")] + fn key_agg_helper(&self, secp: &Secp256k1) -> bitcoin::XOnlyPublicKey { + match self { + KeyExpr::::SingleKey(pk) => pk.to_x_only_pubkey(), + KeyExpr::::MuSig(keys) => { + let xonly = keys + .iter() + .map(|key| key.key_agg_helper(secp)) + .collect::>(); + let key_agg_cache = MusigKeyAggCache::new(secp, &xonly); + key_agg_cache.agg_pk() + } + } + } + + #[cfg(not(feature = "std"))] + fn key_agg_helper(&self, secp: &Secp256k1) -> bitcoin::XOnlyPublicKey { + match self { + KeyExpr::::SingleKey(pk) => pk.to_x_only_pubkey(), + KeyExpr::::MuSig(keys) => { + unimplemented!("Musig not supported for no-std"); + } + } + } +} + +impl ForEachKey for KeyExpr { + fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool + where + Pk: 'a, + Pk::RawPkHash: 'a, + { + let keys_res = self.iter().all(|key| pred(key)); + keys_res + } +} + +impl TranslatePk for KeyExpr

+where + P: MiniscriptKey, + Q: MiniscriptKey, +{ + type Output = KeyExpr; + fn translate_pk(&self, t: &mut T) -> Result + where + T: Translator, + { + match self { + KeyExpr::

::SingleKey(pk) => Ok(KeyExpr::SingleKey(t.pk(pk)?)), + KeyExpr::

::MuSig(vec) => { + let mut new_vec: Vec> = vec![]; + for x in vec { + new_vec.push(x.translate_pk(t)?) + } + Ok(KeyExpr::MuSig(new_vec)) + } + } + } +} + +impl KeyExpr { + /// Returns the Pk if KeyExpr is SingleKey, otherwise None + pub fn single_key(&self) -> Option<&Pk> { + match self { + KeyExpr::::SingleKey(ref pk) => Some(pk), + KeyExpr::::MuSig(_) => None, + } + } + + /// Return vector of bytes, in case of musig, it is first aggregated + /// and then converted to bytes + pub fn to_vec(&self) -> Vec + where + Pk: ToPublicKey, + { + match self { + KeyExpr::::SingleKey(ref pk) => pk.to_public_key().to_bytes(), + KeyExpr::::MuSig(_) => { + let agg_key = self.key_agg(); + agg_key.to_public_key().to_bytes() + } + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + fn test_one(musig_key: &str) { + let pk = KeyExpr::::from_str(musig_key).unwrap(); + println!("{}", pk); + assert_eq!(musig_key, format!("{}", pk)) + } + + #[test] + fn test_from_str_and_fmt() { + test_one("musig(A,B,musig(C,musig(D,E)))"); + test_one("musig(A)"); + test_one("A"); + test_one("musig(,,)"); + } + + #[test] + fn test_iterator() { + let pk = KeyExpr::::from_str("musig(A,B,musig(C,musig(D,E)))").unwrap(); + let mut my_iter = pk.iter(); + assert_eq!(my_iter.next(), Some(&String::from("A"))); + assert_eq!(my_iter.next(), Some(&String::from("B"))); + assert_eq!(my_iter.next(), Some(&String::from("C"))); + assert_eq!(my_iter.next(), Some(&String::from("D"))); + assert_eq!(my_iter.next(), Some(&String::from("E"))); + assert_eq!(my_iter.next(), None); + } + + fn test_helper(musig_key: &str, comma_separated_key: &str) { + let pk = KeyExpr::::from_str(musig_key).unwrap(); + let var: Vec<&str> = comma_separated_key.split(",").collect(); + let key_names: Vec<&String> = pk.iter().collect(); + for (key1, key2) in key_names.iter().zip(var.iter()) { + assert_eq!(key1, key2); + } + } + + #[test] + fn test_iterator_multi() { + test_helper("musig(A)", "A"); + test_helper("A", "A"); + test_helper("musig(,,)", ""); + test_helper("musig(musig(A,B),musig(musig(C)))", "A,B,C"); + } +} diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 37723117c..81fd778d9 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -28,6 +28,7 @@ use sync::Arc; use crate::miniscript::limits::{ LOCKTIME_THRESHOLD, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_TYPE_FLAG, }; +use crate::miniscript::musig_key::KeyExpr; use crate::prelude::*; use crate::util::witness_size; use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; @@ -546,21 +547,27 @@ impl Witness { /// Turn a signature into (part of) a satisfaction fn signature, Ctx: ScriptContext>( sat: S, - pk: &Pk, + pk: &KeyExpr, leaf_hash: &TapLeafHash, ) -> 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_tap_leaf_script_sig(pk, leaf_hash) - { - Some(sig) => Witness::Stack(vec![sig.to_vec()]), - // Signatures cannot be forged - None => Witness::Impossible, - }, + match pk { + KeyExpr::::SingleKey(pkk) => { + match Ctx::sig_type() { + super::context::SigType::Ecdsa => match sat.lookup_ecdsa_sig(pkk) { + Some(sig) => Witness::Stack(vec![sig.to_vec()]), + // Signatures cannot be forged + None => Witness::Impossible, + }, + super::context::SigType::Schnorr => { + match sat.lookup_tap_leaf_script_sig(pkk, leaf_hash) { + Some(sig) => Witness::Stack(vec![sig.to_vec()]), + // Signatures cannot be forged + None => Witness::Impossible, + } + } + } + } + KeyExpr::::MuSig(_) => Witness::Impossible, // Musig satisfaction is not implemented } } @@ -1144,7 +1151,11 @@ impl Satisfaction { let mut sig_count = 0; let mut sigs = Vec::with_capacity(k); for pk in keys { - match Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash) { + match Witness::signature::<_, _, Ctx>( + stfr, + &KeyExpr::SingleKey(pk.clone()), + leaf_hash, + ) { Witness::Stack(sig) => { sigs.push(sig); sig_count += 1; diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 0cd97f78b..f3db7009e 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -911,7 +911,7 @@ impl Property for ExtData { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k::()), Terminal::PkH(..) | Terminal::RawPkH(..) => Ok(Self::from_pk_h::()), - Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { + Terminal::Multi(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -924,11 +924,22 @@ impl Property for ExtData { error: ErrorKind::OverThreshold(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!(), + Ok(Self::from_multi(k, pks.len())) + } + Terminal::MultiA(k, ref pks) => { + if k == 0 { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::ZeroThreshold, + }); + } + if k > pks.len() { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::OverThreshold(k, pks.len()), + }); } + Ok(Self::from_multi_a(k, pks.len())) } 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 161185c4f..07d014a15 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -414,7 +414,7 @@ pub trait Property: Sized { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k::()), Terminal::PkH(..) | Terminal::RawPkH(..) => Ok(Self::from_pk_h::()), - Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { + Terminal::Multi(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -427,11 +427,22 @@ pub trait Property: Sized { error: ErrorKind::OverThreshold(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!(), + Ok(Self::from_multi(k, pks.len())) + } + Terminal::MultiA(k, ref pks) => { + if k == 0 { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::ZeroThreshold, + }); + } + if k > pks.len() { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::OverThreshold(k, pks.len()), + }); } + Ok(Self::from_multi_a(k, pks.len())) } Terminal::After(t) => { // Note that for CLTV this is a limitation not of Bitcoin but Miniscript. The @@ -797,7 +808,7 @@ impl Property for Type { Terminal::False => Ok(Self::from_false()), Terminal::PkK(..) => Ok(Self::from_pk_k::()), Terminal::PkH(..) | Terminal::RawPkH(..) => Ok(Self::from_pk_h::()), - Terminal::Multi(k, ref pks) | Terminal::MultiA(k, ref pks) => { + Terminal::Multi(k, ref pks) => { if k == 0 { return Err(Error { fragment: fragment.clone(), @@ -810,11 +821,22 @@ impl Property for Type { error: ErrorKind::OverThreshold(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!(), + Ok(Self::from_multi(k, pks.len())) + } + Terminal::MultiA(k, ref pks) => { + if k == 0 { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::ZeroThreshold, + }); + } + if k > pks.len() { + return Err(Error { + fragment: fragment.clone(), + error: ErrorKind::OverThreshold(k, pks.len()), + }); } + Ok(Self::from_multi_a(k, pks.len())) } 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 c5cb7cdea..4a5ccbc76 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -27,6 +27,7 @@ use sync::Arc; use crate::miniscript::context::SigType; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::types::{self, ErrorKind, ExtData, Property, Type}; use crate::miniscript::ScriptContext; use crate::policy::Concrete; @@ -841,7 +842,9 @@ where } Concrete::Key(ref pk) => { insert_wrap!(AstElemExt::terminal(Terminal::PkH(pk.clone()))); - insert_wrap!(AstElemExt::terminal(Terminal::PkK(pk.clone()))); + insert_wrap!(AstElemExt::terminal(Terminal::PkK(KeyExpr::SingleKey( + pk.clone() + )))); } Concrete::After(n) => insert_wrap!(AstElemExt::terminal(Terminal::After(n))), Concrete::Older(n) => insert_wrap!(AstElemExt::terminal(Terminal::Older(n))), @@ -1032,7 +1035,11 @@ where match Ctx::sig_type() { SigType::Schnorr if key_vec.len() == subs.len() => { - insert_wrap!(AstElemExt::terminal(Terminal::MultiA(k, key_vec))) + let mut k_vec: Vec> = vec![]; + for key in key_vec { + k_vec.push(KeyExpr::SingleKey(key)) + } + insert_wrap!(AstElemExt::terminal(Terminal::MultiA(k, k_vec))) } SigType::Ecdsa if key_vec.len() == subs.len() && subs.len() <= MAX_PUBKEYS_PER_MULTISIG => diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 9e91dc7fe..3e0bb9b5f 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -25,6 +25,8 @@ use core::fmt; #[cfg(feature = "std")] use std::error; +use crate::Vec; + #[cfg(feature = "compiler")] pub mod compiler; pub mod concrete; @@ -35,6 +37,7 @@ pub use self::concrete::Policy as Concrete; /// avoid this word because it is a reserved keyword in Rust pub use self::semantic::Policy as Semantic; use crate::descriptor::Descriptor; +use crate::miniscript::musig_key::KeyExpr; use crate::miniscript::{Miniscript, ScriptContext}; use crate::{Error, MiniscriptKey, Terminal}; @@ -124,7 +127,17 @@ impl Liftable for Miniscript impl Liftable for Terminal { fn lift(&self) -> Result, Error> { let ret = match *self { - Terminal::PkK(ref pk) | Terminal::PkH(ref pk) => Semantic::KeyHash(pk.to_pubkeyhash()), + Terminal::PkK(ref pk) => match pk { + KeyExpr::SingleKey(pkk) => Semantic::KeyHash(pkk.to_pubkeyhash()), + KeyExpr::MuSig(pkk) => { + let mut policy_vec: Vec> = vec![]; + for key in pkk { + policy_vec.push((Terminal::::PkK(key.clone())).lift()?) + } + Semantic::Threshold(pkk.len(), policy_vec) + } + }, + Terminal::PkH(ref pk) => Semantic::KeyHash(pk.to_pubkeyhash()), Terminal::RawPkH(ref pkh) => Semantic::KeyHash(pkh.clone()), Terminal::After(t) => Semantic::After(t), Terminal::Older(t) => Semantic::Older(t), @@ -161,12 +174,19 @@ impl Liftable for Terminal { let semantic_subs: Result<_, Error> = subs.iter().map(|s| s.node.lift()).collect(); Semantic::Threshold(k, semantic_subs?) } - Terminal::Multi(k, ref keys) | Terminal::MultiA(k, ref keys) => Semantic::Threshold( + Terminal::Multi(k, ref keys) => Semantic::Threshold( k, keys.iter() .map(|k| Semantic::KeyHash(k.to_pubkeyhash())) .collect(), ), + Terminal::MultiA(k, ref keys) => { + let mut policy_vec: Vec> = vec![]; + for key in keys { + policy_vec.push((Terminal::::PkK(key.clone())).lift()?) + } + Semantic::Threshold(k, policy_vec) + } } .normalized(); Ok(ret) @@ -234,13 +254,13 @@ mod tests { #[cfg(feature = "compiler")] use sync::Arc; - use super::super::miniscript::context::Segwitv0; + use super::super::miniscript::context::{Segwitv0, Tap}; use super::super::miniscript::Miniscript; use super::{Concrete, Liftable, Semantic}; use crate::prelude::*; use crate::DummyKey; #[cfg(feature = "compiler")] - use crate::{descriptor::TapTree, Descriptor, Tap}; + use crate::{descriptor::TapTree, Descriptor}; type ConcretePol = Concrete; type SemanticPol = Semantic; @@ -336,6 +356,51 @@ mod tests { ConcretePol::from_str(&policy_string).unwrap_err(); } + #[test] + fn lift_keyexpr() { + // TEST: regular miniscript without musig + let normal_ms: Miniscript = + Miniscript::::from_str("and_v(v:pk(A),multi_a(2,B,C,D))").unwrap(); + assert_eq!( + Semantic::from_str("and(pkh(A),thresh(2,pkh(B),pkh(C),pkh(D)))").unwrap(), + normal_ms.lift().unwrap() + ); + + // TEST: musig inside pk + let musig_ms: Miniscript = + Miniscript::::from_str("or_b(pk(A),s:pk(musig(B,C)))").unwrap(); + assert_eq!( + Semantic::from_str("or(pkh(A),and(pkh(B),pkh(C)))").unwrap(), + musig_ms.lift().unwrap() + ); + + // TEST: nested musig + let musig_ms: Miniscript = + Miniscript::::from_str("or_b(pk(A),s:pk(musig(B,musig(C,musig(D,E)))))") + .unwrap(); + assert_eq!( + Semantic::from_str("or(pkh(A),and(pkh(B),pkh(C),pkh(D),pkh(E)))").unwrap(), + musig_ms.lift().unwrap() + ); + + // TEST: musig inside multi_a + let musig_ms: Miniscript = + Miniscript::::from_str("and_v(v:pk(A),multi_a(1,musig(B,C),D))").unwrap(); + assert_eq!( + Semantic::from_str("and(pkh(A),or(and(pkh(B),pkh(C)),pkh(D)))").unwrap(), + musig_ms.lift().unwrap() + ); + + // TEST: complex script containing musig inside inside pk + let musig_ms: Miniscript = Miniscript::::from_str( + "or_b(and_b(pk(musig(A,B)),s:pk(F)),a:multi_a(1,C,musig(musig(D,E))))", + ) + .unwrap(); + assert_eq!( + Semantic::from_str("or(and(pkh(A),pkh(B),pkh(F)),pkh(C),and(pkh(D),pkh(E)))").unwrap(), + musig_ms.lift().unwrap() + ); + } #[test] fn lift_andor() { let key_a: bitcoin::PublicKey = diff --git a/src/util.rs b/src/util.rs index b59c2fde4..583cd39a5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,6 +2,7 @@ use bitcoin::blockdata::script; use bitcoin::Script; use crate::miniscript::context; +use crate::miniscript::musig_key::KeyExpr; use crate::prelude::*; use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; pub(crate) fn varint_len(n: usize) -> usize { @@ -28,7 +29,7 @@ pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> Script { // trait for pushing key that depend on context pub(crate) trait MsKeyBuilder { /// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script - fn push_ms_key(self, key: &Pk) -> Self + fn push_ms_key(self, key: &KeyExpr) -> Self where Pk: ToPublicKey, Ctx: ScriptContext; @@ -41,14 +42,18 @@ pub(crate) trait MsKeyBuilder { } impl MsKeyBuilder for script::Builder { - fn push_ms_key(self, key: &Pk) -> Self + fn push_ms_key(self, key: &KeyExpr) -> Self where Pk: ToPublicKey, Ctx: ScriptContext, { 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()), + context::SigType::Ecdsa => self.push_key( + &key.single_key() + .expect("Unreachable, Found musig in Ecsdsa context") + .to_public_key(), + ), + context::SigType::Schnorr => self.push_slice(key.key_agg().serialize().as_ref()), } }