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

bitcoindevkit / bdk / 9475497656

12 Jun 2024 02:00AM UTC coverage: 82.974% (-0.4%) from 83.405%
9475497656

Pull #1468

github

web-flow
Merge 44e446faa into 473ef9714
Pull Request #1468: wip(feat): use `Weight` type instead of `usize`

112 of 190 new or added lines in 19 files covered. (58.95%)

13 existing lines in 3 files now uncovered.

11253 of 13562 relevant lines covered (82.97%)

16764.07 hits per line

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

78.37
/crates/wallet/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_wallet::descriptor::*;
24
//! # use bdk_wallet::wallet::signer::*;
25
//! # use bdk_wallet::bitcoin::secp256k1::Secp256k1;
26
//! use bdk_wallet::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).unwrap());
36
//! # Ok::<(), anyhow::Error>(())
37
//! ```
38

39
use crate::collections::{BTreeMap, HashSet, VecDeque};
40
use alloc::string::String;
41
use alloc::vec::Vec;
42
use core::cmp::max;
43
use miniscript::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG};
44

45
use core::fmt;
46

47
use serde::ser::SerializeMap;
48
use serde::{Serialize, Serializer};
49

50
use bitcoin::bip32::Fingerprint;
51
use bitcoin::hashes::{hash160, ripemd160, sha256};
52
use bitcoin::{absolute, key::XOnlyPublicKey, relative, PublicKey, Sequence};
53

54
use miniscript::descriptor::{
55
    DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
56
};
57
use miniscript::{hash256, Threshold};
58
use miniscript::{
59
    Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
60
};
61

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

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

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

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

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

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

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

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

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

183
    let mut answer = Vec::new();
325✔
184

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

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

204
    answer
325✔
205
}
325✔
206

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

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

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

222
    while let Some(vals) = queue.pop_front() {
1,122✔
223
        if vals.len() >= size {
661✔
224
            answer.push(vals);
485✔
225
        } else {
485✔
226
            let level = vals.len();
176✔
227
            for i in &vec[level] {
184✔
228
                let mut cloned = vals.clone();
184✔
229
                cloned.push(i.clone());
184✔
230
                queue.push_front(cloned);
184✔
231
            }
184✔
232
        }
233
    }
234

235
    answer
461✔
236
}
461✔
237

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

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

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

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

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

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

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

348
                Ok(())
632✔
349
            }
350
        }
351
    }
1,443✔
352

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

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

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

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

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

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

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

481
    pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
2,405✔
482
        match (self.csv, other.csv) {
2,405✔
483
            (Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
×
484
            (None, any) => self.csv = any,
2,358✔
485
            _ => {}
47✔
486
        }
487

488
        match (self.timelock, other.timelock) {
2,405✔
489
            (Some(a), Some(b)) => self.timelock = Some(Self::merge_nlocktime(a, b)?),
×
490
            (None, any) => self.timelock = any,
2,357✔
491
            _ => {}
48✔
492
        }
493

494
        Ok(self)
2,405✔
495
    }
2,405✔
496

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

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

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

533
#[cfg(feature = "std")]
534
impl std::error::Error for PolicyError {}
535

536
impl Policy {
537
    fn new(item: SatisfiableItem) -> Self {
3,208✔
538
        Policy {
3,208✔
539
            id: item.id(),
3,208✔
540
            item,
3,208✔
541
            satisfaction: Satisfaction::None,
3,208✔
542
            contribution: Satisfaction::None,
3,208✔
543
        }
3,208✔
544
    }
3,208✔
545

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

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

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

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

580
        contribution.finalize();
312✔
581
        satisfaction.finalize();
312✔
582

312✔
583
        let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
312✔
584
        policy.contribution = contribution;
312✔
585
        policy.satisfaction = satisfaction;
312✔
586

312✔
587
        Ok(Some(policy))
312✔
588
    }
312✔
589

590
    fn make_multi<Ctx: ScriptContext + 'static>(
15✔
591
        threshold: &Threshold<DescriptorPublicKey, MAX_PUBKEYS_PER_MULTISIG>,
15✔
592
        signers: &SignersContainer,
15✔
593
        build_sat: BuildSatisfaction,
15✔
594
        sorted: bool,
15✔
595
        secp: &SecpCtx,
15✔
596
    ) -> Result<Option<Policy>, PolicyError> {
15✔
597
        let parsed_keys = threshold.iter().map(|k| PkOrF::from_key(k, secp)).collect();
30✔
598

15✔
599
        let mut contribution = Satisfaction::Partial {
15✔
600
            n: threshold.n(),
15✔
601
            m: threshold.k(),
15✔
602
            items: vec![],
15✔
603
            conditions: Default::default(),
15✔
604
            sorted: Some(sorted),
15✔
605
        };
15✔
606
        let mut satisfaction = contribution.clone();
15✔
607

608
        for (index, key) in threshold.iter().enumerate() {
30✔
609
            if signers.find(signer_id(key, secp)).is_some() {
30✔
610
                contribution.add(
25✔
611
                    &Satisfaction::Complete {
25✔
612
                        condition: Default::default(),
25✔
613
                    },
25✔
614
                    index,
25✔
615
                )?;
25✔
616
            }
5✔
617

618
            if let Some(psbt) = build_sat.psbt() {
30✔
619
                if Ctx::find_signature(psbt, key, secp) {
6✔
620
                    satisfaction.add(
4✔
621
                        &Satisfaction::Complete {
4✔
622
                            condition: Default::default(),
4✔
623
                        },
4✔
624
                        index,
4✔
625
                    )?;
4✔
626
                }
2✔
627
            }
24✔
628
        }
629
        satisfaction.finalize();
15✔
630
        contribution.finalize();
15✔
631

15✔
632
        let mut policy: Policy = SatisfiableItem::Multisig {
15✔
633
            keys: parsed_keys,
15✔
634
            threshold: threshold.k(),
15✔
635
        }
15✔
636
        .into();
15✔
637
        policy.contribution = contribution;
15✔
638
        policy.satisfaction = satisfaction;
15✔
639

15✔
640
        Ok(Some(policy))
15✔
641
    }
15✔
642

NEW
643
    fn make_multi_a<Ctx: ScriptContext + 'static>(
×
NEW
644
        threshold: &Threshold<DescriptorPublicKey, MAX_PUBKEYS_IN_CHECKSIGADD>,
×
NEW
645
        signers: &SignersContainer,
×
NEW
646
        build_sat: BuildSatisfaction,
×
NEW
647
        sorted: bool,
×
NEW
648
        secp: &SecpCtx,
×
NEW
649
    ) -> Result<Option<Policy>, PolicyError> {
×
NEW
650
        let parsed_keys = threshold.iter().map(|k| PkOrF::from_key(k, secp)).collect();
×
NEW
651

×
NEW
652
        let mut contribution = Satisfaction::Partial {
×
NEW
653
            n: threshold.n(),
×
NEW
654
            m: threshold.k(),
×
NEW
655
            items: vec![],
×
NEW
656
            conditions: Default::default(),
×
NEW
657
            sorted: Some(sorted),
×
NEW
658
        };
×
NEW
659
        let mut satisfaction = contribution.clone();
×
660

NEW
661
        for (index, key) in threshold.iter().enumerate() {
×
NEW
662
            if signers.find(signer_id(key, secp)).is_some() {
×
NEW
663
                contribution.add(
×
NEW
664
                    &Satisfaction::Complete {
×
NEW
665
                        condition: Default::default(),
×
NEW
666
                    },
×
NEW
667
                    index,
×
NEW
668
                )?;
×
NEW
669
            }
×
NEW
670
            if let Some(psbt) = build_sat.psbt() {
×
NEW
671
                if Ctx::find_signature(psbt, key, secp) {
×
NEW
672
                    satisfaction.add(
×
NEW
673
                        &Satisfaction::Complete {
×
NEW
674
                            condition: Default::default(),
×
NEW
675
                        },
×
NEW
676
                        index,
×
NEW
677
                    )?;
×
NEW
678
                }
×
NEW
679
            }
×
680
        }
NEW
681
        satisfaction.finalize();
×
NEW
682
        contribution.finalize();
×
NEW
683

×
NEW
684
        let mut policy: Policy = SatisfiableItem::Multisig {
×
NEW
685
            keys: parsed_keys,
×
NEW
686
            threshold: threshold.k(),
×
NEW
687
        }
×
NEW
688
        .into();
×
NEW
689
        policy.contribution = contribution;
×
NEW
690
        policy.satisfaction = satisfaction;
×
NEW
691
        Ok(Some(policy))
×
NEW
692
    }
×
693

694
    /// Return whether or not a specific path in the policy tree is required to unambiguously
695
    /// create a transaction
696
    ///
697
    /// What this means is that for some spending policies the user should select which paths in
698
    /// the tree it intends to satisfy while signing, because the transaction must be created differently based
699
    /// on that.
700
    pub fn requires_path(&self) -> bool {
2,416✔
701
        self.get_condition(&BTreeMap::new()).is_err()
2,416✔
702
    }
2,416✔
703

704
    /// Return the conditions that are set by the spending policy for a given path in the
705
    /// policy tree
706
    pub fn get_condition(
5,941✔
707
        &self,
5,941✔
708
        path: &BTreeMap<String, Vec<usize>>,
5,941✔
709
    ) -> Result<Condition, PolicyError> {
5,941✔
710
        // if items.len() == threshold, selected can be omitted and we take all of them by default
711
        let default = match &self.item {
5,941✔
712
            SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
464✔
713
                (0..*threshold).collect()
248✔
714
            }
715
            SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
21✔
716
            _ => HashSet::new(),
5,672✔
717
        };
718
        let selected: HashSet<_> = match path.get(&self.id) {
5,941✔
719
            Some(arr) => arr.iter().copied().collect(),
36✔
720
            _ => default,
5,905✔
721
        };
722

723
        match &self.item {
5,941✔
724
            SatisfiableItem::Thresh { items, threshold } => {
464✔
725
                let mapped_req = items
464✔
726
                    .iter()
464✔
727
                    .map(|i| i.get_condition(path))
1,088✔
728
                    .collect::<Vec<_>>();
464✔
729

464✔
730
                // if all the requirements are null we don't care about `selected` because there
464✔
731
                // are no requirements
464✔
732
                if mapped_req
464✔
733
                    .iter()
464✔
734
                    .all(|cond| matches!(cond, Ok(c) if c.is_null()))
1,056✔
735
                {
736
                    return Ok(Condition::default());
144✔
737
                }
320✔
738

739
                // make sure all the indexes in the `selected` list are within range
740
                for index in &selected {
840✔
741
                    if *index >= items.len() {
520✔
742
                        return Err(PolicyError::IndexOutOfRange(*index));
×
743
                    }
520✔
744
                }
745

746
                // if we have something, make sure we have enough items. note that the user can set
747
                // an empty value for this step in case of n-of-n, because `selected` is set to all
748
                // the elements above
749
                if selected.len() < *threshold {
320✔
750
                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
48✔
751
                }
272✔
752

272✔
753
                // check the selected items, see if there are conflicting requirements
272✔
754
                mapped_req
272✔
755
                    .into_iter()
272✔
756
                    .enumerate()
272✔
757
                    .filter(|(index, _)| selected.contains(index))
552✔
758
                    .try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
520✔
759
            }
760
            SatisfiableItem::Multisig { keys, threshold } => {
21✔
761
                if selected.len() < *threshold {
21✔
762
                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
1✔
763
                }
20✔
764
                if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
38✔
765
                    return Err(PolicyError::IndexOutOfRange(item));
1✔
766
                }
19✔
767

19✔
768
                Ok(Condition::default())
19✔
769
            }
770
            SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
128✔
771
                csv: None,
128✔
772
                timelock: Some(*value),
128✔
773
            }),
128✔
774
            SatisfiableItem::RelativeTimelock { value } => Ok(Condition {
120✔
775
                csv: Some((*value).into()),
120✔
776
                timelock: None,
120✔
777
            }),
120✔
778
            _ => Ok(Condition::default()),
5,208✔
779
        }
780
    }
5,941✔
781
}
782

783
impl From<SatisfiableItem> for Policy {
784
    fn from(other: SatisfiableItem) -> Self {
3,208✔
785
        Self::new(other)
3,208✔
786
    }
3,208✔
787
}
788

789
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
2,748✔
790
    // For consistency we always compute the key hash in "ecdsa" form (with the leading sign
791
    // prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
792
    // for a key, so it doesn't really matter how the identifier is computed.
793
    match key {
1,264✔
794
        DescriptorPublicKey::Single(SinglePub {
795
            key: SinglePubKey::FullKey(pk),
1,160✔
796
            ..
1,160✔
797
        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
1,160✔
798
        DescriptorPublicKey::Single(SinglePub {
799
            key: SinglePubKey::XOnly(pk),
104✔
800
            ..
104✔
801
        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
104✔
802
        DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
1,484✔
803
        DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
×
804
    }
805
}
2,748✔
806

807
fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
2,718✔
808
    key: &DescriptorPublicKey,
2,718✔
809
    signers: &SignersContainer,
2,718✔
810
    build_sat: BuildSatisfaction,
2,718✔
811
    secp: &SecpCtx,
2,718✔
812
    make_policy: M,
2,718✔
813
    find_sig: F,
2,718✔
814
) -> Policy {
2,718✔
815
    let mut policy: Policy = make_policy().into();
2,718✔
816

817
    policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
2,718✔
818
        Satisfaction::Complete {
2,565✔
819
            condition: Default::default(),
2,565✔
820
        }
2,565✔
821
    } else {
822
        Satisfaction::None
153✔
823
    };
824

825
    if let Some(psbt) = build_sat.psbt() {
2,718✔
826
        policy.satisfaction = if find_sig(psbt) {
12✔
827
            Satisfaction::Complete {
4✔
828
                condition: Default::default(),
4✔
829
            }
4✔
830
        } else {
831
            Satisfaction::None
8✔
832
        };
833
    }
2,706✔
834

835
    policy
2,718✔
836
}
2,718✔
837

838
fn generic_sig_in_psbt<
18✔
839
    // C is for "check", it's a closure we use to *check* if a psbt input contains the signature
18✔
840
    // for a specific key
18✔
841
    C: Fn(&psbt::Input, &SinglePubKey) -> bool,
18✔
842
    // E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
18✔
843
    E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
18✔
844
>(
18✔
845
    psbt: &Psbt,
18✔
846
    key: &DescriptorPublicKey,
18✔
847
    secp: &SecpCtx,
18✔
848
    check: C,
18✔
849
    extract: E,
18✔
850
) -> bool {
18✔
851
    //TODO check signature validity
18✔
852
    psbt.inputs.iter().all(|input| match key {
18✔
853
        DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
×
854
        DescriptorPublicKey::XPub(xpub) => {
18✔
855
            //TODO check actual derivation matches
18✔
856
            match extract(input, xpub.root_fingerprint(secp)) {
18✔
857
                Some(pubkey) => check(input, &pubkey),
18✔
858
                None => false,
×
859
            }
860
        }
861
        DescriptorPublicKey::MultiXPub(xpub) => {
×
862
            //TODO check actual derivation matches
×
863
            match extract(input, xpub.root_fingerprint(secp)) {
×
864
                Some(pubkey) => check(input, &pubkey),
×
865
                None => false,
×
866
            }
867
        }
868
    })
18✔
869
}
18✔
870

871
trait SigExt: ScriptContext {
872
    fn make_signature(
873
        key: &DescriptorPublicKey,
874
        signers: &SignersContainer,
875
        build_sat: BuildSatisfaction,
876
        secp: &SecpCtx,
877
    ) -> Policy;
878

879
    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
880
}
881

882
impl<T: ScriptContext + 'static> SigExt for T {
883
    fn make_signature(
2,718✔
884
        key: &DescriptorPublicKey,
2,718✔
885
        signers: &SignersContainer,
2,718✔
886
        build_sat: BuildSatisfaction,
2,718✔
887
        secp: &SecpCtx,
2,718✔
888
    ) -> Policy {
2,718✔
889
        if T::as_enum().is_taproot() {
2,718✔
890
            make_generic_signature(
481✔
891
                key,
481✔
892
                signers,
481✔
893
                build_sat,
481✔
894
                secp,
481✔
895
                || SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
481✔
896
                |psbt| Self::find_signature(psbt, key, secp),
481✔
897
            )
481✔
898
        } else {
899
            make_generic_signature(
2,237✔
900
                key,
2,237✔
901
                signers,
2,237✔
902
                build_sat,
2,237✔
903
                secp,
2,237✔
904
                || SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
2,237✔
905
                |psbt| Self::find_signature(psbt, key, secp),
2,237✔
906
            )
2,237✔
907
        }
908
    }
2,718✔
909

910
    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
18✔
911
        if T::as_enum().is_taproot() {
18✔
912
            generic_sig_in_psbt(
6✔
913
                psbt,
6✔
914
                key,
6✔
915
                secp,
6✔
916
                |input, pk| {
6✔
917
                    let pk = match pk {
6✔
918
                        SinglePubKey::XOnly(pk) => pk,
6✔
919
                        _ => return false,
×
920
                    };
921

922
                    if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
6✔
923
                        true
2✔
924
                    } else {
925
                        input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
4✔
926
                    }
927
                },
6✔
928
                |input, fing| {
6✔
929
                    input
6✔
930
                        .tap_key_origins
6✔
931
                        .iter()
6✔
932
                        .find(|(_, (_, (f, _)))| f == &fing)
8✔
933
                        .map(|(pk, _)| SinglePubKey::XOnly(*pk))
6✔
934
                },
6✔
935
            )
6✔
936
        } else {
937
            generic_sig_in_psbt(
12✔
938
                psbt,
12✔
939
                key,
12✔
940
                secp,
12✔
941
                |input, pk| match pk {
12✔
942
                    SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
12✔
943
                    _ => false,
×
944
                },
12✔
945
                |input, fing| {
12✔
946
                    input
12✔
947
                        .bip32_derivation
12✔
948
                        .iter()
12✔
949
                        .find(|(_, (f, _))| f == &fing)
18✔
950
                        .map(|(pk, _)| SinglePubKey::FullKey(PublicKey::new(*pk)))
12✔
951
                },
12✔
952
            )
12✔
953
        }
954
    }
18✔
955
}
956

957
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
958
    fn extract_policy(
1,404✔
959
        &self,
1,404✔
960
        signers: &SignersContainer,
1,404✔
961
        build_sat: BuildSatisfaction,
1,404✔
962
        secp: &SecpCtx,
1,404✔
963
    ) -> Result<Option<Policy>, Error> {
1,404✔
964
        Ok(match &self.node {
1,404✔
965
            // Leaves
966
            Terminal::True | Terminal::False => None,
×
967
            Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
379✔
968
            Terminal::PkH(pubkey_hash) => {
33✔
969
                Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
33✔
970
            }
971
            Terminal::After(value) => {
64✔
972
                let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
64✔
973
                    value: (*value).into(),
64✔
974
                }
64✔
975
                .into();
64✔
976
                policy.contribution = Satisfaction::Complete {
64✔
977
                    condition: Condition {
64✔
978
                        timelock: Some((*value).into()),
64✔
979
                        csv: None,
64✔
980
                    },
64✔
981
                };
64✔
982
                if let BuildSatisfaction::PsbtTimelocks {
983
                    current_height,
×
984
                    psbt,
×
985
                    ..
986
                } = build_sat
64✔
987
                {
988
                    let after = After::new(Some(current_height), false);
×
989
                    let after_sat =
×
990
                        Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
×
991
                    let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
×
992
                        Satisfier::<bitcoin::PublicKey>::check_after(&sat, (*value).into())
×
993
                    });
×
994
                    if after_sat && inputs_sat {
×
995
                        policy.satisfaction = policy.contribution.clone();
×
996
                    }
×
997
                }
64✔
998

999
                Some(policy)
64✔
1000
            }
1001
            Terminal::Older(value) => {
99✔
1002
                let mut policy: Policy = SatisfiableItem::RelativeTimelock {
99✔
1003
                    value: (*value).into(),
99✔
1004
                }
99✔
1005
                .into();
99✔
1006
                policy.contribution = Satisfaction::Complete {
99✔
1007
                    condition: Condition {
99✔
1008
                        timelock: None,
99✔
1009
                        csv: Some((*value).into()),
99✔
1010
                    },
99✔
1011
                };
99✔
1012
                if let BuildSatisfaction::PsbtTimelocks {
1013
                    current_height,
3✔
1014
                    input_max_height,
3✔
1015
                    psbt,
3✔
1016
                } = build_sat
99✔
1017
                {
1018
                    let older = Older::new(Some(current_height), Some(input_max_height), false);
3✔
1019
                    let older_sat =
3✔
1020
                        Satisfier::<bitcoin::PublicKey>::check_older(&older, (*value).into());
3✔
1021
                    let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
3✔
1022
                        Satisfier::<bitcoin::PublicKey>::check_older(&sat, (*value).into())
3✔
1023
                    });
3✔
1024
                    if older_sat && inputs_sat {
3✔
1025
                        policy.satisfaction = policy.contribution.clone();
2✔
1026
                    }
2✔
1027
                }
96✔
1028

1029
                Some(policy)
99✔
1030
            }
1031
            Terminal::Sha256(hash) => Some(SatisfiableItem::Sha256Preimage { hash: *hash }.into()),
×
1032
            Terminal::Hash256(hash) => {
×
1033
                Some(SatisfiableItem::Hash256Preimage { hash: *hash }.into())
×
1034
            }
1035
            Terminal::Ripemd160(hash) => {
×
1036
                Some(SatisfiableItem::Ripemd160Preimage { hash: *hash }.into())
×
1037
            }
1038
            Terminal::Hash160(hash) => {
×
1039
                Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
×
1040
            }
1041
            Terminal::Multi(threshold) => {
15✔
1042
                Policy::make_multi::<Ctx>(threshold, signers, build_sat, false, secp)?
15✔
1043
            }
NEW
1044
            Terminal::MultiA(threshold) => {
×
NEW
1045
                Policy::make_multi_a::<Ctx>(threshold, signers, build_sat, false, secp)?
×
1046
            }
1047
            // Identities
1048
            Terminal::Alt(inner)
×
1049
            | Terminal::Swap(inner)
6✔
1050
            | Terminal::Check(inner)
411✔
1051
            | Terminal::DupIf(inner)
3✔
1052
            | Terminal::Verify(inner)
163✔
1053
            | Terminal::NonZero(inner)
×
1054
            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, build_sat, secp)?,
586✔
1055
            // Complex policies
1056
            Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
160✔
1057
                a.extract_policy(signers, build_sat, secp)?,
160✔
1058
                b.extract_policy(signers, build_sat, secp)?,
160✔
1059
            )?,
×
1060
            Terminal::AndOr(x, y, z) => Policy::make_or(
1✔
1061
                Policy::make_and(
1✔
1062
                    x.extract_policy(signers, build_sat, secp)?,
1✔
1063
                    y.extract_policy(signers, build_sat, secp)?,
1✔
1064
                )?,
×
1065
                z.extract_policy(signers, build_sat, secp)?,
1✔
1066
            )?,
×
1067
            Terminal::OrB(a, b)
×
1068
            | Terminal::OrD(a, b)
48✔
1069
            | Terminal::OrC(a, b)
×
1070
            | Terminal::OrI(a, b) => Policy::make_or(
16✔
1071
                a.extract_policy(signers, build_sat, secp)?,
64✔
1072
                b.extract_policy(signers, build_sat, secp)?,
64✔
1073
            )?,
×
1074
            Terminal::Thresh(threshold) => {
3✔
1075
                let mut k = threshold.k();
3✔
1076
                let nodes = threshold.data();
3✔
1077
                let mapped: Vec<_> = nodes
3✔
1078
                    .iter()
3✔
1079
                    .map(|n| n.extract_policy(signers, build_sat, secp))
9✔
1080
                    .collect::<Result<Vec<_>, _>>()?
3✔
1081
                    .into_iter()
3✔
1082
                    .flatten()
3✔
1083
                    .collect();
3✔
1084

3✔
1085
                if mapped.len() < nodes.len() {
3✔
NEW
1086
                    k = match k.checked_sub(nodes.len() - mapped.len()) {
×
1087
                        None => return Ok(None),
×
1088
                        Some(x) => x,
×
1089
                    };
1090
                }
3✔
1091

1092
                Policy::make_thresh(mapped, k)?
3✔
1093
            }
1094

1095
            // Unsupported
1096
            Terminal::RawPkH(_) => None,
×
1097
        })
1098
    }
1,404✔
1099
}
1100

1101
fn psbt_inputs_sat(psbt: &Psbt) -> impl Iterator<Item = PsbtInputSatisfier> {
3✔
1102
    (0..psbt.inputs.len()).map(move |i| PsbtInputSatisfier::new(psbt, i))
3✔
1103
}
3✔
1104

1105
/// Options to build the satisfaction field in the policy
1106
#[derive(Debug, Clone, Copy)]
1107
pub enum BuildSatisfaction<'a> {
1108
    /// Don't generate `satisfaction` field
1109
    None,
1110
    /// Analyze the given PSBT to check for existing signatures
1111
    Psbt(&'a Psbt),
1112
    /// Like `Psbt` variant and also check for expired timelocks
1113
    PsbtTimelocks {
1114
        /// Given PSBT
1115
        psbt: &'a Psbt,
1116
        /// Current blockchain height
1117
        current_height: u32,
1118
        /// The highest confirmation height between the inputs
1119
        /// CSV should consider different inputs, but we consider the worst condition for the tx as whole
1120
        input_max_height: u32,
1121
    },
1122
}
1123
impl<'a> BuildSatisfaction<'a> {
1124
    fn psbt(&self) -> Option<&'a Psbt> {
2,748✔
1125
        match self {
2,748✔
1126
            BuildSatisfaction::None => None,
2,730✔
1127
            BuildSatisfaction::Psbt(psbt) => Some(psbt),
12✔
1128
            BuildSatisfaction::PsbtTimelocks { psbt, .. } => Some(psbt),
6✔
1129
        }
1130
    }
2,748✔
1131
}
1132

1133
impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
1134
    fn extract_policy(
2,501✔
1135
        &self,
2,501✔
1136
        signers: &SignersContainer,
2,501✔
1137
        build_sat: BuildSatisfaction,
2,501✔
1138
        secp: &SecpCtx,
2,501✔
1139
    ) -> Result<Option<Policy>, Error> {
2,501✔
1140
        fn make_sortedmulti<Ctx: ScriptContext + 'static>(
2,501✔
1141
            keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
×
1142
            signers: &SignersContainer,
×
1143
            build_sat: BuildSatisfaction,
×
1144
            secp: &SecpCtx,
×
1145
        ) -> Result<Option<Policy>, Error> {
×
NEW
1146
            let threshold = Threshold::new(keys.k(), keys.pks().to_vec())
×
NEW
1147
                .expect("valid threshold and pks collection");
×
NEW
1148
            Ok(Policy::make_multi::<Ctx>(
×
NEW
1149
                &threshold, signers, build_sat, true, secp,
×
UNCOV
1150
            )?)
×
1151
        }
2,501✔
1152

2,501✔
1153
        match self {
2,501✔
1154
            Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
24✔
1155
                pk.as_inner(),
24✔
1156
                signers,
24✔
1157
                build_sat,
24✔
1158
                secp,
24✔
1159
            ))),
24✔
1160
            Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
1,948✔
1161
                pk.as_inner(),
1,948✔
1162
                signers,
1,948✔
1163
                build_sat,
1,948✔
1164
                secp,
1,948✔
1165
            ))),
1,948✔
1166
            Descriptor::Sh(sh) => match sh.as_inner() {
51✔
1167
                ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
16✔
1168
                    pk.as_inner(),
16✔
1169
                    signers,
16✔
1170
                    build_sat,
16✔
1171
                    secp,
16✔
1172
                ))),
16✔
1173
                ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
19✔
1174
                ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
×
1175
                ShInner::Wsh(wsh) => match wsh.as_inner() {
16✔
1176
                    WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
16✔
1177
                    WshInner::SortedMulti(ref keys) => {
×
1178
                        make_sortedmulti(keys, signers, build_sat, secp)
×
1179
                    }
1180
                },
1181
            },
1182
            Descriptor::Wsh(wsh) => match wsh.as_inner() {
160✔
1183
                WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
160✔
1184
                WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
×
1185
            },
1186
            Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
×
1187
            Descriptor::Tr(tr) => {
318✔
1188
                // If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
318✔
1189
                // node with threshold = 1 and the key spend signature plus all the tree leaves
318✔
1190
                let key_spend_sig =
318✔
1191
                    miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
318✔
1192

318✔
1193
                if tr.tap_tree().is_none() {
318✔
1194
                    Ok(Some(key_spend_sig))
235✔
1195
                } else {
1196
                    let mut items = vec![key_spend_sig];
83✔
1197
                    items.append(
83✔
1198
                        &mut tr
83✔
1199
                            .iter_scripts()
83✔
1200
                            .filter_map(|(_, ms)| {
163✔
1201
                                ms.extract_policy(signers, build_sat, secp).transpose()
163✔
1202
                            })
163✔
1203
                            .collect::<Result<Vec<_>, _>>()?,
83✔
1204
                    );
1205

1206
                    Ok(Policy::make_thresh(items, 1)?)
83✔
1207
                }
1208
            }
1209
        }
1210
    }
2,501✔
1211
}
1212

1213
#[cfg(test)]
1214
mod test {
1215
    use crate::descriptor;
1216
    use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
1217

1218
    use super::*;
1219
    use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
1220
    use crate::keys::{DescriptorKey, IntoDescriptorKey};
1221
    use crate::wallet::signer::SignersContainer;
1222
    use alloc::{string::ToString, sync::Arc};
1223
    use assert_matches::assert_matches;
1224
    use bitcoin::bip32;
1225
    use bitcoin::secp256k1::Secp256k1;
1226
    use bitcoin::Network;
1227
    use core::str::FromStr;
1228

1229
    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
1230
    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
1231

1232
    const PATH: &str = "m/44'/1'/0'/0";
1233

1234
    fn setup_keys<Ctx: ScriptContext>(
23✔
1235
        tprv: &str,
23✔
1236
        path: &str,
23✔
1237
        secp: &SecpCtx,
23✔
1238
    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
23✔
1239
        let path = bip32::DerivationPath::from_str(path).unwrap();
23✔
1240
        let tprv = bip32::Xpriv::from_str(tprv).unwrap();
23✔
1241
        let tpub = bip32::Xpub::from_priv(secp, &tprv);
23✔
1242
        let fingerprint = tprv.fingerprint(secp);
23✔
1243
        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
23✔
1244
        let pubkey = (tpub, path).into_descriptor_key().unwrap();
23✔
1245

23✔
1246
        (prvkey, pubkey, fingerprint)
23✔
1247
    }
23✔
1248

1249
    // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
1250

1251
    #[test]
1252
    fn test_extract_policy_for_wpkh() {
1✔
1253
        let secp = Secp256k1::new();
1✔
1254

1✔
1255
        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1256
        let desc = descriptor!(wpkh(pubkey)).unwrap();
1✔
1257
        let (wallet_desc, keymap) = desc
1✔
1258
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1259
            .unwrap();
1✔
1260
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1261
        let policy = wallet_desc
1✔
1262
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1263
            .unwrap()
1✔
1264
            .unwrap();
1✔
1265

1266
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
1✔
1267
        assert_matches!(&policy.contribution, Satisfaction::None);
1✔
1268

1269
        let desc = descriptor!(wpkh(prvkey)).unwrap();
1✔
1270
        let (wallet_desc, keymap) = desc
1✔
1271
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1272
            .unwrap();
1✔
1273
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1274
        let policy = wallet_desc
1✔
1275
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1276
            .unwrap()
1✔
1277
            .unwrap();
1✔
1278

1279
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
1✔
1280
        assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
1✔
1281
    }
1✔
1282

1283
    // 2 pub keys descriptor, required 2 prv keys
1284
    #[test]
1285
    fn test_extract_policy_for_sh_multi_partial_0of2() {
1✔
1286
        let secp = Secp256k1::new();
1✔
1287
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1288
        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
1✔
1289
        let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap();
1✔
1290
        let (wallet_desc, keymap) = desc
1✔
1291
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1292
            .unwrap();
1✔
1293
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1294
        let policy = wallet_desc
1✔
1295
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1296
            .unwrap()
1✔
1297
            .unwrap();
1✔
1298

1299
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
1✔
1300
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
1✔
1301
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
1✔
1302
        );
1303
        // TODO should this be "Satisfaction::None" since we have no prv keys?
1304
        // TODO should items and conditions not be empty?
1305
        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
1✔
1306
            && m == &2usize
1✔
1307
            && items.is_empty()
1✔
1308
            && conditions.is_empty()
1✔
1309
        );
1✔
1310
    }
1✔
1311

1312
    // 1 prv and 1 pub key descriptor, required 2 prv keys
1313
    #[test]
1314
    fn test_extract_policy_for_sh_multi_partial_1of2() {
1✔
1315
        let secp = Secp256k1::new();
1✔
1316
        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1317
        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
1✔
1318
        let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap();
1✔
1319
        let (wallet_desc, keymap) = desc
1✔
1320
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1321
            .unwrap();
1✔
1322
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1323
        let policy = wallet_desc
1✔
1324
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1325
            .unwrap()
1✔
1326
            .unwrap();
1✔
1327
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
1✔
1328
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
1✔
1329
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
1✔
1330
        );
1331

1332
        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
1✔
1333
             && m == &2usize
1✔
1334
             && items.len() == 1
1✔
1335
             && conditions.contains_key(&0)
1✔
1336
        );
1✔
1337
    }
1✔
1338

1339
    // 1 prv and 1 pub key descriptor, required 1 prv keys
1340
    #[test]
1341
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1342
    fn test_extract_policy_for_sh_multi_complete_1of2() {
×
1343
        let secp = Secp256k1::new();
×
1344

×
1345
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1346
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1347
        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
×
1348
        let (wallet_desc, keymap) = desc
×
1349
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1350
            .unwrap();
×
1351
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1352
        let policy = wallet_desc
×
1353
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1354
            .unwrap()
×
1355
            .unwrap();
×
1356

1357
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
×
1358
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
×
1359
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
×
1360
        );
1361
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
×
1362
             && m == &1
×
1363
             && items.len() == 2
×
1364
             && conditions.contains_key(&vec![0])
×
1365
             && conditions.contains_key(&vec![1])
×
1366
        );
×
1367
    }
×
1368

1369
    // 2 prv keys descriptor, required 2 prv keys
1370
    #[test]
1371
    fn test_extract_policy_for_sh_multi_complete_2of2() {
1✔
1372
        let secp = Secp256k1::new();
1✔
1373

1✔
1374
        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1375
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
1✔
1376
        let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
1✔
1377
        let (wallet_desc, keymap) = desc
1✔
1378
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1379
            .unwrap();
1✔
1380
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1381
        let policy = wallet_desc
1✔
1382
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1383
            .unwrap()
1✔
1384
            .unwrap();
1✔
1385

1386
        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
1✔
1387
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
1✔
1388
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
1✔
1389
        );
1390

1391
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
1✔
1392
             && m == &2
1✔
1393
             && items.len() == 2
1✔
1394
             && conditions.contains_key(&vec![0,1])
1✔
1395
        );
1✔
1396
    }
1✔
1397

1398
    // test ExtractPolicy trait with extended and single keys
1399

1400
    #[test]
1401
    fn test_extract_policy_for_single_wpkh() {
1✔
1402
        let secp = Secp256k1::new();
1✔
1403

1✔
1404
        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1405
        let desc = descriptor!(wpkh(pubkey)).unwrap();
1✔
1406
        let (wallet_desc, keymap) = desc
1✔
1407
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1408
            .unwrap();
1✔
1409
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1410
        let policy = wallet_desc
1✔
1411
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1412
            .unwrap()
1✔
1413
            .unwrap();
1✔
1414

1415
        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
1✔
1416
        assert_matches!(&policy.contribution, Satisfaction::None);
1✔
1417

1418
        let desc = descriptor!(wpkh(prvkey)).unwrap();
1✔
1419
        let (wallet_desc, keymap) = desc
1✔
1420
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1421
            .unwrap();
1✔
1422
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1423
        let policy = wallet_desc
1✔
1424
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1425
            .unwrap()
1✔
1426
            .unwrap();
1✔
1427

1428
        assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
1✔
1429
        assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
1✔
1430
    }
1✔
1431

1432
    // single key, 1 prv and 1 pub key descriptor, required 1 prv keys
1433
    #[test]
1434
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1435
    fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
×
1436
        let secp = Secp256k1::new();
×
1437

×
1438
        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1439
        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1440
        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
×
1441
        let (wallet_desc, keymap) = desc
×
1442
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1443
            .unwrap();
×
1444
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1445
        let policy = wallet_desc
×
1446
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1447
            .unwrap()
×
1448
            .unwrap();
×
1449

1450
        assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
×
1451
            && keys[0] == PkOrF::Fingerprint(fingerprint0)
×
1452
            && keys[1] == PkOrF::Fingerprint(fingerprint1)
×
1453
        );
1454
        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
×
1455
             && m == 1
×
1456
             && items.len() == 2
×
1457
             && conditions.contains_key(&vec![0])
×
1458
             && conditions.contains_key(&vec![1])
×
1459
        );
×
1460
    }
×
1461

1462
    // test ExtractPolicy trait with descriptors containing timelocks in a thresh()
1463

1464
    #[test]
1465
    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
1466
    fn test_extract_policy_for_wsh_multi_timelock() {
×
1467
        let secp = Secp256k1::new();
×
1468

×
1469
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1470
        let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
×
1471
        let sequence = 50;
×
1472
        #[rustfmt::skip]
×
1473
        let desc = descriptor!(wsh(thresh(
×
1474
            2,
1475
            pk(prvkey0),
1476
            s:pk(pubkey1),
1477
            s:d:v:older(sequence)
1478
        )))
1479
        .unwrap();
×
1480

×
1481
        let (wallet_desc, keymap) = desc
×
1482
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1483
            .unwrap();
×
1484
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1485
        let policy = wallet_desc
×
1486
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1487
            .unwrap()
×
1488
            .unwrap();
×
1489

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

1492
        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
×
1493
             && m == &2
×
1494
             && items.len() == 3
×
1495
             && conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
×
1496
             && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
×
1497
             && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
×
1498
        );
×
1499
    }
×
1500

1501
    // - mixed timelocks should fail
1502

1503
    #[test]
1504
    #[ignore]
1505
    fn test_extract_policy_for_wsh_mixed_timelocks() {
×
1506
        let secp = Secp256k1::new();
×
1507
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1508
        let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
×
1509
        let locktime_blocks = 100;
×
1510
        let locktime_seconds = locktime_blocks + locktime_threshold;
×
1511
        let desc = descriptor!(sh(and_v(
×
1512
            v: pk(prvkey0),
1513
            and_v(v: after(locktime_seconds), after(locktime_blocks))
1514
        )))
1515
        .unwrap();
×
1516
        let (wallet_desc, keymap) = desc
×
1517
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1518
            .unwrap();
×
1519
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1520
        let _policy = wallet_desc
×
1521
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1522
            .unwrap()
×
1523
            .unwrap();
×
1524
        // println!("desc policy = {:?}", policy); // TODO remove
×
1525
        // TODO how should this fail with mixed timelocks?
×
1526
    }
×
1527

1528
    // - multiple timelocks of the same type should be correctly merged together
1529
    #[test]
1530
    #[ignore]
1531
    fn test_extract_policy_for_multiple_same_timelocks() {
×
1532
        let secp = Secp256k1::new();
×
1533
        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
×
1534
        let locktime_blocks0 = 100;
×
1535
        let locktime_blocks1 = 200;
×
1536
        let desc = descriptor!(sh(and_v(
×
1537
            v: pk(prvkey0),
1538
            and_v(v: after(locktime_blocks0), after(locktime_blocks1))
1539
        )))
1540
        .unwrap();
×
1541
        let (wallet_desc, keymap) = desc
×
1542
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1543
            .unwrap();
×
1544
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1545
        let _policy = wallet_desc
×
1546
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1547
            .unwrap()
×
1548
            .unwrap();
×
1549
        // println!("desc policy = {:?}", policy); // TODO remove
×
1550
        // TODO how should this merge timelocks?
×
1551
        let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
×
1552
        let locktime_seconds0 = 500000100;
×
1553
        let locktime_seconds1 = 500000200;
×
1554
        let desc = descriptor!(sh(and_v(
×
1555
            v: pk(prvkey1),
1556
            and_v(v: after(locktime_seconds0), after(locktime_seconds1))
1557
        )))
1558
        .unwrap();
×
1559
        let (wallet_desc, keymap) = desc
×
1560
            .into_wallet_descriptor(&secp, Network::Testnet)
×
1561
            .unwrap();
×
1562
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
×
1563
        let _policy = wallet_desc
×
1564
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
×
1565
            .unwrap()
×
1566
            .unwrap();
×
1567

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

×
1570
        // TODO how should this merge timelocks?
×
1571
    }
×
1572

1573
    #[test]
1574
    fn test_get_condition_multisig() {
1✔
1575
        let secp = Secp256k1::new();
1✔
1576

1✔
1577
        let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
1✔
1578
        let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
1✔
1579

1✔
1580
        let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
1✔
1581
        let (wallet_desc, keymap) = desc
1✔
1582
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1583
            .unwrap();
1✔
1584
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1585

1✔
1586
        let policy = wallet_desc
1✔
1587
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1588
            .unwrap()
1✔
1589
            .unwrap();
1✔
1590

1✔
1591
        // no args, choose the default
1✔
1592
        let no_args = policy.get_condition(&vec![].into_iter().collect());
1✔
1593
        assert_eq!(no_args, Ok(Condition::default()));
1✔
1594

1595
        // enough args
1596
        let eq_thresh =
1✔
1597
            policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
1✔
1598
        assert_eq!(eq_thresh, Ok(Condition::default()));
1✔
1599

1600
        // more args, it doesn't really change anything
1601
        let gt_thresh =
1✔
1602
            policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
1✔
1603
        assert_eq!(gt_thresh, Ok(Condition::default()));
1✔
1604

1605
        // not enough args, error
1606
        let lt_thresh =
1✔
1607
            policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
1✔
1608
        assert_eq!(
1✔
1609
            lt_thresh,
1✔
1610
            Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
1✔
1611
        );
1✔
1612

1613
        // index out of range
1614
        let out_of_range =
1✔
1615
            policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
1✔
1616
        assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
1✔
1617
    }
1✔
1618

1619
    const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
1620
    const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
1621
    const CAROL_TPRV_STR:&str = "tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt";
1622
    const ALICE_BOB_PATH: &str = "m/0'";
1623

1624
    #[test]
1625
    fn test_extract_satisfaction() {
1✔
1626
        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";
1✔
1627
        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==";
1✔
1628
        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";
1✔
1629

1✔
1630
        let secp = Secp256k1::new();
1✔
1631

1✔
1632
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1633
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1634

1✔
1635
        let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
1✔
1636

1✔
1637
        let (wallet_desc, keymap) = desc
1✔
1638
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1639
            .unwrap();
1✔
1640

1✔
1641
        let addr = wallet_desc
1✔
1642
            .at_derivation_index(0)
1✔
1643
            .unwrap()
1✔
1644
            .address(Network::Testnet)
1✔
1645
            .unwrap();
1✔
1646
        assert_eq!(
1✔
1647
            "tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
1✔
1648
            addr.to_string()
1✔
1649
        );
1✔
1650

1651
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1652

1✔
1653
        let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
1✔
1654

1✔
1655
        let policy_alice_psbt = wallet_desc
1✔
1656
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
1✔
1657
            .unwrap()
1✔
1658
            .unwrap();
1✔
1659
        //println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
1660

1661
        assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
1✔
1662
             && m == &2
1✔
1663
             && items == &vec![0]
1✔
1664
        );
1✔
1665

1✔
1666
        let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
1✔
1667
        let policy_bob_psbt = wallet_desc
1✔
1668
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
1✔
1669
            .unwrap()
1✔
1670
            .unwrap();
1✔
1671
        //println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
1672

1673
        assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
1✔
1674
             && m == &2
1✔
1675
             && items == &vec![1]
1✔
1676
        );
1✔
1677

1✔
1678
        let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
1✔
1679
        let policy_alice_bob_psbt = wallet_desc
1✔
1680
            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
1✔
1681
            .unwrap()
1✔
1682
            .unwrap();
1✔
1683
        assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
1✔
1684
             && m == &2
1✔
1685
             && items == &vec![0, 1]
1✔
1686
        );
1✔
1687
    }
1✔
1688

1689
    #[test]
1690
    fn test_extract_satisfaction_timelock() {
1✔
1691
        //const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
1✔
1692
        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED:     &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
1✔
1693
        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==";
1✔
1694

1✔
1695
        let secp = Secp256k1::new();
1✔
1696

1✔
1697
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1698
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1699

1✔
1700
        let desc =
1✔
1701
            descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
1✔
1702

1✔
1703
        let (wallet_desc, keymap) = desc
1✔
1704
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1705
            .unwrap();
1✔
1706
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1707

1✔
1708
        let addr = wallet_desc
1✔
1709
            .at_derivation_index(0)
1✔
1710
            .unwrap()
1✔
1711
            .address(Network::Testnet)
1✔
1712
            .unwrap();
1✔
1713
        assert_eq!(
1✔
1714
            "tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58",
1✔
1715
            addr.to_string()
1✔
1716
        );
1✔
1717

1718
        let psbt = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap();
1✔
1719

1✔
1720
        let build_sat = BuildSatisfaction::PsbtTimelocks {
1✔
1721
            psbt: &psbt,
1✔
1722
            current_height: 10,
1✔
1723
            input_max_height: 9,
1✔
1724
        };
1✔
1725

1✔
1726
        let policy = wallet_desc
1✔
1727
            .extract_policy(&signers_container, build_sat, &secp)
1✔
1728
            .unwrap()
1✔
1729
            .unwrap();
1✔
1730
        assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
1✔
1731
             && m == &2
1✔
1732
             && items.is_empty()
1✔
1733
        );
1✔
1734
        //println!("{}", serde_json::to_string(&policy).unwrap());
1✔
1735

1✔
1736
        let build_sat_expired = BuildSatisfaction::PsbtTimelocks {
1✔
1737
            psbt: &psbt,
1✔
1738
            current_height: 12,
1✔
1739
            input_max_height: 9,
1✔
1740
        };
1✔
1741

1✔
1742
        let policy_expired = wallet_desc
1✔
1743
            .extract_policy(&signers_container, build_sat_expired, &secp)
1✔
1744
            .unwrap()
1✔
1745
            .unwrap();
1✔
1746
        assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
1✔
1747
             && m == &2
1✔
1748
             && items == &vec![0]
1✔
1749
        );
1✔
1750
        //println!("{}", serde_json::to_string(&policy_expired).unwrap());
1✔
1751

1✔
1752
        let psbt_signed = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap();
1✔
1753

1✔
1754
        let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
1✔
1755
            psbt: &psbt_signed,
1✔
1756
            current_height: 12,
1✔
1757
            input_max_height: 9,
1✔
1758
        };
1✔
1759

1✔
1760
        let policy_expired_signed = wallet_desc
1✔
1761
            .extract_policy(&signers_container, build_sat_expired_signed, &secp)
1✔
1762
            .unwrap()
1✔
1763
            .unwrap();
1✔
1764
        assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
1✔
1765
             && m == &2
1✔
1766
             && items == &vec![0, 1]
1✔
1767
        );
1✔
1768
        //println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
1✔
1769
    }
1✔
1770

1771
    #[test]
1772
    fn test_extract_pkh() {
1✔
1773
        let secp = Secp256k1::new();
1✔
1774

1✔
1775
        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1776
        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1777
        let (prvkey_carol, _, _) = setup_keys(CAROL_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1778

1✔
1779
        let desc = descriptor!(wsh(c: andor(
1✔
1780
            pk(prvkey_alice),
1781
            pk_k(prvkey_bob),
1782
            pk_h(prvkey_carol),
1783
        )))
1784
        .unwrap();
1✔
1785

1✔
1786
        let (wallet_desc, keymap) = desc
1✔
1787
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1788
            .unwrap();
1✔
1789
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1790

1✔
1791
        let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
1✔
1792
        assert!(policy.is_ok());
1✔
1793
    }
1✔
1794

1795
    #[test]
1796
    fn test_extract_tr_key_spend() {
1✔
1797
        let secp = Secp256k1::new();
1✔
1798

1✔
1799
        let (prvkey, _, fingerprint) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1800

1✔
1801
        let desc = descriptor!(tr(prvkey)).unwrap();
1✔
1802
        let (wallet_desc, keymap) = desc
1✔
1803
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1804
            .unwrap();
1✔
1805
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1806

1✔
1807
        let policy = wallet_desc
1✔
1808
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1809
            .unwrap();
1✔
1810
        assert_eq!(
1✔
1811
            policy,
1✔
1812
            Some(Policy {
1✔
1813
                id: "48u0tz0n".to_string(),
1✔
1814
                item: SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(fingerprint)),
1✔
1815
                satisfaction: Satisfaction::None,
1✔
1816
                contribution: Satisfaction::Complete {
1✔
1817
                    condition: Condition::default()
1✔
1818
                }
1✔
1819
            })
1✔
1820
        );
1✔
1821
    }
1✔
1822

1823
    #[test]
1824
    fn test_extract_tr_script_spend() {
1✔
1825
        let secp = Secp256k1::new();
1✔
1826

1✔
1827
        let (alice_prv, _, alice_fing) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1828
        let (_, bob_pub, bob_fing) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1829

1✔
1830
        let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap();
1✔
1831
        let (wallet_desc, keymap) = desc
1✔
1832
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1833
            .unwrap();
1✔
1834
        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
1✔
1835

1✔
1836
        let policy = wallet_desc
1✔
1837
            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
1✔
1838
            .unwrap()
1✔
1839
            .unwrap();
1✔
1840

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

1✔
1844
        let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
1✔
1845
        let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
1✔
1846

1847
        let thresh_items = match policy.item {
1✔
1848
            SatisfiableItem::Thresh { items, .. } => items,
1✔
1849
            _ => unreachable!(),
×
1850
        };
1851

1852
        assert_eq!(thresh_items[0].item, bob_sig);
1✔
1853
        assert_eq!(thresh_items[1].item, alice_sig);
1✔
1854
    }
1✔
1855

1856
    #[test]
1857
    fn test_extract_tr_satisfaction_key_spend() {
1✔
1858
        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA==";
1✔
1859
        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA=";
1✔
1860

1✔
1861
        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
1✔
1862
        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
1✔
1863

1✔
1864
        let secp = Secp256k1::new();
1✔
1865

1✔
1866
        let (_, pubkey, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1867

1✔
1868
        let desc = descriptor!(tr(pubkey)).unwrap();
1✔
1869
        let (wallet_desc, _) = desc
1✔
1870
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1871
            .unwrap();
1✔
1872

1✔
1873
        let policy_unsigned = wallet_desc
1✔
1874
            .extract_policy(
1✔
1875
                &SignersContainer::default(),
1✔
1876
                BuildSatisfaction::Psbt(&unsigned_psbt),
1✔
1877
                &secp,
1✔
1878
            )
1✔
1879
            .unwrap()
1✔
1880
            .unwrap();
1✔
1881
        let policy_signed = wallet_desc
1✔
1882
            .extract_policy(
1✔
1883
                &SignersContainer::default(),
1✔
1884
                BuildSatisfaction::Psbt(&signed_psbt),
1✔
1885
                &secp,
1✔
1886
            )
1✔
1887
            .unwrap()
1✔
1888
            .unwrap();
1✔
1889

1✔
1890
        assert_eq!(policy_unsigned.satisfaction, Satisfaction::None);
1✔
1891
        assert_eq!(
1✔
1892
            policy_signed.satisfaction,
1✔
1893
            Satisfaction::Complete {
1✔
1894
                condition: Default::default()
1✔
1895
            }
1✔
1896
        );
1✔
1897
    }
1✔
1898

1899
    #[test]
1900
    fn test_extract_tr_satisfaction_script_spend() {
1✔
1901
        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
1✔
1902
        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==";
1✔
1903

1✔
1904
        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
1✔
1905
        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
1✔
1906

1✔
1907
        let secp = Secp256k1::new();
1✔
1908

1✔
1909
        let (_, alice_pub, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1910
        let (_, bob_pub, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
1✔
1911

1✔
1912
        let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap();
1✔
1913
        let (wallet_desc, _) = desc
1✔
1914
            .into_wallet_descriptor(&secp, Network::Testnet)
1✔
1915
            .unwrap();
1✔
1916

1✔
1917
        let policy_unsigned = wallet_desc
1✔
1918
            .extract_policy(
1✔
1919
                &SignersContainer::default(),
1✔
1920
                BuildSatisfaction::Psbt(&unsigned_psbt),
1✔
1921
                &secp,
1✔
1922
            )
1✔
1923
            .unwrap()
1✔
1924
            .unwrap();
1✔
1925
        let policy_signed = wallet_desc
1✔
1926
            .extract_policy(
1✔
1927
                &SignersContainer::default(),
1✔
1928
                BuildSatisfaction::Psbt(&signed_psbt),
1✔
1929
                &secp,
1✔
1930
            )
1✔
1931
            .unwrap()
1✔
1932
            .unwrap();
1✔
1933

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

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

1940
        let satisfied_items = match policy_signed.item {
1✔
1941
            SatisfiableItem::Thresh { items, .. } => items,
1✔
1942
            _ => unreachable!(),
×
1943
        };
1944

1945
        assert_eq!(
1✔
1946
            satisfied_items[0].satisfaction,
1✔
1947
            Satisfaction::Complete {
1✔
1948
                condition: Default::default()
1✔
1949
            }
1✔
1950
        );
1✔
1951
        assert_eq!(
1✔
1952
            satisfied_items[1].satisfaction,
1✔
1953
            Satisfaction::Complete {
1✔
1954
                condition: Default::default()
1✔
1955
            }
1✔
1956
        );
1✔
1957
    }
1✔
1958
}
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