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