• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 5834188079

pending completion
5834188079

Pull #1071

github

web-flow
Merge 68b42331c into 0ba6bbe11
Pull Request #1071: Update rust bitcoin (BDK 0.28)

563 of 563 new or added lines in 28 files covered. (100.0%)

14625 of 18342 relevant lines covered (79.74%)

9267.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.46
/src/descriptor/policy.rs
1
// Bitcoin Dev Kit
2
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3
//
4
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5
//
6
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9
// You may not use this file except in accordance with one or both of these
10
// licenses.
11

12
//! Descriptor policy
13
//!
14
//! This module implements the logic to extract and represent the spending policies of a descriptor
15
//! in a more human-readable format.
16
//!
17
//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
18
//!
19
//! ## Example
20
//!
21
//! ```
22
//! # use std::sync::Arc;
23
//! # use bdk::descriptor::*;
24
//! # use bdk::wallet::signer::*;
25
//! # use bdk::bitcoin::secp256k1::Secp256k1;
26
//! use bdk::descriptor::policy::BuildSatisfaction;
27
//! let secp = Secp256k1::new();
28
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
29
//!
30
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
31
//! println!("{:?}", extended_desc);
32
//!
33
//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp));
34
//! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
35
//! println!("policy: {}", serde_json::to_string(&policy)?);
36
//! # Ok::<(), bdk::Error>(())
37
//! ```
38

39
use std::cmp::max;
40
use std::collections::{BTreeMap, HashSet, VecDeque};
41
use std::fmt;
42

43
use serde::ser::SerializeMap;
44
use serde::{Serialize, Serializer};
45

46
use bitcoin::bip32::Fingerprint;
47
use bitcoin::hashes::{hash160, ripemd160, sha256};
48
use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
49

50
use miniscript::descriptor::{
51
    DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
52
};
53
use miniscript::hash256;
54
use miniscript::{
55
    Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
56
};
57

58
#[allow(unused_imports)]
59
use log::{debug, error, info, trace};
60

61
use crate::descriptor::ExtractPolicy;
62
use crate::keys::ExtScriptContext;
63
use crate::wallet::signer::{SignerId, SignersContainer};
64
use crate::wallet::utils::{After, Older, SecpCtx};
65

66
use super::checksum::calc_checksum;
67
use super::error::Error;
68
use super::XKeyUtils;
69
use bitcoin::psbt::{self, Psbt};
70
use miniscript::psbt::PsbtInputSatisfier;
71

72
/// A unique identifier for a key
73
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
1,148✔
74
#[serde(rename_all = "snake_case")]
75
pub enum PkOrF {
76
    /// A legacy public key
77
    Pubkey(PublicKey),
78
    /// A x-only public key
79
    XOnlyPubkey(XOnlyPublicKey),
80
    /// An extended key fingerprint
81
    Fingerprint(Fingerprint),
82
}
83

84
impl PkOrF {
85
    fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
86
        match k {
550✔
87
            DescriptorPublicKey::Single(SinglePub {
88
                key: SinglePubKey::FullKey(pk),
512✔
89
                ..
512✔
90
            }) => PkOrF::Pubkey(*pk),
512✔
91
            DescriptorPublicKey::Single(SinglePub {
92
                key: SinglePubKey::XOnly(pk),
38✔
93
                ..
38✔
94
            }) => PkOrF::XOnlyPubkey(*pk),
38✔
95
            DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
308✔
96
            DescriptorPublicKey::MultiXPub(multi) => {
×
97
                PkOrF::Fingerprint(multi.root_fingerprint(secp))
×
98
            }
99
        }
100
    }
858✔
101
}
102

103
/// An item that needs to be satisfied
104
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1,589✔
105
#[serde(tag = "type", rename_all = "UPPERCASE")]
106
pub enum SatisfiableItem {
107
    // Leaves
108
    /// ECDSA Signature for a raw public key
109
    EcdsaSignature(PkOrF),
110
    /// Schnorr Signature for a raw public key
111
    SchnorrSignature(PkOrF),
112
    /// SHA256 preimage hash
113
    Sha256Preimage {
114
        /// The digest value
115
        hash: sha256::Hash,
116
    },
117
    /// Double SHA256 preimage hash
118
    Hash256Preimage {
119
        /// The digest value
120
        hash: hash256::Hash,
121
    },
122
    /// RIPEMD160 preimage hash
123
    Ripemd160Preimage {
124
        /// The digest value
125
        hash: ripemd160::Hash,
126
    },
127
    /// SHA256 then RIPEMD160 preimage hash
128
    Hash160Preimage {
129
        /// The digest value
130
        hash: hash160::Hash,
131
    },
132
    /// Absolute timeclock timestamp
133
    AbsoluteTimelock {
134
        /// The timelock value
135
        value: absolute::LockTime,
136
    },
137
    /// Relative timelock locktime
138
    RelativeTimelock {
139
        /// The timelock value
140
        value: Sequence,
141
    },
142
    /// Multi-signature public keys with threshold count
143
    Multisig {
144
        /// The raw public key or extended key fingerprint
145
        keys: Vec<PkOrF>,
146
        /// The required threshold count
147
        threshold: usize,
148
    },
149

150
    // Complex item
151
    /// Threshold items with threshold count
152
    Thresh {
153
        /// The policy items
154
        items: Vec<Policy>,
155
        /// The required threshold count
156
        threshold: usize,
157
    },
158
}
159

160
impl SatisfiableItem {
161
    /// Returns whether the [`SatisfiableItem`] is a leaf item
162
    pub fn is_leaf(&self) -> bool {
×
163
        !matches!(
×
164
            self,
×
165
            SatisfiableItem::Thresh {
166
                items: _,
167
                threshold: _,
168
            }
169
        )
170
    }
×
171

172
    /// Returns a unique id for the [`SatisfiableItem`]
173
    pub fn id(&self) -> String {
1,065✔
174
        calc_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
1,065✔
175
            .expect("Failed to compute a SatisfiableItem id")
1,065✔
176
    }
1,065✔
177
}
178

179
fn combinations(vec: &[usize], size: usize) -> Vec<Vec<usize>> {
160✔
180
    assert!(vec.len() >= size);
160✔
181

182
    let mut answer = Vec::new();
160✔
183

160✔
184
    let mut queue = VecDeque::new();
160✔
185
    for (index, val) in vec.iter().enumerate() {
304✔
186
        let mut new_vec = Vec::with_capacity(size);
304✔
187
        new_vec.push(*val);
304✔
188
        queue.push_back((index, new_vec));
304✔
189
    }
304✔
190

191
    while let Some((index, vals)) = queue.pop_front() {
565✔
192
        if vals.len() >= size {
405✔
193
            answer.push(vals);
221✔
194
        } else {
221✔
195
            for (new_index, val) in vec.iter().skip(index + 1).enumerate() {
184✔
196
                let mut cloned = vals.clone();
101✔
197
                cloned.push(*val);
101✔
198
                queue.push_front((new_index, cloned));
101✔
199
            }
101✔
200
        }
201
    }
202

203
    answer
160✔
204
}
160✔
205

206
fn mix<T: Clone>(vec: Vec<Vec<T>>) -> Vec<Vec<T>> {
221✔
207
    if vec.is_empty() || vec.iter().any(Vec::is_empty) {
221✔
208
        return vec![];
×
209
    }
221✔
210

221✔
211
    let mut answer = Vec::new();
221✔
212
    let size = vec.len();
221✔
213

221✔
214
    let mut queue = VecDeque::new();
221✔
215
    for i in &vec[0] {
225✔
216
        let mut new_vec = Vec::with_capacity(size);
225✔
217
        new_vec.push(i.clone());
225✔
218
        queue.push_back(new_vec);
225✔
219
    }
225✔
220

221
    while let Some(vals) = queue.pop_front() {
562✔
222
        if vals.len() >= size {
341✔
223
            answer.push(vals);
240✔
224
        } else {
240✔
225
            let level = vals.len();
101✔
226
            for i in &vec[level] {
116✔
227
                let mut cloned = vals.clone();
116✔
228
                cloned.push(i.clone());
116✔
229
                queue.push_front(cloned);
116✔
230
            }
116✔
231
        }
232
    }
233

234
    answer
221✔
235
}
221✔
236

237
/// Type for a map of sets of [`Condition`] items keyed by each set's index
238
pub type ConditionMap = BTreeMap<usize, HashSet<Condition>>;
239
/// Type for a map of folded sets of [`Condition`] items keyed by a vector of the combined set's indexes
240
pub type FoldedConditionMap = BTreeMap<Vec<usize>, HashSet<Condition>>;
241

242
fn serialize_folded_cond_map<S>(
93✔
243
    input_map: &FoldedConditionMap,
93✔
244
    serializer: S,
93✔
245
) -> Result<S::Ok, S::Error>
93✔
246
where
93✔
247
    S: Serializer,
93✔
248
{
93✔
249
    let mut map = serializer.serialize_map(Some(input_map.len()))?;
93✔
250
    for (k, v) in input_map {
220✔
251
        let k_string = format!("{:?}", k);
127✔
252
        map.serialize_entry(&k_string, v)?;
127✔
253
    }
254
    map.end()
93✔
255
}
93✔
256

257
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
258
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1,113✔
259
#[serde(tag = "type", rename_all = "UPPERCASE")]
260
pub enum Satisfaction {
261
    /// Only a partial satisfaction of some kind of threshold policy
262
    Partial {
263
        /// Total number of items
264
        n: usize,
265
        /// Threshold
266
        m: usize,
267
        /// The items that can be satisfied by the descriptor or are satisfied in the PSBT
268
        items: Vec<usize>,
269
        #[serde(skip_serializing_if = "Option::is_none")]
270
        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
271
        sorted: Option<bool>,
272
        #[serde(skip_serializing_if = "BTreeMap::is_empty")]
273
        /// Extra conditions that also need to be satisfied
274
        conditions: ConditionMap,
275
    },
276
    /// Can reach the threshold of some kind of threshold policy
277
    PartialComplete {
278
        /// Total number of items
279
        n: usize,
280
        /// Threshold
281
        m: usize,
282
        /// The items that can be satisfied by the descriptor
283
        items: Vec<usize>,
284
        #[serde(skip_serializing_if = "Option::is_none")]
285
        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
286
        sorted: Option<bool>,
287
        #[serde(
288
            serialize_with = "serialize_folded_cond_map",
289
            skip_serializing_if = "BTreeMap::is_empty"
290
        )]
291
        /// Extra conditions that also need to be satisfied
292
        conditions: FoldedConditionMap,
293
    },
294

295
    /// Can satisfy the policy item
296
    Complete {
297
        /// Extra conditions that also need to be satisfied
298
        condition: Condition,
299
    },
300
    /// Cannot satisfy or contribute to the policy item
301
    None,
302
}
303

304
impl Satisfaction {
305
    /// Returns whether the [`Satisfaction`] is a leaf item
306
    pub fn is_leaf(&self) -> bool {
×
307
        match self {
×
308
            Satisfaction::None | Satisfaction::Complete { .. } => true,
×
309
            Satisfaction::PartialComplete { .. } | Satisfaction::Partial { .. } => false,
×
310
        }
311
    }
×
312

313
    // add `inner` as one of self's partial items. this only makes sense on partials
314
    fn add(&mut self, inner: &Satisfaction, inner_index: usize) -> Result<(), PolicyError> {
670✔
315
        match self {
670✔
316
            Satisfaction::None | Satisfaction::Complete { .. } => Err(PolicyError::AddOnLeaf),
×
317
            Satisfaction::PartialComplete { .. } => Err(PolicyError::AddOnPartialComplete),
×
318
            Satisfaction::Partial {
319
                n,
670✔
320
                ref mut conditions,
670✔
321
                ref mut items,
670✔
322
                ..
670✔
323
            } => {
670✔
324
                if inner_index >= *n || items.contains(&inner_index) {
670✔
325
                    return Err(PolicyError::IndexOutOfRange(inner_index));
×
326
                }
670✔
327

670✔
328
                match inner {
670✔
329
                    // not relevant if not completed yet
330
                    Satisfaction::None | Satisfaction::Partial { .. } => return Ok(()),
358✔
331
                    Satisfaction::Complete { condition } => {
257✔
332
                        items.push(inner_index);
257✔
333
                        conditions.insert(inner_index, vec![*condition].into_iter().collect());
257✔
334
                    }
257✔
335
                    Satisfaction::PartialComplete {
336
                        conditions: other_conditions,
55✔
337
                        ..
55✔
338
                    } => {
55✔
339
                        items.push(inner_index);
55✔
340
                        let conditions_set = other_conditions
55✔
341
                            .values()
55✔
342
                            .fold(HashSet::new(), |set, i| set.union(i).cloned().collect());
95✔
343
                        conditions.insert(inner_index, conditions_set);
55✔
344
                    }
55✔
345
                }
346

347
                Ok(())
312✔
348
            }
349
        }
350
    }
670✔
351

352
    fn finalize(&mut self) {
353
        // if partial try to bump it to a partialcomplete
354
        if let Satisfaction::Partial {
355
            n,
328✔
356
            m,
328✔
357
            items,
328✔
358
            conditions,
328✔
359
            sorted,
328✔
360
        } = self
328✔
361
        {
362
            if items.len() >= *m {
328✔
363
                let mut map = BTreeMap::new();
160✔
364
                let indexes = combinations(items, *m);
160✔
365
                // `indexes` at this point is a Vec<Vec<usize>>, with the "n choose k" of items (m of n)
160✔
366
                indexes
160✔
367
                    .into_iter()
160✔
368
                    // .inspect(|x| println!("--- orig --- {:?}", x))
160✔
369
                    // we map each of the combinations of elements into a tuple of ([chosen items], [conditions]). unfortunately, those items have potentially more than one
160✔
370
                    // 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
160✔
371
                    // consider every possible options and check whether or not they are compatible.
160✔
372
                    // since this step can turn one item of the iterator into multiple ones, we use `flat_map()` to expand them out
160✔
373
                    .flat_map(|i_vec| {
288✔
374
                        mix(i_vec
221✔
375
                            .iter()
221✔
376
                            .map(|i| {
322✔
377
                                conditions
322✔
378
                                    .get(i)
322✔
379
                                    .map(|set| set.clone().into_iter().collect())
322✔
380
                                    .unwrap_or_default()
322✔
381
                            })
322✔
382
                            .collect())
221✔
383
                        .into_iter()
221✔
384
                        .map(|x| (i_vec.clone(), x))
240✔
385
                        .collect::<Vec<(Vec<usize>, Vec<Condition>)>>()
221✔
386
                    })
288✔
387
                    // .inspect(|x| println!("flat {:?}", x))
160✔
388
                    // try to fold all the conditions for this specific combination of indexes/options. if they are not compatible, try_fold will be Err
160✔
389
                    .map(|(key, val)| {
307✔
390
                        (
240✔
391
                            key,
240✔
392
                            val.into_iter()
240✔
393
                                .try_fold(Condition::default(), |acc, v| acc.merge(&v)),
356✔
394
                        )
240✔
395
                    })
307✔
396
                    // .inspect(|x| println!("try_fold {:?}", x))
160✔
397
                    // filter out all the incompatible combinations
160✔
398
                    .filter(|(_, val)| val.is_ok())
307✔
399
                    // .inspect(|x| println!("filter {:?}", x))
160✔
400
                    // push them into the map
160✔
401
                    .for_each(|(key, val)| {
307✔
402
                        map.entry(key)
240✔
403
                            .or_insert_with(HashSet::new)
240✔
404
                            .insert(val.unwrap());
240✔
405
                    });
307✔
406
                // TODO: if the map is empty, the conditions are not compatible, return an error?
160✔
407
                *self = Satisfaction::PartialComplete {
160✔
408
                    n: *n,
160✔
409
                    m: *m,
160✔
410
                    items: items.clone(),
160✔
411
                    conditions: map,
160✔
412
                    sorted: *sorted,
160✔
413
                };
160✔
414
            }
168✔
415
        }
×
416
    }
328✔
417
}
418

419
impl From<bool> for Satisfaction {
420
    fn from(other: bool) -> Self {
×
421
        if other {
×
422
            Satisfaction::Complete {
×
423
                condition: Default::default(),
×
424
            }
×
425
        } else {
426
            Satisfaction::None
×
427
        }
428
    }
×
429
}
430

431
/// Descriptor spending policy
432
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
522✔
433
pub struct Policy {
434
    /// Identifier for this policy node
435
    pub id: String,
436

437
    /// Type of this policy node
438
    #[serde(flatten)]
439
    pub item: SatisfiableItem,
440
    /// How much a given PSBT already satisfies this policy node in terms of signatures
441
    pub satisfaction: Satisfaction,
442
    /// How the wallet's descriptor can satisfy this policy node
443
    pub contribution: Satisfaction,
444
}
445

446
/// An extra condition that must be satisfied but that is out of control of the user
447
/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
448
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
3,137✔
449
pub struct Condition {
450
    /// Optional CheckSequenceVerify condition
451
    #[serde(skip_serializing_if = "Option::is_none")]
452
    pub csv: Option<Sequence>,
453
    /// Optional timelock condition
454
    #[serde(skip_serializing_if = "Option::is_none")]
455
    pub timelock: Option<absolute::LockTime>,
456
}
457

458
impl Condition {
459
    fn merge_nlocktime(
×
460
        a: absolute::LockTime,
×
461
        b: absolute::LockTime,
×
462
    ) -> Result<absolute::LockTime, PolicyError> {
×
463
        if !a.is_same_unit(b) {
×
464
            Err(PolicyError::MixedTimelockUnits)
×
465
        } else if a > b {
×
466
            Ok(a)
×
467
        } else {
468
            Ok(b)
×
469
        }
470
    }
×
471

472
    fn merge_nsequence(a: Sequence, b: Sequence) -> Result<Sequence, PolicyError> {
4✔
473
        if a.is_time_locked() != b.is_time_locked() {
4✔
474
            Err(PolicyError::MixedTimelockUnits)
×
475
        } else {
476
            Ok(max(a, b))
4✔
477
        }
478
    }
4✔
479

480
    pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
481
        match (self.csv, other.csv) {
1,005✔
482
            (Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
4✔
483
            (None, any) => self.csv = any,
977✔
484
            _ => {}
24✔
485
        }
486

487
        match (self.timelock, other.timelock) {
1,005✔
488
            (Some(a), Some(b)) => self.timelock = Some(Self::merge_nlocktime(a, b)?),
×
489
            (None, any) => self.timelock = any,
993✔
490
            _ => {}
12✔
491
        }
492

493
        Ok(self)
1,005✔
494
    }
1,005✔
495

496
    /// Returns `true` if there are no extra conditions to verify
497
    pub fn is_null(&self) -> bool {
312✔
498
        self.csv.is_none() && self.timelock.is_none()
312✔
499
    }
312✔
500
}
501

502
/// Errors that can happen while extracting and manipulating policies
503
#[derive(Debug, PartialEq, Eq)]
4✔
504
pub enum PolicyError {
505
    /// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
506
    NotEnoughItemsSelected(String),
507
    /// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
508
    IndexOutOfRange(usize),
509
    /// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
510
    AddOnLeaf,
511
    /// Can not add to an item that is [`Satisfaction::PartialComplete`]
512
    AddOnPartialComplete,
513
    /// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000
514
    MixedTimelockUnits,
515
    /// Incompatible conditions (not currently used)
516
    IncompatibleConditions,
517
}
518

519
impl fmt::Display for PolicyError {
520
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
521
        match self {
×
522
            Self::NotEnoughItemsSelected(err) => write!(f, "Not enought items selected: {}", err),
×
523
            Self::IndexOutOfRange(index) => write!(f, "Index out of range: {}", index),
×
524
            Self::AddOnLeaf => write!(f, "Add on leaf"),
×
525
            Self::AddOnPartialComplete => write!(f, "Add on partial complete"),
×
526
            Self::MixedTimelockUnits => write!(f, "Mixed timelock units"),
×
527
            Self::IncompatibleConditions => write!(f, "Incompatible conditions"),
×
528
        }
529
    }
×
530
}
531

532
impl std::error::Error for PolicyError {}
533

534
impl Policy {
535
    fn new(item: SatisfiableItem) -> Self {
1,065✔
536
        Policy {
1,065✔
537
            id: item.id(),
1,065✔
538
            item,
1,065✔
539
            satisfaction: Satisfaction::None,
1,065✔
540
            contribution: Satisfaction::None,
1,065✔
541
        }
1,065✔
542
    }
1,065✔
543

544
    fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
545
        match (a, b) {
71✔
546
            (None, None) => Ok(None),
×
547
            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
×
548
            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
71✔
549
        }
550
    }
71✔
551

552
    fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
553
        match (a, b) {
31✔
554
            (None, None) => Ok(None),
×
555
            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
×
556
            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
31✔
557
        }
558
    }
31✔
559

560
    fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
148✔
561
        if threshold == 0 {
148✔
562
            return Ok(None);
×
563
        }
148✔
564

148✔
565
        let mut contribution = Satisfaction::Partial {
148✔
566
            n: items.len(),
148✔
567
            m: threshold,
148✔
568
            items: vec![],
148✔
569
            conditions: Default::default(),
148✔
570
            sorted: None,
148✔
571
        };
148✔
572
        let mut satisfaction = contribution.clone();
148✔
573
        for (index, item) in items.iter().enumerate() {
320✔
574
            contribution.add(&item.contribution, index)?;
320✔
575
            satisfaction.add(&item.satisfaction, index)?;
320✔
576
        }
577

578
        contribution.finalize();
148✔
579
        satisfaction.finalize();
148✔
580

148✔
581
        let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
148✔
582
        policy.contribution = contribution;
148✔
583
        policy.satisfaction = satisfaction;
148✔
584

148✔
585
        Ok(Some(policy))
148✔
586
    }
148✔
587

588
    fn make_multisig<Ctx: ScriptContext + 'static>(
16✔
589
        keys: &[DescriptorPublicKey],
16✔
590
        signers: &SignersContainer,
16✔
591
        build_sat: BuildSatisfaction,
16✔
592
        threshold: usize,
16✔
593
        sorted: bool,
16✔
594
        secp: &SecpCtx,
16✔
595
    ) -> Result<Option<Policy>, PolicyError> {
16✔
596
        if threshold == 0 {
16✔
597
            return Ok(None);
×
598
        }
16✔
599

16✔
600
        let parsed_keys = keys.iter().map(|k| PkOrF::from_key(k, secp)).collect();
32✔
601

16✔
602
        let mut contribution = Satisfaction::Partial {
16✔
603
            n: keys.len(),
16✔
604
            m: threshold,
16✔
605
            items: vec![],
16✔
606
            conditions: Default::default(),
16✔
607
            sorted: Some(sorted),
16✔
608
        };
16✔
609
        let mut satisfaction = contribution.clone();
16✔
610

611
        for (index, key) in keys.iter().enumerate() {
32✔
612
            if signers.find(signer_id(key, secp)).is_some() {
32✔
613
                contribution.add(
22✔
614
                    &Satisfaction::Complete {
22✔
615
                        condition: Default::default(),
22✔
616
                    },
22✔
617
                    index,
22✔
618
                )?;
22✔
619
            }
10✔
620

621
            if let Some(psbt) = build_sat.psbt() {
32✔
622
                if Ctx::find_signature(psbt, key, secp) {
12✔
623
                    satisfaction.add(
8✔
624
                        &Satisfaction::Complete {
8✔
625
                            condition: Default::default(),
8✔
626
                        },
8✔
627
                        index,
8✔
628
                    )?;
8✔
629
                }
4✔
630
            }
20✔
631
        }
632
        satisfaction.finalize();
16✔
633
        contribution.finalize();
16✔
634

16✔
635
        let mut policy: Policy = SatisfiableItem::Multisig {
16✔
636
            keys: parsed_keys,
16✔
637
            threshold,
16✔
638
        }
16✔
639
        .into();
16✔
640
        policy.contribution = contribution;
16✔
641
        policy.satisfaction = satisfaction;
16✔
642

16✔
643
        Ok(Some(policy))
16✔
644
    }
16✔
645

646
    /// Return whether or not a specific path in the policy tree is required to unambiguously
647
    /// create a transaction
648
    ///
649
    /// What this means is that for some spending policies the user should select which paths in
650
    /// the tree it intends to satisfy while signing, because the transaction must be created differently based
651
    /// on that.
652
    pub fn requires_path(&self) -> bool {
659✔
653
        self.get_condition(&BTreeMap::new()).is_err()
659✔
654
    }
659✔
655

656
    /// Return the conditions that are set by the spending policy for a given path in the
657
    /// policy tree
658
    pub fn get_condition(
1,665✔
659
        &self,
1,665✔
660
        path: &BTreeMap<String, Vec<usize>>,
1,665✔
661
    ) -> Result<Condition, PolicyError> {
1,665✔
662
        // if items.len() == threshold, selected can be omitted and we take all of them by default
663
        let default = match &self.item {
1,665✔
664
            SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
144✔
665
                (0..*threshold).collect()
78✔
666
            }
667
            SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
14✔
668
            _ => HashSet::new(),
1,573✔
669
        };
670
        let selected: HashSet<_> = match path.get(&self.id) {
1,665✔
671
            Some(arr) => arr.iter().copied().collect(),
24✔
672
            _ => default,
1,641✔
673
        };
674

675
        match &self.item {
1,665✔
676
            SatisfiableItem::Thresh { items, threshold } => {
144✔
677
                let mapped_req = items
144✔
678
                    .iter()
144✔
679
                    .map(|i| i.get_condition(path))
396✔
680
                    .collect::<Vec<_>>();
144✔
681

144✔
682
                // if all the requirements are null we don't care about `selected` because there
144✔
683
                // are no requirements
144✔
684
                if mapped_req
144✔
685
                    .iter()
144✔
686
                    .all(|cond| matches!(cond, Ok(c) if c.is_null()))
388✔
687
                {
688
                    return Ok(Condition::default());
32✔
689
                }
112✔
690

691
                // make sure all the indexes in the `selected` list are within range
692
                for index in &selected {
282✔
693
                    if *index >= items.len() {
170✔
694
                        return Err(PolicyError::IndexOutOfRange(*index));
×
695
                    }
170✔
696
                }
697

698
                // if we have something, make sure we have enough items. note that the user can set
699
                // an empty value for this step in case of n-of-n, because `selected` is set to all
700
                // the elements above
701
                if selected.len() < *threshold {
112✔
702
                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
20✔
703
                }
92✔
704

92✔
705
                // check the selected items, see if there are conflicting requirements
92✔
706
                mapped_req
92✔
707
                    .into_iter()
92✔
708
                    .enumerate()
92✔
709
                    .filter(|(index, _)| selected.contains(index))
232✔
710
                    .try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
216✔
711
            }
712
            SatisfiableItem::Multisig { keys, threshold } => {
14✔
713
                if selected.len() < *threshold {
14✔
714
                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
2✔
715
                }
12✔
716
                if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
26✔
717
                    return Err(PolicyError::IndexOutOfRange(item));
2✔
718
                }
10✔
719

10✔
720
                Ok(Condition::default())
10✔
721
            }
722
            SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
32✔
723
                csv: None,
32✔
724
                timelock: Some(*value),
32✔
725
            }),
32✔
726
            SatisfiableItem::RelativeTimelock { value } => Ok(Condition {
46✔
727
                csv: Some(*value),
46✔
728
                timelock: None,
46✔
729
            }),
46✔
730
            _ => Ok(Condition::default()),
1,429✔
731
        }
732
    }
1,665✔
733
}
734

735
impl From<SatisfiableItem> for Policy {
736
    fn from(other: SatisfiableItem) -> Self {
1,065✔
737
        Self::new(other)
1,065✔
738
    }
1,065✔
739
}
740

741
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
742
    // For consistency we always compute the key hash in "ecdsa" form (with the leading sign
743
    // prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
744
    // for a key, so it doesn't really matter how the identifier is computed.
745
    match key {
550✔
746
        DescriptorPublicKey::Single(SinglePub {
747
            key: SinglePubKey::FullKey(pk),
512✔
748
            ..
512✔
749
        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
512✔
750
        DescriptorPublicKey::Single(SinglePub {
751
            key: SinglePubKey::XOnly(pk),
38✔
752
            ..
38✔
753
        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
38✔
754
        DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
308✔
755
        DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
×
756
    }
757
}
858✔
758

759
fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
826✔
760
    key: &DescriptorPublicKey,
826✔
761
    signers: &SignersContainer,
826✔
762
    build_sat: BuildSatisfaction,
826✔
763
    secp: &SecpCtx,
826✔
764
    make_policy: M,
826✔
765
    find_sig: F,
826✔
766
) -> Policy {
826✔
767
    let mut policy: Policy = make_policy().into();
826✔
768

769
    policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
826✔
770
        Satisfaction::Complete {
760✔
771
            condition: Default::default(),
760✔
772
        }
760✔
773
    } else {
774
        Satisfaction::None
66✔
775
    };
776

777
    if let Some(psbt) = build_sat.psbt() {
826✔
778
        policy.satisfaction = if find_sig(psbt) {
24✔
779
            Satisfaction::Complete {
8✔
780
                condition: Default::default(),
8✔
781
            }
8✔
782
        } else {
783
            Satisfaction::None
16✔
784
        };
785
    }
802✔
786

787
    policy
826✔
788
}
826✔
789

790
fn generic_sig_in_psbt<
36✔
791
    // C is for "check", it's a closure we use to *check* if a psbt input contains the signature
36✔
792
    // for a specific key
36✔
793
    C: Fn(&psbt::Input, &SinglePubKey) -> bool,
36✔
794
    // E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
36✔
795
    E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
36✔
796
>(
36✔
797
    psbt: &Psbt,
36✔
798
    key: &DescriptorPublicKey,
36✔
799
    secp: &SecpCtx,
36✔
800
    check: C,
36✔
801
    extract: E,
36✔
802
) -> bool {
36✔
803
    //TODO check signature validity
36✔
804
    psbt.inputs.iter().all(|input| match key {
36✔
805
        DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
×
806
        DescriptorPublicKey::XPub(xpub) => {
36✔
807
            //TODO check actual derivation matches
36✔
808
            match extract(input, xpub.root_fingerprint(secp)) {
36✔
809
                Some(pubkey) => check(input, &pubkey),
36✔
810
                None => false,
×
811
            }
812
        }
813
        DescriptorPublicKey::MultiXPub(xpub) => {
×
814
            //TODO check actual derivation matches
×
815
            match extract(input, xpub.root_fingerprint(secp)) {
×
816
                Some(pubkey) => check(input, &pubkey),
×
817
                None => false,
×
818
            }
819
        }
820
    })
36✔
821
}
36✔
822

823
trait SigExt: ScriptContext {
824
    fn make_signature(
825
        key: &DescriptorPublicKey,
826
        signers: &SignersContainer,
827
        build_sat: BuildSatisfaction,
828
        secp: &SecpCtx,
829
    ) -> Policy;
830

831
    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
832
}
833

834
impl<T: ScriptContext + 'static> SigExt for T {
835
    fn make_signature(
826✔
836
        key: &DescriptorPublicKey,
826✔
837
        signers: &SignersContainer,
826✔
838
        build_sat: BuildSatisfaction,
826✔
839
        secp: &SecpCtx,
826✔
840
    ) -> Policy {
826✔
841
        if T::as_enum().is_taproot() {
826✔
842
            make_generic_signature(
128✔
843
                key,
128✔
844
                signers,
128✔
845
                build_sat,
128✔
846
                secp,
128✔
847
                || SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
192✔
848
                |psbt| Self::find_signature(psbt, key, secp),
134✔
849
            )
128✔
850
        } else {
851
            make_generic_signature(
698✔
852
                key,
698✔
853
                signers,
698✔
854
                build_sat,
698✔
855
                secp,
698✔
856
                || SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
943✔
857
                |psbt| Self::find_signature(psbt, key, secp),
704✔
858
            )
698✔
859
        }
860
    }
826✔
861

862
    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
36✔
863
        if T::as_enum().is_taproot() {
36✔
864
            generic_sig_in_psbt(
12✔
865
                psbt,
12✔
866
                key,
12✔
867
                secp,
12✔
868
                |input, pk| {
18✔
869
                    let pk = match pk {
12✔
870
                        SinglePubKey::XOnly(pk) => pk,
12✔
871
                        _ => return false,
×
872
                    };
873

874
                    if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
12✔
875
                        true
4✔
876
                    } else {
877
                        input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
8✔
878
                    }
879
                },
18✔
880
                |input, fing| {
18✔
881
                    input
12✔
882
                        .tap_key_origins
12✔
883
                        .iter()
12✔
884
                        .find(|(_, (_, (f, _)))| f == &fing)
16✔
885
                        .map(|(pk, _)| SinglePubKey::XOnly(*pk))
12✔
886
                },
18✔
887
            )
12✔
888
        } else {
889
            generic_sig_in_psbt(
24✔
890
                psbt,
24✔
891
                key,
24✔
892
                secp,
24✔
893
                |input, pk| match pk {
36✔
894
                    SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
24✔
895
                    _ => false,
×
896
                },
36✔
897
                |input, fing| {
36✔
898
                    input
24✔
899
                        .bip32_derivation
24✔
900
                        .iter()
24✔
901
                        .find(|(_, (f, _))| f == &fing)
36✔
902
                        .map(|(pk, _)| SinglePubKey::FullKey(PublicKey::new(*pk)))
24✔
903
                },
36✔
904
            )
24✔
905
        }
906
    }
36✔
907
}
908

909
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
910
    fn extract_policy(
618✔
911
        &self,
618✔
912
        signers: &SignersContainer,
618✔
913
        build_sat: BuildSatisfaction,
618✔
914
        secp: &SecpCtx,
618✔
915
    ) -> Result<Option<Policy>, Error> {
618✔
916
        Ok(match &self.node {
618✔
917
            // Leaves
918
            Terminal::True | Terminal::False => None,
×
919
            Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
152✔
920
            Terminal::PkH(pubkey_hash) => {
10✔
921
                Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
10✔
922
            }
923
            Terminal::After(value) => {
16✔
924
                let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
16✔
925
                    value: (*value).into(),
16✔
926
                }
16✔
927
                .into();
16✔
928
                policy.contribution = Satisfaction::Complete {
16✔
929
                    condition: Condition {
16✔
930
                        timelock: Some((*value).into()),
16✔
931
                        csv: None,
16✔
932
                    },
16✔
933
                };
16✔
934
                if let BuildSatisfaction::PsbtTimelocks {
935
                    current_height,
×
936
                    psbt,
×
937
                    ..
938
                } = build_sat
16✔
939
                {
940
                    let after = After::new(Some(current_height), false);
×
941
                    let after_sat =
×
942
                        Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
×
943
                    let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
×
944
                        Satisfier::<bitcoin::PublicKey>::check_after(&sat, (*value).into())
×
945
                    });
×
946
                    if after_sat && inputs_sat {
×
947
                        policy.satisfaction = policy.contribution.clone();
×
948
                    }
×
949
                }
16✔
950

951
                Some(policy)
16✔
952
            }
953
            Terminal::Older(value) => {
59✔
954
                let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
59✔
955
                policy.contribution = Satisfaction::Complete {
59✔
956
                    condition: Condition {
59✔
957
                        timelock: None,
59✔
958
                        csv: Some(*value),
59✔
959
                    },
59✔
960
                };
59✔
961
                if let BuildSatisfaction::PsbtTimelocks {
962
                    current_height,
6✔
963
                    input_max_height,
6✔
964
                    psbt,
6✔
965
                } = build_sat
59✔
966
                {
967
                    let older = Older::new(Some(current_height), Some(input_max_height), false);
6✔
968
                    let older_sat = Satisfier::<bitcoin::PublicKey>::check_older(&older, *value);
6✔
969
                    let inputs_sat = psbt_inputs_sat(psbt)
6✔
970
                        .all(|sat| Satisfier::<bitcoin::PublicKey>::check_older(&sat, *value));
9✔
971
                    if older_sat && inputs_sat {
6✔
972
                        policy.satisfaction = policy.contribution.clone();
4✔
973
                    }
4✔
974
                }
53✔
975

976
                Some(policy)
59✔
977
            }
978
            Terminal::Sha256(hash) => Some(SatisfiableItem::Sha256Preimage { hash: *hash }.into()),
×
979
            Terminal::Hash256(hash) => {
×
980
                Some(SatisfiableItem::Hash256Preimage { hash: *hash }.into())
×
981
            }
982
            Terminal::Ripemd160(hash) => {
×
983
                Some(SatisfiableItem::Ripemd160Preimage { hash: *hash }.into())
×
984
            }
985
            Terminal::Hash160(hash) => {
×
986
                Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
×
987
            }
988
            Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => {
16✔
989
                Policy::make_multisig::<Ctx>(pks, signers, build_sat, *k, false, secp)?
16✔
990
            }
991
            // Identities
992
            Terminal::Alt(inner)
×
993
            | Terminal::Swap(inner)
12✔
994
            | Terminal::Check(inner)
160✔
995
            | Terminal::DupIf(inner)
6✔
996
            | Terminal::Verify(inner)
75✔
997
            | Terminal::NonZero(inner)
×
998
            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, build_sat, secp)?,
259✔
999
            // Complex policies
1000
            Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
69✔
1001
                a.extract_policy(signers, build_sat, secp)?,
69✔
1002
                b.extract_policy(signers, build_sat, secp)?,
69✔
1003
            )?,
×
1004
            Terminal::AndOr(x, y, z) => Policy::make_or(
2✔
1005
                Policy::make_and(
2✔
1006
                    x.extract_policy(signers, build_sat, secp)?,
2✔
1007
                    y.extract_policy(signers, build_sat, secp)?,
2✔
1008
                )?,
×
1009
                z.extract_policy(signers, build_sat, secp)?,
2✔
1010
            )?,
×
1011
            Terminal::OrB(a, b)
×
1012
            | Terminal::OrD(a, b)
25✔
1013
            | Terminal::OrC(a, b)
×
1014
            | Terminal::OrI(a, b) => Policy::make_or(
4✔
1015
                a.extract_policy(signers, build_sat, secp)?,
29✔
1016
                b.extract_policy(signers, build_sat, secp)?,
29✔
1017
            )?,
×
1018
            Terminal::Thresh(k, nodes) => {
6✔
1019
                let mut threshold = *k;
6✔
1020
                let mapped: Vec<_> = nodes
6✔
1021
                    .iter()
6✔
1022
                    .map(|n| n.extract_policy(signers, build_sat, secp))
21✔
1023
                    .collect::<Result<Vec<_>, _>>()?
6✔
1024
                    .into_iter()
6✔
1025
                    .flatten()
6✔
1026
                    .collect();
6✔
1027

6✔
1028
                if mapped.len() < nodes.len() {
6✔
1029
                    threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
×
1030
                        None => return Ok(None),
×
1031
                        Some(x) => x,
×
1032
                    };
1033
                }
6✔
1034

1035
                Policy::make_thresh(mapped, threshold)?
6✔
1036
            }
1037

1038
            // Unsupported
1039
            Terminal::RawPkH(_) => None,
×
1040
        })
1041
    }
618✔
1042
}
1043

1044
fn psbt_inputs_sat(psbt: &Psbt) -> impl Iterator<Item = PsbtInputSatisfier> {
6✔
1045
    (0..psbt.inputs.len()).map(move |i| PsbtInputSatisfier::new(psbt, i))
9✔
1046
}
6✔
1047

1048
/// Options to build the satisfaction field in the policy
1049
#[derive(Debug, Clone, Copy)]
×
1050
pub enum BuildSatisfaction<'a> {
1051
    /// Don't generate `satisfaction` field
1052
    None,
1053
    /// Analyze the given PSBT to check for existing signatures
1054
    Psbt(&'a Psbt),
1055
    /// Like `Psbt` variant and also check for expired timelocks
1056
    PsbtTimelocks {
1057
        /// Given PSBT
1058
        psbt: &'a Psbt,
1059
        /// Current blockchain height
1060
        current_height: u32,
1061
        /// The highest confirmation height between the inputs
1062
        /// CSV should consider different inputs, but we consider the worst condition for the tx as whole
1063
        input_max_height: u32,
1064
    },
1065
}
1066
impl<'a> BuildSatisfaction<'a> {
1067
    fn psbt(&self) -> Option<&'a Psbt> {
858✔
1068
        match self {
858✔
1069
            BuildSatisfaction::None => None,
822✔
1070
            BuildSatisfaction::Psbt(psbt) => Some(psbt),
24✔
1071
            BuildSatisfaction::PsbtTimelocks { psbt, .. } => Some(psbt),
12✔
1072
        }
1073
    }
858✔
1074
}
1075

1076
impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
1077
    fn extract_policy(
745✔
1078
        &self,
745✔
1079
        signers: &SignersContainer,
745✔
1080
        build_sat: BuildSatisfaction,
745✔
1081
        secp: &SecpCtx,
745✔
1082
    ) -> Result<Option<Policy>, Error> {
745✔
1083
        fn make_sortedmulti<Ctx: ScriptContext + 'static>(
745✔
1084
            keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
275✔
1085
            signers: &SignersContainer,
275✔
1086
            build_sat: BuildSatisfaction,
275✔
1087
            secp: &SecpCtx,
275✔
1088
        ) -> Result<Option<Policy>, Error> {
275✔
1089
            Ok(Policy::make_multisig::<Ctx>(
275✔
1090
                keys.pks.as_ref(),
275✔
1091
                signers,
275✔
1092
                build_sat,
275✔
1093
                keys.k,
275✔
1094
                true,
275✔
1095
                secp,
275✔
1096
            )?)
275✔
1097
        }
745✔
1098

745✔
1099
        match self {
745✔
1100
            Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
8✔
1101
                pk.as_inner(),
8✔
1102
                signers,
8✔
1103
                build_sat,
8✔
1104
                secp,
8✔
1105
            ))),
8✔
1106
            Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
582✔
1107
                pk.as_inner(),
582✔
1108
                signers,
582✔
1109
                build_sat,
582✔
1110
                secp,
582✔
1111
            ))),
582✔
1112
            Descriptor::Sh(sh) => match sh.as_inner() {
18✔
1113
                ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
4✔
1114
                    pk.as_inner(),
4✔
1115
                    signers,
4✔
1116
                    build_sat,
4✔
1117
                    secp,
4✔
1118
                ))),
4✔
1119
                ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
10✔
1120
                ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
×
1121
                ShInner::Wsh(wsh) => match wsh.as_inner() {
4✔
1122
                    WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
4✔
1123
                    WshInner::SortedMulti(ref keys) => {
×
1124
                        make_sortedmulti(keys, signers, build_sat, secp)
×
1125
                    }
1126
                },
1127
            },
1128
            Descriptor::Wsh(wsh) => match wsh.as_inner() {
67✔
1129
                WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
67✔
1130
                WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
×
1131
            },
1132
            Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
×
1133
            Descriptor::Tr(tr) => {
70✔
1134
                // If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
70✔
1135
                // node with threshold = 1 and the key spend signature plus all the tree leaves
70✔
1136
                let key_spend_sig =
70✔
1137
                    miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
70✔
1138

70✔
1139
                if tr.taptree().is_none() {
70✔
1140
                    Ok(Some(key_spend_sig))
30✔
1141
                } else {
1142
                    let mut items = vec![key_spend_sig];
40✔
1143
                    items.append(
40✔
1144
                        &mut tr
40✔
1145
                            .iter_scripts()
40✔
1146
                            .filter_map(|(_, ms)| {
78✔
1147
                                ms.extract_policy(signers, build_sat, secp).transpose()
58✔
1148
                            })
78✔
1149
                            .collect::<Result<Vec<_>, _>>()?,
40✔
1150
                    );
1151

1152
                    Ok(Policy::make_thresh(items, 1)?)
40✔
1153
                }
1154
            }
1155
        }
1156
    }
745✔
1157
}
1158

1159
#[cfg(test)]
1160
mod test {
1161
    use crate::descriptor;
1162
    use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
1163

1164
    use super::*;
1165
    use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
1166
    use crate::keys::{DescriptorKey, IntoDescriptorKey};
1167
    use crate::wallet::signer::SignersContainer;
1168
    use assert_matches::assert_matches;
1169
    use bitcoin::bip32;
1170
    use bitcoin::secp256k1::Secp256k1;
1171
    use bitcoin::Network;
1172
    use std::str::FromStr;
1173
    use std::sync::Arc;
1174

1175
    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
1176
    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
1177

1178
    const PATH: &str = "m/44'/1'/0'/0";
1179

1180
    fn setup_keys<Ctx: ScriptContext>(
46✔
1181
        tprv: &str,
46✔
1182
        path: &str,
46✔
1183
        secp: &SecpCtx,
46✔
1184
    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
46✔
1185
        let path = bip32::DerivationPath::from_str(path).unwrap();
46✔
1186
        let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
46✔
1187
        let tpub = bip32::ExtendedPubKey::from_priv(secp, &tprv);
46✔
1188
        let fingerprint = tprv.fingerprint(secp);
46✔
1189
        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
46✔
1190
        let pubkey = (tpub, path).into_descriptor_key().unwrap();
46✔
1191

46✔
1192
        (prvkey, pubkey, fingerprint)
46✔
1193
    }
46✔
1194

1195
    // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
1196

1197
    #[test]
2✔
1198
    fn test_extract_policy_for_wpkh() {
2✔
1199
        let secp = Secp256k1::new();
2✔
1200

2✔
1201
        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1202
        let desc = descriptor!(wpkh(pubkey)).unwrap();
9✔
1203
        let (wallet_desc, keymap) = desc
2✔
1204
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1205
            .unwrap();
2✔
1206
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1207
        let policy = wallet_desc
2✔
1208
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1209
            .unwrap()
2✔
1210
            .unwrap();
2✔
1211

1212
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
2✔
1213
        assert_matches!(&policy.contribution, Satisfaction::None);
2✔
1214

1215
        let desc = descriptor!(wpkh(prvkey)).unwrap();
9✔
1216
        let (wallet_desc, keymap) = desc
2✔
1217
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1218
            .unwrap();
2✔
1219
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1220
        let policy = wallet_desc
2✔
1221
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1222
            .unwrap()
2✔
1223
            .unwrap();
2✔
1224

1225
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
2✔
1226
        assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
2✔
1227
    }
2✔
1228

1229
    // 2 pub keys descriptor, required 2 prv keys
1230
    #[test]
2✔
1231
    fn test_extract_policy_for_sh_multi_partial_0of2() {
2✔
1232
        let secp = Secp256k1::new();
2✔
1233
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1234
        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
2✔
1235
        let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap();
3✔
1236
        let (wallet_desc, keymap) = desc
2✔
1237
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1238
            .unwrap();
2✔
1239
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1240
        let policy = wallet_desc
2✔
1241
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1242
            .unwrap()
2✔
1243
            .unwrap();
2✔
1244

1245
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
2✔
1246
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
2✔
1247
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
2✔
1248
        );
1249
        // TODO should this be "Satisfaction::None" since we have no prv keys?
1250
        // TODO should items and conditions not be empty?
1251
        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
2✔
1252
            && m == &2usize
2✔
1253
            && items.is_empty()
2✔
1254
            && conditions.is_empty()
2✔
1255
        );
2✔
1256
    }
2✔
1257

1258
    // 1 prv and 1 pub key descriptor, required 2 prv keys
1259
    #[test]
2✔
1260
    fn test_extract_policy_for_sh_multi_partial_1of2() {
2✔
1261
        let secp = Secp256k1::new();
2✔
1262
        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1263
        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
2✔
1264
        let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap();
3✔
1265
        let (wallet_desc, keymap) = desc
2✔
1266
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1267
            .unwrap();
2✔
1268
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1269
        let policy = wallet_desc
2✔
1270
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1271
            .unwrap()
2✔
1272
            .unwrap();
2✔
1273
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
2✔
1274
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
2✔
1275
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
2✔
1276
        );
1277

1278
        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
2✔
1279
             && m == &2usize
2✔
1280
             && items.len() == 1
2✔
1281
             && conditions.contains_key(&0)
2✔
1282
        );
2✔
1283
    }
2✔
1284

1285
    // 1 prv and 1 pub key descriptor, required 1 prv keys
1286
    #[test]
×
1287
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1288
    fn test_extract_policy_for_sh_multi_complete_1of2() {
×
1289
        let secp = Secp256k1::new();
×
1290

×
1291
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1292
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1293
        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
×
1294
        let (wallet_desc, keymap) = desc
×
1295
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1296
            .unwrap();
×
1297
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1298
        let policy = wallet_desc
×
1299
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1300
            .unwrap()
×
1301
            .unwrap();
×
1302

1303
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
×
1304
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
×
1305
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
×
1306
        );
1307
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
×
1308
             && m == &1
×
1309
             && items.len() == 2
×
1310
             && conditions.contains_key(&vec![0])
×
1311
             && conditions.contains_key(&vec![1])
×
1312
        );
×
1313
    }
×
1314

1315
    // 2 prv keys descriptor, required 2 prv keys
1316
    #[test]
2✔
1317
    fn test_extract_policy_for_sh_multi_complete_2of2() {
2✔
1318
        let secp = Secp256k1::new();
2✔
1319

2✔
1320
        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1321
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
2✔
1322
        let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
3✔
1323
        let (wallet_desc, keymap) = desc
2✔
1324
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1325
            .unwrap();
2✔
1326
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1327
        let policy = wallet_desc
2✔
1328
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1329
            .unwrap()
2✔
1330
            .unwrap();
2✔
1331

1332
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
2✔
1333
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
2✔
1334
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
2✔
1335
        );
1336

1337
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
2✔
1338
             && m == &2
2✔
1339
             && items.len() == 2
2✔
1340
             && conditions.contains_key(&vec![0,1])
2✔
1341
        );
2✔
1342
    }
2✔
1343

1344
    // test ExtractPolicy trait with extended and single keys
1345

1346
    #[test]
2✔
1347
    fn test_extract_policy_for_single_wpkh() {
2✔
1348
        let secp = Secp256k1::new();
2✔
1349

2✔
1350
        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1351
        let desc = descriptor!(wpkh(pubkey)).unwrap();
9✔
1352
        let (wallet_desc, keymap) = desc
2✔
1353
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1354
            .unwrap();
2✔
1355
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1356
        let policy = wallet_desc
2✔
1357
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1358
            .unwrap()
2✔
1359
            .unwrap();
2✔
1360

1361
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
2✔
1362
        assert_matches!(&policy.contribution, Satisfaction::None);
2✔
1363

1364
        let desc = descriptor!(wpkh(prvkey)).unwrap();
9✔
1365
        let (wallet_desc, keymap) = desc
2✔
1366
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1367
            .unwrap();
2✔
1368
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1369
        let policy = wallet_desc
2✔
1370
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1371
            .unwrap()
2✔
1372
            .unwrap();
2✔
1373

1374
        assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
2✔
1375
        assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
2✔
1376
    }
2✔
1377

1378
    // single key, 1 prv and 1 pub key descriptor, required 1 prv keys
1379
    #[test]
×
1380
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1381
    fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
×
1382
        let secp = Secp256k1::new();
×
1383

×
1384
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1385
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1386
        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
×
1387
        let (wallet_desc, keymap) = desc
×
1388
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1389
            .unwrap();
×
1390
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1391
        let policy = wallet_desc
×
1392
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1393
            .unwrap()
×
1394
            .unwrap();
×
1395

1396
        assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
×
1397
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
×
1398
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
×
1399
        );
1400
        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
×
1401
             && m == 1
×
1402
             && items.len() == 2
×
1403
             && conditions.contains_key(&vec![0])
×
1404
             && conditions.contains_key(&vec![1])
×
1405
        );
×
1406
    }
×
1407

1408
    // test ExtractPolicy trait with descriptors containing timelocks in a thresh()
1409

1410
    #[test]
×
1411
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1412
    fn test_extract_policy_for_wsh_multi_timelock() {
×
1413
        let secp = Secp256k1::new();
×
1414

×
1415
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1416
        let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1417
        let sequence = 50;
×
1418
        #[rustfmt::skip]
×
1419
        let desc = descriptor!(wsh(thresh(
×
1420
            2,
×
1421
            pk(prvkey0),
×
1422
            s:pk(pubkey1),
×
1423
            s:d:v:older(sequence)
×
1424
        )))
×
1425
        .unwrap();
×
1426

×
1427
        let (wallet_desc, keymap) = desc
×
1428
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1429
            .unwrap();
×
1430
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1431
        let policy = wallet_desc
×
1432
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1433
            .unwrap()
×
1434
            .unwrap();
×
1435

1436
        assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
×
1437

1438
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
×
1439
             && m == &2
×
1440
             && items.len() == 3
×
1441
             && conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
×
1442
             && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
×
1443
             && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
×
1444
        );
×
1445
    }
×
1446

1447
    // - mixed timelocks should fail
1448

1449
    #[test]
×
1450
    #[ignore]
1451
    fn test_extract_policy_for_wsh_mixed_timelocks() {
×
1452
        let secp = Secp256k1::new();
×
1453
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1454
        let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
×
1455
        let locktime_blocks = 100;
×
1456
        let locktime_seconds = locktime_blocks + locktime_threshold;
×
1457
        let desc = descriptor!(sh(and_v(
×
1458
            v: pk(prvkey0),
×
1459
            and_v(v: after(locktime_seconds), after(locktime_blocks))
×
1460
        )))
×
1461
        .unwrap();
×
1462
        let (wallet_desc, keymap) = desc
×
1463
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1464
            .unwrap();
×
1465
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1466
        let policy = wallet_desc
×
1467
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1468
            .unwrap()
×
1469
            .unwrap();
×
1470
        println!("desc policy = {:?}", policy); // TODO remove
×
1471
                                                // TODO how should this fail with mixed timelocks?
×
1472
    }
×
1473

1474
    // - multiple timelocks of the same type should be correctly merged together
1475
    #[test]
×
1476
    #[ignore]
1477
    fn test_extract_policy_for_multiple_same_timelocks() {
×
1478
        let secp = Secp256k1::new();
×
1479
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1480
        let locktime_blocks0 = 100;
×
1481
        let locktime_blocks1 = 200;
×
1482
        let desc = descriptor!(sh(and_v(
×
1483
            v: pk(prvkey0),
×
1484
            and_v(v: after(locktime_blocks0), after(locktime_blocks1))
×
1485
        )))
×
1486
        .unwrap();
×
1487
        let (wallet_desc, keymap) = desc
×
1488
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1489
            .unwrap();
×
1490
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1491
        let policy = wallet_desc
×
1492
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1493
            .unwrap()
×
1494
            .unwrap();
×
1495
        println!("desc policy = {:?}", policy); // TODO remove
×
1496
                                                // TODO how should this merge timelocks?
×
1497
        let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
×
1498
        let locktime_seconds0 = 500000100;
×
1499
        let locktime_seconds1 = 500000200;
×
1500
        let desc = descriptor!(sh(and_v(
×
1501
            v: pk(prvkey1),
×
1502
            and_v(v: after(locktime_seconds0), after(locktime_seconds1))
×
1503
        )))
×
1504
        .unwrap();
×
1505
        let (wallet_desc, keymap) = desc
×
1506
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1507
            .unwrap();
×
1508
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1509
        let policy = wallet_desc
×
1510
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1511
            .unwrap()
×
1512
            .unwrap();
×
1513

×
1514
        println!("desc policy = {:?}", policy); // TODO remove
×
1515

×
1516
        // TODO how should this merge timelocks?
×
1517
    }
×
1518

1519
    #[test]
2✔
1520
    fn test_get_condition_multisig() {
2✔
1521
        let secp = Secp256k1::new();
2✔
1522

2✔
1523
        let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
2✔
1524
        let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
2✔
1525

2✔
1526
        let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
3✔
1527
        let (wallet_desc, keymap) = desc
2✔
1528
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1529
            .unwrap();
2✔
1530
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1531

2✔
1532
        let policy = wallet_desc
2✔
1533
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1534
            .unwrap()
2✔
1535
            .unwrap();
2✔
1536

2✔
1537
        // no args, choose the default
2✔
1538
        let no_args = policy.get_condition(&vec![].into_iter().collect());
2✔
1539
        assert_eq!(no_args, Ok(Condition::default()));
2✔
1540

1541
        // enough args
1542
        let eq_thresh =
2✔
1543
            policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
2✔
1544
        assert_eq!(eq_thresh, Ok(Condition::default()));
2✔
1545

1546
        // more args, it doesn't really change anything
1547
        let gt_thresh =
2✔
1548
            policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
2✔
1549
        assert_eq!(gt_thresh, Ok(Condition::default()));
2✔
1550

1551
        // not enough args, error
1552
        let lt_thresh =
2✔
1553
            policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
2✔
1554
        assert_eq!(
2✔
1555
            lt_thresh,
2✔
1556
            Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
2✔
1557
        );
2✔
1558

1559
        // index out of range
1560
        let out_of_range =
2✔
1561
            policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
2✔
1562
        assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
2✔
1563
    }
2✔
1564

1565
    const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
1566
    const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
1567
    const CAROL_TPRV_STR:&str = "tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt";
1568
    const ALICE_BOB_PATH: &str = "m/0'";
1569

1570
    #[test]
2✔
1571
    fn test_extract_satisfaction() {
2✔
1572
        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";
2✔
1573
        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==";
2✔
1574
        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";
2✔
1575

2✔
1576
        let secp = Secp256k1::new();
2✔
1577

2✔
1578
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1579
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1580

2✔
1581
        let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
3✔
1582

2✔
1583
        let (wallet_desc, keymap) = desc
2✔
1584
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1585
            .unwrap();
2✔
1586

2✔
1587
        let addr = wallet_desc
2✔
1588
            .at_derivation_index(0)
2✔
1589
            .unwrap()
2✔
1590
            .address(Network::Testnet)
2✔
1591
            .unwrap();
2✔
1592
        assert_eq!(
2✔
1593
            "tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
2✔
1594
            addr.to_string()
2✔
1595
        );
2✔
1596

1597
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1598

2✔
1599
        let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
2✔
1600

2✔
1601
        let policy_alice_psbt = wallet_desc
2✔
1602
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
2✔
1603
            .unwrap()
2✔
1604
            .unwrap();
2✔
1605
        //println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
1606

1607
        assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
2✔
1608
             && m == &2
2✔
1609
             && items == &vec![0]
2✔
1610
        );
2✔
1611

2✔
1612
        let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
2✔
1613
        let policy_bob_psbt = wallet_desc
2✔
1614
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
2✔
1615
            .unwrap()
2✔
1616
            .unwrap();
2✔
1617
        //println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
1618

1619
        assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
2✔
1620
             && m == &2
2✔
1621
             && items == &vec![1]
2✔
1622
        );
2✔
1623

2✔
1624
        let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
2✔
1625
        let policy_alice_bob_psbt = wallet_desc
2✔
1626
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
2✔
1627
            .unwrap()
2✔
1628
            .unwrap();
2✔
1629
        assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
2✔
1630
             && m == &2
2✔
1631
             && items == &vec![0, 1]
2✔
1632
        );
2✔
1633
    }
2✔
1634

1635
    #[test]
2✔
1636
    fn test_extract_satisfaction_timelock() {
2✔
1637
        //const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
2✔
1638
        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED:     &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
2✔
1639
        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==";
2✔
1640

2✔
1641
        let secp = Secp256k1::new();
2✔
1642

2✔
1643
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1644
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1645

2✔
1646
        let desc =
2✔
1647
            descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
23✔
1648

2✔
1649
        let (wallet_desc, keymap) = desc
2✔
1650
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1651
            .unwrap();
2✔
1652
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1653

2✔
1654
        let addr = wallet_desc
2✔
1655
            .at_derivation_index(0)
2✔
1656
            .unwrap()
2✔
1657
            .address(Network::Testnet)
2✔
1658
            .unwrap();
2✔
1659
        assert_eq!(
2✔
1660
            "tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58",
2✔
1661
            addr.to_string()
2✔
1662
        );
2✔
1663

1664
        let psbt = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap();
2✔
1665

2✔
1666
        let build_sat = BuildSatisfaction::PsbtTimelocks {
2✔
1667
            psbt: &psbt,
2✔
1668
            current_height: 10,
2✔
1669
            input_max_height: 9,
2✔
1670
        };
2✔
1671

2✔
1672
        let policy = wallet_desc
2✔
1673
            .extract_policy(&signers_container, build_sat, &secp)
2✔
1674
            .unwrap()
2✔
1675
            .unwrap();
2✔
1676
        assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
2✔
1677
             && m == &2
2✔
1678
             && items.is_empty()
2✔
1679
        );
2✔
1680
        //println!("{}", serde_json::to_string(&policy).unwrap());
2✔
1681

2✔
1682
        let build_sat_expired = BuildSatisfaction::PsbtTimelocks {
2✔
1683
            psbt: &psbt,
2✔
1684
            current_height: 12,
2✔
1685
            input_max_height: 9,
2✔
1686
        };
2✔
1687

2✔
1688
        let policy_expired = wallet_desc
2✔
1689
            .extract_policy(&signers_container, build_sat_expired, &secp)
2✔
1690
            .unwrap()
2✔
1691
            .unwrap();
2✔
1692
        assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
2✔
1693
             && m == &2
2✔
1694
             && items == &vec![0]
2✔
1695
        );
2✔
1696
        //println!("{}", serde_json::to_string(&policy_expired).unwrap());
2✔
1697

2✔
1698
        let psbt_signed = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap();
2✔
1699

2✔
1700
        let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
2✔
1701
            psbt: &psbt_signed,
2✔
1702
            current_height: 12,
2✔
1703
            input_max_height: 9,
2✔
1704
        };
2✔
1705

2✔
1706
        let policy_expired_signed = wallet_desc
2✔
1707
            .extract_policy(&signers_container, build_sat_expired_signed, &secp)
2✔
1708
            .unwrap()
2✔
1709
            .unwrap();
2✔
1710
        assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
2✔
1711
             && m == &2
2✔
1712
             && items == &vec![0, 1]
2✔
1713
        );
2✔
1714
        //println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
2✔
1715
    }
2✔
1716

1717
    #[test]
2✔
1718
    fn test_extract_pkh() {
2✔
1719
        let secp = Secp256k1::new();
2✔
1720

2✔
1721
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1722
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1723
        let (prvkey_carol, _, _) = setup_keys(CAROL_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1724

2✔
1725
        let desc = descriptor!(wsh(c: andor(
11✔
1726
            pk(prvkey_alice),
2✔
1727
            pk_k(prvkey_bob),
2✔
1728
            pk_h(prvkey_carol),
2✔
1729
        )))
2✔
1730
        .unwrap();
2✔
1731

2✔
1732
        let (wallet_desc, keymap) = desc
2✔
1733
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1734
            .unwrap();
2✔
1735
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1736

2✔
1737
        let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
2✔
1738
        assert!(policy.is_ok());
2✔
1739
    }
2✔
1740

1741
    #[test]
2✔
1742
    fn test_extract_tr_key_spend() {
2✔
1743
        let secp = Secp256k1::new();
2✔
1744

2✔
1745
        let (prvkey, _, fingerprint) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1746

2✔
1747
        let desc = descriptor!(tr(prvkey)).unwrap();
5✔
1748
        let (wallet_desc, keymap) = desc
2✔
1749
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1750
            .unwrap();
2✔
1751
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1752

2✔
1753
        let policy = wallet_desc
2✔
1754
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1755
            .unwrap();
2✔
1756
        assert_eq!(
2✔
1757
            policy,
2✔
1758
            Some(Policy {
2✔
1759
                id: "48u0tz0n".to_string(),
2✔
1760
                item: SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(fingerprint)),
2✔
1761
                satisfaction: Satisfaction::None,
2✔
1762
                contribution: Satisfaction::Complete {
2✔
1763
                    condition: Condition::default()
2✔
1764
                }
2✔
1765
            })
2✔
1766
        );
2✔
1767
    }
2✔
1768

1769
    #[test]
2✔
1770
    fn test_extract_tr_script_spend() {
2✔
1771
        let secp = Secp256k1::new();
2✔
1772

2✔
1773
        let (alice_prv, _, alice_fing) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1774
        let (_, bob_pub, bob_fing) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1775

2✔
1776
        let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap();
5✔
1777
        let (wallet_desc, keymap) = desc
2✔
1778
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1779
            .unwrap();
2✔
1780
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
2✔
1781

2✔
1782
        let policy = wallet_desc
2✔
1783
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
2✔
1784
            .unwrap()
2✔
1785
            .unwrap();
2✔
1786

1787
        assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
2✔
1788
        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
2✔
1789

2✔
1790
        let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
2✔
1791
        let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
2✔
1792

1793
        let thresh_items = match policy.item {
2✔
1794
            SatisfiableItem::Thresh { items, .. } => items,
2✔
1795
            _ => unreachable!(),
×
1796
        };
1797

1798
        assert_eq!(thresh_items[0].item, bob_sig);
2✔
1799
        assert_eq!(thresh_items[1].item, alice_sig);
2✔
1800
    }
2✔
1801

1802
    #[test]
2✔
1803
    fn test_extract_tr_satisfaction_key_spend() {
2✔
1804
        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA==";
2✔
1805
        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA=";
2✔
1806

2✔
1807
        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
2✔
1808
        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
2✔
1809

2✔
1810
        let secp = Secp256k1::new();
2✔
1811

2✔
1812
        let (_, pubkey, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1813

2✔
1814
        let desc = descriptor!(tr(pubkey)).unwrap();
5✔
1815
        let (wallet_desc, _) = desc
2✔
1816
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1817
            .unwrap();
2✔
1818

2✔
1819
        let policy_unsigned = wallet_desc
2✔
1820
            .extract_policy(
2✔
1821
                &SignersContainer::default(),
2✔
1822
                BuildSatisfaction::Psbt(&unsigned_psbt),
2✔
1823
                &secp,
2✔
1824
            )
2✔
1825
            .unwrap()
2✔
1826
            .unwrap();
2✔
1827
        let policy_signed = wallet_desc
2✔
1828
            .extract_policy(
2✔
1829
                &SignersContainer::default(),
2✔
1830
                BuildSatisfaction::Psbt(&signed_psbt),
2✔
1831
                &secp,
2✔
1832
            )
2✔
1833
            .unwrap()
2✔
1834
            .unwrap();
2✔
1835

2✔
1836
        assert_eq!(policy_unsigned.satisfaction, Satisfaction::None);
2✔
1837
        assert_eq!(
2✔
1838
            policy_signed.satisfaction,
2✔
1839
            Satisfaction::Complete {
2✔
1840
                condition: Default::default()
2✔
1841
            }
2✔
1842
        );
2✔
1843
    }
2✔
1844

1845
    #[test]
2✔
1846
    fn test_extract_tr_satisfaction_script_spend() {
2✔
1847
        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
2✔
1848
        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==";
2✔
1849

2✔
1850
        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
2✔
1851
        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
2✔
1852

2✔
1853
        let secp = Secp256k1::new();
2✔
1854

2✔
1855
        let (_, alice_pub, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1856
        let (_, bob_pub, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
2✔
1857

2✔
1858
        let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap();
5✔
1859
        let (wallet_desc, _) = desc
2✔
1860
            .into_wallet_descriptor(&secp, Network::Testnet)
2✔
1861
            .unwrap();
2✔
1862

2✔
1863
        let policy_unsigned = wallet_desc
2✔
1864
            .extract_policy(
2✔
1865
                &SignersContainer::default(),
2✔
1866
                BuildSatisfaction::Psbt(&unsigned_psbt),
2✔
1867
                &secp,
2✔
1868
            )
2✔
1869
            .unwrap()
2✔
1870
            .unwrap();
2✔
1871
        let policy_signed = wallet_desc
2✔
1872
            .extract_policy(
2✔
1873
                &SignersContainer::default(),
2✔
1874
                BuildSatisfaction::Psbt(&signed_psbt),
2✔
1875
                &secp,
2✔
1876
            )
2✔
1877
            .unwrap()
2✔
1878
            .unwrap();
2✔
1879

1880
        assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
2✔
1881
        assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
2✔
1882

1883
        assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
2✔
1884
        assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
2✔
1885

1886
        let satisfied_items = match policy_signed.item {
2✔
1887
            SatisfiableItem::Thresh { items, .. } => items,
2✔
1888
            _ => unreachable!(),
×
1889
        };
1890

1891
        assert_eq!(
2✔
1892
            satisfied_items[0].satisfaction,
2✔
1893
            Satisfaction::Complete {
2✔
1894
                condition: Default::default()
2✔
1895
            }
2✔
1896
        );
2✔
1897
        assert_eq!(
2✔
1898
            satisfied_items[1].satisfaction,
2✔
1899
            Satisfaction::Complete {
2✔
1900
                condition: Default::default()
2✔
1901
            }
2✔
1902
        );
2✔
1903
    }
2✔
1904
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc