use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::fmt;
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use bitcoin::hashes::*;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey;
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy};
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, SecpCtx};
use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
#[derive(Debug, Clone, Default, Serialize)]
pub struct PKOrF {
#[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pubkey_hash: Option<hash160::Hash>,
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
}
impl PKOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k {
DescriptorPublicKey::SinglePub(pubkey) => PKOrF {
pubkey: Some(pubkey.key),
..Default::default()
},
DescriptorPublicKey::XPub(xpub) => PKOrF {
fingerprint: Some(xpub.root_fingerprint(secp)),
..Default::default()
},
}
}
fn from_key_hash(k: hash160::Hash) -> Self {
PKOrF {
pubkey_hash: Some(k),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
Signature(PKOrF),
SignatureKey(PKOrF),
SHA256Preimage {
hash: sha256::Hash,
},
HASH256Preimage {
hash: sha256d::Hash,
},
RIPEMD160Preimage {
hash: ripemd160::Hash,
},
HASH160Preimage {
hash: hash160::Hash,
},
AbsoluteTimelock {
value: u32,
},
RelativeTimelock {
value: u32,
},
Multisig {
keys: Vec<PKOrF>,
threshold: usize,
},
Thresh {
items: Vec<Policy>,
threshold: usize,
},
}
impl SatisfiableItem {
pub fn is_leaf(&self) -> bool {
!matches!(
self,
SatisfiableItem::Thresh {
items: _,
threshold: _,
}
)
}
pub fn id(&self) -> String {
get_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
.expect("Failed to compute a SatisfiableItem id")
}
}
fn combinations(vec: &[usize], size: usize) -> Vec<Vec<usize>> {
assert!(vec.len() >= size);
let mut answer = Vec::new();
let mut queue = VecDeque::new();
for (index, val) in vec.iter().enumerate() {
let mut new_vec = Vec::with_capacity(size);
new_vec.push(*val);
queue.push_back((index, new_vec));
}
while let Some((index, vals)) = queue.pop_front() {
if vals.len() >= size {
answer.push(vals);
} else {
for (new_index, val) in vec.iter().skip(index + 1).enumerate() {
let mut cloned = vals.clone();
cloned.push(*val);
queue.push_front((new_index, cloned));
}
}
}
answer
}
fn mix<T: Clone>(vec: Vec<Vec<T>>) -> Vec<Vec<T>> {
if vec.is_empty() || vec.iter().any(Vec::is_empty) {
return vec![];
}
let mut answer = Vec::new();
let size = vec.len();
let mut queue = VecDeque::new();
for i in &vec[0] {
let mut new_vec = Vec::with_capacity(size);
new_vec.push(i.clone());
queue.push_back(new_vec);
}
while let Some(vals) = queue.pop_front() {
if vals.len() >= size {
answer.push(vals);
} else {
let level = vals.len();
for i in &vec[level] {
let mut cloned = vals.clone();
cloned.push(i.clone());
queue.push_front(cloned);
}
}
}
answer
}
pub type ConditionMap = BTreeMap<usize, HashSet<Condition>>;
pub type FoldedConditionMap = BTreeMap<Vec<usize>, HashSet<Condition>>;
fn serialize_folded_cond_map<S>(
input_map: &FoldedConditionMap,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(input_map.len()))?;
for (k, v) in input_map {
let k_string = format!("{:?}", k);
map.serialize_entry(&k_string, v)?;
}
map.end()
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum Satisfaction {
Partial {
n: usize,
m: usize,
items: Vec<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
sorted: Option<bool>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
conditions: ConditionMap,
},
PartialComplete {
n: usize,
m: usize,
items: Vec<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
sorted: Option<bool>,
#[serde(
serialize_with = "serialize_folded_cond_map",
skip_serializing_if = "BTreeMap::is_empty"
)]
conditions: FoldedConditionMap,
},
Complete {
condition: Condition,
},
None,
}
impl Satisfaction {
pub fn is_leaf(&self) -> bool {
match self {
Satisfaction::None | Satisfaction::Complete { .. } => true,
Satisfaction::PartialComplete { .. } | Satisfaction::Partial { .. } => false,
}
}
fn add(&mut self, inner: &Satisfaction, inner_index: usize) -> Result<(), PolicyError> {
match self {
Satisfaction::None | Satisfaction::Complete { .. } => Err(PolicyError::AddOnLeaf),
Satisfaction::PartialComplete { .. } => Err(PolicyError::AddOnPartialComplete),
Satisfaction::Partial {
n,
ref mut conditions,
ref mut items,
..
} => {
if inner_index >= *n || items.contains(&inner_index) {
return Err(PolicyError::IndexOutOfRange(inner_index));
}
match inner {
Satisfaction::None | Satisfaction::Partial { .. } => return Ok(()),
Satisfaction::Complete { condition } => {
items.push(inner_index);
conditions.insert(inner_index, vec![*condition].into_iter().collect());
}
Satisfaction::PartialComplete {
conditions: other_conditions,
..
} => {
items.push(inner_index);
let conditions_set = other_conditions
.values()
.fold(HashSet::new(), |set, i| set.union(&i).cloned().collect());
conditions.insert(inner_index, conditions_set);
}
}
Ok(())
}
}
}
fn finalize(&mut self) {
if let Satisfaction::Partial {
n,
m,
items,
conditions,
sorted,
} = self
{
if items.len() >= *m {
let mut map = BTreeMap::new();
let indexes = combinations(items, *m);
indexes
.into_iter()
.map(|i_vec| {
mix(i_vec
.iter()
.map(|i| {
conditions
.get(i)
.map(|set| set.clone().into_iter().collect())
.unwrap_or_default()
})
.collect())
.into_iter()
.map(|x| (i_vec.clone(), x))
.collect::<Vec<(Vec<usize>, Vec<Condition>)>>()
})
.flatten()
.map(|(key, val)| {
(
key,
val.into_iter()
.try_fold(Condition::default(), |acc, v| acc.merge(&v)),
)
})
.filter(|(_, val)| val.is_ok())
.for_each(|(key, val)| {
map.entry(key)
.or_insert_with(HashSet::new)
.insert(val.unwrap());
});
*self = Satisfaction::PartialComplete {
n: *n,
m: *m,
items: items.clone(),
conditions: map,
sorted: *sorted,
};
}
}
}
}
impl From<bool> for Satisfaction {
fn from(other: bool) -> Self {
if other {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Policy {
pub id: String,
#[serde(flatten)]
pub item: SatisfiableItem,
pub satisfaction: Satisfaction,
pub contribution: Satisfaction,
}
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
pub struct Condition {
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<u32>,
}
impl Condition {
fn merge_nlocktime(a: u32, b: u32) -> Result<u32, PolicyError> {
if (a < utils::BLOCKS_TIMELOCK_THRESHOLD) != (b < utils::BLOCKS_TIMELOCK_THRESHOLD) {
Err(PolicyError::MixedTimelockUnits)
} else {
Ok(max(a, b))
}
}
fn merge_nsequence(a: u32, b: u32) -> Result<u32, PolicyError> {
let mask = utils::SEQUENCE_LOCKTIME_TYPE_FLAG | utils::SEQUENCE_LOCKTIME_MASK;
let a = a & mask;
let b = b & mask;
if (a < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) != (b < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) {
Err(PolicyError::MixedTimelockUnits)
} else {
Ok(max(a, b))
}
}
pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
match (self.csv, other.csv) {
(Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
(None, any) => self.csv = any,
_ => {}
}
match (self.timelock, other.timelock) {
(Some(a), Some(b)) => self.timelock = Some(Self::merge_nlocktime(a, b)?),
(None, any) => self.timelock = any,
_ => {}
}
Ok(self)
}
pub fn is_null(&self) -> bool {
self.csv.is_none() && self.timelock.is_none()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum PolicyError {
NotEnoughItemsSelected(String),
IndexOutOfRange(usize),
AddOnLeaf,
AddOnPartialComplete,
MixedTimelockUnits,
IncompatibleConditions,
}
impl fmt::Display for PolicyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for PolicyError {}
impl Policy {
fn new(item: SatisfiableItem) -> Self {
Policy {
id: item.id(),
item,
satisfaction: Satisfaction::None,
contribution: Satisfaction::None,
}
}
fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) {
(None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
}
}
fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
match (a, b) {
(None, None) => Ok(None),
(Some(x), None) | (None, Some(x)) => Ok(Some(x)),
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
}
}
fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
return Ok(None);
}
let mut contribution = Satisfaction::Partial {
n: items.len(),
m: threshold,
items: vec![],
conditions: Default::default(),
sorted: None,
};
for (index, item) in items.iter().enumerate() {
contribution.add(&item.contribution, index)?;
}
contribution.finalize();
let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
policy.contribution = contribution;
Ok(Some(policy))
}
fn make_multisig(
keys: &[DescriptorPublicKey],
signers: &SignersContainer,
threshold: usize,
sorted: bool,
secp: &SecpCtx,
) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
return Ok(None);
}
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k, secp)).collect();
let mut contribution = Satisfaction::Partial {
n: keys.len(),
m: threshold,
items: vec![],
conditions: Default::default(),
sorted: Some(sorted),
};
for (index, key) in keys.iter().enumerate() {
if signers.find(signer_id(key, secp)).is_some() {
contribution.add(
&Satisfaction::Complete {
condition: Default::default(),
},
index,
)?;
}
}
contribution.finalize();
let mut policy: Policy = SatisfiableItem::Multisig {
keys: parsed_keys,
threshold,
}
.into();
policy.contribution = contribution;
Ok(Some(policy))
}
pub fn requires_path(&self) -> bool {
self.get_condition(&BTreeMap::new()).is_err()
}
pub fn get_condition(
&self,
path: &BTreeMap<String, Vec<usize>>,
) -> Result<Condition, PolicyError> {
let default = match &self.item {
SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
(0..*threshold).collect()
}
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
_ => vec![],
};
let selected = match path.get(&self.id) {
Some(arr) => arr,
_ => &default,
};
match &self.item {
SatisfiableItem::Thresh { items, threshold } => {
let mapped_req = items
.iter()
.map(|i| i.get_condition(path))
.collect::<Result<Vec<_>, _>>()?;
if mapped_req.iter().all(Condition::is_null) {
return Ok(Condition::default());
}
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
}
let mut requirements = Condition::default();
for item_index in selected {
requirements = requirements.merge(
mapped_req
.get(*item_index)
.ok_or(PolicyError::IndexOutOfRange(*item_index))?,
)?;
}
Ok(requirements)
}
SatisfiableItem::Multisig { keys, threshold } => {
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
}
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(*item));
}
Ok(Condition::default())
}
SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
csv: None,
timelock: Some(*value),
}),
SatisfiableItem::RelativeTimelock { value } => Ok(Condition {
csv: Some(*value),
timelock: None,
}),
_ => Ok(Condition::default()),
}
}
}
impl From<SatisfiableItem> for Policy {
fn from(other: SatisfiableItem) -> Self {
Self::new(other)
}
}
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
match key {
DescriptorPublicKey::SinglePub(pubkey) => pubkey.key.to_pubkeyhash().into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
}
}
fn signature(key: &DescriptorPublicKey, signers: &SignersContainer, secp: &SecpCtx) -> Policy {
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key, secp)).into();
policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
policy
}
fn signature_key(
key: &<DescriptorPublicKey as MiniscriptKey>::Hash,
signers: &SignersContainer,
secp: &SecpCtx,
) -> Policy {
let key_hash = DerivedDescriptorKey::new(key.clone(), secp)
.to_public_key()
.to_pubkeyhash();
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(key_hash)).into();
if signers.find(SignerId::PkHash(key_hash)).is_some() {
policy.contribution = Satisfaction::Complete {
condition: Default::default(),
}
}
policy
}
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
fn extract_policy(
&self,
signers: &SignersContainer,
secp: &SecpCtx,
) -> Result<Option<Policy>, Error> {
Ok(match &self.node {
Terminal::True | Terminal::False => None,
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, secp)),
Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, signers, secp)),
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: Condition {
timelock: Some(*value),
csv: None,
},
};
Some(policy)
}
Terminal::Older(value) => {
let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: Condition {
timelock: None,
csv: Some(*value),
},
};
Some(policy)
}
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => {
Some(SatisfiableItem::HASH256Preimage { hash: *hash }.into())
}
Terminal::Ripemd160(hash) => {
Some(SatisfiableItem::RIPEMD160Preimage { hash: *hash }.into())
}
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
Terminal::Multi(k, pks) => Policy::make_multisig(pks, signers, *k, false, secp)?,
Terminal::Alt(inner)
| Terminal::Swap(inner)
| Terminal::Check(inner)
| Terminal::DupIf(inner)
| Terminal::Verify(inner)
| Terminal::NonZero(inner)
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, secp)?,
Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
a.extract_policy(signers, secp)?,
b.extract_policy(signers, secp)?,
)?,
Terminal::AndOr(x, y, z) => Policy::make_or(
Policy::make_and(
x.extract_policy(signers, secp)?,
y.extract_policy(signers, secp)?,
)?,
z.extract_policy(signers, secp)?,
)?,
Terminal::OrB(a, b)
| Terminal::OrD(a, b)
| Terminal::OrC(a, b)
| Terminal::OrI(a, b) => Policy::make_or(
a.extract_policy(signers, secp)?,
b.extract_policy(signers, secp)?,
)?,
Terminal::Thresh(k, nodes) => {
let mut threshold = *k;
let mapped: Vec<_> = nodes
.iter()
.map(|n| n.extract_policy(signers, secp))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.filter_map(|x| x)
.collect();
if mapped.len() < nodes.len() {
threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
None => return Ok(None),
Some(x) => x,
};
}
Policy::make_thresh(mapped, threshold)?
}
})
}
}
impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
fn extract_policy(
&self,
signers: &SignersContainer,
secp: &SecpCtx,
) -> Result<Option<Policy>, Error> {
fn make_sortedmulti<Ctx: ScriptContext>(
keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
signers: &SignersContainer,
secp: &SecpCtx,
) -> Result<Option<Policy>, Error> {
Ok(Policy::make_multisig(
keys.pks.as_ref(),
signers,
keys.k,
true,
secp,
)?)
}
match self {
Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
Descriptor::Sh(sh) => match sh.as_inner() {
ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
ShInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
ShInner::Wsh(wsh) => match wsh.as_inner() {
WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
},
},
Descriptor::Wsh(wsh) => match wsh.as_inner() {
WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
},
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, secp)?),
}
}
}
#[cfg(test)]
mod test {
use crate::descriptor;
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
use super::*;
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32;
use bitcoin::Network;
use std::str::FromStr;
use std::sync::Arc;
const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
const PATH: &str = "m/44'/1'/0'/0";
fn setup_keys<Ctx: ScriptContext>(
tprv: &str,
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let secp: Secp256k1<All> = Secp256k1::new();
let path = bip32::DerivationPath::from_str(PATH).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv);
let fingerprint = tprv.fingerprint(&secp);
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();
(prvkey, pubkey, fingerprint)
}
#[test]
fn test_extract_policy_for_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
#[test]
#[ignore]
fn test_extract_policy_for_sh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
&& keys[0].fingerprint.unwrap() == fingerprint0
&& keys[1].fingerprint.unwrap() == fingerprint1)
);
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
#[test]
fn test_extract_policy_for_sh_multi_complete_2of2() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
&& keys[0].fingerprint.unwrap() == fingerprint0
&& keys[1].fingerprint.unwrap() == fingerprint1)
);
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &2
&& items.len() == 2
&& conditions.contains_key(&vec![0,1])
)
);
}
#[test]
fn test_extract_policy_for_single_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
let desc = descriptor!(wpkh(pubkey)).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
#[test]
#[ignore]
fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
&& keys[0].fingerprint.unwrap() == fingerprint0
&& keys[1].fingerprint.unwrap() == fingerprint1)
);
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
#[test]
#[ignore]
fn test_extract_policy_for_wsh_multi_timelock() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
let sequence = 50;
#[rustfmt::skip]
let desc = descriptor!(wsh(thresh(
2,
pk(prvkey0),
s:pk(pubkey1),
s:d:v:older(sequence)
)))
.unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc
.extract_policy(&signers_container, &Secp256k1::new())
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
);
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
&& m == &2
&& items.len() == 3
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
)
);
}
#[test]
fn test_get_condition_multisig() {
let secp = Secp256k1::gen_new();
let (_, pk0, _) = setup_keys(TPRV0_STR);
let (_, pk1, _) = setup_keys(TPRV1_STR);
let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers = keymap.into();
let policy = wallet_desc
.extract_policy(&signers, &secp)
.unwrap()
.unwrap();
let no_args = policy.get_condition(&vec![].into_iter().collect());
assert_eq!(no_args, Ok(Condition::default()));
let eq_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
assert_eq!(eq_thresh, Ok(Condition::default()));
let gt_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
assert_eq!(gt_thresh, Ok(Condition::default()));
let lt_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
assert_eq!(
lt_thresh,
Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
);
let out_of_range =
policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
}
}