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

bitcoindevkit / bdk / 9638796845

24 Jun 2024 02:52AM UTC coverage: 83.122% (+0.1%) from 83.024%
9638796845

Pull #1486

github

web-flow
Merge 95f76e0f3 into 6dab68d35
Pull Request #1486: refact(chain): Use hash of SPK at index 0 as Descriptor unique ID

7 of 9 new or added lines in 2 files covered. (77.78%)

148 existing lines in 3 files now uncovered.

11214 of 13491 relevant lines covered (83.12%)

16554.98 hits per line

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

92.31
/crates/chain/src/descriptor_ext.rs
1
use crate::{
2
    alloc::vec::Vec,
3
    miniscript::{Descriptor, DescriptorPublicKey},
4
};
5
use bitcoin::bip32;
6
use bitcoin::hashes::{hash_newtype, sha256, Hash};
7
use miniscript::descriptor::{ConversionError, DescriptorXKey, Wildcard};
8
use miniscript::{translate_hash_clone, TranslatePk, Translator};
9

10
hash_newtype! {
11
    /// Represents the ID of a descriptor, defined as the sha256 hash of
12
    /// the descriptor string, checksum excluded.
13
    ///
14
    /// This is useful for having a fixed-length unique representation of a descriptor,
15
    /// in particular, we use it to persist application state changes related to the
16
    /// descriptor without having to re-write the whole descriptor each time.
17
    ///
18
    pub struct DescriptorId(pub sha256::Hash);
19
}
20

21
/// A trait to extend the functionality of a miniscript descriptor.
22
pub trait DescriptorExt {
23
    /// Returns the minimum value (in satoshis) at which an output is broadcastable.
24
    /// Panics if the descriptor wildcard is hardened.
25
    fn dust_value(&self) -> u64;
26

27
    /// Returns the descriptor id, calculated as the sha256 of the descriptor, checksum not
28
    /// included.
29
    fn descriptor_id(&self) -> DescriptorId;
30
}
31

32
impl DescriptorExt for Descriptor<DescriptorPublicKey> {
UNCOV
33
    fn dust_value(&self) -> u64 {
×
UNCOV
34
        self.at_derivation_index(0)
×
NEW
35
            .expect("descriptor can't have hardened derivation")
×
NEW
36
            .script_pubkey()
×
UNCOV
37
            .minimal_non_dust()
×
UNCOV
38
            .to_sat()
×
UNCOV
39
    }
×
40

41
    // fn descriptor_id(&self) -> DescriptorId {
42
    //     let desc = self.to_string();
43
    //     let desc_without_checksum = desc.split('#').next().expect("Must be here");
44
    //     let descriptor_bytes = <Vec<u8>>::from(desc_without_checksum.as_bytes());
45
    //     DescriptorId(sha256::Hash::hash(&descriptor_bytes))
46
    // }
47

48
    /// Use the hash of the spk at index 0 as the descriptor's unique id.
49
    fn descriptor_id(&self) -> DescriptorId {
10,990✔
50
        let desc = self.clone();
10,990✔
51
        let translated_desc = desc.translate_pk(&mut NoWildcardFixer {}).unwrap();
10,990✔
52

10,990✔
53
        // derived the spk at index 0 for xpubs in the translated descriptor
10,990✔
54
        let spk = translated_desc
10,990✔
55
            .at_derivation_index(0)
10,990✔
56
            .unwrap()
10,990✔
57
            .script_pubkey();
10,990✔
58
        let spk_bytes = <Vec<u8>>::from(spk.as_bytes());
10,990✔
59
        DescriptorId(Hash::hash(&spk_bytes))
10,990✔
60
    }
10,990✔
61
}
62

63
/// Removes the last index of xpubs that don't end in a wildcard and adds a wildcard.
64
struct NoWildcardFixer {}
65
impl Translator<DescriptorPublicKey, DescriptorPublicKey, ConversionError>
66
    for crate::descriptor_ext::NoWildcardFixer
67
{
68
    fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<DescriptorPublicKey, ConversionError> {
11,672✔
69
        let pk = pk.clone();
11,672✔
70
        match pk {
11,672✔
71
            DescriptorPublicKey::XPub(xkey) => {
7,734✔
72
                if xkey.wildcard == Wildcard::None {
7,734✔
73
                    let new_path_len = &xkey.derivation_path.len().saturating_sub(1);
3,373✔
74
                    let new_path = xkey.derivation_path[..*new_path_len].to_vec();
3,373✔
75
                    let new_path = bip32::DerivationPath::from(new_path);
3,373✔
76
                    let new_xkey = DescriptorXKey {
3,373✔
77
                        origin: xkey.origin,
3,373✔
78
                        xkey: xkey.xkey,
3,373✔
79
                        derivation_path: new_path,
3,373✔
80
                        wildcard: Wildcard::Unhardened,
3,373✔
81
                    };
3,373✔
82
                    Ok(DescriptorPublicKey::XPub(new_xkey))
3,373✔
83
                } else {
84
                    Ok(DescriptorPublicKey::XPub(xkey))
4,361✔
85
                }
86
            }
87
            pk => Ok(pk),
3,938✔
88
        }
89
    }
11,672✔
90
    translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ConversionError);
91
}
92

93
#[cfg(test)]
94
mod test {
95
    use crate::DescriptorExt;
96
    use bitcoin::bip32::{DerivationPath, Xpriv, Xpub};
97
    use bitcoin::key::Secp256k1;
98
    use core::str::FromStr;
99
    use miniscript::{Descriptor, DescriptorPublicKey};
100

101
    #[test]
102
    fn test_descriptor_id_equivalents() {
1✔
103
        let desc_a = test_desc("44'/0'/1'", "2/3", false);
1✔
104
        let desc_b = test_desc("44'/0'/1'/2", "3", false);
1✔
105
        assert!(equal_ids(desc_a, desc_b));
1✔
106

107
        let desc_a = test_desc("44'/0'/1'", "2/3", true);
1✔
108
        let desc_b = test_desc("44'/0'/1'/2", "3", true);
1✔
109
        assert!(equal_ids(desc_a, desc_b));
1✔
110

111
        let desc_a = test_desc("44'/0'/1'/2", "3/4", false);
1✔
112
        let desc_b = test_desc("44'/0'/1'/2", "3", true);
1✔
113
        assert!(equal_ids(desc_a, desc_b));
1✔
114

115
        let desc_a = test_desc("44'/0'/1'/2", "3", false);
1✔
116
        let desc_b = test_desc("44'/0'/1'/2", "0", false);
1✔
117
        assert!(equal_ids(desc_a, desc_b));
1✔
118

119
        // found/fixed in wallet psbt test_psbt_fee_rate_with_missing_txout()
120
        let secp = Secp256k1::new();
1✔
121
        let (desc_a, _keymap) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)").unwrap();
1✔
122
        let (desc_b, _keymap) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)").unwrap();
1✔
123
        assert!(equal_ids(desc_a, desc_b));
1✔
124

125
        // this test fails, no good solution allowing two non-overlapping fixed indexes
126
        // let desc_a = test_desc("44'/0'/1'/2", "3/4", false);
127
        // let desc_b = test_desc("44'/0'/1'/2", "3/5", false);
128
        // assert!(!equal_ids(desc_a, desc_b));
129
    }
1✔
130

131
    fn test_desc(
8✔
132
        parent_path: &str,
8✔
133
        extended_path: &str,
8✔
134
        add_wildcard: bool,
8✔
135
    ) -> Descriptor<DescriptorPublicKey> {
8✔
136
        let secp = Secp256k1::new();
8✔
137

8✔
138
        // Create master extended private key
8✔
139
        let master_xprv = Xpriv::from_str("xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2").unwrap();
8✔
140

8✔
141
        let parent_path = DerivationPath::from_str(parent_path).unwrap();
8✔
142
        let extended_path = DerivationPath::from_str(extended_path).unwrap();
8✔
143

8✔
144
        let derived_xprv = master_xprv
8✔
145
            .derive_priv(&secp, &parent_path)
8✔
146
            .expect("derived xprv");
8✔
147
        let derived_xpub = Xpub::from_priv(&secp, &derived_xprv);
8✔
148
        let descriptor = if add_wildcard {
8✔
149
            format!(
3✔
150
                "wpkh([{}/{}]{}/{}/*)",
3✔
151
                &derived_xpub.parent_fingerprint, &parent_path, &derived_xpub, &extended_path
3✔
152
            )
3✔
153
        } else {
154
            format!(
5✔
155
                "wpkh([{}/{}]{}/{})",
5✔
156
                &derived_xpub.parent_fingerprint, &parent_path, &derived_xpub, &extended_path
5✔
157
            )
5✔
158
        };
159
        Descriptor::from_str(&descriptor).unwrap()
8✔
160
    }
8✔
161

162
    fn equal_ids(
5✔
163
        desc_a: Descriptor<DescriptorPublicKey>,
5✔
164
        desc_b: Descriptor<DescriptorPublicKey>,
5✔
165
    ) -> bool {
5✔
166
        let desc_a_id = desc_a.descriptor_id();
5✔
167
        let desc_b_id = desc_b.descriptor_id();
5✔
168
        desc_a_id == desc_b_id
5✔
169
    }
5✔
170
}
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