2021-03-03 13:22:05 -08:00
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
2020-08-31 11:26:36 +02:00
//
2021-03-03 13:22:05 -08:00
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
2020-08-31 11:26:36 +02:00
//
2021-03-03 13:22:05 -08:00
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
2020-08-31 11:26:36 +02:00
2020-09-04 11:44:49 +02:00
//! Descriptor policy
//!
//! This module implements the logic to extract and represent the spending policies of a descriptor
//! in a more human-readable format.
//!
2020-12-13 21:04:17 -08:00
//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
//!
2020-09-04 11:44:49 +02:00
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
2020-09-14 14:25:38 +02:00
//! # use bdk::descriptor::*;
2022-05-12 17:28:41 +02:00
//! # use bdk::wallet::signer::*;
2020-11-16 22:07:38 +01:00
//! # use bdk::bitcoin::secp256k1::Secp256k1;
2021-04-02 16:39:18 +02:00
//! use bdk::descriptor::policy::BuildSatisfaction;
2020-11-16 22:07:38 +01:00
//! let secp = Secp256k1::new();
2020-09-04 11:44:49 +02:00
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//!
2021-02-02 20:06:40 -05:00
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
2020-09-04 11:44:49 +02:00
//! println!("{:?}", extended_desc);
//!
2022-05-12 17:28:41 +02:00
//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp));
2021-04-02 16:39:18 +02:00
//! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
2020-09-04 11:44:49 +02:00
//! println!("policy: {}", serde_json::to_string(&policy)?);
2020-09-14 14:25:38 +02:00
//! # Ok::<(), bdk::Error>(())
2020-09-04 11:44:49 +02:00
//! ```
[policy] Remove the `TooManyItemsSelected` error
The `TooManyItemsSelected` error has been removed, since it's not technically an
error but potentailly more of an "over-constraint" over a tx: for instance,
given a `thresh(3,pk(a),pk(b),older(10),older(20))` descriptor one could create
a spending tx with the `[0,1,2]` items that would only be spendable after `10`
blocks, or a tx with the `[0,2,3]` items that would be spendable after `20`.
In this case specifying more items than the threshold would create a tx with
the maximum constraint possible, in this case the `20` blocks. This is not
necessarily an error, so we should allow it without failing.
2021-02-13 10:58:26 -05:00
use std ::cmp ::max ;
2020-02-17 14:22:53 +01:00
use std ::collections ::{ BTreeMap , HashSet , VecDeque } ;
2020-08-31 10:49:44 +02:00
use std ::fmt ;
2020-02-07 23:22:28 +01:00
2020-02-17 14:22:53 +01:00
use serde ::ser ::SerializeMap ;
use serde ::{ Serialize , Serializer } ;
2020-02-07 23:22:28 +01:00
use bitcoin ::hashes ::* ;
use bitcoin ::util ::bip32 ::Fingerprint ;
2022-04-14 17:20:46 +02:00
use bitcoin ::{ PublicKey , XOnlyPublicKey } ;
2020-02-07 23:22:28 +01:00
2022-04-14 17:20:46 +02:00
use miniscript ::descriptor ::{
DescriptorPublicKey , DescriptorSinglePub , ShInner , SinglePubKey , SortedMultiVec , WshInner ,
} ;
2021-09-15 10:21:38 +02:00
use miniscript ::{ Descriptor , Miniscript , MiniscriptKey , Satisfier , ScriptContext , Terminal } ;
2020-02-07 23:22:28 +01:00
2020-02-17 14:22:53 +01:00
#[ allow(unused_imports) ]
use log ::{ debug , error , info , trace } ;
2020-02-07 23:22:28 +01:00
2021-09-15 10:21:38 +02:00
use crate ::descriptor ::ExtractPolicy ;
2022-04-14 17:20:46 +02:00
use crate ::keys ::ExtScriptContext ;
2020-08-12 12:51:50 +02:00
use crate ::wallet ::signer ::{ SignerId , SignersContainer } ;
2021-04-02 16:39:18 +02:00
use crate ::wallet ::utils ::{ self , After , Older , SecpCtx } ;
2020-08-12 12:51:50 +02:00
2020-05-16 16:48:31 +02:00
use super ::checksum ::get_checksum ;
2020-02-17 14:22:53 +01:00
use super ::error ::Error ;
2020-08-12 12:51:50 +02:00
use super ::XKeyUtils ;
2022-04-14 17:20:46 +02:00
use bitcoin ::util ::psbt ::{ Input as PsbtInput , PartiallySignedTransaction as Psbt } ;
2021-04-16 10:45:11 +02:00
use miniscript ::psbt ::PsbtInputSatisfier ;
2020-02-17 14:22:53 +01:00
2022-05-24 10:39:17 +02:00
/// A unique identifier for a key
#[ derive(Debug, Clone, PartialEq, Eq, Hash, Serialize) ]
#[ serde(rename_all = " snake_case " ) ]
pub enum PkOrF {
/// A legacy public key
Pubkey ( PublicKey ) ,
/// A x-only public key
XOnlyPubkey ( XOnlyPublicKey ) ,
/// An extended key fingerprint
Fingerprint ( Fingerprint ) ,
2020-02-07 23:22:28 +01:00
}
2021-03-30 16:33:07 +02:00
impl PkOrF {
2020-11-16 22:07:38 +01:00
fn from_key ( k : & DescriptorPublicKey , secp : & SecpCtx ) -> Self {
2020-08-12 12:51:50 +02:00
match k {
2022-04-14 17:20:46 +02:00
DescriptorPublicKey ::SinglePub ( DescriptorSinglePub {
key : SinglePubKey ::FullKey ( pk ) ,
..
2022-05-24 10:39:17 +02:00
} ) = > PkOrF ::Pubkey ( * pk ) ,
2022-04-14 17:20:46 +02:00
DescriptorPublicKey ::SinglePub ( DescriptorSinglePub {
key : SinglePubKey ::XOnly ( pk ) ,
..
2022-05-24 10:39:17 +02:00
} ) = > PkOrF ::XOnlyPubkey ( * pk ) ,
DescriptorPublicKey ::XPub ( xpub ) = > PkOrF ::Fingerprint ( xpub . root_fingerprint ( secp ) ) ,
2020-08-12 12:51:50 +02:00
}
}
2020-02-07 23:22:28 +01:00
}
2020-12-10 14:24:30 -08:00
/// An item that needs to be satisfied
2022-05-24 10:39:17 +02:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize) ]
2020-02-07 23:22:28 +01:00
#[ serde(tag = " type " , rename_all = " UPPERCASE " ) ]
pub enum SatisfiableItem {
// Leaves
2022-04-14 17:20:46 +02:00
/// ECDSA Signature for a raw public key
EcdsaSignature ( PkOrF ) ,
/// Schnorr Signature for a raw public key
SchnorrSignature ( PkOrF ) ,
2020-12-10 14:24:30 -08:00
/// SHA256 preimage hash
2021-03-30 16:33:07 +02:00
Sha256Preimage {
2020-12-10 14:24:30 -08:00
/// The digest value
2020-02-07 23:22:28 +01:00
hash : sha256 ::Hash ,
} ,
2020-12-10 14:24:30 -08:00
/// Double SHA256 preimage hash
2021-03-30 16:33:07 +02:00
Hash256Preimage {
2020-12-10 14:24:30 -08:00
/// The digest value
2020-02-07 23:22:28 +01:00
hash : sha256d ::Hash ,
} ,
2020-12-10 14:24:30 -08:00
/// RIPEMD160 preimage hash
2021-03-30 16:33:07 +02:00
Ripemd160Preimage {
2020-12-10 14:24:30 -08:00
/// The digest value
2020-02-07 23:22:28 +01:00
hash : ripemd160 ::Hash ,
} ,
2020-12-10 14:24:30 -08:00
/// SHA256 then RIPEMD160 preimage hash
2021-03-30 16:33:07 +02:00
Hash160Preimage {
2020-12-10 14:24:30 -08:00
/// The digest value
2020-02-07 23:22:28 +01:00
hash : hash160 ::Hash ,
} ,
2020-12-10 14:24:30 -08:00
/// Absolute timeclock timestamp
2020-02-07 23:22:28 +01:00
AbsoluteTimelock {
2020-12-10 14:24:30 -08:00
/// The timestamp value
2020-02-15 21:27:51 +01:00
value : u32 ,
2020-02-07 23:22:28 +01:00
} ,
2020-12-10 14:24:30 -08:00
/// Relative timelock locktime
2020-02-07 23:22:28 +01:00
RelativeTimelock {
2020-12-13 20:36:38 -08:00
/// The locktime value
2020-02-15 21:27:51 +01:00
value : u32 ,
2020-02-07 23:22:28 +01:00
} ,
2020-12-10 14:24:30 -08:00
/// Multi-signature public keys with threshold count
Multisig {
/// The raw public key or extended key fingerprint
2021-03-30 16:33:07 +02:00
keys : Vec < PkOrF > ,
2020-12-10 14:24:30 -08:00
/// The required threshold count
threshold : usize ,
} ,
2020-02-07 23:22:28 +01:00
// Complex item
2020-12-10 14:24:30 -08:00
/// Threshold items with threshold count
2020-02-07 23:22:28 +01:00
Thresh {
2020-12-10 14:24:30 -08:00
/// The policy items
2020-02-07 23:22:28 +01:00
items : Vec < Policy > ,
2020-12-10 14:24:30 -08:00
/// The required threshold count
2020-02-07 23:22:28 +01:00
threshold : usize ,
} ,
}
impl SatisfiableItem {
2020-12-10 14:24:30 -08:00
/// Returns whether the [`SatisfiableItem`] is a leaf item
2020-02-07 23:22:28 +01:00
pub fn is_leaf ( & self ) -> bool {
2020-12-22 14:37:53 +11:00
! matches! (
self ,
SatisfiableItem ::Thresh {
items : _ ,
threshold : _ ,
}
)
2020-02-07 23:22:28 +01:00
}
2020-05-16 16:48:31 +02:00
2020-12-10 14:24:30 -08:00
/// Returns a unique id for the [`SatisfiableItem`]
2020-05-16 16:48:31 +02:00
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 " )
}
2020-02-17 14:22:53 +01:00
}
2020-02-15 21:27:51 +01:00
2020-10-07 14:18:50 -07:00
fn combinations ( vec : & [ usize ] , size : usize ) -> Vec < Vec < usize > > {
2020-02-17 14:22:53 +01:00
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 ) ) ;
2020-02-15 21:27:51 +01:00
}
2020-02-17 14:22:53 +01:00
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
2020-02-07 23:22:28 +01:00
}
2020-02-17 14:22:53 +01:00
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
}
2020-12-10 14:24:30 -08:00
/// Type for a map of sets of [`Condition`] items keyed by each set's index
2020-02-17 14:22:53 +01:00
pub type ConditionMap = BTreeMap < usize , HashSet < Condition > > ;
2020-12-10 14:24:30 -08:00
/// Type for a map of folded sets of [`Condition`] items keyed by a vector of the combined set's indexes
2020-02-17 14:22:53 +01:00
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 ( )
}
2020-09-04 11:44:49 +02:00
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
2022-05-24 10:39:17 +02:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize) ]
2020-02-15 21:27:51 +01:00
#[ serde(tag = " type " , rename_all = " UPPERCASE " ) ]
pub enum Satisfaction {
2020-09-04 11:44:49 +02:00
/// Only a partial satisfaction of some kind of threshold policy
2020-02-15 21:27:51 +01:00
Partial {
2020-09-04 11:44:49 +02:00
/// Total number of items
2020-02-17 14:22:53 +01:00
n : usize ,
2020-09-04 11:44:49 +02:00
/// Threshold
2020-02-15 21:27:51 +01:00
m : usize ,
2021-02-23 17:04:31 +01:00
/// The items that can be satisfied by the descriptor or are satisfied in the PSBT
2020-02-17 14:22:53 +01:00
items : Vec < usize > ,
2020-11-16 22:07:38 +01:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
/// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
sorted : Option < bool > ,
2020-02-17 14:22:53 +01:00
#[ serde(skip_serializing_if = " BTreeMap::is_empty " ) ]
2020-09-04 11:44:49 +02:00
/// Extra conditions that also need to be satisfied
2020-02-17 14:22:53 +01:00
conditions : ConditionMap ,
} ,
2020-09-04 11:44:49 +02:00
/// Can reach the threshold of some kind of threshold policy
2020-02-17 14:22:53 +01:00
PartialComplete {
2020-09-04 11:44:49 +02:00
/// Total number of items
2020-02-15 21:27:51 +01:00
n : usize ,
2020-09-04 11:44:49 +02:00
/// Threshold
2020-02-17 14:22:53 +01:00
m : usize ,
2020-09-04 11:44:49 +02:00
/// The items that can be satisfied by the descriptor
2020-02-17 14:22:53 +01:00
items : Vec < usize > ,
2020-11-16 22:07:38 +01:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
/// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
sorted : Option < bool > ,
2020-02-17 14:22:53 +01:00
#[ serde(
serialize_with = " serialize_folded_cond_map " ,
skip_serializing_if = " BTreeMap::is_empty "
) ]
2020-09-04 11:44:49 +02:00
/// Extra conditions that also need to be satisfied
2020-02-17 14:22:53 +01:00
conditions : FoldedConditionMap ,
} ,
2020-09-04 11:44:49 +02:00
/// Can satisfy the policy item
2020-02-17 14:22:53 +01:00
Complete {
2020-09-04 11:44:49 +02:00
/// Extra conditions that also need to be satisfied
2020-02-17 14:22:53 +01:00
condition : Condition ,
2020-02-15 21:27:51 +01:00
} ,
2020-09-04 11:44:49 +02:00
/// Cannot satisfy or contribute to the policy item
2020-02-15 21:27:51 +01:00
None ,
}
impl Satisfaction {
2020-12-10 14:24:30 -08:00
/// Returns whether the [`Satisfaction`] is a leaf item
2020-02-17 14:22:53 +01:00
pub fn is_leaf ( & self ) -> bool {
match self {
Satisfaction ::None | Satisfaction ::Complete { .. } = > true ,
Satisfaction ::PartialComplete { .. } | Satisfaction ::Partial { .. } = > false ,
2020-02-15 21:27:51 +01:00
}
}
2020-02-17 14:22:53 +01:00
// 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 ( )
2021-06-08 13:57:55 +10:00
. fold ( HashSet ::new ( ) , | set , i | set . union ( i ) . cloned ( ) . collect ( ) ) ;
2020-02-17 14:22:53 +01:00
conditions . insert ( inner_index , conditions_set ) ;
}
}
2020-02-15 21:27:51 +01:00
2020-02-17 14:22:53 +01:00
Ok ( ( ) )
}
}
2020-02-15 21:27:51 +01:00
}
2021-02-11 10:58:44 -08:00
fn finalize ( & mut self ) {
2020-02-17 14:22:53 +01:00
// if partial try to bump it to a partialcomplete
if let Satisfaction ::Partial {
n ,
m ,
items ,
conditions ,
2020-11-16 22:07:38 +01:00
sorted ,
2020-02-17 14:22:53 +01:00
} = 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))
2021-11-23 13:40:58 -05:00
// we map each of the combinations of elements into a tuple of ([chosen items], [conditions]). unfortunately, those items have potentially more than one
2020-02-17 14:22:53 +01:00
// 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
2021-11-23 13:40:58 -05:00
// consider every possible options and check whether or not they are compatible.
2022-05-03 12:41:22 +02:00
// since this step can turn one item of the iterator into multiple ones, we use `flat_map()` to expand them out
. flat_map ( | i_vec | {
2020-02-17 14:22:53 +01:00
mix ( i_vec
. iter ( )
. map ( | i | {
conditions
. get ( i )
2020-10-07 14:18:50 -07:00
. map ( | set | set . clone ( ) . into_iter ( ) . collect ( ) )
. unwrap_or_default ( )
2020-02-17 14:22:53 +01:00
} )
. collect ( ) )
. into_iter ( )
. map ( | x | ( i_vec . clone ( ) , x ) )
. collect ::< Vec < ( Vec < usize > , Vec < Condition > ) > > ( )
} )
// .inspect(|x| println!("flat {:?}", x))
2020-12-10 14:24:30 -08:00
// try to fold all the conditions for this specific combination of indexes/options. if they are not compatible, try_fold will be Err
2020-02-17 14:22:53 +01:00
. 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 ,
2020-11-16 22:07:38 +01:00
sorted : * sorted ,
2020-02-17 14:22:53 +01:00
} ;
2020-02-15 21:27:51 +01:00
}
2020-02-17 14:22:53 +01:00
}
}
}
impl From < bool > for Satisfaction {
fn from ( other : bool ) -> Self {
if other {
Satisfaction ::Complete {
condition : Default ::default ( ) ,
2020-02-15 21:27:51 +01:00
}
2020-02-17 14:22:53 +01:00
} else {
Satisfaction ::None
2020-02-15 21:27:51 +01:00
}
}
2020-02-07 23:22:28 +01:00
}
2020-09-04 11:44:49 +02:00
/// Descriptor spending policy
2022-05-24 10:39:17 +02:00
#[ derive(Debug, Clone, PartialEq, Eq, Serialize) ]
2020-02-07 23:22:28 +01:00
pub struct Policy {
2020-09-04 15:45:11 +02:00
/// Identifier for this policy node
pub id : String ,
2020-05-16 16:48:31 +02:00
2020-09-04 15:45:11 +02:00
/// Type of this policy node
2020-02-07 23:22:28 +01:00
#[ serde(flatten) ]
2020-09-04 15:45:11 +02:00
pub item : SatisfiableItem ,
2021-03-09 16:59:27 +01:00
/// How much a given PSBT already satisfies this policy node in terms of signatures
2020-09-04 15:45:11 +02:00
pub satisfaction : Satisfaction ,
/// How the wallet's descriptor can satisfy this policy node
pub contribution : Satisfaction ,
2020-02-07 23:22:28 +01:00
}
2020-09-04 11:44:49 +02:00
/// An extra condition that must be satisfied but that is out of control of the user
2020-02-17 14:22:53 +01:00
#[ derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize) ]
pub struct Condition {
2020-12-10 14:24:30 -08:00
/// Optional CheckSequenceVerify condition
2020-02-15 21:27:51 +01:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-02-07 23:22:28 +01:00
pub csv : Option < u32 > ,
2020-12-10 14:24:30 -08:00
/// Optional timelock condition
2020-02-15 21:27:51 +01:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-02-07 23:22:28 +01:00
pub timelock : Option < u32 > ,
}
2020-02-17 14:22:53 +01:00
impl Condition {
2020-12-07 14:48:17 +01:00
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 ;
2020-02-17 14:22:53 +01:00
2020-12-07 14:48:17 +01:00
if ( a < utils ::SEQUENCE_LOCKTIME_TYPE_FLAG ) ! = ( b < utils ::SEQUENCE_LOCKTIME_TYPE_FLAG ) {
2020-02-17 14:22:53 +01:00
Err ( PolicyError ::MixedTimelockUnits )
} else {
Ok ( max ( a , b ) )
2020-02-07 23:22:28 +01:00
}
2020-02-17 14:22:53 +01:00
}
2020-02-07 23:22:28 +01:00
2020-11-10 15:06:14 +01:00
pub ( crate ) fn merge ( mut self , other : & Condition ) -> Result < Self , PolicyError > {
2020-02-07 23:22:28 +01:00
match ( self . csv , other . csv ) {
2020-12-07 14:48:17 +01:00
( Some ( a ) , Some ( b ) ) = > self . csv = Some ( Self ::merge_nsequence ( a , b ) ? ) ,
2020-02-17 14:22:53 +01:00
( None , any ) = > self . csv = any ,
_ = > { }
}
2020-02-07 23:22:28 +01:00
match ( self . timelock , other . timelock ) {
2020-12-07 14:48:17 +01:00
( Some ( a ) , Some ( b ) ) = > self . timelock = Some ( Self ::merge_nlocktime ( a , b ) ? ) ,
2020-02-17 14:22:53 +01:00
( None , any ) = > self . timelock = any ,
_ = > { }
}
2020-02-07 23:22:28 +01:00
2020-02-17 14:22:53 +01:00
Ok ( self )
2020-02-07 23:22:28 +01:00
}
2020-12-10 14:24:30 -08:00
/// Returns `true` if there are no extra conditions to verify
2020-02-07 23:22:28 +01:00
pub fn is_null ( & self ) -> bool {
self . csv . is_none ( ) & & self . timelock . is_none ( )
}
}
2020-09-04 11:44:49 +02:00
/// Errors that can happen while extracting and manipulating policies
2021-02-13 11:00:03 -05:00
#[ derive(Debug, PartialEq, Eq) ]
2020-02-07 23:22:28 +01:00
pub enum PolicyError {
2021-02-13 11:00:03 -05:00
/// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
2020-05-16 16:48:31 +02:00
NotEnoughItemsSelected ( String ) ,
2021-02-13 11:00:03 -05:00
/// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
2020-02-17 14:22:53 +01:00
IndexOutOfRange ( usize ) ,
2020-12-10 14:24:30 -08:00
/// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
2020-02-17 14:22:53 +01:00
AddOnLeaf ,
2020-12-10 14:24:30 -08:00
/// Can not add to an item that is [`Satisfaction::PartialComplete`]
2020-02-17 14:22:53 +01:00
AddOnPartialComplete ,
2020-12-10 14:24:30 -08:00
/// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000
2020-02-17 14:22:53 +01:00
MixedTimelockUnits ,
2020-12-10 14:24:30 -08:00
/// Incompatible conditions (not currently used)
2020-02-17 14:22:53 +01:00
IncompatibleConditions ,
2020-02-07 23:22:28 +01:00
}
2020-08-31 10:49:44 +02:00
impl fmt ::Display for PolicyError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
write! ( f , " {:?} " , self )
}
}
impl std ::error ::Error for PolicyError { }
2020-02-07 23:22:28 +01:00
impl Policy {
2020-08-31 10:49:44 +02:00
fn new ( item : SatisfiableItem ) -> Self {
2020-02-07 23:22:28 +01:00
Policy {
2020-05-16 16:48:31 +02:00
id : item . id ( ) ,
2020-02-07 23:22:28 +01:00
item ,
2020-02-15 21:27:51 +01:00
satisfaction : Satisfaction ::None ,
contribution : Satisfaction ::None ,
2020-02-07 23:22:28 +01:00
}
}
2020-08-31 10:49:44 +02:00
fn make_and ( a : Option < Policy > , b : Option < Policy > ) -> Result < Option < Policy > , PolicyError > {
2020-02-07 23:22:28 +01:00
match ( a , b ) {
2020-02-17 14:22:53 +01:00
( None , None ) = > Ok ( None ) ,
( Some ( x ) , None ) | ( None , Some ( x ) ) = > Ok ( Some ( x ) ) ,
2020-02-15 21:27:51 +01:00
( Some ( a ) , Some ( b ) ) = > Self ::make_thresh ( vec! [ a , b ] , 2 ) ,
2020-02-07 23:22:28 +01:00
}
}
2020-08-31 10:49:44 +02:00
fn make_or ( a : Option < Policy > , b : Option < Policy > ) -> Result < Option < Policy > , PolicyError > {
2020-02-07 23:22:28 +01:00
match ( a , b ) {
2020-02-17 14:22:53 +01:00
( None , None ) = > Ok ( None ) ,
( Some ( x ) , None ) | ( None , Some ( x ) ) = > Ok ( Some ( x ) ) ,
2020-02-15 21:27:51 +01:00
( Some ( a ) , Some ( b ) ) = > Self ::make_thresh ( vec! [ a , b ] , 1 ) ,
2020-02-07 23:22:28 +01:00
}
}
2020-08-31 10:49:44 +02:00
fn make_thresh ( items : Vec < Policy > , threshold : usize ) -> Result < Option < Policy > , PolicyError > {
2020-02-07 23:22:28 +01:00
if threshold = = 0 {
2020-02-17 14:22:53 +01:00
return Ok ( None ) ;
2020-02-07 23:22:28 +01:00
}
2020-02-17 14:22:53 +01:00
let mut contribution = Satisfaction ::Partial {
n : items . len ( ) ,
m : threshold ,
items : vec ! [ ] ,
conditions : Default ::default ( ) ,
2020-11-16 22:07:38 +01:00
sorted : None ,
2020-02-17 14:22:53 +01:00
} ;
2021-04-02 16:39:18 +02:00
let mut satisfaction = contribution . clone ( ) ;
2020-02-17 14:22:53 +01:00
for ( index , item ) in items . iter ( ) . enumerate ( ) {
contribution . add ( & item . contribution , index ) ? ;
2021-04-02 16:39:18 +02:00
satisfaction . add ( & item . satisfaction , index ) ? ;
2020-02-17 14:22:53 +01:00
}
2021-04-02 16:39:18 +02:00
2021-02-11 10:58:44 -08:00
contribution . finalize ( ) ;
2021-04-02 16:39:18 +02:00
satisfaction . finalize ( ) ;
2020-02-17 14:22:53 +01:00
2020-02-15 21:27:51 +01:00
let mut policy : Policy = SatisfiableItem ::Thresh { items , threshold } . into ( ) ;
policy . contribution = contribution ;
2021-04-02 16:39:18 +02:00
policy . satisfaction = satisfaction ;
2020-02-15 21:27:51 +01:00
2020-02-17 14:22:53 +01:00
Ok ( Some ( policy ) )
2020-02-07 23:22:28 +01:00
}
2022-05-24 11:24:48 +02:00
fn make_multisig < Ctx : ScriptContext + 'static > (
2020-10-07 14:18:50 -07:00
keys : & [ DescriptorPublicKey ] ,
2020-11-17 12:05:32 -06:00
signers : & SignersContainer ,
2021-04-02 16:39:18 +02:00
build_sat : BuildSatisfaction ,
2020-02-17 14:22:53 +01:00
threshold : usize ,
2020-11-16 22:07:38 +01:00
sorted : bool ,
secp : & SecpCtx ,
2020-02-17 14:22:53 +01:00
) -> Result < Option < Policy > , PolicyError > {
if threshold = = 0 {
return Ok ( None ) ;
}
2021-03-30 16:33:07 +02:00
let parsed_keys = keys . iter ( ) . map ( | k | PkOrF ::from_key ( k , secp ) ) . collect ( ) ;
2020-02-17 14:22:53 +01:00
let mut contribution = Satisfaction ::Partial {
n : keys . len ( ) ,
m : threshold ,
items : vec ! [ ] ,
conditions : Default ::default ( ) ,
2020-11-16 22:07:38 +01:00
sorted : Some ( sorted ) ,
2020-02-17 14:22:53 +01:00
} ;
2021-04-02 16:39:18 +02:00
let mut satisfaction = contribution . clone ( ) ;
2020-02-17 14:22:53 +01:00
for ( index , key ) in keys . iter ( ) . enumerate ( ) {
2020-11-16 22:07:38 +01:00
if signers . find ( signer_id ( key , secp ) ) . is_some ( ) {
2020-08-12 12:51:50 +02:00
contribution . add (
& Satisfaction ::Complete {
condition : Default ::default ( ) ,
} ,
index ,
) ? ;
}
2021-04-02 16:39:18 +02:00
if let Some ( psbt ) = build_sat . psbt ( ) {
2022-05-24 11:24:48 +02:00
if Ctx ::find_signature ( psbt , key , secp ) {
2021-04-02 16:39:18 +02:00
satisfaction . add (
& Satisfaction ::Complete {
condition : Default ::default ( ) ,
} ,
index ,
) ? ;
}
}
2020-02-17 14:22:53 +01:00
}
2021-04-02 16:39:18 +02:00
satisfaction . finalize ( ) ;
2021-02-11 10:58:44 -08:00
contribution . finalize ( ) ;
2020-02-17 14:22:53 +01:00
2020-02-15 21:27:51 +01:00
let mut policy : Policy = SatisfiableItem ::Multisig {
keys : parsed_keys ,
threshold ,
}
. into ( ) ;
2020-02-17 14:22:53 +01:00
policy . contribution = contribution ;
2021-04-02 16:39:18 +02:00
policy . satisfaction = satisfaction ;
2020-02-17 14:22:53 +01:00
Ok ( Some ( policy ) )
2020-02-15 21:27:51 +01:00
}
2020-09-04 11:44:49 +02:00
/// 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.
2020-02-07 23:22:28 +01:00
pub fn requires_path ( & self ) -> bool {
2020-09-04 11:44:49 +02:00
self . get_condition ( & BTreeMap ::new ( ) ) . is_err ( )
2020-02-07 23:22:28 +01:00
}
2020-09-04 11:44:49 +02:00
/// Return the conditions that are set by the spending policy for a given path in the
/// policy tree
pub fn get_condition (
2020-02-07 23:22:28 +01:00
& self ,
2020-05-16 16:48:31 +02:00
path : & BTreeMap < String , Vec < usize > > ,
2020-02-17 14:22:53 +01:00
) -> Result < Condition , PolicyError > {
2020-02-07 23:22:28 +01:00
// 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 = > {
2020-10-07 14:18:50 -07:00
( 0 .. * threshold ) . collect ( )
2020-02-07 23:22:28 +01:00
}
2021-02-13 11:00:03 -05:00
SatisfiableItem ::Multisig { keys , .. } = > ( 0 .. keys . len ( ) ) . collect ( ) ,
2020-02-07 23:22:28 +01:00
_ = > vec! [ ] ,
} ;
2020-05-16 16:48:31 +02:00
let selected = match path . get ( & self . id ) {
2020-02-07 23:22:28 +01:00
Some ( arr ) = > arr ,
_ = > & default ,
} ;
match & self . item {
SatisfiableItem ::Thresh { items , threshold } = > {
let mapped_req = items
. iter ( )
2020-09-04 11:44:49 +02:00
. map ( | i | i . get_condition ( path ) )
2020-02-07 23:22:28 +01:00
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
// if all the requirements are null we don't care about `selected` because there
// are no requirements
2020-02-17 14:22:53 +01:00
if mapped_req . iter ( ) . all ( Condition ::is_null ) {
return Ok ( Condition ::default ( ) ) ;
2020-02-07 23:22:28 +01:00
}
// 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
[policy] Remove the `TooManyItemsSelected` error
The `TooManyItemsSelected` error has been removed, since it's not technically an
error but potentailly more of an "over-constraint" over a tx: for instance,
given a `thresh(3,pk(a),pk(b),older(10),older(20))` descriptor one could create
a spending tx with the `[0,1,2]` items that would only be spendable after `10`
blocks, or a tx with the `[0,2,3]` items that would be spendable after `20`.
In this case specifying more items than the threshold would create a tx with
the maximum constraint possible, in this case the `20` blocks. This is not
necessarily an error, so we should allow it without failing.
2021-02-13 10:58:26 -05:00
if selected . len ( ) < * threshold {
return Err ( PolicyError ::NotEnoughItemsSelected ( self . id . clone ( ) ) ) ;
2020-02-07 23:22:28 +01:00
}
// check the selected items, see if there are conflicting requirements
2020-02-17 14:22:53 +01:00
let mut requirements = Condition ::default ( ) ;
2020-02-07 23:22:28 +01:00
for item_index in selected {
2020-02-17 14:22:53 +01:00
requirements = requirements . merge (
2020-02-07 23:22:28 +01:00
mapped_req
. get ( * item_index )
2020-02-17 14:22:53 +01:00
. ok_or ( PolicyError ::IndexOutOfRange ( * item_index ) ) ? ,
2020-02-07 23:22:28 +01:00
) ? ;
}
Ok ( requirements )
}
2021-02-13 11:00:03 -05:00
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 ( ) )
}
2020-02-17 14:22:53 +01:00
SatisfiableItem ::AbsoluteTimelock { value } = > Ok ( Condition {
2020-02-07 23:22:28 +01:00
csv : None ,
2020-02-15 21:27:51 +01:00
timelock : Some ( * value ) ,
2020-02-07 23:22:28 +01:00
} ) ,
2020-02-17 14:22:53 +01:00
SatisfiableItem ::RelativeTimelock { value } = > Ok ( Condition {
2020-02-15 21:27:51 +01:00
csv : Some ( * value ) ,
2020-02-07 23:22:28 +01:00
timelock : None ,
} ) ,
2020-02-17 14:22:53 +01:00
_ = > Ok ( Condition ::default ( ) ) ,
2020-02-07 23:22:28 +01:00
}
}
}
impl From < SatisfiableItem > for Policy {
fn from ( other : SatisfiableItem ) -> Self {
Self ::new ( other )
}
}
2020-11-16 22:07:38 +01:00
fn signer_id ( key : & DescriptorPublicKey , secp : & SecpCtx ) -> SignerId {
2020-08-12 12:51:50 +02:00
match key {
2022-04-14 17:20:46 +02:00
DescriptorPublicKey ::SinglePub ( DescriptorSinglePub {
key : SinglePubKey ::FullKey ( pk ) ,
..
} ) = > pk . to_pubkeyhash ( ) . into ( ) ,
DescriptorPublicKey ::SinglePub ( DescriptorSinglePub {
key : SinglePubKey ::XOnly ( pk ) ,
..
} ) = > pk . to_pubkeyhash ( ) . into ( ) ,
2020-11-16 22:07:38 +01:00
DescriptorPublicKey ::XPub ( xpub ) = > xpub . root_fingerprint ( secp ) . into ( ) ,
2020-08-12 12:51:50 +02:00
}
}
2020-02-15 21:27:51 +01:00
2022-05-24 11:24:48 +02:00
fn make_generic_signature < M : Fn ( ) -> SatisfiableItem , F : Fn ( & Psbt ) -> bool > (
2021-04-02 16:39:18 +02:00
key : & DescriptorPublicKey ,
signers : & SignersContainer ,
build_sat : BuildSatisfaction ,
secp : & SecpCtx ,
2022-05-24 11:24:48 +02:00
make_policy : M ,
find_sig : F ,
2021-04-02 16:39:18 +02:00
) -> Policy {
2022-05-24 11:24:48 +02:00
let mut policy : Policy = make_policy ( ) . into ( ) ;
2020-08-12 12:51:50 +02:00
2020-11-16 22:07:38 +01:00
policy . contribution = if signers . find ( signer_id ( key , secp ) ) . is_some ( ) {
2020-08-12 12:51:50 +02:00
Satisfaction ::Complete {
condition : Default ::default ( ) ,
}
} else {
Satisfaction ::None
} ;
2021-04-02 16:39:18 +02:00
if let Some ( psbt ) = build_sat . psbt ( ) {
2022-05-24 11:24:48 +02:00
policy . satisfaction = if find_sig ( psbt ) {
2021-04-02 16:39:18 +02:00
Satisfaction ::Complete {
condition : Default ::default ( ) ,
}
} else {
Satisfaction ::None
} ;
}
2020-08-12 12:51:50 +02:00
policy
2020-02-07 23:22:28 +01:00
}
2022-04-14 17:20:46 +02:00
fn generic_sig_in_psbt <
2022-05-24 15:50:49 +02:00
// C is for "check", it's a closure we use to *check* if a psbt input contains the signature
// for a specific key
2022-04-14 17:20:46 +02:00
C : Fn ( & PsbtInput , & SinglePubKey ) -> bool ,
2022-05-24 15:50:49 +02:00
// E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
E : Fn ( & PsbtInput , Fingerprint ) -> Option < SinglePubKey > ,
2022-04-14 17:20:46 +02:00
> (
psbt : & Psbt ,
key : & DescriptorPublicKey ,
secp : & SecpCtx ,
check : C ,
2022-05-24 15:50:49 +02:00
extract : E ,
2022-04-14 17:20:46 +02:00
) -> bool {
2021-04-02 16:39:18 +02:00
//TODO check signature validity
psbt . inputs . iter ( ) . all ( | input | match key {
2022-04-14 17:20:46 +02:00
DescriptorPublicKey ::SinglePub ( DescriptorSinglePub { key , .. } ) = > check ( input , key ) ,
2021-04-02 16:39:18 +02:00
DescriptorPublicKey ::XPub ( xpub ) = > {
//TODO check actual derivation matches
2022-05-24 15:50:49 +02:00
match extract ( input , xpub . root_fingerprint ( secp ) ) {
Some ( pubkey ) = > check ( input , & pubkey ) ,
2021-04-02 16:39:18 +02:00
None = > false ,
}
}
} )
}
2022-05-24 11:24:48 +02:00
trait SigExt : ScriptContext {
fn make_signature (
key : & DescriptorPublicKey ,
signers : & SignersContainer ,
build_sat : BuildSatisfaction ,
secp : & SecpCtx ,
) -> Policy ;
fn find_signature ( psbt : & Psbt , key : & DescriptorPublicKey , secp : & SecpCtx ) -> bool ;
2022-04-14 17:20:46 +02:00
}
2022-05-24 11:24:48 +02:00
impl < T : ScriptContext + 'static > SigExt for T {
fn make_signature (
key : & DescriptorPublicKey ,
signers : & SignersContainer ,
build_sat : BuildSatisfaction ,
secp : & SecpCtx ,
) -> Policy {
if T ::as_enum ( ) . is_taproot ( ) {
make_generic_signature (
key ,
signers ,
build_sat ,
secp ,
| | SatisfiableItem ::SchnorrSignature ( PkOrF ::from_key ( key , secp ) ) ,
| psbt | Self ::find_signature ( psbt , key , secp ) ,
)
} else {
make_generic_signature (
key ,
signers ,
build_sat ,
secp ,
| | SatisfiableItem ::EcdsaSignature ( PkOrF ::from_key ( key , secp ) ) ,
| psbt | Self ::find_signature ( psbt , key , secp ) ,
)
}
}
fn find_signature ( psbt : & Psbt , key : & DescriptorPublicKey , secp : & SecpCtx ) -> bool {
if T ::as_enum ( ) . is_taproot ( ) {
generic_sig_in_psbt (
psbt ,
key ,
secp ,
| input , pk | {
let pk = match pk {
SinglePubKey ::XOnly ( pk ) = > pk ,
_ = > return false ,
} ;
if input . tap_internal_key = = Some ( * pk ) & & input . tap_key_sig . is_some ( ) {
true
} else {
input . tap_script_sigs . keys ( ) . any ( | ( sk , _ ) | sk = = pk )
}
} ,
2022-05-24 15:50:49 +02:00
| input , fing | {
input
. tap_key_origins
. iter ( )
. find ( | ( _ , ( _ , ( f , _ ) ) ) | f = = & fing )
. map ( | ( pk , _ ) | SinglePubKey ::XOnly ( * pk ) )
} ,
2022-05-24 11:24:48 +02:00
)
} else {
generic_sig_in_psbt (
psbt ,
key ,
secp ,
| input , pk | match pk {
SinglePubKey ::FullKey ( pk ) = > input . partial_sigs . contains_key ( pk ) ,
_ = > false ,
} ,
2022-05-24 15:50:49 +02:00
| input , fing | {
input
. bip32_derivation
. iter ( )
. find ( | ( _ , ( f , _ ) ) | f = = & fing )
. map ( | ( pk , _ ) | SinglePubKey ::FullKey ( PublicKey ::new ( * pk ) ) )
} ,
2022-05-24 11:24:48 +02:00
)
}
}
2022-04-14 17:20:46 +02:00
}
impl < Ctx : ScriptContext + 'static > ExtractPolicy for Miniscript < DescriptorPublicKey , Ctx > {
2020-11-16 22:07:38 +01:00
fn extract_policy (
& self ,
2020-11-17 12:05:32 -06:00
signers : & SignersContainer ,
2021-04-02 16:39:18 +02:00
build_sat : BuildSatisfaction ,
2020-11-16 22:07:38 +01:00
secp : & SecpCtx ,
) -> Result < Option < Policy > , Error > {
2020-02-17 14:22:53 +01:00
Ok ( match & self . node {
2020-02-07 23:22:28 +01:00
// Leaves
Terminal ::True | Terminal ::False = > None ,
2022-05-24 11:24:48 +02:00
Terminal ::PkK ( pubkey ) = > Some ( Ctx ::make_signature ( pubkey , signers , build_sat , secp ) ) ,
Terminal ::PkH ( pubkey_hash ) = > {
Some ( Ctx ::make_signature ( pubkey_hash , signers , build_sat , secp ) )
}
2020-02-15 21:27:51 +01:00
Terminal ::After ( value ) = > {
let mut policy : Policy = SatisfiableItem ::AbsoluteTimelock { value : * value } . into ( ) ;
policy . contribution = Satisfaction ::Complete {
2020-02-17 14:22:53 +01:00
condition : Condition {
2020-02-15 21:27:51 +01:00
timelock : Some ( * value ) ,
2020-02-17 14:22:53 +01:00
csv : None ,
2020-02-15 21:27:51 +01:00
} ,
} ;
2021-04-16 10:45:11 +02:00
if let BuildSatisfaction ::PsbtTimelocks {
current_height ,
psbt ,
..
} = build_sat
{
2021-04-02 16:39:18 +02:00
let after = After ::new ( Some ( current_height ) , false ) ;
2021-04-16 10:45:11 +02:00
let after_sat = Satisfier ::< bitcoin ::PublicKey > ::check_after ( & after , * value ) ;
let inputs_sat = psbt_inputs_sat ( psbt )
. all ( | sat | Satisfier ::< bitcoin ::PublicKey > ::check_after ( & sat , * value ) ) ;
if after_sat & & inputs_sat {
2021-04-02 16:39:18 +02:00
policy . satisfaction = policy . contribution . clone ( ) ;
}
}
2020-02-15 21:27:51 +01:00
Some ( policy )
2020-02-07 23:22:28 +01:00
}
2020-02-15 21:27:51 +01:00
Terminal ::Older ( value ) = > {
let mut policy : Policy = SatisfiableItem ::RelativeTimelock { value : * value } . into ( ) ;
policy . contribution = Satisfaction ::Complete {
2020-02-17 14:22:53 +01:00
condition : Condition {
2020-02-15 21:27:51 +01:00
timelock : None ,
2020-02-17 14:22:53 +01:00
csv : Some ( * value ) ,
2020-02-15 21:27:51 +01:00
} ,
} ;
2021-04-02 16:39:18 +02:00
if let BuildSatisfaction ::PsbtTimelocks {
current_height ,
input_max_height ,
2021-04-16 10:45:11 +02:00
psbt ,
2021-04-02 16:39:18 +02:00
} = build_sat
{
let older = Older ::new ( Some ( current_height ) , Some ( input_max_height ) , false ) ;
2021-04-16 10:45:11 +02:00
let older_sat = Satisfier ::< bitcoin ::PublicKey > ::check_older ( & older , * value ) ;
let inputs_sat = psbt_inputs_sat ( psbt )
. all ( | sat | Satisfier ::< bitcoin ::PublicKey > ::check_older ( & sat , * value ) ) ;
if older_sat & & inputs_sat {
2021-04-02 16:39:18 +02:00
policy . satisfaction = policy . contribution . clone ( ) ;
}
}
2020-02-15 21:27:51 +01:00
Some ( policy )
2020-02-07 23:22:28 +01:00
}
2021-03-30 16:33:07 +02:00
Terminal ::Sha256 ( hash ) = > Some ( SatisfiableItem ::Sha256Preimage { hash : * hash } . into ( ) ) ,
2020-02-07 23:22:28 +01:00
Terminal ::Hash256 ( hash ) = > {
2021-03-30 16:33:07 +02:00
Some ( SatisfiableItem ::Hash256Preimage { hash : * hash } . into ( ) )
2020-02-07 23:22:28 +01:00
}
Terminal ::Ripemd160 ( hash ) = > {
2021-03-30 16:33:07 +02:00
Some ( SatisfiableItem ::Ripemd160Preimage { hash : * hash } . into ( ) )
2020-02-07 23:22:28 +01:00
}
Terminal ::Hash160 ( hash ) = > {
2021-03-30 16:33:07 +02:00
Some ( SatisfiableItem ::Hash160Preimage { hash : * hash } . into ( ) )
2020-02-07 23:22:28 +01:00
}
2022-05-24 11:24:48 +02:00
Terminal ::Multi ( k , pks ) | Terminal ::MultiA ( k , pks ) = > {
Policy ::make_multisig ::< Ctx > ( pks , signers , build_sat , * k , false , secp ) ?
}
2020-02-07 23:22:28 +01:00
// Identities
Terminal ::Alt ( inner )
| Terminal ::Swap ( inner )
| Terminal ::Check ( inner )
| Terminal ::DupIf ( inner )
| Terminal ::Verify ( inner )
| Terminal ::NonZero ( inner )
2021-04-02 16:39:18 +02:00
| Terminal ::ZeroNotEqual ( inner ) = > inner . extract_policy ( signers , build_sat , secp ) ? ,
2020-02-07 23:22:28 +01:00
// Complex policies
2020-08-12 12:51:50 +02:00
Terminal ::AndV ( a , b ) | Terminal ::AndB ( a , b ) = > Policy ::make_and (
2021-04-02 16:39:18 +02:00
a . extract_policy ( signers , build_sat , secp ) ? ,
b . extract_policy ( signers , build_sat , secp ) ? ,
2020-08-12 12:51:50 +02:00
) ? ,
2020-02-07 23:22:28 +01:00
Terminal ::AndOr ( x , y , z ) = > Policy ::make_or (
2020-08-12 12:51:50 +02:00
Policy ::make_and (
2021-04-02 16:39:18 +02:00
x . extract_policy ( signers , build_sat , secp ) ? ,
y . extract_policy ( signers , build_sat , secp ) ? ,
2020-08-12 12:51:50 +02:00
) ? ,
2021-04-02 16:39:18 +02:00
z . extract_policy ( signers , build_sat , secp ) ? ,
2020-02-17 14:22:53 +01:00
) ? ,
2020-02-07 23:22:28 +01:00
Terminal ::OrB ( a , b )
| Terminal ::OrD ( a , b )
| Terminal ::OrC ( a , b )
2020-08-12 12:51:50 +02:00
| Terminal ::OrI ( a , b ) = > Policy ::make_or (
2021-04-02 16:39:18 +02:00
a . extract_policy ( signers , build_sat , secp ) ? ,
b . extract_policy ( signers , build_sat , secp ) ? ,
2020-08-12 12:51:50 +02:00
) ? ,
2020-02-07 23:22:28 +01:00
Terminal ::Thresh ( k , nodes ) = > {
let mut threshold = * k ;
let mapped : Vec < _ > = nodes
. iter ( )
2021-04-02 16:39:18 +02:00
. map ( | n | n . extract_policy ( signers , build_sat , secp ) )
2020-02-17 14:22:53 +01:00
. collect ::< Result < Vec < _ > , _ > > ( ) ?
. into_iter ( )
2021-04-08 13:37:59 -07:00
. flatten ( )
2020-02-07 23:22:28 +01:00
. collect ( ) ;
if mapped . len ( ) < nodes . len ( ) {
threshold = match threshold . checked_sub ( nodes . len ( ) - mapped . len ( ) ) {
2020-02-17 14:22:53 +01:00
None = > return Ok ( None ) ,
2020-02-07 23:22:28 +01:00
Some ( x ) = > x ,
} ;
}
2020-02-17 14:22:53 +01:00
Policy ::make_thresh ( mapped , threshold ) ?
2020-02-07 23:22:28 +01:00
}
2020-02-17 14:22:53 +01:00
} )
2020-02-07 23:22:28 +01:00
}
}
2021-05-07 10:25:45 +10:00
fn psbt_inputs_sat ( psbt : & Psbt ) -> impl Iterator < Item = PsbtInputSatisfier > {
2021-04-16 10:45:11 +02:00
( 0 .. psbt . inputs . len ( ) ) . map ( move | i | PsbtInputSatisfier ::new ( psbt , i ) )
}
2021-04-02 16:39:18 +02:00
/// Options to build the satisfaction field in the policy
#[ derive(Debug, Clone, Copy) ]
pub enum BuildSatisfaction < ' a > {
/// Don't generate `satisfaction` field
None ,
/// Analyze the given PSBT to check for existing signatures
2021-05-07 10:25:45 +10:00
Psbt ( & ' a Psbt ) ,
2021-04-02 16:39:18 +02:00
/// Like `Psbt` variant and also check for expired timelocks
PsbtTimelocks {
2021-04-15 16:02:22 +02:00
/// Given PSBT
2021-05-07 10:25:45 +10:00
psbt : & ' a Psbt ,
2021-04-02 16:39:18 +02:00
/// Current blockchain height
current_height : u32 ,
/// The highest confirmation height between the inputs
/// CSV should consider different inputs, but we consider the worst condition for the tx as whole
input_max_height : u32 ,
} ,
}
impl < ' a > BuildSatisfaction < ' a > {
2021-05-07 10:25:45 +10:00
fn psbt ( & self ) -> Option < & ' a Psbt > {
2021-04-02 16:39:18 +02:00
match self {
BuildSatisfaction ::None = > None ,
BuildSatisfaction ::Psbt ( psbt ) = > Some ( psbt ) ,
BuildSatisfaction ::PsbtTimelocks { psbt , .. } = > Some ( psbt ) ,
}
}
}
2020-08-12 12:51:50 +02:00
impl ExtractPolicy for Descriptor < DescriptorPublicKey > {
2020-11-16 22:07:38 +01:00
fn extract_policy (
& self ,
2020-11-17 12:05:32 -06:00
signers : & SignersContainer ,
2021-04-02 16:39:18 +02:00
build_sat : BuildSatisfaction ,
2020-11-16 22:07:38 +01:00
secp : & SecpCtx ,
) -> Result < Option < Policy > , Error > {
2022-05-24 11:24:48 +02:00
fn make_sortedmulti < Ctx : ScriptContext + 'static > (
2020-11-16 22:07:38 +01:00
keys : & SortedMultiVec < DescriptorPublicKey , Ctx > ,
2020-11-17 12:05:32 -06:00
signers : & SignersContainer ,
2021-04-02 16:39:18 +02:00
build_sat : BuildSatisfaction ,
2020-11-16 22:07:38 +01:00
secp : & SecpCtx ,
) -> Result < Option < Policy > , Error > {
2022-05-24 11:24:48 +02:00
Ok ( Policy ::make_multisig ::< Ctx > (
2020-11-16 22:07:38 +01:00
keys . pks . as_ref ( ) ,
signers ,
2021-04-02 16:39:18 +02:00
build_sat ,
2020-11-16 22:07:38 +01:00
keys . k ,
true ,
secp ,
) ? )
}
2020-02-07 23:22:28 +01:00
match self {
2022-05-24 11:24:48 +02:00
Descriptor ::Pkh ( pk ) = > Ok ( Some ( miniscript ::Legacy ::make_signature (
pk . as_inner ( ) ,
signers ,
build_sat ,
secp ,
) ) ) ,
Descriptor ::Wpkh ( pk ) = > Ok ( Some ( miniscript ::Segwitv0 ::make_signature (
pk . as_inner ( ) ,
signers ,
build_sat ,
secp ,
) ) ) ,
2021-02-02 20:06:40 -05:00
Descriptor ::Sh ( sh ) = > match sh . as_inner ( ) {
2022-05-24 11:24:48 +02:00
ShInner ::Wpkh ( pk ) = > Ok ( Some ( miniscript ::Segwitv0 ::make_signature (
pk . as_inner ( ) ,
signers ,
build_sat ,
secp ,
) ) ) ,
2021-04-02 16:39:18 +02:00
ShInner ::Ms ( ms ) = > Ok ( ms . extract_policy ( signers , build_sat , secp ) ? ) ,
ShInner ::SortedMulti ( ref keys ) = > make_sortedmulti ( keys , signers , build_sat , secp ) ,
2021-02-02 20:06:40 -05:00
ShInner ::Wsh ( wsh ) = > match wsh . as_inner ( ) {
2021-04-02 16:39:18 +02:00
WshInner ::Ms ( ms ) = > Ok ( ms . extract_policy ( signers , build_sat , secp ) ? ) ,
WshInner ::SortedMulti ( ref keys ) = > {
make_sortedmulti ( keys , signers , build_sat , secp )
}
2021-02-02 20:06:40 -05:00
} ,
} ,
Descriptor ::Wsh ( wsh ) = > match wsh . as_inner ( ) {
2021-04-02 16:39:18 +02:00
WshInner ::Ms ( ms ) = > Ok ( ms . extract_policy ( signers , build_sat , secp ) ? ) ,
WshInner ::SortedMulti ( ref keys ) = > make_sortedmulti ( keys , signers , build_sat , secp ) ,
2021-02-02 20:06:40 -05:00
} ,
2021-04-02 16:39:18 +02:00
Descriptor ::Bare ( ms ) = > Ok ( ms . as_inner ( ) . extract_policy ( signers , build_sat , secp ) ? ) ,
2022-04-14 17:20:46 +02:00
Descriptor ::Tr ( tr ) = > {
2022-05-24 11:24:48 +02:00
// If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
// node with threshold = 1 and the key spend signature plus all the tree leaves
let key_spend_sig =
miniscript ::Tap ::make_signature ( tr . internal_key ( ) , signers , build_sat , secp ) ;
if tr . taptree ( ) . is_none ( ) {
Ok ( Some ( key_spend_sig ) )
} else {
let mut items = vec! [ key_spend_sig ] ;
items . append (
& mut tr
. iter_scripts ( )
. filter_map ( | ( _ , ms ) | {
ms . extract_policy ( signers , build_sat , secp ) . transpose ( )
} )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ,
) ;
Ok ( Policy ::make_thresh ( items , 1 ) ? )
}
2022-04-14 17:20:46 +02:00
}
2020-02-07 23:22:28 +01:00
}
}
}
2020-10-01 15:54:59 -07:00
#[ cfg(test) ]
mod test {
use crate ::descriptor ;
2021-02-12 22:34:43 -08:00
use crate ::descriptor ::{ ExtractPolicy , IntoWalletDescriptor } ;
2020-10-01 15:54:59 -07:00
use super ::* ;
2021-02-23 17:04:31 +01:00
use crate ::descriptor ::derived ::AsDerived ;
2022-04-14 17:20:46 +02:00
use crate ::descriptor ::policy ::SatisfiableItem ::{ EcdsaSignature , Multisig , Thresh } ;
2021-02-12 23:02:13 -08:00
use crate ::keys ::{ DescriptorKey , IntoDescriptorKey } ;
2020-10-01 15:54:59 -07:00
use crate ::wallet ::signer ::SignersContainer ;
2021-02-22 15:20:09 +01:00
use bitcoin ::secp256k1 ::Secp256k1 ;
2020-10-01 15:54:59 -07:00
use bitcoin ::util ::bip32 ;
use bitcoin ::Network ;
2021-02-23 17:04:31 +01:00
use miniscript ::DescriptorTrait ;
2020-10-01 15:54:59 -07:00
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 ,
2021-02-23 17:04:31 +01:00
path : & str ,
2021-02-22 11:11:44 +01:00
secp : & SecpCtx ,
2020-10-01 15:54:59 -07:00
) -> ( DescriptorKey < Ctx > , DescriptorKey < Ctx > , Fingerprint ) {
2021-02-23 17:04:31 +01:00
let path = bip32 ::DerivationPath ::from_str ( path ) . unwrap ( ) ;
2020-10-01 15:54:59 -07:00
let tprv = bip32 ::ExtendedPrivKey ::from_str ( tprv ) . unwrap ( ) ;
2022-04-14 17:20:46 +02:00
let tpub = bip32 ::ExtendedPubKey ::from_priv ( secp , & tprv ) ;
2021-06-08 13:57:55 +10:00
let fingerprint = tprv . fingerprint ( secp ) ;
2021-02-11 11:00:48 -08:00
let prvkey = ( tprv , path . clone ( ) ) . into_descriptor_key ( ) . unwrap ( ) ;
let pubkey = ( tpub , path ) . into_descriptor_key ( ) . unwrap ( ) ;
2020-10-01 15:54:59 -07:00
( prvkey , pubkey , fingerprint )
}
// test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
#[ test ]
fn test_extract_policy_for_wpkh ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey , pubkey , fingerprint ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
2020-10-01 15:54:59 -07:00
let desc = descriptor! ( wpkh ( pubkey ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
2022-05-24 10:39:17 +02:00
assert! ( matches! ( & policy . item , EcdsaSignature ( PkOrF ::Fingerprint ( f ) ) if f = = & fingerprint ) ) ;
2020-10-01 15:54:59 -07:00
assert! ( matches! ( & policy . contribution , Satisfaction ::None ) ) ;
let desc = descriptor! ( wpkh ( prvkey ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
2022-05-24 10:39:17 +02:00
assert! ( matches! ( & policy . item , EcdsaSignature ( PkOrF ::Fingerprint ( f ) ) if f = = & fingerprint ) ) ;
2020-10-01 15:54:59 -07:00
assert! (
matches! ( & policy . contribution , Satisfaction ::Complete { condition } if condition . csv = = None & & condition . timelock = = None )
) ;
}
// 2 pub keys descriptor, required 2 prv keys
2021-02-22 15:20:09 +01:00
#[ test ]
fn test_extract_policy_for_sh_multi_partial_0of2 ( ) {
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( _prvkey1 , pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2021-02-22 15:20:09 +01:00
let desc = descriptor! ( sh ( multi ( 2 , pubkey0 , pubkey1 ) ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-22 15:20:09 +01:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-22 15:20:09 +01:00
. unwrap ( )
. unwrap ( ) ;
2021-02-23 17:04:31 +01:00
2021-02-22 15:20:09 +01:00
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 2 usize
2022-05-24 10:39:17 +02:00
& & keys [ 0 ] = = PkOrF ::Fingerprint ( fingerprint0 )
& & keys [ 1 ] = = PkOrF ::Fingerprint ( fingerprint1 ) )
2021-02-22 15:20:09 +01:00
) ;
// 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 = = & 2 usize
& & m = = & 2 usize
& & items . is_empty ( )
& & conditions . is_empty ( )
)
) ;
}
2020-10-01 15:54:59 -07:00
// 1 prv and 1 pub key descriptor, required 2 prv keys
2021-02-22 15:20:09 +01:00
#[ test ]
fn test_extract_policy_for_sh_multi_partial_1of2 ( ) {
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey0 , _pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( _prvkey1 , pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2021-02-22 15:20:09 +01:00
let desc = descriptor! ( sh ( multi ( 2 , prvkey0 , pubkey1 ) ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-22 15:20:09 +01:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-22 15:20:09 +01:00
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 2 usize
2022-05-24 10:39:17 +02:00
& & keys [ 0 ] = = PkOrF ::Fingerprint ( fingerprint0 )
& & keys [ 1 ] = = PkOrF ::Fingerprint ( fingerprint1 ) )
2021-02-22 15:20:09 +01:00
) ;
assert! (
matches! ( & policy . contribution , Satisfaction ::Partial { n , m , items , conditions , .. } if n = = & 2 usize
& & m = = & 2 usize
& & items . len ( ) = = 1
& & conditions . contains_key ( & 0 )
)
) ;
}
2020-10-01 15:54:59 -07:00
// 1 prv and 1 pub key descriptor, required 1 prv keys
#[ test ]
2020-12-14 10:17:12 +07:00
#[ ignore ] // see https://github.com/bitcoindevkit/bdk/issues/225
2020-10-01 15:54:59 -07:00
fn test_extract_policy_for_sh_multi_complete_1of2 ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2020-12-16 16:10:22 +01:00
let desc = descriptor! ( sh ( multi ( 1 , pubkey0 , prvkey1 ) ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 1
2022-05-24 10:39:17 +02:00
& & keys [ 0 ] = = PkOrF ::Fingerprint ( fingerprint0 )
& & keys [ 1 ] = = PkOrF ::Fingerprint ( fingerprint1 ) )
2020-10-01 15:54:59 -07:00
) ;
assert! (
2020-11-16 22:07:38 +01:00
matches! ( & policy . contribution , Satisfaction ::PartialComplete { n , m , items , conditions , .. } if n = = & 2
2020-10-01 15:54:59 -07:00
& & 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 ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey0 , _pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2020-12-16 16:10:22 +01:00
let desc = descriptor! ( sh ( multi ( 2 , prvkey0 , prvkey1 ) ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 2
2022-05-24 10:39:17 +02:00
& & keys [ 0 ] = = PkOrF ::Fingerprint ( fingerprint0 )
& & keys [ 1 ] = = PkOrF ::Fingerprint ( fingerprint1 ) )
2020-10-01 15:54:59 -07:00
) ;
assert! (
2020-11-16 22:07:38 +01:00
matches! ( & policy . contribution , Satisfaction ::PartialComplete { n , m , items , conditions , .. } if n = = & 2
2020-10-01 15:54:59 -07:00
& & 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 ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey , pubkey , fingerprint ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
2020-10-01 15:54:59 -07:00
let desc = descriptor! ( wpkh ( pubkey ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2021-02-02 20:06:40 -05:00
let single_key = wallet_desc . derive ( 0 ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = single_key
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
2022-05-24 10:39:17 +02:00
assert! ( matches! ( & policy . item , EcdsaSignature ( PkOrF ::Fingerprint ( f ) ) if f = = & fingerprint ) ) ;
2020-10-01 15:54:59 -07:00
assert! ( matches! ( & policy . contribution , Satisfaction ::None ) ) ;
let desc = descriptor! ( wpkh ( prvkey ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2021-02-02 20:06:40 -05:00
let single_key = wallet_desc . derive ( 0 ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = single_key
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
2022-05-24 10:39:17 +02:00
assert! ( matches! ( & policy . item , EcdsaSignature ( PkOrF ::Fingerprint ( f ) ) if f = = & fingerprint ) ) ;
2020-10-01 15:54:59 -07:00
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 ]
2020-12-14 10:17:12 +07:00
#[ ignore ] // see https://github.com/bitcoindevkit/bdk/issues/225
2020-10-01 15:54:59 -07:00
fn test_extract_policy_for_single_wsh_multi_complete_1of2 ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2020-12-16 16:10:22 +01:00
let desc = descriptor! ( sh ( multi ( 1 , pubkey0 , prvkey1 ) ) ) . unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2021-02-02 20:06:40 -05:00
let single_key = wallet_desc . derive ( 0 ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = single_key
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 1
2022-05-24 10:39:17 +02:00
& & keys [ 0 ] = = PkOrF ::Fingerprint ( fingerprint0 )
& & keys [ 1 ] = = PkOrF ::Fingerprint ( fingerprint1 ) )
2020-10-01 15:54:59 -07:00
) ;
assert! (
2020-11-16 22:07:38 +01:00
matches! ( & policy . contribution , Satisfaction ::PartialComplete { n , m , items , conditions , .. } if n = = & 2
2020-10-01 15:54:59 -07:00
& & 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 ]
2020-12-14 10:17:12 +07:00
#[ ignore ] // see https://github.com/bitcoindevkit/bdk/issues/225
2020-10-01 15:54:59 -07:00
fn test_extract_policy_for_wsh_multi_timelock ( ) {
2021-02-02 20:06:40 -05:00
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( _prvkey1 , pubkey1 , _fingerprint1 ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2020-10-01 15:54:59 -07:00
let sequence = 50 ;
2021-01-11 13:12:01 +01:00
#[ rustfmt::skip ]
2020-12-16 16:10:22 +01:00
let desc = descriptor! ( wsh ( thresh (
2 ,
pk ( prvkey0 ) ,
2021-01-11 13:12:01 +01:00
s :pk ( pubkey1 ) ,
s :d :v :older ( sequence )
2020-12-16 16:10:22 +01:00
) ) )
2020-10-01 15:54:59 -07:00
. unwrap ( ) ;
2021-02-11 11:00:48 -08:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2020-10-01 15:54:59 -07:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2020-10-01 15:54:59 -07:00
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Thresh { items , threshold } if items . len ( ) = = 3 & & threshold = = & 2 )
) ;
assert! (
2020-11-16 22:07:38 +01:00
matches! ( & policy . contribution , Satisfaction ::PartialComplete { n , m , items , conditions , .. } if n = = & 3
2020-10-01 15:54:59 -07:00
& & 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
2021-02-22 15:20:09 +01:00
#[ test ]
#[ ignore ]
fn test_extract_policy_for_wsh_mixed_timelocks ( ) {
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
2021-02-22 15:20:09 +01:00
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 ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-22 15:20:09 +01:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-22 15:20:09 +01:00
. unwrap ( )
. unwrap ( ) ;
println! ( " desc policy = {:?} " , policy ) ; // TODO remove
// TODO how should this fail with mixed timelocks?
}
2020-10-01 15:54:59 -07:00
// - multiple timelocks of the same type should be correctly merged together
2021-02-22 15:20:09 +01:00
#[ test ]
#[ ignore ]
fn test_extract_policy_for_multiple_same_timelocks ( ) {
let secp = Secp256k1 ::new ( ) ;
2021-02-23 17:04:31 +01:00
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
2021-02-22 15:20:09 +01:00
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 ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-22 15:20:09 +01:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-22 15:20:09 +01:00
. unwrap ( )
. unwrap ( ) ;
println! ( " desc policy = {:?} " , policy ) ; // TODO remove
// TODO how should this merge timelocks?
2021-02-23 17:04:31 +01:00
let ( prvkey1 , _pubkey1 , _fingerprint1 ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
2021-02-22 15:20:09 +01:00
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 ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-22 15:20:09 +01:00
let policy = wallet_desc
2021-04-02 16:39:18 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-22 15:20:09 +01:00
. unwrap ( )
. unwrap ( ) ;
2020-10-01 15:54:59 -07:00
2021-02-22 15:20:09 +01:00
println! ( " desc policy = {:?} " , policy ) ; // TODO remove
// TODO how should this merge timelocks?
}
2021-02-13 11:00:03 -05:00
#[ test ]
fn test_get_condition_multisig ( ) {
2021-02-22 11:11:44 +01:00
let secp = Secp256k1 ::new ( ) ;
2021-02-13 11:00:03 -05:00
2021-02-23 17:04:31 +01:00
let ( _ , pk0 , _ ) = setup_keys ( TPRV0_STR , PATH , & secp ) ;
let ( _ , pk1 , _ ) = setup_keys ( TPRV1_STR , PATH , & secp ) ;
2021-02-13 11:00:03 -05:00
let desc = descriptor! ( wsh ( multi ( 1 , pk0 , pk1 ) ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-13 11:00:03 -05:00
let policy = wallet_desc
2022-05-12 17:28:41 +02:00
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
2021-02-13 11:00:03 -05:00
. 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 ) ) ) ;
}
2021-02-23 17:04:31 +01:00
2021-04-02 16:39:18 +02:00
const ALICE_TPRV_STR :& str = " tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP " ;
const BOB_TPRV_STR :& str = " tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp " ;
2021-09-15 10:36:50 +02:00
const CAROL_TPRV_STR :& str = " tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt " ;
2021-04-02 16:39:18 +02:00
const ALICE_BOB_PATH : & str = " m/0' " ;
2021-02-23 17:04:31 +01:00
#[ test ]
fn test_extract_satisfaction ( ) {
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 ( )
) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-02-23 17:04:31 +01:00
2021-06-10 15:14:00 +02:00
let psbt = Psbt ::from_str ( ALICE_SIGNED_PSBT ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let policy_alice_psbt = wallet_desc
. extract_policy ( & signers_container , BuildSatisfaction ::Psbt ( & psbt ) , & secp )
2021-02-23 17:04:31 +01:00
. unwrap ( )
. unwrap ( ) ;
2021-04-15 16:02:22 +02:00
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
2021-02-23 17:04:31 +01:00
assert! (
2021-04-02 16:39:18 +02:00
matches! ( & policy_alice_psbt . satisfaction , Satisfaction ::Partial { n , m , items , .. } if n = = & 2
2021-02-23 17:04:31 +01:00
& & m = = & 2
& & items = = & vec! [ 0 ]
)
) ;
2021-06-10 15:14:00 +02:00
let psbt = Psbt ::from_str ( BOB_SIGNED_PSBT ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let policy_bob_psbt = wallet_desc
. extract_policy ( & signers_container , BuildSatisfaction ::Psbt ( & psbt ) , & secp )
. unwrap ( )
2021-02-23 17:04:31 +01:00
. unwrap ( ) ;
2021-04-02 16:39:18 +02:00
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
2021-02-23 17:04:31 +01:00
assert! (
2021-04-02 16:39:18 +02:00
matches! ( & policy_bob_psbt . satisfaction , Satisfaction ::Partial { n , m , items , .. } if n = = & 2
2021-02-23 17:04:31 +01:00
& & m = = & 2
& & items = = & vec! [ 1 ]
)
) ;
2021-06-10 15:14:00 +02:00
let psbt = Psbt ::from_str ( ALICE_BOB_SIGNED_PSBT ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let policy_alice_bob_psbt = wallet_desc
. extract_policy ( & signers_container , BuildSatisfaction ::Psbt ( & psbt ) , & secp )
. unwrap ( )
2021-02-23 17:04:31 +01:00
. unwrap ( ) ;
assert! (
2021-04-02 16:39:18 +02:00
matches! ( & policy_alice_bob_psbt . satisfaction , Satisfaction ::PartialComplete { n , m , items , .. } if n = = & 2
2021-02-23 17:04:31 +01:00
& & m = = & 2
& & items = = & vec! [ 0 , 1 ]
)
) ;
}
2021-04-02 16:39:18 +02:00
#[ test ]
fn test_extract_satisfaction_timelock ( ) {
//const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED : & str = " cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA " ;
const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED : & str = " cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstIMEUCIQCtZxNm6H3Ux3pnc64DSpgohMdBj+57xhFHcURYt2BpPAIgG3OnI7bcj/3GtWX1HHyYGSI7QGa/zq5YnsmK1Cw29NABAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEIoAQASDBFAiEArWcTZuh91Md6Z3OuA0qYKITHQY/ue8YRR3FEWLdgaTwCIBtzpyO23I/9xrVl9Rx8mBkiO0Bmv86uWJ7JitQsNvTQAQEBUnZjUrJpaHwhA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLrJN8IQL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiKyTUocAAA== " ;
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 =
2022-04-20 18:25:26 +02:00
descriptor! ( wsh ( thresh ( 2 , n :d :v :older ( 2 ) , s :pk ( prvkey_alice ) , s :pk ( prvkey_bob ) ) ) ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-04-02 16:39:18 +02:00
let addr = wallet_desc
. as_derived ( 0 , & secp )
. address ( Network ::Testnet )
. unwrap ( ) ;
assert_eq! (
2022-04-20 18:25:26 +02:00
" tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58 " ,
2021-04-02 16:39:18 +02:00
addr . to_string ( )
) ;
2021-06-10 15:14:00 +02:00
let psbt = Psbt ::from_str ( PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let build_sat = BuildSatisfaction ::PsbtTimelocks {
psbt : & psbt ,
current_height : 10 ,
input_max_height : 9 ,
} ;
let policy = wallet_desc
. extract_policy ( & signers_container , build_sat , & secp )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . satisfaction , Satisfaction ::Partial { n , m , items , .. } if n = = & 3
& & m = = & 2
& & items . is_empty ( )
)
) ;
2021-04-15 16:02:22 +02:00
//println!("{}", serde_json::to_string(&policy).unwrap());
2021-04-02 16:39:18 +02:00
let build_sat_expired = BuildSatisfaction ::PsbtTimelocks {
psbt : & psbt ,
current_height : 12 ,
input_max_height : 9 ,
} ;
let policy_expired = wallet_desc
. extract_policy ( & signers_container , build_sat_expired , & secp )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy_expired . satisfaction , Satisfaction ::Partial { n , m , items , .. } if n = = & 3
& & m = = & 2
& & items = = & vec! [ 0 ]
)
) ;
2021-04-15 16:02:22 +02:00
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
2021-04-02 16:39:18 +02:00
2021-06-10 15:14:00 +02:00
let psbt_signed = Psbt ::from_str ( PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED ) . unwrap ( ) ;
2021-04-02 16:39:18 +02:00
let build_sat_expired_signed = BuildSatisfaction ::PsbtTimelocks {
psbt : & psbt_signed ,
current_height : 12 ,
input_max_height : 9 ,
} ;
let policy_expired_signed = wallet_desc
. extract_policy ( & signers_container , build_sat_expired_signed , & secp )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy_expired_signed . satisfaction , Satisfaction ::PartialComplete { n , m , items , .. } if n = = & 3
& & m = = & 2
& & items = = & vec! [ 0 , 1 ]
)
) ;
2021-04-15 16:02:22 +02:00
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
2021-04-02 16:39:18 +02:00
}
2021-09-15 10:36:50 +02:00
#[ test ]
fn test_extract_pkh ( ) {
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 ( prvkey_carol , _ , _ ) = setup_keys ( CAROL_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let desc = descriptor! ( wsh ( c : andor (
pk ( prvkey_alice ) ,
pk_k ( prvkey_bob ) ,
pk_h ( prvkey_carol ) ,
) ) )
. unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
2022-05-12 17:28:41 +02:00
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
2021-09-15 10:36:50 +02:00
let policy = wallet_desc . extract_policy ( & signers_container , BuildSatisfaction ::None , & secp ) ;
assert! ( policy . is_ok ( ) ) ;
}
2022-05-24 11:49:31 +02:00
#[ test ]
fn test_extract_tr_key_spend ( ) {
let secp = Secp256k1 ::new ( ) ;
let ( prvkey , _ , fingerprint ) = setup_keys ( ALICE_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let desc = descriptor! ( tr ( prvkey ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
let policy = wallet_desc
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
. unwrap ( ) ;
assert_eq! (
policy ,
Some ( Policy {
id : " 48u0tz0n " . to_string ( ) ,
item : SatisfiableItem ::SchnorrSignature ( PkOrF ::Fingerprint ( fingerprint ) ) ,
satisfaction : Satisfaction ::None ,
contribution : Satisfaction ::Complete {
condition : Condition ::default ( )
}
} )
) ;
}
#[ test ]
fn test_extract_tr_script_spend ( ) {
let secp = Secp256k1 ::new ( ) ;
let ( alice_prv , _ , alice_fing ) = setup_keys ( ALICE_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let ( _ , bob_pub , bob_fing ) = setup_keys ( BOB_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let desc = descriptor! ( tr ( bob_pub , pk ( alice_prv ) ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::build ( keymap , & wallet_desc , & secp ) ) ;
let policy = wallet_desc
. extract_policy ( & signers_container , BuildSatisfaction ::None , & secp )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( policy . item , SatisfiableItem ::Thresh { ref items , threshold : 1 } if items . len ( ) = = 2 )
) ;
assert! (
matches! ( policy . contribution , Satisfaction ::PartialComplete { n : 2 , m : 1 , items , .. } if items = = vec! [ 1 ] )
) ;
let alice_sig = SatisfiableItem ::SchnorrSignature ( PkOrF ::Fingerprint ( alice_fing ) ) ;
let bob_sig = SatisfiableItem ::SchnorrSignature ( PkOrF ::Fingerprint ( bob_fing ) ) ;
let thresh_items = match policy . item {
SatisfiableItem ::Thresh { items , .. } = > items ,
_ = > unreachable! ( ) ,
} ;
assert_eq! ( thresh_items [ 0 ] . item , bob_sig ) ;
assert_eq! ( thresh_items [ 1 ] . item , alice_sig ) ;
}
#[ test ]
fn test_extract_tr_satisfaction_key_spend ( ) {
const UNSIGNED_PSBT : & str = " cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA== " ;
const SIGNED_PSBT : & str = " cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA= " ;
let unsigned_psbt = Psbt ::from_str ( UNSIGNED_PSBT ) . unwrap ( ) ;
let signed_psbt = Psbt ::from_str ( SIGNED_PSBT ) . unwrap ( ) ;
let secp = Secp256k1 ::new ( ) ;
let ( _ , pubkey , _ ) = setup_keys ( ALICE_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let desc = descriptor! ( tr ( pubkey ) ) . unwrap ( ) ;
let ( wallet_desc , _ ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
let policy_unsigned = wallet_desc
. extract_policy (
& SignersContainer ::default ( ) ,
BuildSatisfaction ::Psbt ( & unsigned_psbt ) ,
& secp ,
)
. unwrap ( )
. unwrap ( ) ;
let policy_signed = wallet_desc
. extract_policy (
& SignersContainer ::default ( ) ,
BuildSatisfaction ::Psbt ( & signed_psbt ) ,
& secp ,
)
. unwrap ( )
. unwrap ( ) ;
assert_eq! ( policy_unsigned . satisfaction , Satisfaction ::None ) ;
assert_eq! (
policy_signed . satisfaction ,
Satisfaction ::Complete {
condition : Default ::default ( )
}
) ;
}
#[ test ]
fn test_extract_tr_satisfaction_script_spend ( ) {
const UNSIGNED_PSBT : & str = " cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA== " ;
const SIGNED_PSBT : & str = " cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2AQcAAQhCAUALcP9w/+Ddly9DWdhHTnQ9uCDWLPZjR6vKbKePswW2Ee6W5KNfrklus/8z98n7BQ1U4vADHk0FbadeeL8rrbHlARNAC3D/cP/g3ZcvQ1nYR050Pbgg1iz2Y0erymynj7MFthHuluSjX65JbrP/M/fJ+wUNVOLwAx5NBW2nXni/K62x5UEUeEbK57HG1FUp69HHhjBZH9bSvss8e3qhLoMuXPK5hBr2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHUAXNmWieJ80Fs+PMa2C186YOBPZbYG/ieEUkagMwzJ788SoCucNdp5wnxfpuJVygFhglDrXGzujFtC82PrMohwuIhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA== " ;
let unsigned_psbt = Psbt ::from_str ( UNSIGNED_PSBT ) . unwrap ( ) ;
let signed_psbt = Psbt ::from_str ( SIGNED_PSBT ) . unwrap ( ) ;
let secp = Secp256k1 ::new ( ) ;
let ( _ , alice_pub , _ ) = setup_keys ( ALICE_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let ( _ , bob_pub , _ ) = setup_keys ( BOB_TPRV_STR , ALICE_BOB_PATH , & secp ) ;
let desc = descriptor! ( tr ( bob_pub , pk ( alice_pub ) ) ) . unwrap ( ) ;
let ( wallet_desc , _ ) = desc
. into_wallet_descriptor ( & secp , Network ::Testnet )
. unwrap ( ) ;
let policy_unsigned = wallet_desc
. extract_policy (
& SignersContainer ::default ( ) ,
BuildSatisfaction ::Psbt ( & unsigned_psbt ) ,
& secp ,
)
. unwrap ( )
. unwrap ( ) ;
let policy_signed = wallet_desc
. extract_policy (
& SignersContainer ::default ( ) ,
BuildSatisfaction ::Psbt ( & signed_psbt ) ,
& secp ,
)
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( policy_unsigned . item , SatisfiableItem ::Thresh { ref items , threshold : 1 } if items . len ( ) = = 2 )
) ;
assert! (
matches! ( policy_unsigned . satisfaction , Satisfaction ::Partial { n : 2 , m : 1 , items , .. } if items . is_empty ( ) )
) ;
assert! (
matches! ( policy_signed . item , SatisfiableItem ::Thresh { ref items , threshold : 1 } if items . len ( ) = = 2 )
) ;
assert! (
matches! ( policy_signed . satisfaction , Satisfaction ::PartialComplete { n : 2 , m : 1 , items , .. } if items = = vec! [ 0 , 1 ] )
) ;
let satisfied_items = match policy_signed . item {
SatisfiableItem ::Thresh { items , .. } = > items ,
_ = > unreachable! ( ) ,
} ;
assert_eq! (
satisfied_items [ 0 ] . satisfaction ,
Satisfaction ::Complete {
condition : Default ::default ( )
}
) ;
assert_eq! (
satisfied_items [ 1 ] . satisfaction ,
Satisfaction ::Complete {
condition : Default ::default ( )
}
) ;
}
2020-10-01 15:54:59 -07:00
}