bdk/src/descriptor/policy.rs

1457 lines
55 KiB
Rust

// Magical Bitcoin Library
// Written in 2020 by
// Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020 Magical Bitcoin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Descriptor policy
//!
//! This module implements the logic to extract and represent the spending policies of a descriptor
//! in a more human-readable format.
//!
//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
//!
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
//! # use bdk::descriptor::*;
//! # use bdk::bitcoin::secp256k1::Secp256k1;
//! let secp = Secp256k1::new();
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//!
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
//! println!("{:?}", extended_desc);
//!
//! let signers = Arc::new(key_map.into());
//! let policy = extended_desc.extract_policy(&signers, &secp)?;
//! println!("policy: {}", serde_json::to_string(&policy)?);
//! # Ok::<(), bdk::Error>(())
//! ```
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, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal,
ToPublicKey,
};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use crate::descriptor::{
DerivedDescriptor, DerivedDescriptorKey, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
};
use crate::psbt::PSBTUtils;
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, SecpCtx};
use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use miniscript::psbt::PsbtInputSatisfier;
/// Raw public key or extended key fingerprint
#[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()
}
}
}
/// An item that needs to be satisfied
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
// Leaves
/// Signature for a raw public key
Signature(PKOrF),
/// Signature for an extended key fingerprint
SignatureKey(PKOrF),
/// SHA256 preimage hash
SHA256Preimage {
/// The digest value
hash: sha256::Hash,
},
/// Double SHA256 preimage hash
HASH256Preimage {
/// The digest value
hash: sha256d::Hash,
},
/// RIPEMD160 preimage hash
RIPEMD160Preimage {
/// The digest value
hash: ripemd160::Hash,
},
/// SHA256 then RIPEMD160 preimage hash
HASH160Preimage {
/// The digest value
hash: hash160::Hash,
},
/// Absolute timeclock timestamp
AbsoluteTimelock {
/// The timestamp value
value: u32,
},
/// Relative timelock locktime
RelativeTimelock {
/// The locktime value
value: u32,
},
/// Multi-signature public keys with threshold count
Multisig {
/// The raw public key or extended key fingerprint
keys: Vec<PKOrF>,
/// The required threshold count
threshold: usize,
},
// Complex item
/// Threshold items with threshold count
Thresh {
/// The policy items
items: Vec<Policy>,
/// The required threshold count
threshold: usize,
},
}
impl SatisfiableItem {
/// Returns whether the [`SatisfiableItem`] is a leaf item
pub fn is_leaf(&self) -> bool {
!matches!(
self,
SatisfiableItem::Thresh {
items: _,
threshold: _,
}
)
}
/// Returns a unique id for the [`SatisfiableItem`]
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
}
/// Type for a map of sets of [`Condition`] items keyed by each set's index
pub type ConditionMap = BTreeMap<usize, HashSet<Condition>>;
/// Type for a map of folded sets of [`Condition`] items keyed by a vector of the combined set's indexes
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()
}
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum Satisfaction {
/// Only a partial satisfaction of some kind of threshold policy
Partial {
/// Total number of items
n: usize,
/// Threshold
m: usize,
/// The items that can be satisfied by the descriptor or are satisfied in the PSBT
items: Vec<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
sorted: Option<bool>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
/// Extra conditions that also need to be satisfied
conditions: ConditionMap,
},
/// Can reach the threshold of some kind of threshold policy
PartialComplete {
/// Total number of items
n: usize,
/// Threshold
m: usize,
/// The items that can be satisfied by the descriptor
items: Vec<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
sorted: Option<bool>,
#[serde(
serialize_with = "serialize_folded_cond_map",
skip_serializing_if = "BTreeMap::is_empty"
)]
/// Extra conditions that also need to be satisfied
conditions: FoldedConditionMap,
},
/// Can satisfy the policy item
Complete {
/// Extra conditions that also need to be satisfied
condition: Condition,
},
/// Cannot satisfy or contribute to the policy item
None,
}
impl Satisfaction {
/// Returns whether the [`Satisfaction`] is a leaf item
pub fn is_leaf(&self) -> bool {
match self {
Satisfaction::None | Satisfaction::Complete { .. } => true,
Satisfaction::PartialComplete { .. } | Satisfaction::Partial { .. } => false,
}
}
// add `inner` as one of self's partial items. this only makes sense on partials
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 {
// not relevant if not completed yet
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 partial try to bump it to a partialcomplete
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` at this point is a Vec<Vec<usize>>, with the "n choose k" of items (m of n)
indexes
.into_iter()
// .inspect(|x| println!("--- orig --- {:?}", x))
// we map each of the combinations of elements into a tuple of ([choosen items], [conditions]). unfortunately, those items have potentially more than one
// condition (think about ORs), so we also use `mix` to expand those, i.e. [[0], [1, 2]] becomes [[0, 1], [0, 2]]. This is necessary to make sure that we
// consider every possibile options and check whether or not they are compatible.
.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>)>>()
})
// .inspect(|x: &Vec<(Vec<usize>, Vec<Condition>)>| println!("fetch {:?}", x))
// since the previous step can turn one item of the iterator into multiple ones, we call flatten to expand them out
.flatten()
// .inspect(|x| println!("flat {:?}", x))
// try to fold all the conditions for this specific combination of indexes/options. if they are not compatible, try_fold will be Err
.map(|(key, val)| {
(
key,
val.into_iter()
.try_fold(Condition::default(), |acc, v| acc.merge(&v)),
)
})
// .inspect(|x| println!("try_fold {:?}", x))
// filter out all the incompatible combinations
.filter(|(_, val)| val.is_ok())
// .inspect(|x| println!("filter {:?}", x))
// push them into the map
.for_each(|(key, val)| {
map.entry(key)
.or_insert_with(HashSet::new)
.insert(val.unwrap());
});
// TODO: if the map is empty, the conditions are not compatible, return an error?
*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
}
}
}
/// Descriptor spending policy
#[derive(Debug, Clone, Serialize)]
pub struct Policy {
/// Identifier for this policy node
pub id: String,
/// Type of this policy node
#[serde(flatten)]
pub item: SatisfiableItem,
/// How a much given PSBT already satisfies this polcy node **(currently unused)**
pub satisfaction: Satisfaction,
/// How the wallet's descriptor can satisfy this policy node
pub contribution: Satisfaction,
}
/// An extra condition that must be satisfied but that is out of control of the user
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
pub struct Condition {
/// Optional CheckSequenceVerify condition
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<u32>,
/// Optional timelock condition
#[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)
}
/// Returns `true` if there are no extra conditions to verify
pub fn is_null(&self) -> bool {
self.csv.is_none() && self.timelock.is_none()
}
}
/// Errors that can happen while extracting and manipulating policies
#[derive(Debug, PartialEq, Eq)]
pub enum PolicyError {
/// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
NotEnoughItemsSelected(String),
/// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
IndexOutOfRange(usize),
/// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
AddOnLeaf,
/// Can not add to an item that is [`Satisfaction::PartialComplete`]
AddOnPartialComplete,
/// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000
MixedTimelockUnits,
/// Incompatible conditions (not currently used)
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))
}
/// Return whether or not a specific path in the policy tree is required to unambiguously
/// create a transaction
///
/// What this means is that for some spending policies the user should select which paths in
/// the tree it intends to satisfy while signing, because the transaction must be created differently based
/// on that.
pub fn requires_path(&self) -> bool {
self.get_condition(&BTreeMap::new()).is_err()
}
/// Return the conditions that are set by the spending policy for a given path in the
/// policy tree
pub fn get_condition(
&self,
path: &BTreeMap<String, Vec<usize>>,
) -> Result<Condition, PolicyError> {
// if items.len() == threshold, selected can be omitted and we take all of them by default
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 all the requirements are null we don't care about `selected` because there
// are no requirements
if mapped_req.iter().all(Condition::is_null) {
return Ok(Condition::default());
}
// if we have something, make sure we have enough items. note that the user can set
// an empty value for this step in case of n-of-n, because `selected` is set to all
// the elements above
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
}
// check the selected items, see if there are conflicting requirements
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()),
}
}
/// fill self.satisfaction with what we already have in the PSBT
pub fn fill_satisfactions(
&mut self,
psbt: &PSBT,
desc: &ExtendedDescriptor, // can't put in self because non Serialize
secp: &SecpCtx,
) -> Result<(), Error> {
// Start from an empty Satisfaction (like a contribution without signers)
let policy = desc.extract_policy(&SignersContainer::default(), &secp)?;
if let Some(policy) = policy {
self.satisfaction = policy.contribution;
for (i, input) in psbt.inputs.iter().enumerate() {
let s = PsbtInputSatisfier::new(psbt, i);
let derived_desc = desc.derive_from_psbt_input(input, psbt.get_utxo_for(i), secp);
self.add_satisfaction(s, derived_desc);
}
self.satisfaction.finalize();
}
Ok(())
}
fn add_satisfaction<S: Satisfier<bitcoin::PublicKey>>(
&mut self,
satisfier: S,
derived_desc: Option<DerivedDescriptor>,
) {
if let Some(derived_desc) = derived_desc {
let mut index = 0;
derived_desc.for_each_key(|k| {
if satisfier.lookup_sig(&k.as_key().to_public_key()).is_some() {
let _ = self.satisfaction.add(
&Satisfaction::Complete {
condition: Default::default(),
},
index,
);
}
index += 1;
true
});
}
}
}
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 {
// Leaves
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)?,
// Identities
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)?,
// Complex policies
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::bitcoin::consensus::deserialize;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32;
use bitcoin::Network;
use miniscript::DescriptorTrait;
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,
path: &str,
secp: &SecpCtx,
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
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 ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
#[test]
fn test_extract_policy_for_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
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, &secp)
.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, &secp)
.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)
);
}
// 2 pub keys descriptor, required 2 prv keys
#[test]
fn test_extract_policy_for_sh_multi_partial_0of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).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, &secp)
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& &keys[0].fingerprint.unwrap() == &fingerprint0
&& &keys[1].fingerprint.unwrap() == &fingerprint1)
);
// TODO should this be "Satisfaction::None" since we have no prv keys?
// TODO should items and conditions not be empty?
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.is_empty()
&& conditions.is_empty()
)
);
}
// 1 prv and 1 pub key descriptor, required 2 prv keys
#[test]
fn test_extract_policy_for_sh_multi_partial_1of2() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).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, &secp)
.unwrap()
.unwrap();
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& &keys[0].fingerprint.unwrap() == &fingerprint0
&& &keys[1].fingerprint.unwrap() == &fingerprint1)
);
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.len() == 1
&& conditions.contains_key(&0)
)
);
}
// 1 prv and 1 pub key descriptor, required 1 prv keys
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_sh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
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, &secp)
.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])
)
);
}
// 2 prv keys descriptor, required 2 prv keys
#[test]
fn test_extract_policy_for_sh_multi_complete_2of2() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
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, &secp)
.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 ExtractPolicy trait with extended and single keys
#[test]
fn test_extract_policy_for_single_wpkh() {
let secp = Secp256k1::new();
let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
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, &secp)
.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, &secp)
.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)
);
}
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
let secp = Secp256k1::new();
let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
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, &secp)
.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 ExtractPolicy trait with descriptors containing timelocks in a thresh()
#[test]
#[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
fn test_extract_policy_for_wsh_multi_timelock() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
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, &secp)
.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)
)
);
}
// - mixed timelocks should fail
#[test]
#[ignore]
fn test_extract_policy_for_wsh_mixed_timelocks() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
let locktime_blocks = 100;
let locktime_seconds = locktime_blocks + locktime_threshold;
let desc = descriptor!(sh(and_v(
v: pk(prvkey0),
and_v(v: after(locktime_seconds), after(locktime_blocks))
)))
.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, &secp)
.unwrap()
.unwrap();
println!("desc policy = {:?}", policy); // TODO remove
// TODO how should this fail with mixed timelocks?
}
// - multiple timelocks of the same type should be correctly merged together
#[test]
#[ignore]
fn test_extract_policy_for_multiple_same_timelocks() {
let secp = Secp256k1::new();
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
let locktime_blocks0 = 100;
let locktime_blocks1 = 200;
let desc = descriptor!(sh(and_v(
v: pk(prvkey0),
and_v(v: after(locktime_blocks0), after(locktime_blocks1))
)))
.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, &secp)
.unwrap()
.unwrap();
println!("desc policy = {:?}", policy); // TODO remove
// TODO how should this merge timelocks?
let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
let locktime_seconds0 = 500000100;
let locktime_seconds1 = 500000200;
let desc = descriptor!(sh(and_v(
v: pk(prvkey1),
and_v(v: after(locktime_seconds0), after(locktime_seconds1))
)))
.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, &secp)
.unwrap()
.unwrap();
println!("desc policy = {:?}", policy); // TODO remove
// TODO how should this merge timelocks?
}
#[test]
fn test_get_condition_multisig() {
let secp = Secp256k1::new();
let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
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();
// no args, choose the default
let no_args = policy.get_condition(&vec![].into_iter().collect());
assert_eq!(no_args, Ok(Condition::default()));
// enough args
let eq_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
assert_eq!(eq_thresh, Ok(Condition::default()));
// more args, it doesn't really change anything
let gt_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
assert_eq!(gt_thresh, Ok(Condition::default()));
// not enough args, error
let lt_thresh =
policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
assert_eq!(
lt_thresh,
Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
);
// index out of range
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)));
}
#[test]
fn test_extract_satisfaction() {
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
const ALICE_BOB_PATH: &str = "m/0'";
const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA==";
const ALICE_BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA";
let secp = Secp256k1::new();
let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let addr = wallet_desc
.as_derived(0, &secp)
.address(Network::Testnet)
.unwrap();
assert_eq!(
"tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
addr.to_string()
);
let signers_container = Arc::new(SignersContainer::from(keymap));
let original_policy = wallet_desc
.extract_policy(&signers_container, &secp)
.unwrap()
.unwrap();
let psbt: PSBT = deserialize(&base64::decode(ALICE_SIGNED_PSBT).unwrap()).unwrap();
let mut policy_clone = original_policy.clone();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0]
)
);
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![1]
)
);
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(ALICE_BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!(
matches!(&policy_clone.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0, 1]
)
);
}
}