diff --git a/src/descriptors/analysis.rs b/src/descriptors/analysis.rs index a4a47c49..9d1b3bbe 100644 --- a/src/descriptors/analysis.rs +++ b/src/descriptors/analysis.rs @@ -1,18 +1,20 @@ use miniscript::{ - bitcoin::util::bip32, + bitcoin::{util::bip32, Sequence}, descriptor, policy::{Liftable, Semantic as SemanticPolicy}, + Miniscript, ScriptContext, Terminal, }; use std::{ collections::{HashMap, HashSet}, convert::TryFrom, + sync, }; -use crate::descriptors::LianaDescError; +use crate::descriptors::{keys::DescKeyError, LianaDescError}; -/// Whether a Miniscript policy node represents a key check (or several of them). -pub fn is_single_key_or_multisig(policy: &SemanticPolicy) -> bool { +// Whether a Miniscript policy node represents a key check (or several of them). +fn is_single_key_or_multisig(policy: &SemanticPolicy) -> bool { match policy { SemanticPolicy::Key(..) => true, SemanticPolicy::Threshold(_, subs) => { @@ -22,12 +24,12 @@ pub fn is_single_key_or_multisig(policy: &SemanticPolicy bool { +// We require the descriptor key to: +// - Be deriveable (to contain a wildcard) +// - Be multipath (to contain a step in the derivation path with multiple indexes) +// - The multipath step to only contain two indexes, 0 and 1. +// - Be 'signable' by an external signer (to contain an origin) +fn is_valid_desc_key(key: &descriptor::DescriptorPublicKey) -> bool { match *key { descriptor::DescriptorPublicKey::Single(..) | descriptor::DescriptorPublicKey::XPub(..) => { false @@ -229,9 +231,50 @@ impl PathInfo { signed_pubkeys, } } + + // TODO: avoid using a vec... + /// Get the keys contained in this spending path. + pub fn keys(&self) -> Vec { + match self { + PathInfo::Single(ref key) => vec![key.clone()], + PathInfo::Multi(_, keys) => keys.clone(), + } + } + + /// Returns `None` if it is a multisig that does not fit inside a CHECKMULTISIG. + pub fn into_miniscript( + self, + as_hash: bool, + ) -> Option> { + match self { + PathInfo::Single(key) => Some( + Miniscript::from_ast(Terminal::Check(sync::Arc::from( + Miniscript::from_ast(if as_hash { + Terminal::PkH(key) + } else { + Terminal::PkK(key) + }) + .expect("pk_k is a valid Miniscript"), + ))) + .expect("Well typed"), + ), + PathInfo::Multi(thresh, keys) => { + if thresh < 1 || keys.len() > 20 || thresh > keys.len() { + None + } else { + Some( + Miniscript::from_ast(Terminal::Multi(thresh, keys)) + .expect("multi is a valid Miniscript"), + ) + } + } + } + } } -/// A Liana spending policy. Can be inferred from a Miniscript semantic policy. +/// A Liana spending policy. Can be created from some settings (the primary and recovery keys, the +/// timelock(s)) and be used to derive a descriptor. It can also be inferred from a descriptor and +/// be used to retrieve the settings. #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)] pub struct LianaPolicy { pub(super) primary_path: PathInfo, @@ -239,6 +282,66 @@ pub struct LianaPolicy { } impl LianaPolicy { + /// Create a new Liana policy from a given configuration. + pub fn new( + primary_path: PathInfo, + recovery_path: PathInfo, + recovery_timelock: u16, + ) -> Result { + // We require the locktime to: + // - not be disabled + // - be in number of blocks + // - be 'clean' / minimal, ie all bits without consensus meaning should be 0 + // - be positive (Miniscript requires it not to be 0) + // + // All this is achieved through asking for a 16-bit integer. + if recovery_timelock == 0 { + return Err(LianaDescError::InsaneTimelock(recovery_timelock as u32)); + } + + // If any of the paths is a multisig, make sure they are within the CHECKMULTISIG bounds. + for path_info in &[&primary_path, &recovery_path] { + if let PathInfo::Multi(thresh, keys) = path_info { + if keys.len() < 2 || keys.len() > 20 { + return Err(LianaDescError::DescKey(DescKeyError::InvalidMultiKeys( + keys.len(), + ))); + } + if thresh == &0 || thresh > &keys.len() { + return Err(LianaDescError::DescKey(DescKeyError::InvalidMultiThresh( + *thresh, + ))); + } + } + } + + // Check all keys are valid according to our standard (this checks all are multipath keys). + let (prim_keys, rec_keys) = (primary_path.keys(), recovery_path.keys()); + let all_keys = prim_keys.iter().chain(rec_keys.iter()); + if let Some(key) = all_keys.clone().find(|k| !is_valid_desc_key(k)) { + return Err(LianaDescError::InvalidKey((*key).clone().into())); + } + + // Check for key duplicates. They are invalid in (nonmalleable) miniscripts. + let mut key_set = HashSet::new(); + for key in all_keys { + let xpub = match key { + descriptor::DescriptorPublicKey::MultiXPub(ref multi_xpub) => multi_xpub.xkey, + _ => unreachable!("Just checked it was a multixpub above"), + }; + if key_set.contains(&xpub) { + return Err(LianaDescError::DuplicateKey(key.clone().into())); + } + key_set.insert(xpub); + } + assert!(!key_set.is_empty()); + + Ok(LianaPolicy { + primary_path, + recovery_path: (recovery_timelock, recovery_path), + }) + } + /// Create a Liana policy from a descriptor. This will check the descriptor is correctly formed /// (P2WSH, multipath, ..) and has a valid Liana semantic. pub fn from_multipath_descriptor( @@ -319,6 +422,49 @@ impl LianaPolicy { pub fn recovery_path(&self) -> (u16, &PathInfo) { (self.recovery_path.0, &self.recovery_path.1) } + + /// Create a descriptor from this spending policy with multipath key expressions. + /// + /// Although for now this function is deterministic, it **will not** be in the future. + pub fn into_multipath_descriptor( + self, + ) -> descriptor::Descriptor { + let LianaPolicy { + primary_path, + recovery_path: (timelock, recovery_path), + } = self; + + // Create the timelocked spending path. If there is a single key we make it a pk_h() in + // order to save on the script size (since we assume the timelocked recovery path will + // seldom be used). + let recovery_timelock = Terminal::Older(Sequence::from_height(timelock)); + let recovery_keys = recovery_path + .into_miniscript(true) + .expect("We check the multisig never overflows in our constructors."); + let recovery_branch = Miniscript::from_ast(Terminal::AndV( + Miniscript::from_ast(Terminal::Verify(recovery_keys.into())) + .expect("Well typed") + .into(), + Miniscript::from_ast(recovery_timelock) + .expect("Well typed") + .into(), + )) + .expect("Well typed"); + + // Combine the timelocked spending path with the simple "primary" path. For the primary key + // we don't use a pkh since it's the one that will likely always be used. + let primary_keys = primary_path + .into_miniscript(false) + .expect("We check the multisig never overflows in our constructors."); + let tl_miniscript = + Miniscript::from_ast(Terminal::OrD(primary_keys.into(), recovery_branch.into())) + .expect("Well typed"); + miniscript::Segwitv0::check_local_validity(&tl_miniscript) + .expect("Miniscript must be sane"); + descriptor::Descriptor::Wsh( + descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"), + ) + } } /// Partial spend information for a specific spending path within a descriptor. diff --git a/src/descriptors/keys.rs b/src/descriptors/keys.rs index cf5c5538..422ef59b 100644 --- a/src/descriptors/keys.rs +++ b/src/descriptors/keys.rs @@ -4,10 +4,10 @@ use miniscript::{ hashes::{hash160, ripemd160, sha256}, util::bip32, }, - descriptor, hash256, Miniscript, MiniscriptKey, Terminal, ToPublicKey, + hash256, MiniscriptKey, ToPublicKey, }; -use std::{error, fmt, str, sync}; +use std::{error, fmt, str}; #[derive(Debug)] pub enum DescKeyError { @@ -139,64 +139,3 @@ impl ToPublicKey for DerivedPublicKey { *hash } } - -/// The keys in one of the two spending paths of a Liana descriptor. -/// May either be a single key, or between 2 and 20 keys along with a threshold (between two and -/// the number of keys). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LianaDescKeys { - thresh: Option, - keys: Vec, -} - -impl LianaDescKeys { - pub fn from_single(key: descriptor::DescriptorPublicKey) -> LianaDescKeys { - LianaDescKeys { - thresh: None, - keys: vec![key], - } - } - - pub fn from_multi( - thresh: usize, - keys: Vec, - ) -> Result { - if keys.len() < 2 || keys.len() > 20 { - return Err(DescKeyError::InvalidMultiKeys(keys.len())); - } - if thresh == 0 || thresh > keys.len() { - return Err(DescKeyError::InvalidMultiThresh(thresh)); - } - Ok(LianaDescKeys { - thresh: Some(thresh), - keys, - }) - } - - pub fn keys(&self) -> &Vec { - &self.keys - } - - pub fn into_miniscript( - mut self, - as_hash: bool, - ) -> Miniscript { - if let Some(thresh) = self.thresh { - assert!(self.keys.len() >= 2 && self.keys.len() <= 20); - Miniscript::from_ast(Terminal::Multi(thresh, self.keys)) - .expect("multi is a valid Miniscript") - } else { - assert_eq!(self.keys.len(), 1); - let key = self.keys.pop().expect("Length was just asserted"); - Miniscript::from_ast(Terminal::Check(sync::Arc::from( - Miniscript::from_ast(if as_hash { - Terminal::PkH(key) - } else { - Terminal::PkK(key) - }) - .expect("pk_k is a valid Miniscript"), - ))) - .expect("Well typed") - } - } -} diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs index e9f6c3ca..142787e5 100644 --- a/src/descriptors/mod.rs +++ b/src/descriptors/mod.rs @@ -1,22 +1,15 @@ use miniscript::{ bitcoin::{ - self, - blockdata::transaction::Sequence, - secp256k1, + self, secp256k1, util::{ bip32, psbt::{Input as PsbtIn, Psbt}, }, }, - descriptor, - miniscript::{decode::Terminal, Miniscript}, - translate_hash_clone, ForEachKey, ScriptContext, TranslatePk, Translator, + descriptor, translate_hash_clone, ForEachKey, TranslatePk, Translator, }; -use std::{ - collections::{BTreeMap, HashSet}, - error, fmt, str, -}; +use std::{collections::BTreeMap, error, fmt, str}; use serde::{Deserialize, Serialize}; @@ -144,69 +137,9 @@ impl PartialEq> for Inhe } impl MultipathDescriptor { - pub fn new( - owner_keys: LianaDescKeys, - heir_keys: LianaDescKeys, - timelock: u16, - ) -> Result { - // We require the locktime to: - // - not be disabled - // - be in number of blocks - // - be 'clean' / minimal, ie all bits without consensus meaning should be 0 - // - be positive (Miniscript requires it not to be 0) - // - // All this is achieved through asking for a 16-bit integer. - if timelock == 0 { - return Err(LianaDescError::InsaneTimelock(timelock as u32)); - } - let timelock = Sequence::from_height(timelock); - - // Check all keys are valid according to our standard (this checks all are multipath keys). - let all_keys = owner_keys.keys().iter().chain(heir_keys.keys().iter()); - if let Some(key) = all_keys.clone().find(|k| !is_valid_desc_key(k)) { - return Err(LianaDescError::InvalidKey((*key).clone().into())); - } - - // Check for key duplicates. They are invalid in (nonmalleable) miniscripts. - let mut key_set = HashSet::new(); - for key in all_keys { - let xpub = match key { - descriptor::DescriptorPublicKey::MultiXPub(ref multi_xpub) => multi_xpub.xkey, - _ => unreachable!("Just checked it was a multixpub above"), - }; - if key_set.contains(&xpub) { - return Err(LianaDescError::DuplicateKey(key.clone().into())); - } - key_set.insert(xpub); - } - assert!(!key_set.is_empty()); - - // Create the timelocked spending path. If there is a single key we make it a pk_h() in - // order to save on the script size (since we assume the timelocked recovery path will - // seldom be used). - let heir_timelock = Terminal::Older(timelock); - let heir_branch = Miniscript::from_ast(Terminal::AndV( - Miniscript::from_ast(Terminal::Verify(heir_keys.into_miniscript(true).into())) - .expect("Well typed") - .into(), - Miniscript::from_ast(heir_timelock) - .expect("Well typed") - .into(), - )) - .expect("Well typed"); - - // Combine the timelocked spending path with the simple "primary" path. For the primary key - // we don't use a pkh since it's the one that will likely always be used. - let tl_miniscript = Miniscript::from_ast(Terminal::OrD( - owner_keys.into_miniscript(false).into(), - heir_branch.into(), - )) - .expect("Well typed"); - miniscript::Segwitv0::check_local_validity(&tl_miniscript) - .expect("Miniscript must be sane"); - let multi_desc = descriptor::Descriptor::Wsh( - descriptor::Wsh::new(tl_miniscript).expect("Must pass sanity checks"), - ); + pub fn new(spending_policy: LianaPolicy) -> MultipathDescriptor { + // Get the descriptor from the chosen spending policy. + let multi_desc = spending_policy.into_multipath_descriptor(); // Compute the receive and change "sub" descriptors right away. According to our pubkey // check above, there must be only two of those, 0 and 1. @@ -221,11 +154,11 @@ impl MultipathDescriptor { let receive_desc = InheritanceDescriptor(singlepath_descs.next().expect("First of 2")); let change_desc = InheritanceDescriptor(singlepath_descs.next().expect("Second of 2")); - Ok(MultipathDescriptor { + MultipathDescriptor { multi_desc, receive_desc, change_desc, - }) + } } /// Whether all xpubs contained in this descriptor are for the passed expected network. @@ -465,126 +398,127 @@ impl DerivedInheritanceDescriptor { mod tests { use super::*; + use bitcoin::Sequence; + use std::str::FromStr; + use crate::signer::HotSigner; + #[test] fn descriptor_creation() { - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap()); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap()); let timelock = 52560; - assert_eq!(MultipathDescriptor::new(owner_key.clone(), heir_key.clone(), timelock).unwrap().to_string(), "wsh(or_d(pk([abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*),and_v(v:pkh([abcdef01]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*),older(52560))))#g7vk9r5l"); + let policy = LianaPolicy::new(owner_key.clone(), heir_key.clone(), timelock).unwrap(); + assert_eq!(MultipathDescriptor::new(policy).to_string(), "wsh(or_d(pk([abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*),and_v(v:pkh([abcdef01]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*),older(52560))))#g7vk9r5l"); // A decaying multisig after 6 months. Note we can't duplicate the keys, so different ones // are used. In practice they would both be controlled by the same entity. - let primary_keys = LianaDescKeys::from_multi( + let primary_keys = PathInfo::Multi( 3, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[aabb0011/10/4893]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*").unwrap() ] - ) - .unwrap(); - let recovery_keys = LianaDescKeys::from_multi( + ); + let recovery_keys = PathInfo::Multi( 2, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[aabb0011/10/4893]xpub6AyxexvxizZJffF153evmfqHcE9MV88fCNCAtP3jQjXJHwrAKri71Tq9jWUkPxj9pja4u6AkCPHY7atgxzSEa2HtDwJfrRWKK4fsfQg4o77/<0;1>/*").unwrap(), ], - ) - .unwrap(); - assert_eq!(MultipathDescriptor::new(primary_keys, recovery_keys, 26352).unwrap().to_string(), "wsh(or_d(multi(3,[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0011/10/4893]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*),and_v(v:multi(2,[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*,[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*,[aabb0011/10/4893]xpub6AyxexvxizZJffF153evmfqHcE9MV88fCNCAtP3jQjXJHwrAKri71Tq9jWUkPxj9pja4u6AkCPHY7atgxzSEa2HtDwJfrRWKK4fsfQg4o77/<0;1>/*),older(26352))))#s0zsa6uc"); + ); + let policy = LianaPolicy::new(primary_keys, recovery_keys, 26352).unwrap(); + assert_eq!(MultipathDescriptor::new(policy).to_string(), "wsh(or_d(multi(3,[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,[aabb0011/10/4893]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*,[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*),and_v(v:multi(2,[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*,[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*,[aabb0011/10/4893]xpub6AyxexvxizZJffF153evmfqHcE9MV88fCNCAtP3jQjXJHwrAKri71Tq9jWUkPxj9pja4u6AkCPHY7atgxzSEa2HtDwJfrRWKK4fsfQg4o77/<0;1>/*),older(26352))))#s0zsa6uc"); // We prevent footguns with timelocks by requiring a u16. Note how the following wouldn't // compile: - //MultipathDescriptor::new(owner_key.clone(), heir_key.clone(), 0x00_01_0f_00).unwrap_err(); - //MultipathDescriptor::new(owner_key.clone(), heir_key.clone(), (1 << 31) + 1).unwrap_err(); - //MultipathDescriptor::new(owner_key, heir_key, (1 << 22) + 1).unwrap_err(); + //LianaPolicy::new(owner_key.clone(), heir_key.clone(), 0x00_01_0f_00).unwrap_err(); + //LianaPolicy::new(owner_key.clone(), heir_key.clone(), (1 << 31) + 1).unwrap_err(); + //LianaPolicy::new(owner_key, heir_key, (1 << 22) + 1).unwrap_err(); // You can't use a null timelock in Miniscript. - MultipathDescriptor::new(owner_key, heir_key, 0).unwrap_err(); + LianaPolicy::new(owner_key, heir_key, 0).unwrap_err(); - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/<0;1>/*").unwrap()); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/<0;1>/*").unwrap()); let timelock = 57600; - assert_eq!(MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap().to_string(), "wsh(or_d(pk([aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/<0;1>/*),and_v(v:pkh([abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/<0;1>/*),older(57600))))#ak4cm093"); + let policy = LianaPolicy::new(owner_key.clone(), heir_key, timelock).unwrap(); + assert_eq!(MultipathDescriptor::new(policy).to_string(), "wsh(or_d(pk([aabb0011/10/4893]xpub661MyMwAqRbcFG59fiikD8UV762quhruT8K8bdjqy6N2o3LG7yohoCdLg1m2HAY1W6rfBrtauHkBhbfA4AQ3iazaJj5wVPhwgaRCHBW2DBg/<0;1>/*),and_v(v:pkh([abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/24/32/<0;1>/*),older(57600))))#ak4cm093"); // We can't pass a raw key, an xpub that is not deriveable, only hardened derivable, // without both the change and receive derivation paths, or with more than 2 different // derivation paths. - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/<0;1>/354").unwrap()); - MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/<0;1>/*'").unwrap()); - MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); - let heir_key = LianaDescKeys::from_single( + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/<0;1>/354").unwrap()); + LianaPolicy::new(owner_key.clone(), heir_key, timelock).unwrap_err(); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/<0;1>/*'").unwrap()); + LianaPolicy::new(owner_key.clone(), heir_key, timelock).unwrap_err(); + let heir_key = PathInfo::Single( descriptor::DescriptorPublicKey::from_str( "[abcdef01]02e24913be26dbcfdf8e8e94870b28725cdae09b448b6c127767bf0154e3a3c8e5", ) .unwrap(), ); - MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/*'").unwrap()); - MultipathDescriptor::new(owner_key.clone(), heir_key, timelock).unwrap_err(); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/<0;1;2>/*'").unwrap()); - MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + LianaPolicy::new(owner_key.clone(), heir_key, timelock).unwrap_err(); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/*'").unwrap()); + LianaPolicy::new(owner_key.clone(), heir_key, timelock).unwrap_err(); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/<0;1;2>/*'").unwrap()); + LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err(); // And it's checked even in a multisig. For instance: - let primary_keys = LianaDescKeys::from_multi( + let primary_keys = PathInfo::Multi( 1, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub661MyMwAqRbcFfxf71L4Dx4w5TmyNXrBicTEAM7vLzumxangwATWWgdJPb6xH1JHcJH9S3jNZx3fCnkkB1WyqrqGgavj1rehHcbythmruvZ/0/<0;1>/354").unwrap(), ] - ) - .unwrap(); - let recovery_keys = LianaDescKeys::from_multi( + ); + let recovery_keys = PathInfo::Multi( 1, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*").unwrap(), ], - ) - .unwrap(); - MultipathDescriptor::new(primary_keys, recovery_keys, 26352).unwrap_err(); + ); + LianaPolicy::new(primary_keys, recovery_keys, 26352).unwrap_err(); // You can't pass duplicate keys, even if they are encoded differently. - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[11223344/2/98]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err(); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err(); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[00aabb44]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[11223344/2/98]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err(); // You can't pass duplicate keys, even across multisigs. - let primary_keys = LianaDescKeys::from_multi( + let primary_keys = PathInfo::Multi( 3, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub67zuTXF9Ln4731avKTBSawoVVNRuMfmRvkL7kLUaLBRqma9ZqdHBJg9qx8cPUm3oNQMiXT4TmGovXNoQPuwg17RFcVJ8YrnbcooN7pxVJqC/<0;1>/*").unwrap() ] - ) - .unwrap(); - let recovery_keys = LianaDescKeys::from_multi( + ); + let recovery_keys = PathInfo::Multi( 2, vec![ descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub69cP4Y7S9TWcbSNxmk6CEDBsoaqr3ZEdjHuZcHxEFFKGh569RsJNr2V27XGhsbH9FXgWUEmKXRN7c5wQfq2VPjt31xP9VsYnVUyU8HcVevm/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6AA2N8RALRYgLD6jT1iXYCEDkndTeZndMtWPbtNX6sY5dPiLtf2T88ahdxrGXMUPoNadgR86sFhBXWQVgifPzDYbY9ZtwK4gqzx4y5Da1DW/<0;1>/*").unwrap(), descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Bw79HbNSeS2xXw1sngPE3ehnk1U3iSPCgLYzC9LpN8m9nDuaKLZvkg8QXxL5pDmEmQtYscmUD8B9MkAAZbh6vxPzNXMaLfGQ9Sb3z85qhR/<0;1>/*").unwrap(), ], - ) - .unwrap(); - MultipathDescriptor::new(primary_keys, recovery_keys, 26352).unwrap_err(); + ); + LianaPolicy::new(primary_keys, recovery_keys, 26352).unwrap_err(); // No origin in one of the keys - let owner_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); - let heir_key = LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap()); + let owner_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap()); + let heir_key = PathInfo::Single(descriptor::DescriptorPublicKey::from_str("xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap()); let timelock = 52560; - MultipathDescriptor::new(owner_key, heir_key, timelock).unwrap_err(); + LianaPolicy::new(owner_key, heir_key, timelock).unwrap_err(); } #[test] @@ -646,19 +580,66 @@ mod tests { #[test] fn liana_desc_keys() { - let desc_key_a = descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*").unwrap(); - let desc_key_b = descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*").unwrap(); - LianaDescKeys::from_single(desc_key_a.clone()); + let secp = secp256k1::Secp256k1::signing_only(); + let random_desc_key = || { + let xpub_str = format!( + "[aabbccdd]{}/<0;1>/*", + HotSigner::generate(bitcoin::Network::Bitcoin) + .unwrap() + .xpub_at(&bip32::DerivationPath::from_str("m").unwrap(), &secp) + ); + descriptor::DescriptorPublicKey::from_str(&xpub_str).unwrap() + }; + let prim_path = PathInfo::Single(random_desc_key()); + let twenty_keys: Vec = + (0..20).map(|_| random_desc_key()).collect(); + let mut twenty_one_keys = twenty_keys.clone(); + twenty_one_keys.push(random_desc_key()); - LianaDescKeys::from_multi(1, vec![desc_key_a.clone()]).unwrap_err(); - LianaDescKeys::from_multi(2, vec![desc_key_a.clone()]).unwrap_err(); - LianaDescKeys::from_multi(1, vec![desc_key_a.clone(), desc_key_b.clone()]).unwrap(); - LianaDescKeys::from_multi(0, vec![desc_key_a.clone(), desc_key_b.clone()]).unwrap_err(); - LianaDescKeys::from_multi(2, vec![desc_key_a.clone(), desc_key_b.clone()]).unwrap(); - LianaDescKeys::from_multi(3, vec![desc_key_a.clone(), desc_key_b]).unwrap_err(); - LianaDescKeys::from_multi(3, (0..20).map(|_| desc_key_a.clone()).collect()).unwrap(); - LianaDescKeys::from_multi(20, (0..20).map(|_| desc_key_a.clone()).collect()).unwrap(); - LianaDescKeys::from_multi(20, (0..21).map(|_| desc_key_a.clone()).collect()).unwrap_err(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(1, vec![random_desc_key()]), + 1, + ) + .unwrap_err(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(2, vec![random_desc_key()]), + 1, + ) + .unwrap_err(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(1, vec![random_desc_key(), random_desc_key()]), + 1, + ) + .unwrap(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(0, vec![random_desc_key(), random_desc_key()]), + 1, + ) + .unwrap_err(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(2, vec![random_desc_key(), random_desc_key()]), + 1, + ) + .unwrap(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(3, vec![random_desc_key(), random_desc_key()]), + 1, + ) + .unwrap_err(); + LianaPolicy::new( + prim_path.clone(), + PathInfo::Multi(3, twenty_keys.clone()), + 1, + ) + .unwrap(); + LianaPolicy::new(prim_path.clone(), PathInfo::Multi(20, twenty_keys), 1).unwrap(); + LianaPolicy::new(prim_path, PathInfo::Multi(20, twenty_one_keys), 1).unwrap_err(); } fn roundtrip(desc_str: &str) { diff --git a/src/signer.rs b/src/signer.rs index 6d415392..d2fe8cf6 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -409,9 +409,7 @@ mod tests { .unwrap(), wildcard: Wildcard::Unhardened, }); - let prim_keys = - descriptors::LianaDescKeys::from_multi(2, vec![prim_key_a, prim_key_b, prim_key_c]) - .unwrap(); + let prim_keys = descriptors::PathInfo::Multi(2, vec![prim_key_a, prim_key_b, prim_key_c]); let origin_der = bip32::DerivationPath::from_str("m/1/2'/3/4'").unwrap(); let xkey = recov_signer.xpub_at(&origin_der, &secp); let recov_key = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { @@ -424,8 +422,9 @@ mod tests { .unwrap(), wildcard: Wildcard::Unhardened, }); - let recov_keys = descriptors::LianaDescKeys::from_single(recov_key); - let desc = descriptors::MultipathDescriptor::new(prim_keys, recov_keys, 42).unwrap(); + let recov_keys = descriptors::PathInfo::Single(recov_key); + let policy = descriptors::LianaPolicy::new(prim_keys, recov_keys, 42).unwrap(); + let desc = descriptors::MultipathDescriptor::new(policy); // Create a dummy PSBT spending a coin from this descriptor with a single input and single // (external) output. We'll be modifying it as we go. diff --git a/src/testutils.rs b/src/testutils.rs index dcdb1018..4d4300cd 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -399,9 +399,10 @@ impl DummyLiana { poll_interval_secs: time::Duration::from_secs(2), }; - let owner_key = descriptors::LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/<0;1>/*").unwrap()); - let heir_key = descriptors::LianaDescKeys::from_single(descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/<0;1>/*").unwrap()); - let desc = descriptors::MultipathDescriptor::new(owner_key, heir_key, 10_000).unwrap(); + let owner_key = descriptors::PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/<0;1>/*").unwrap()); + let heir_key = descriptors::PathInfo::Single(descriptor::DescriptorPublicKey::from_str("[aabbccdd]xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/<0;1>/*").unwrap()); + let policy = descriptors::LianaPolicy::new(owner_key, heir_key, 10_000).unwrap(); + let desc = descriptors::MultipathDescriptor::new(policy); let config = Config { bitcoin_config, bitcoind_config: None,