From 6f4d2846d328d7e71262319242b403292f24e83a Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 17 Nov 2020 23:40:31 +0100 Subject: [PATCH] [descriptor] Add support for `sortedmulti` in `descriptor!` --- src/descriptor/dsl.rs | 114 +++++++++++++++++++++++++++++++++++++++--- src/keys/mod.rs | 63 ++++++++++++++++------- 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index adda9e7b..969fb845 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -27,7 +27,24 @@ #[doc(hidden)] #[macro_export] macro_rules! impl_top_level_sh { - ( $descriptor_variant:ident, $( $minisc:tt )* ) => { + // disallow `sortedmulti` in `bare()` + ( Bare, Bare, sortedmulti $( $inner:tt )* ) => { + compile_error!("`bare()` descriptors can't contain any `sortedmulti` operands"); + }; + ( Bare, Bare, sortedmulti_vec $( $inner:tt )* ) => { + compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec` operands"); + }; + + ( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti $( $inner:tt )* ) => { + $crate::impl_sortedmulti!(sortedmulti $( $inner )*) + .and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks))) + }; + ( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti_vec $( $inner:tt )* ) => { + $crate::impl_sortedmulti!(sortedmulti_vec $( $inner )*) + .and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks))) + }; + + ( $descriptor_variant:ident, $sortedmulti_variant:ident, $( $minisc:tt )* ) => { $crate::fragment!($( $minisc )*) .map(|(minisc, keymap, networks)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap, networks)) }; @@ -160,6 +177,28 @@ macro_rules! impl_node_opcode_three { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! impl_sortedmulti { + ( sortedmulti_vec $thresh:expr, $keys:expr ) => ({ + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); + $crate::keys::make_sortedmulti_inner($thresh, $keys, &secp) + }); + ( sortedmulti $thresh:expr $(, $key:expr )+ ) => ({ + use $crate::keys::ToDescriptorKey; + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); + + let mut keys = vec![]; + $( + keys.push($key.to_descriptor_key()); + )* + + keys.into_iter().collect::, _>>() + .and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp)) + }); + +} + /// Macro to write full descriptors with code /// /// This macro expands to an object of type `Result<(Descriptor, KeyMap, ValidNetworks), Error>`. @@ -238,13 +277,13 @@ macro_rules! impl_node_opcode_three { #[macro_export] macro_rules! descriptor { ( bare ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Bare, $( $minisc )*) + $crate::impl_top_level_sh!(Bare, Bare, $( $minisc )*) }); ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({ $crate::descriptor!(shwsh ($( $minisc )*)) }); ( shwsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(ShWsh, $( $minisc )*) + $crate::impl_top_level_sh!(ShWsh, ShWshSortedMulti, $( $minisc )*) }); ( pk $key:expr ) => ({ $crate::impl_top_level_pk!(Pk, $crate::miniscript::Legacy, $key) @@ -262,10 +301,10 @@ macro_rules! descriptor { $crate::impl_top_level_pk!(ShWpkh, $crate::miniscript::Segwitv0, $key) }); ( sh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Sh, $( $minisc )*) + $crate::impl_top_level_sh!(Sh, ShSortedMulti, $( $minisc )*) }); ( wsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Wsh, $( $minisc )*) + $crate::impl_top_level_sh!(Wsh, WshSortedMulti, $( $minisc )*) }); } @@ -404,6 +443,13 @@ macro_rules! fragment { .and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp)) }); + // `sortedmulti()` is handled separately + ( sortedmulti $( $inner:tt )* ) => ({ + compile_error!("`sortedmulti` can only be used as the root operand of a descriptor"); + }); + ( sortedmulti_vec $( $inner:tt )* ) => ({ + compile_error!("`sortedmulti_vec` can only be used as the root operand of a descriptor"); + }); } #[cfg(test)] @@ -444,8 +490,8 @@ mod test { desc.derive(ChildNumber::from_normal_idx(index).unwrap()) }; let address = child_desc.address(Regtest, deriv_ctx); - if address.is_some() { - assert_eq!(address.unwrap().to_string(), *expected.get(i).unwrap()); + if let Some(address) = address { + assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { let script = child_desc.script_pubkey(deriv_ctx); assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); @@ -635,6 +681,60 @@ mod test { ); } + #[test] + fn test_dsl_sortedmulti() { + let key_1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path_1 = bip32::DerivationPath::from_str("m/0").unwrap(); + + let key_2 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path_2 = bip32::DerivationPath::from_str("m/1").unwrap(); + + let desc_key1 = (key_1, path_1); + let desc_key2 = (key_2, path_2); + + check( + descriptor!(sh(sortedmulti 1, desc_key1.clone(), desc_key2.clone())), + false, + false, + &[ + "2MsxzPEJDBzpGffJXPaDpfXZAUNnZhaMh2N", + "2My3x3DLPK3UbGWGpxrXr1RnbD8MNC4FpgS", + "2NByEuiQT7YLqHCTNxL5KwYjvtuCYcXNBSC", + "2N1TGbP81kj2VUKTSWgrwxoMfuWjvfUdyu7", + "2N3Bomq2fpAcLRNfZnD3bCWK9quan28CxCR", + "2N9nrZaEzEFDqEAU9RPvDnXGT6AVwBDKAQb", + ], + ); + + check( + descriptor!(sh(wsh(sortedmulti 1, desc_key1.clone(), desc_key2.clone()))), + true, + false, + &[ + "2NCogc5YyM4N6ruv1hUa7WLMW1BPeCK7N9B", + "2N6mkSAKi1V2oaBXby7XHdvBMKEDRQcFpNe", + "2NFmTSttm9v6bXeoWaBvpMcgfPQcZhNn3Eh", + "2Mvib87RBPUHXNEpX5S5Kv1qqrhBfgBGsJM", + "2MtMv5mcK2EjcLsH8Txpx2JxLLzHr4ttczL", + "2MsWCB56rb4T6yPv8QudZGHERTwNgesE4f6", + ], + ); + + check( + descriptor!(wsh(sortedmulti_vec 1, vec![desc_key1, desc_key2])), + true, + false, + &[ + "bcrt1qcvq0lg8q7a47ytrd7zk5y7uls7mulrenjgvflwylpppgwf8029es4vhpnj", + "bcrt1q80yn8sdt6l7pjvkz25lglyaqctlmsq9ugk80rmxt8yu0npdsj97sc7l4de", + "bcrt1qrvf6024v9s50qhffe3t2fr2q9ckdhx2g6jz32chm2pp24ymgtr5qfrdmct", + "bcrt1q6srfmra0ynypym35c7jvsxt2u4yrugeajq95kg2ps7lk6h2gaunsq9lzxn", + "bcrt1qhl8rrzzcdpu7tcup3lcg7tge52sqvwy5fcv4k78v6kxtwmqf3v6qpvyjza", + "bcrt1ql2elz9mhm9ll27ddpewhxs732xyl2fk2kpkqz9gdyh33wgcun4vstrd49k", + ], + ); + } + // - verify the valid_networks returned is correctly computed based on the keys present in the descriptor #[test] fn test_valid_networks() { diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 1930eb02..8367122b 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -36,6 +36,7 @@ use bitcoin::{Network, PrivateKey, PublicKey}; pub use miniscript::descriptor::{ DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, + SortedMultiVec, }; use miniscript::descriptor::{DescriptorXKey, KeyMap}; pub use miniscript::ScriptContext; @@ -530,6 +531,31 @@ impl> ToDescriptorKey } } +fn expand_multi_keys, Ctx: ScriptContext>( + pks: Vec, + secp: &SecpCtx, +) -> Result<(Vec, KeyMap, ValidNetworks), KeyError> { + let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks + .into_iter() + .map(|key| Ok::<_, KeyError>(key.to_descriptor_key()?.extract(secp)?)) + .collect::, _>>()? + .into_iter() + .map(|(a, b, c)| (a, (b, c))) + .unzip(); + + let (key_map, valid_networks) = key_maps_networks.into_iter().fold( + (KeyMap::default(), any_network()), + |(mut keys_acc, net_acc), (key, net)| { + keys_acc.extend(key.into_iter()); + let net_acc = merge_networks(&net_acc, &net); + + (keys_acc, net_acc) + }, + ); + + Ok((pks, key_map, valid_networks)) +} + // Used internally by `bdk::fragment!` to build `pk_k()` fragments #[doc(hidden)] pub fn make_pk, Ctx: ScriptContext>( @@ -552,23 +578,7 @@ pub fn make_multi, Ctx: ScriptContext>( pks: Vec, secp: &SecpCtx, ) -> Result<(Miniscript, KeyMap, ValidNetworks), KeyError> { - let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks - .into_iter() - .map(|key| Ok::<_, KeyError>(key.to_descriptor_key()?.extract(secp)?)) - .collect::, _>>()? - .into_iter() - .map(|(a, b, c)| (a, (b, c))) - .unzip(); - - let (key_map, valid_networks) = key_maps_networks.into_iter().fold( - (KeyMap::default(), any_network()), - |(mut keys_acc, net_acc), (key, net)| { - keys_acc.extend(key.into_iter()); - let net_acc = merge_networks(&net_acc, &net); - - (keys_acc, net_acc) - }, - ); + let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?; Ok(( Miniscript::from_ast(Terminal::Multi(thresh, pks))?, @@ -577,6 +587,25 @@ pub fn make_multi, Ctx: ScriptContext>( )) } +// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments +#[doc(hidden)] +pub fn make_sortedmulti_inner, Ctx: ScriptContext>( + thresh: usize, + pks: Vec, + secp: &SecpCtx, +) -> Result< + ( + SortedMultiVec, + KeyMap, + ValidNetworks, + ), + KeyError, +> { + let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?; + + Ok((SortedMultiVec::new(thresh, pks)?, key_map, valid_networks)) +} + /// The "identity" conversion is used internally by some `bdk::fragment`s impl ToDescriptorKey for DescriptorKey { fn to_descriptor_key(self) -> Result, KeyError> {