bitcoin_encrypted_backup/
descriptor.rs

1#[cfg(feature = "miniscript_12_0")]
2pub use mscript_12_0 as miniscript;
3#[cfg(feature = "miniscript_12_3_5")]
4pub use mscript_12_3_5 as miniscript;
5
6use std::collections::{BTreeSet, HashSet};
7use std::str::FromStr;
8
9use miniscript::{
10    bitcoin::{self, bip32::DerivationPath, secp256k1},
11    Descriptor, DescriptorPublicKey, ForEachKey,
12};
13
14use crate::Error;
15
16pub fn dpk_to_pk(key: &DescriptorPublicKey) -> bitcoin::secp256k1::PublicKey {
17    match key {
18        DescriptorPublicKey::Single(key) => match key.key {
19            miniscript::descriptor::SinglePubKey::FullKey(pk) => pk.inner,
20            miniscript::descriptor::SinglePubKey::XOnly(pk) => {
21                pk.public_key(bitcoin::key::Parity::Even)
22            }
23        },
24        DescriptorPublicKey::XPub(key) => key.xkey.public_key,
25        DescriptorPublicKey::MultiXPub(key) => key.xkey.public_key,
26    }
27}
28
29fn dpk_to_deriv_path(key: &DescriptorPublicKey) -> Option<DerivationPath> {
30    match key {
31        DescriptorPublicKey::Single(key) => key.origin.clone().map(|(_, p)| p),
32        DescriptorPublicKey::XPub(key) => key.origin.clone().map(|(_, p)| p),
33        DescriptorPublicKey::MultiXPub(key) => key.origin.clone().map(|(_, p)| p),
34    }
35}
36
37// See
38// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs:
39// > One example of such a point is H =
40// > lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) which is constructed
41// > by taking the hash of the standard uncompressed encoding of the secp256k1 base point G as X
42// > coordinate.
43pub fn bip341_nums() -> bitcoin::secp256k1::PublicKey {
44    bitcoin::secp256k1::PublicKey::from_str(
45        "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
46    )
47    .expect("Valid pubkey: NUMS from BIP341")
48}
49
50pub fn descr_to_dpks(
51    descriptor: &Descriptor<DescriptorPublicKey>,
52) -> Result<Vec<DescriptorPublicKey>, Error> {
53    let mut keys = BTreeSet::new();
54    descriptor.for_each_key(|k| {
55        let pk = dpk_to_pk(k);
56        if pk != bip341_nums() {
57            keys.insert(k.clone());
58        }
59        true
60    });
61    let keys: Vec<_> = keys.into_iter().collect();
62
63    if keys.is_empty() {
64        Err(Error::DescriptorHasNoKeys)
65    } else {
66        Ok(keys)
67    }
68}
69
70pub fn dpks_to_derivation_keys_paths(
71    dpks: &Vec<DescriptorPublicKey>,
72) -> (Vec<secp256k1::PublicKey>, Vec<DerivationPath>) {
73    let mut derivation_paths = HashSet::new();
74    let mut keys = HashSet::new();
75    for k in dpks {
76        keys.insert(dpk_to_pk(k));
77        if let Some(path) = dpk_to_deriv_path(k) {
78            derivation_paths.insert(path);
79        }
80    }
81    let deriv = derivation_paths.into_iter().collect();
82    let keys = keys.into_iter().collect();
83    (keys, deriv)
84}
85
86#[cfg(test)]
87pub mod tests {
88    use super::*;
89    use std::str::FromStr;
90
91    use miniscript::{
92        bitcoin::bip32::{self, ChainCode, ChildNumber, Fingerprint},
93        descriptor::{
94            self, DerivPaths, DescriptorMultiXKey, DescriptorXKey, SinglePub, SinglePubKey,
95            Wildcard,
96        },
97        Descriptor, DescriptorPublicKey, ToPublicKey,
98    };
99
100    pub fn descr_1() -> Descriptor<DescriptorPublicKey> {
101        let descr_str = "wsh(or_d(pk([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*),and_v(v:pkh([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<2;3>/*),older(52596))))#pggrcdd0";
102
103        Descriptor::<DescriptorPublicKey>::from_str(descr_str).unwrap()
104    }
105
106    pub fn dpk_1() -> DescriptorPublicKey {
107        let dpk_str = "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*";
108        DescriptorPublicKey::from_str(dpk_str).unwrap()
109    }
110
111    fn dpk_2() -> DescriptorPublicKey {
112        let dpk_str = "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<2;3>/*";
113        DescriptorPublicKey::from_str(dpk_str).unwrap()
114    }
115
116    fn dpk_3() -> DescriptorPublicKey {
117        let dpk_str = "tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<2;3>/*";
118        DescriptorPublicKey::from_str(dpk_str).unwrap()
119    }
120    pub fn pk() -> secp256k1::PublicKey {
121        let raw = [
122            3, 235, 210, 82, 202, 8, 119, 170, 224, 155, 157, 5, 130, 25, 104, 39, 117, 170, 60,
123            188, 208, 73, 193, 47, 7, 131, 47, 44, 246, 163, 181, 23, 8,
124        ];
125        secp256k1::PublicKey::from_slice(&raw).unwrap()
126    }
127
128    #[test]
129    fn test_dpk_to_pk() {
130        let expected = pk();
131        let pk = dpk_to_pk(&dpk_1());
132        assert_eq!(pk, expected);
133        let pk = dpk_to_pk(&dpk_2());
134        assert_eq!(pk, expected);
135
136        // Single
137        let single_str = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
138        let dpk = DescriptorPublicKey::from_str(single_str).unwrap();
139        let pk = dpk_to_pk(&dpk);
140        let expected = bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap();
141        assert_eq!(expected, pk);
142
143        // Single Xonly
144        let xonly = bitcoin::PublicKey::from_str(single_str)
145            .unwrap()
146            .to_x_only_pubkey();
147        let dpk = DescriptorPublicKey::Single(SinglePub {
148            origin: None,
149            key: descriptor::SinglePubKey::XOnly(xonly),
150        });
151        let pk = dpk_to_pk(&dpk);
152        assert_eq!(expected, pk);
153
154        // Xpub
155        let xpub = bip32::Xpub {
156            network: bitcoin::NetworkKind::Test,
157            depth: 1,
158            parent_fingerprint: Fingerprint::from_str("00000000").unwrap(),
159            child_number: ChildNumber::from_normal_idx(0).unwrap(),
160            public_key: bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap(),
161            chain_code: ChainCode::from(&[1u8; 32]),
162        };
163        let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
164            origin: None,
165            xkey: xpub,
166            derivation_path: DerivationPath::default(),
167            wildcard: Wildcard::None,
168        });
169        let pk = dpk_to_pk(&dpk);
170        assert_eq!(expected, pk);
171
172        // MultiXpub
173        let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
174            origin: None,
175            xkey: xpub,
176            derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
177                .unwrap(),
178            wildcard: Wildcard::None,
179        });
180        let pk = dpk_to_pk(&dpk);
181        assert_eq!(expected, pk);
182    }
183
184    #[test]
185    fn test_dpk_to_deriv() {
186        let deriv_1 = dpk_to_deriv_path(&dpk_1()).unwrap();
187        assert_eq!(deriv_1, DerivationPath::from_str("48'/1'/0'/2'").unwrap());
188        let deriv_2 = dpk_to_deriv_path(&dpk_2()).unwrap();
189        assert_eq!(deriv_2, DerivationPath::from_str("48'/1'/0'/2'").unwrap());
190        let deriv_3 = dpk_to_deriv_path(&dpk_3());
191        assert!(deriv_3.is_none());
192
193        let dp = DerivationPath::from_str("0/0").unwrap();
194        let origin = Some((Fingerprint::from_str("aabbccdd").unwrap(), dp.clone()));
195
196        // Single
197        let single_str = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
198        let dpk = DescriptorPublicKey::from_str(single_str).unwrap();
199        let none = dpk_to_deriv_path(&dpk);
200        assert!(none.is_none());
201        let single_pk = SinglePubKey::FullKey(dpk_to_pk(&dpk).into());
202        let dpk = DescriptorPublicKey::Single(SinglePub {
203            origin: origin.clone(),
204            key: single_pk,
205        });
206        let deriv = dpk_to_deriv_path(&dpk).unwrap();
207        assert_eq!(deriv, dp);
208
209        // Xpub
210        let xpub = bip32::Xpub {
211            network: bitcoin::NetworkKind::Test,
212            depth: 1,
213            parent_fingerprint: Fingerprint::from_str("00000000").unwrap(),
214            child_number: ChildNumber::from_normal_idx(0).unwrap(),
215            public_key: bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap(),
216            chain_code: ChainCode::from(&[1u8; 32]),
217        };
218        let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
219            origin: None,
220            xkey: xpub,
221            derivation_path: DerivationPath::default(),
222            wildcard: Wildcard::None,
223        });
224        let none = dpk_to_deriv_path(&dpk);
225        assert!(none.is_none());
226        let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
227            origin: origin.clone(),
228            xkey: xpub,
229            derivation_path: DerivationPath::default(),
230            wildcard: Wildcard::None,
231        });
232        let deriv = dpk_to_deriv_path(&dpk).unwrap();
233        assert_eq!(deriv, dp);
234
235        // MultiXpub
236        let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
237            origin: None,
238            xkey: xpub,
239            derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
240                .unwrap(),
241            wildcard: Wildcard::None,
242        });
243        let none = dpk_to_deriv_path(&dpk);
244        assert!(none.is_none());
245        let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
246            origin: origin.clone(),
247            xkey: xpub,
248            derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
249                .unwrap(),
250            wildcard: Wildcard::None,
251        });
252        let deriv = dpk_to_deriv_path(&dpk).unwrap();
253        assert_eq!(deriv, dp);
254    }
255
256    #[test]
257    fn test_descript_to_dpk() {
258        let dpks = descr_to_dpks(&descr_1()).unwrap();
259        let expected = vec![dpk_1(), dpk_2()];
260        assert_eq!(dpks, expected);
261    }
262
263    #[test]
264    fn test_descriptor_to_dpk_unspendable() {
265        let descr_str = "tr(tpubD6NzVbkrYhZ4XWBqjZ7DTB4eFvi8eQZ79UvNbQFsxXiaMNaBn83jpMWTXLX2Gx6JgC5n9jWvx6vnijcAUgxXmRtFd4ntasRGNsYSCvQteSr/<0;1>/*,{and_v(v:and_v(v:pk([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<2;3>/*),pk([79af2d8a/48'/1'/0'/2']tpubDEtHs6m9crfv1oeETj6EXteAtW7eoSSBVBaypEdWZt8VftbHF9R12xSZpzWGNuAofeGPL6cz48dLdCYbVioHL8ygA56yuPW76Xz5WZ3dt8o/<2;3>/*)),older(52596)),and_v(v:pk([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<0;1>/*),pk([79af2d8a/48'/1'/0'/2']tpubDEtHs6m9crfv1oeETj6EXteAtW7eoSSBVBaypEdWZt8VftbHF9R12xSZpzWGNuAofeGPL6cz48dLdCYbVioHL8ygA56yuPW76Xz5WZ3dt8o/<0;1>/*))})#vudj49fm";
266        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(descr_str).unwrap();
267        // unspendable keys must have been dropped
268        let keys = descr_to_dpks(&descriptor).unwrap();
269        for key in keys {
270            let pk = dpk_to_pk(&key);
271            assert_ne!(pk, bip341_nums());
272        }
273        // but the descriptor contains unspendable
274        let contains_unspendable = descriptor.for_any_key(|k| {
275            let pk = dpk_to_pk(k);
276            pk == bip341_nums()
277        });
278        assert!(contains_unspendable);
279    }
280
281    #[test]
282    fn test_dpks_to_deriv_paths() {
283        let dpks = vec![dpk_1(), dpk_2()];
284        let pks = vec![pk()];
285        let deriv = vec![DerivationPath::from_str("48'/1'/0'/2'").unwrap()];
286        let res = dpks_to_derivation_keys_paths(&dpks);
287        assert_eq!(res, (pks, deriv));
288    }
289}