descriptors: create Liana descriptors through the policy
This makes it possible for a LianaPolicy to be created from a user configuration. This in turn centralizes the descriptor creation inside it as well and make `MultipathDescriptor` take a `LianaPolicy` directly. This is useful to centralize all the Miniscript and Miniscript policy handling under in a single place as we'll soon be managing much more complex policies (and make use of the Minsicript policy compiler). Unfortunately this is an invasive API change. But at least the API now makes a lot more sense: you can create a spending policy from a configuration and create a descriptor from it. And vice-versa you can infer a spending policy from a descriptor and inspect the configuration from it.
This commit is contained in:
parent
9b866300be
commit
647d65fe04
@ -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<descriptor::DescriptorPublicKey>) -> bool {
|
||||
// Whether a Miniscript policy node represents a key check (or several of them).
|
||||
fn is_single_key_or_multisig(policy: &SemanticPolicy<descriptor::DescriptorPublicKey>) -> bool {
|
||||
match policy {
|
||||
SemanticPolicy::Key(..) => true,
|
||||
SemanticPolicy::Threshold(_, subs) => {
|
||||
@ -22,12 +24,12 @@ pub fn is_single_key_or_multisig(policy: &SemanticPolicy<descriptor::DescriptorP
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
pub fn is_valid_desc_key(key: &descriptor::DescriptorPublicKey) -> 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<descriptor::DescriptorPublicKey> {
|
||||
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<Miniscript<descriptor::DescriptorPublicKey, miniscript::Segwitv0>> {
|
||||
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<LianaPolicy, LianaDescError> {
|
||||
// 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<descriptor::DescriptorPublicKey> {
|
||||
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.
|
||||
|
||||
@ -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<usize>,
|
||||
keys: Vec<descriptor::DescriptorPublicKey>,
|
||||
}
|
||||
|
||||
impl LianaDescKeys {
|
||||
pub fn from_single(key: descriptor::DescriptorPublicKey) -> LianaDescKeys {
|
||||
LianaDescKeys {
|
||||
thresh: None,
|
||||
keys: vec![key],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_multi(
|
||||
thresh: usize,
|
||||
keys: Vec<descriptor::DescriptorPublicKey>,
|
||||
) -> Result<LianaDescKeys, DescKeyError> {
|
||||
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<descriptor::DescriptorPublicKey> {
|
||||
&self.keys
|
||||
}
|
||||
|
||||
pub fn into_miniscript(
|
||||
mut self,
|
||||
as_hash: bool,
|
||||
) -> Miniscript<descriptor::DescriptorPublicKey, miniscript::Segwitv0> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<descriptor::Descriptor<descriptor::DescriptorPublicKey>> for Inhe
|
||||
}
|
||||
|
||||
impl MultipathDescriptor {
|
||||
pub fn new(
|
||||
owner_keys: LianaDescKeys,
|
||||
heir_keys: LianaDescKeys,
|
||||
timelock: u16,
|
||||
) -> Result<MultipathDescriptor, LianaDescError> {
|
||||
// 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<descriptor::DescriptorPublicKey> =
|
||||
(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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user