2020-08-31 11:26:36 +02:00
// 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.
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.
//!
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
2020-09-14 14:25:38 +02:00
//! # use bdk::descriptor::*;
2020-09-04 11:44:49 +02:00
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//!
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc)?;
//! println!("{:?}", extended_desc);
//!
//! let signers = Arc::new(key_map.into());
//! let policy = extended_desc.extract_policy(signers)?;
//! 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
//! ```
2020-10-07 14:18:50 -07:00
use std ::cmp ::{ max , Ordering } ;
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-08-12 12:51:50 +02:00
use std ::sync ::Arc ;
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 ;
use bitcoin ::PublicKey ;
2020-08-12 12:51:50 +02:00
use miniscript ::descriptor ::DescriptorPublicKey ;
2020-10-09 12:03:47 +02:00
use miniscript ::{ Descriptor , Miniscript , MiniscriptKey , ScriptContext , Terminal , ToPublicKey } ;
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
2020-08-12 12:51:50 +02:00
use crate ::descriptor ::ExtractPolicy ;
use crate ::wallet ::signer ::{ SignerId , SignersContainer } ;
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 ;
2020-02-17 14:22:53 +01:00
2020-09-04 11:44:49 +02:00
/// Raw public key or extended key fingerprint
2020-02-17 14:22:53 +01:00
#[ derive(Debug, Clone, Default, Serialize) ]
2020-02-07 23:22:28 +01:00
pub struct PKOrF {
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pubkey : Option < PublicKey > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-02-17 14:22:53 +01:00
pubkey_hash : Option < hash160 ::Hash > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2020-02-07 23:22:28 +01:00
fingerprint : Option < Fingerprint > ,
}
impl PKOrF {
2020-08-12 12:51:50 +02:00
fn from_key ( k : & DescriptorPublicKey ) -> Self {
match k {
2020-10-09 12:03:47 +02:00
DescriptorPublicKey ::SinglePub ( pubkey ) = > PKOrF {
pubkey : Some ( pubkey . key ) ,
2020-02-17 14:22:53 +01:00
.. Default ::default ( )
2020-08-12 12:51:50 +02:00
} ,
DescriptorPublicKey ::XPub ( xpub ) = > PKOrF {
fingerprint : Some ( xpub . root_fingerprint ( ) ) ,
2020-02-17 14:22:53 +01:00
.. Default ::default ( )
2020-08-12 12:51:50 +02:00
} ,
}
}
fn from_key_hash ( k : hash160 ::Hash ) -> Self {
PKOrF {
pubkey_hash : Some ( k ) ,
.. Default ::default ( )
2020-02-07 23:22:28 +01:00
}
}
}
2020-09-04 11:44:49 +02:00
/// An item that need to be satisfied
2020-02-17 14:22:53 +01:00
#[ derive(Debug, Clone, Serialize) ]
2020-02-07 23:22:28 +01:00
#[ serde(tag = " type " , rename_all = " UPPERCASE " ) ]
pub enum SatisfiableItem {
// Leaves
Signature ( PKOrF ) ,
2020-02-17 14:22:53 +01:00
SignatureKey ( PKOrF ) ,
2020-02-07 23:22:28 +01:00
SHA256Preimage {
hash : sha256 ::Hash ,
} ,
HASH256Preimage {
hash : sha256d ::Hash ,
} ,
RIPEMD160Preimage {
hash : ripemd160 ::Hash ,
} ,
HASH160Preimage {
hash : hash160 ::Hash ,
} ,
AbsoluteTimelock {
2020-02-15 21:27:51 +01:00
value : u32 ,
2020-02-07 23:22:28 +01:00
} ,
RelativeTimelock {
2020-02-15 21:27:51 +01:00
value : u32 ,
2020-02-07 23:22:28 +01:00
} ,
// Complex item
Thresh {
items : Vec < Policy > ,
threshold : usize ,
} ,
Multisig {
keys : Vec < PKOrF > ,
threshold : usize ,
} ,
}
impl SatisfiableItem {
pub fn is_leaf ( & self ) -> bool {
2020-10-08 16:55:49 -07:00
! matches! ( self ,
SatisfiableItem ::Thresh {
items : _ ,
threshold : _ ,
} )
2020-02-07 23:22:28 +01:00
}
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
}
pub type ConditionMap = BTreeMap < usize , HashSet < Condition > > ;
pub type FoldedConditionMap = BTreeMap < Vec < usize > , HashSet < Condition > > ;
fn serialize_folded_cond_map < S > (
input_map : & FoldedConditionMap ,
serializer : S ,
) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
let mut map = serializer . serialize_map ( Some ( input_map . len ( ) ) ) ? ;
for ( k , v ) in input_map {
let k_string = format! ( " {:?} " , k ) ;
map . serialize_entry ( & k_string , v ) ? ;
}
map . end ( )
}
2020-09-04 11:44:49 +02:00
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
2020-02-17 14:22:53 +01:00
#[ derive(Debug, Clone, 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 ,
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 > ,
#[ 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 > ,
#[ 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-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 ( )
. fold ( HashSet ::new ( ) , | set , i | set . union ( & i ) . cloned ( ) . collect ( ) ) ;
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
}
2020-02-17 14:22:53 +01:00
fn finalize ( & mut self ) -> Result < ( ) , PolicyError > {
// if partial try to bump it to a partialcomplete
if let Satisfaction ::Partial {
n ,
m ,
items ,
conditions ,
} = 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 )
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: &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 compatibile, 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 ,
} ;
2020-02-15 21:27:51 +01:00
}
2020-02-17 14:22:53 +01:00
}
Ok ( ( ) )
}
}
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
2020-02-17 14:22:53 +01:00
#[ derive(Debug, Clone, 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 ,
/// 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 ,
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-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-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 {
fn merge_timelock ( a : u32 , b : u32 ) -> Result < u32 , PolicyError > {
const BLOCKS_TIMELOCK_THRESHOLD : u32 = 500000000 ;
if ( a < BLOCKS_TIMELOCK_THRESHOLD ) ! = ( b < BLOCKS_TIMELOCK_THRESHOLD ) {
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-02-17 14:22:53 +01:00
fn merge ( mut self , other : & Condition ) -> Result < Self , PolicyError > {
2020-02-07 23:22:28 +01:00
match ( self . csv , other . csv ) {
2020-02-17 14:22:53 +01:00
( Some ( a ) , Some ( b ) ) = > self . csv = Some ( Self ::merge_timelock ( a , b ) ? ) ,
( None , any ) = > self . csv = any ,
_ = > { }
}
2020-02-07 23:22:28 +01:00
match ( self . timelock , other . timelock ) {
2020-02-17 14:22:53 +01:00
( Some ( a ) , Some ( b ) ) = > self . timelock = Some ( Self ::merge_timelock ( a , b ) ? ) ,
( 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
}
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
2020-02-07 23:22:28 +01:00
#[ derive(Debug) ]
pub enum PolicyError {
2020-05-16 16:48:31 +02:00
NotEnoughItemsSelected ( String ) ,
TooManyItemsSelected ( String ) ,
2020-02-17 14:22:53 +01:00
IndexOutOfRange ( usize ) ,
AddOnLeaf ,
AddOnPartialComplete ,
MixedTimelockUnits ,
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 ( ) ,
} ;
for ( index , item ) in items . iter ( ) . enumerate ( ) {
contribution . add ( & item . contribution , index ) ? ;
}
contribution . finalize ( ) ? ;
2020-02-15 21:27:51 +01:00
let mut policy : Policy = SatisfiableItem ::Thresh { items , threshold } . into ( ) ;
policy . contribution = contribution ;
2020-02-17 14:22:53 +01:00
Ok ( Some ( policy ) )
2020-02-07 23:22:28 +01:00
}
2020-02-17 14:22:53 +01:00
fn make_multisig (
2020-10-07 14:18:50 -07:00
keys : & [ DescriptorPublicKey ] ,
2020-10-09 12:03:47 +02:00
signers : Arc < SignersContainer > ,
2020-02-17 14:22:53 +01:00
threshold : usize ,
) -> Result < Option < Policy > , PolicyError > {
if threshold = = 0 {
return Ok ( None ) ;
}
2020-08-12 12:51:50 +02:00
let parsed_keys = keys . iter ( ) . map ( | k | PKOrF ::from_key ( k ) ) . collect ( ) ;
2020-02-17 14:22:53 +01:00
let mut contribution = Satisfaction ::Partial {
n : keys . len ( ) ,
m : threshold ,
items : vec ! [ ] ,
conditions : Default ::default ( ) ,
} ;
for ( index , key ) in keys . iter ( ) . enumerate ( ) {
2020-10-07 14:18:50 -07:00
if signers . find ( signer_id ( key ) ) . is_some ( ) {
2020-08-12 12:51:50 +02:00
contribution . add (
& Satisfaction ::Complete {
condition : Default ::default ( ) ,
} ,
index ,
) ? ;
}
2020-02-17 14:22:53 +01:00
}
contribution . finalize ( ) ? ;
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 ;
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
}
_ = > vec! [ ] ,
} ;
2020-05-16 16:48:31 +02:00
let selected = match path . get ( & self . id ) {
2020-02-07 23:22:28 +01:00
_ if ! default . is_empty ( ) = > & default ,
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
2020-10-07 14:18:50 -07:00
match selected . len ( ) . cmp ( threshold ) {
Ordering ::Less = > {
return Err ( PolicyError ::NotEnoughItemsSelected ( self . id . clone ( ) ) )
}
Ordering ::Greater = > {
return Err ( PolicyError ::TooManyItemsSelected ( self . id . clone ( ) ) )
}
Ordering ::Equal = > ( ) ,
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 )
}
2020-05-16 16:48:31 +02:00
_ if ! selected . is_empty ( ) = > Err ( PolicyError ::TooManyItemsSelected ( self . id . clone ( ) ) ) ,
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-10-09 12:03:47 +02:00
fn signer_id ( key : & DescriptorPublicKey ) -> SignerId {
2020-08-12 12:51:50 +02:00
match key {
2020-10-09 12:03:47 +02:00
DescriptorPublicKey ::SinglePub ( pubkey ) = > pubkey . key . to_pubkeyhash ( ) . into ( ) ,
2020-08-12 12:51:50 +02:00
DescriptorPublicKey ::XPub ( xpub ) = > xpub . root_fingerprint ( ) . into ( ) ,
}
}
2020-02-15 21:27:51 +01:00
2020-10-09 12:03:47 +02:00
fn signature ( key : & DescriptorPublicKey , signers : Arc < SignersContainer > ) -> Policy {
2020-08-12 12:51:50 +02:00
let mut policy : Policy = SatisfiableItem ::Signature ( PKOrF ::from_key ( key ) ) . into ( ) ;
policy . contribution = if signers . find ( signer_id ( key ) ) . is_some ( ) {
Satisfaction ::Complete {
condition : Default ::default ( ) ,
}
} else {
Satisfaction ::None
} ;
policy
2020-02-07 23:22:28 +01:00
}
2020-08-12 12:51:50 +02:00
fn signature_key (
2020-10-09 12:03:47 +02:00
key : & < DescriptorPublicKey as MiniscriptKey > ::Hash ,
signers : Arc < SignersContainer > ,
2020-08-12 12:51:50 +02:00
) -> Policy {
2020-10-09 12:03:47 +02:00
let key_hash = key . to_public_key ( ) . to_pubkeyhash ( ) ;
let mut policy : Policy = SatisfiableItem ::Signature ( PKOrF ::from_key_hash ( key_hash ) ) . into ( ) ;
2020-02-07 23:22:28 +01:00
2020-10-09 12:03:47 +02:00
if signers . find ( SignerId ::PkHash ( key_hash ) ) . is_some ( ) {
2020-08-12 12:51:50 +02:00
policy . contribution = Satisfaction ::Complete {
condition : Default ::default ( ) ,
2020-02-07 23:22:28 +01:00
}
2020-08-12 12:51:50 +02:00
}
2020-02-15 21:27:51 +01:00
2020-08-12 12:51:50 +02:00
policy
2020-02-07 23:22:28 +01:00
}
2020-08-12 12:51:50 +02:00
impl < Ctx : ScriptContext > ExtractPolicy for Miniscript < DescriptorPublicKey , Ctx > {
2020-10-09 12:03:47 +02:00
fn extract_policy ( & self , signers : Arc < SignersContainer > ) -> 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 ,
2020-08-12 12:51:50 +02:00
Terminal ::PkK ( pubkey ) = > Some ( signature ( pubkey , Arc ::clone ( & signers ) ) ) ,
Terminal ::PkH ( pubkey_hash ) = > Some ( signature_key ( pubkey_hash , Arc ::clone ( & signers ) ) ) ,
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
} ,
} ;
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
} ,
} ;
Some ( policy )
2020-02-07 23:22:28 +01:00
}
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 ( ) )
}
2020-08-12 12:51:50 +02:00
Terminal ::Multi ( k , pks ) = > Policy ::make_multisig ( pks , Arc ::clone ( & signers ) , * k ) ? ,
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 )
2020-08-12 12:51:50 +02:00
| Terminal ::ZeroNotEqual ( inner ) = > inner . extract_policy ( Arc ::clone ( & signers ) ) ? ,
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 (
a . extract_policy ( Arc ::clone ( & signers ) ) ? ,
b . extract_policy ( Arc ::clone ( & signers ) ) ? ,
) ? ,
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 (
x . extract_policy ( Arc ::clone ( & signers ) ) ? ,
y . extract_policy ( Arc ::clone ( & signers ) ) ? ,
) ? ,
z . extract_policy ( Arc ::clone ( & signers ) ) ? ,
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 (
a . extract_policy ( Arc ::clone ( & signers ) ) ? ,
b . extract_policy ( Arc ::clone ( & signers ) ) ? ,
) ? ,
2020-02-07 23:22:28 +01:00
Terminal ::Thresh ( k , nodes ) = > {
let mut threshold = * k ;
let mapped : Vec < _ > = nodes
. iter ( )
2020-08-12 12:51:50 +02:00
. map ( | n | n . extract_policy ( Arc ::clone ( & signers ) ) )
2020-02-17 14:22:53 +01:00
. collect ::< Result < Vec < _ > , _ > > ( ) ?
. into_iter ( )
. filter_map ( | x | x )
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
}
}
2020-08-12 12:51:50 +02:00
impl ExtractPolicy for Descriptor < DescriptorPublicKey > {
2020-10-09 12:03:47 +02:00
fn extract_policy ( & self , signers : Arc < SignersContainer > ) -> Result < Option < Policy > , Error > {
2020-02-07 23:22:28 +01:00
match self {
Descriptor ::Pk ( pubkey )
| Descriptor ::Pkh ( pubkey )
| Descriptor ::Wpkh ( pubkey )
2020-08-12 12:51:50 +02:00
| Descriptor ::ShWpkh ( pubkey ) = > Ok ( Some ( signature ( pubkey , signers ) ) ) ,
Descriptor ::Bare ( inner ) | Descriptor ::Sh ( inner ) = > Ok ( inner . extract_policy ( signers ) ? ) ,
Descriptor ::Wsh ( inner ) | Descriptor ::ShWsh ( inner ) = > Ok ( inner . extract_policy ( signers ) ? ) ,
2020-02-07 23:22:28 +01:00
}
}
}
2020-10-01 15:54:59 -07:00
#[ cfg(test) ]
mod test {
use crate ::descriptor ;
use crate ::descriptor ::{ ExtractPolicy , ToWalletDescriptor } ;
use super ::* ;
use crate ::descriptor ::policy ::SatisfiableItem ::{ Multisig , Signature , Thresh } ;
use crate ::keys ::{ DescriptorKey , ToDescriptorKey } ;
use crate ::wallet ::signer ::SignersContainer ;
use bitcoin ::secp256k1 ::{ All , Secp256k1 } ;
use bitcoin ::util ::bip32 ;
use bitcoin ::util ::bip32 ::ChildNumber ;
use bitcoin ::Network ;
use std ::str ::FromStr ;
use std ::sync ::Arc ;
const TPRV0_STR :& str = " tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf " ;
const TPRV1_STR :& str = " tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N " ;
const PATH : & str = " m/44'/1'/0'/0 " ;
fn setup_keys < Ctx : ScriptContext > (
tprv : & str ,
) -> ( DescriptorKey < Ctx > , DescriptorKey < Ctx > , Fingerprint ) {
let secp : Secp256k1 < All > = Secp256k1 ::new ( ) ;
let path = bip32 ::DerivationPath ::from_str ( PATH ) . unwrap ( ) ;
let tprv = bip32 ::ExtendedPrivKey ::from_str ( tprv ) . unwrap ( ) ;
let tpub = bip32 ::ExtendedPubKey ::from_private ( & secp , & tprv ) ;
let fingerprint = tprv . fingerprint ( & secp ) ;
let prvkey = ( tprv , path . clone ( ) ) . to_descriptor_key ( ) . unwrap ( ) ;
let pubkey = ( tpub , path ) . to_descriptor_key ( ) . unwrap ( ) ;
( prvkey , pubkey , fingerprint )
}
// test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
#[ test ]
fn test_extract_policy_for_wpkh ( ) {
let ( prvkey , pubkey , fingerprint ) = setup_keys ( TPRV0_STR ) ;
let desc = descriptor! ( wpkh ( pubkey ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( _prvkey1 , pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let desc = descriptor! ( sh ( multi 2 , pubkey0 , pubkey1 ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 2
& & & 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 = = & 2
& & m = = & 2
& & 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 ( prvkey0 , _pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( _prvkey1 , pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let desc = descriptor! ( sh ( multi 2 , prvkey0 , pubkey1 ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. unwrap ( )
. unwrap ( ) ;
assert! (
matches! ( & policy . item , Multisig { keys , threshold } if threshold = = & 2
& & & keys [ 0 ] . fingerprint . unwrap ( ) = = & fingerprint0
& & & keys [ 1 ] . fingerprint . unwrap ( ) = = & fingerprint1 )
) ;
// TODO should this be "Satisfaction::Partial" since we have only one of two prv keys?
assert! (
matches! ( & policy . contribution , Satisfaction ::PartialComplete { n , m , items , conditions } if n = = & 2
& & m = = & 2
& & items . len ( ) = = 2
& & conditions . contains_key ( & vec! [ 0 , 1 ] )
)
) ;
}
// 1 prv and 1 pub key descriptor, required 1 prv keys
#[ test ]
fn test_extract_policy_for_sh_multi_complete_1of2 ( ) {
let ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let desc = descriptor! ( sh ( multi 1 , pubkey0 , prvkey1 ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 ( prvkey0 , _pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let desc = descriptor! ( sh ( multi 2 , prvkey0 , prvkey1 ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 ( prvkey , pubkey , fingerprint ) = setup_keys ( TPRV0_STR ) ;
let desc = descriptor! ( wpkh ( pubkey ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let single_key = wallet_desc . derive ( & [ ChildNumber ::from_normal_idx ( 0 ) . unwrap ( ) ] ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = single_key
. extract_policy ( signers_container )
. 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 . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let single_key = wallet_desc . derive ( & [ ChildNumber ::from_normal_idx ( 0 ) . unwrap ( ) ] ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = single_key
. extract_policy ( signers_container )
. 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 ]
fn test_extract_policy_for_single_wsh_multi_complete_1of2 ( ) {
let ( _prvkey0 , pubkey0 , fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( prvkey1 , _pubkey1 , fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let desc = descriptor! ( sh ( multi 1 , pubkey0 , prvkey1 ) ) . unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let single_key = wallet_desc . derive ( & [ ChildNumber ::from_normal_idx ( 0 ) . unwrap ( ) ] ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = single_key
. extract_policy ( signers_container )
. 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 ]
fn test_extract_policy_for_wsh_multi_timelock ( ) {
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
let ( _prvkey1 , pubkey1 , _fingerprint1 ) = setup_keys ( TPRV1_STR ) ;
let sequence = 50 ;
let desc = descriptor! ( wsh (
thresh 2 , ( pk prvkey0 ) , ( + s pk pubkey1 ) , ( + s + d + v older sequence )
) )
. unwrap ( ) ;
let ( wallet_desc , keymap ) = desc . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 ]
fn test_extract_policy_for_wsh_mixed_timelocks ( ) {
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
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 . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. 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 ]
fn test_extract_policy_for_multiple_same_timelocks ( ) {
let ( prvkey0 , _pubkey0 , _fingerprint0 ) = setup_keys ( TPRV0_STR ) ;
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 . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. unwrap ( )
. unwrap ( ) ;
println! ( " desc policy = {:?} " , policy ) ; // TODO remove
2020-10-03 14:48:13 -07:00
// TODO how should this merge timelocks?
2020-10-01 15:54:59 -07:00
2020-10-03 14:48:13 -07:00
let ( prvkey1 , _pubkey1 , _fingerprint1 ) = setup_keys ( TPRV0_STR ) ;
2020-10-01 15:54:59 -07: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 . to_wallet_descriptor ( Network ::Testnet ) . unwrap ( ) ;
let signers_container = Arc ::new ( SignersContainer ::from ( keymap ) ) ;
let policy = wallet_desc
. extract_policy ( signers_container )
. unwrap ( )
. unwrap ( ) ;
println! ( " desc policy = {:?} " , policy ) ; // TODO remove
// TODO how should this merge timelocks?
}
}