1use std::{cmp::Ordering, collections::BTreeMap, str::FromStr};
2
3use bitcoin::{
4 bip32::{ChildNumber, DerivationPath, KeySource},
5 psbt::Psbt,
6 secp256k1::PublicKey,
7};
8
9use crate::{Error, HWI};
10
11pub struct Bip32DerivationFilter<'a> {
12 psbt: &'a mut Psbt,
13 ignored_bip32_derivations: Vec<BTreeMap<PublicKey, KeySource>>,
14}
15
16impl<'a> Bip32DerivationFilter<'a> {
17 pub fn new(psbt: &'a mut Psbt) -> Self {
18 Self {
19 ignored_bip32_derivations: psbt.inputs.iter().map(|_| BTreeMap::new()).collect(),
20 psbt,
21 }
22 }
23 pub fn ignore_signed_key_derivation(mut self) -> Self {
27 for (i, input) in self.psbt.inputs.iter_mut().enumerate() {
28 for key in input.partial_sigs.keys() {
29 if let Some(derivation) = input.bip32_derivation.remove(&key.inner) {
30 self.ignored_bip32_derivations[i].insert(key.inner, derivation);
31 }
32 }
33 }
34 self
35 }
36
37 pub fn ignore_same_fg_bip32_derivations(mut self) -> Self {
42 let mut priority_order = Vec::<KeySource>::new();
43 for input in &self.psbt.inputs {
44 for source in input.bip32_derivation.values() {
45 priority_order.push(source.clone());
46 }
47 }
48 priority_order.sort_by(|(fg1, path1), (fg2, path2)| match fg1.cmp(fg2) {
49 Ordering::Less => Ordering::Less,
50 Ordering::Greater => Ordering::Greater,
51 Ordering::Equal => path2.cmp(path1),
52 });
53
54 for (i, input) in self.psbt.inputs.iter_mut().enumerate() {
55 let mut to_remove = Vec::<PublicKey>::new();
56 for (key1, source1) in &input.bip32_derivation {
57 for (key2, source2) in &input.bip32_derivation {
58 if source1.0 == source2.0 && source1.1 != source2.1 {
59 if priority_order.iter().position(|s| s == source1)
60 < priority_order.iter().position(|s| s == source2)
61 {
62 to_remove.push(*key2);
63 } else {
64 to_remove.push(*key1);
65 }
66 }
67 }
68 }
69 for key in to_remove {
70 if let Some(derivation) = input.bip32_derivation.remove(&key) {
71 self.ignored_bip32_derivations[i].insert(key, derivation);
72 }
73 }
74 }
75
76 self
77 }
78
79 pub async fn sign_psbt<T: HWI>(mut self, device: &T) -> Result<(), Error> {
81 device.sign_tx(self.psbt).await?;
82
83 for (i, input) in self.psbt.inputs.iter_mut().enumerate() {
84 input
85 .bip32_derivation
86 .append(&mut self.ignored_bip32_derivations[i]);
87 }
88
89 Ok(())
90 }
91}
92
93pub fn merge_signatures(psbt: &mut Psbt, signed_psbt: &Psbt) {
94 for i in 0..signed_psbt.inputs.len() {
95 let psbtin = match psbt.inputs.get_mut(i) {
96 Some(psbtin) => psbtin,
97 None => continue,
98 };
99 let signed_psbtin = match signed_psbt.inputs.get(i) {
100 Some(signed_psbtin) => signed_psbtin,
101 None => continue,
102 };
103 psbtin
104 .partial_sigs
105 .extend(&mut signed_psbtin.partial_sigs.iter());
106 psbtin
107 .tap_script_sigs
108 .extend(&mut signed_psbtin.tap_script_sigs.iter());
109 if let Some(sig) = signed_psbtin.tap_key_sig {
110 psbtin.tap_key_sig = Some(sig);
111 }
112 }
113}
114
115pub fn bip86_path_child_numbers(path: DerivationPath) -> Result<Vec<ChildNumber>, Error> {
116 let children: Vec<ChildNumber> = path.into();
117 if children.len() != 5
118 || children[0] != ChildNumber::from_hardened_idx(86).unwrap()
119 || children[1].is_normal()
120 || children[2].is_normal()
121 || children[3].is_hardened()
122 || children[4].is_hardened()
123 {
124 Err(Error::InvalidParameter(
125 "derivation_path",
126 "path is not bip86 compatible".to_string(),
127 ))
128 } else {
129 Ok(children)
130 }
131}
132
133#[cfg(feature = "regex")]
134pub fn extract_keys_and_template<T: FromStr>(policy: &str) -> Result<(String, Vec<T>), Error> {
135 let re = regex::Regex::new(r"((\[.+?\])?[xyYzZtuUvV]pub[1-9A-HJ-NP-Za-km-z]{79,108})").unwrap();
136 let mut descriptor_template = policy.to_string();
137 let mut pubkeys_str: Vec<&str> = Vec::new();
138 for capture in re.find_iter(policy) {
139 if !pubkeys_str.contains(&capture.as_str()) {
140 pubkeys_str.push(capture.as_str());
141 }
142 }
143
144 let mut pubkeys: Vec<T> = Vec::new();
145 for (i, key_str) in pubkeys_str.iter().enumerate() {
146 descriptor_template = descriptor_template.replace(key_str, &format!("@{}", i));
147 let pubkey = T::from_str(key_str).map_err(|_| Error::UnsupportedInput)?;
148 pubkeys.push(pubkey);
149 }
150
151 if let Some((descriptor_template, _hash)) = descriptor_template.rsplit_once('#') {
153 Ok((descriptor_template.to_string(), pubkeys))
154 } else {
155 Ok((descriptor_template, pubkeys))
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use std::str::FromStr;
162
163 use super::*;
164
165 #[test]
166 fn test_sort_bip32_derivations() {
167 let mut psbt = Psbt::from_str("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").unwrap();
168 assert_eq!(psbt.inputs[0].bip32_derivation.len(), 6);
169 assert_eq!(psbt.inputs[1].bip32_derivation.len(), 6);
170 let filter = Bip32DerivationFilter::new(&mut psbt).ignore_same_fg_bip32_derivations();
171 assert_eq!(filter.ignored_bip32_derivations[0].len(), 4);
172 assert_eq!(filter.ignored_bip32_derivations[1].len(), 4);
173 assert_eq!(psbt.inputs[0].bip32_derivation.len(), 2);
174 assert_eq!(psbt.inputs[1].bip32_derivation.len(), 2);
175 }
176
177 #[test]
178 fn test_extract_keys_and_template() {
179 let res = extract_keys_and_template::<String>("wsh(or_d(pk([f5acc2fd/49'/1'/0']tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP/**),and_v(v:pkh(tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S/**),older(100))))").unwrap();
180 assert_eq!(res.0, "wsh(or_d(pk(@0/**),and_v(v:pkh(@1/**),older(100))))");
181 assert_eq!(res.1.len(), 2);
182 assert_eq!(res.1[0], "[f5acc2fd/49'/1'/0']tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP".to_string());
183 assert_eq!(res.1[1], "tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S".to_string());
184
185 let res = extract_keys_and_template::<String>("wsh(or_d(multi(2,[b0822927/48'/1'/0'/2']tpubDEvZxV86Br8Knbm9tWcr5Hvmg5cYTYsg92vinqH6Bie6U8ix8CsoN9W11NQygdqVwmHUJpsHXxNsi5gXn36g4xNfLWkMqPuFhRZAmMQ7jjQ/<0;1>/*,[7fc39c07/48'/1'/0'/2']tpubDEvjgXtrUuH3Qtkapny9aE8gN847xiXsf9MDM5XueGf9nrvStqAuBSva3ajGyTvtp8Ti55FvVXsgYSXuS1tQkBeopFuodx2hRUDmQbvKxbZ/<0;1>/*),and_v(v:thresh(2,pkh([b0822927/48'/1'/0'/2']tpubDEvZxV86Br8Knbm9tWcr5Hvmg5cYTYsg92vinqH6Bie6U8ix8CsoN9W11NQygdqVwmHUJpsHXxNsi5gXn36g4xNfLWkMqPuFhRZAmMQ7jjQ/<2;3>/*),a:pkh([7fc39c07/48'/1'/0'/2']tpubDEvjgXtrUuH3Qtkapny9aE8gN847xiXsf9MDM5XueGf9nrvStqAuBSva3ajGyTvtp8Ti55FvVXsgYSXuS1tQkBeopFuodx2hRUDmQbvKxbZ/<2;3>/*),a:pkh([1a1ffd98/48'/1'/0'/2']tpubDFZqzTvGijYb13BC73CkS1er8DrP5YdzMhziN3kWCKUFaW51Yj6ggvf99YpdrkTJy4RT85mxQMHXDiFAKRxzf6BykQgT4pRRBNPshSJJcKo/<0;1>/*)),older(300))))#wp0w3hlw").unwrap();
186 assert_eq!(res.0, "wsh(or_d(multi(2,@0/<0;1>/*,@1/<0;1>/*),and_v(v:thresh(2,pkh(@0/<2;3>/*),a:pkh(@1/<2;3>/*),a:pkh(@2/<0;1>/*)),older(300))))");
187 assert_eq!(res.1.len(), 3);
188 assert_eq!(res.1[0], "[b0822927/48'/1'/0'/2']tpubDEvZxV86Br8Knbm9tWcr5Hvmg5cYTYsg92vinqH6Bie6U8ix8CsoN9W11NQygdqVwmHUJpsHXxNsi5gXn36g4xNfLWkMqPuFhRZAmMQ7jjQ".to_string());
189 assert_eq!(res.1[1], "[7fc39c07/48'/1'/0'/2']tpubDEvjgXtrUuH3Qtkapny9aE8gN847xiXsf9MDM5XueGf9nrvStqAuBSva3ajGyTvtp8Ti55FvVXsgYSXuS1tQkBeopFuodx2hRUDmQbvKxbZ".to_string());
190 assert_eq!(res.1[2], "[1a1ffd98/48'/1'/0'/2']tpubDFZqzTvGijYb13BC73CkS1er8DrP5YdzMhziN3kWCKUFaW51Yj6ggvf99YpdrkTJy4RT85mxQMHXDiFAKRxzf6BykQgT4pRRBNPshSJJcKo".to_string());
191 }
192}