1use crate::origin_path::{OriginPath, PathComponent};
22use crate::tag::Tag;
23use crate::tree::{Body, Node};
24
25fn mk_origin(components: &[(bool, u32)]) -> OriginPath {
27 OriginPath {
28 components: components
29 .iter()
30 .map(|&(hardened, value)| PathComponent { hardened, value })
31 .collect(),
32 }
33}
34
35pub(crate) fn is_wsh_inner_multi(tag: Tag) -> bool {
39 matches!(tag, Tag::Multi | Tag::SortedMulti)
40}
41
42pub fn canonical_origin(tree: &Node) -> Option<OriginPath> {
46 match (&tree.tag, &tree.body) {
47 (Tag::Pkh, Body::KeyArg { .. }) => Some(mk_origin(&[(true, 44), (true, 0), (true, 0)])),
49 (Tag::Wpkh, Body::KeyArg { .. }) => Some(mk_origin(&[(true, 84), (true, 0), (true, 0)])),
51 (Tag::Tr, Body::Tr { tree: None, .. }) => {
53 Some(mk_origin(&[(true, 86), (true, 0), (true, 0)]))
54 }
55 (Tag::Tr, Body::Tr { tree: Some(_), .. }) => None,
57 (Tag::Wsh, Body::Children(children))
59 if children.len() == 1 && is_wsh_inner_multi(children[0].tag) =>
60 {
61 Some(mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)]))
62 }
63 (Tag::Sh, Body::Children(children)) if children.len() == 1 => {
66 let inner = &children[0];
67 if inner.tag == Tag::Wsh {
68 if let Body::Children(grand) = &inner.body {
69 if grand.len() == 1 && is_wsh_inner_multi(grand[0].tag) {
70 return Some(mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)]));
71 }
72 }
73 }
74 None
75 }
76 _ => None,
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::tree::{Body, Node};
85
86 fn pkh_at(n: u8) -> Node {
87 Node {
88 tag: Tag::Pkh,
89 body: Body::KeyArg { index: n },
90 }
91 }
92
93 fn wpkh_at(n: u8) -> Node {
94 Node {
95 tag: Tag::Wpkh,
96 body: Body::KeyArg { index: n },
97 }
98 }
99
100 fn tr_keypath(n: u8) -> Node {
101 Node {
102 tag: Tag::Tr,
103 body: Body::Tr {
104 is_nums: false,
105 key_index: n,
106 tree: None,
107 },
108 }
109 }
110
111 fn tr_with_taptree(n: u8) -> Node {
112 Node {
116 tag: Tag::Tr,
117 body: Body::Tr {
118 is_nums: false,
119 key_index: n,
120 tree: Some(Box::new(Node {
121 tag: Tag::PkK,
122 body: Body::KeyArg { index: 1 },
123 })),
124 },
125 }
126 }
127
128 fn multi_2of3() -> Node {
129 Node {
130 tag: Tag::Multi,
131 body: Body::MultiKeys {
132 k: 2,
133 indices: vec![0, 1, 2],
134 },
135 }
136 }
137
138 fn sortedmulti_2of3() -> Node {
139 Node {
140 tag: Tag::SortedMulti,
141 body: Body::MultiKeys {
142 k: 2,
143 indices: vec![0, 1, 2],
144 },
145 }
146 }
147
148 fn wsh_of(inner: Node) -> Node {
149 Node {
150 tag: Tag::Wsh,
151 body: Body::Children(vec![inner]),
152 }
153 }
154
155 fn sh_of(inner: Node) -> Node {
156 Node {
157 tag: Tag::Sh,
158 body: Body::Children(vec![inner]),
159 }
160 }
161
162 #[test]
163 fn pkh_at_n_returns_bip44_origin() {
164 let got = canonical_origin(&pkh_at(0)).unwrap();
165 assert_eq!(got, mk_origin(&[(true, 44), (true, 0), (true, 0)]));
166 }
167
168 #[test]
169 fn wpkh_at_n_returns_bip84_origin() {
170 let got = canonical_origin(&wpkh_at(0)).unwrap();
171 assert_eq!(got, mk_origin(&[(true, 84), (true, 0), (true, 0)]));
172 }
173
174 #[test]
175 fn tr_keypath_only_returns_bip86_origin() {
176 let got = canonical_origin(&tr_keypath(0)).unwrap();
177 assert_eq!(got, mk_origin(&[(true, 86), (true, 0), (true, 0)]));
178 }
179
180 #[test]
181 fn tr_with_taptree_returns_none() {
182 assert_eq!(canonical_origin(&tr_with_taptree(0)), None);
183 }
184
185 #[test]
186 fn wsh_multi_returns_bip48_type_2() {
187 let got = canonical_origin(&wsh_of(multi_2of3())).unwrap();
188 assert_eq!(
189 got,
190 mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)])
191 );
192 }
193
194 #[test]
195 fn wsh_sortedmulti_returns_bip48_type_2() {
196 let got = canonical_origin(&wsh_of(sortedmulti_2of3())).unwrap();
197 assert_eq!(
198 got,
199 mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)])
200 );
201 }
202
203 #[test]
204 fn sh_wsh_multi_returns_bip48_type_1() {
205 let got = canonical_origin(&sh_of(wsh_of(multi_2of3()))).unwrap();
206 assert_eq!(
207 got,
208 mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)])
209 );
210 }
211
212 #[test]
213 fn sh_wsh_sortedmulti_returns_bip48_type_1() {
214 let got = canonical_origin(&sh_of(wsh_of(sortedmulti_2of3()))).unwrap();
215 assert_eq!(
216 got,
217 mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)])
218 );
219 }
220
221 #[test]
222 fn sh_sortedmulti_legacy_returns_none() {
223 assert_eq!(canonical_origin(&sh_of(sortedmulti_2of3())), None);
225 }
226
227 #[test]
228 fn sh_multi_legacy_returns_none() {
229 assert_eq!(canonical_origin(&sh_of(multi_2of3())), None);
231 }
232
233 #[test]
234 fn bare_wsh_at_n_returns_none() {
235 let inner = Node {
238 tag: Tag::PkK,
239 body: Body::KeyArg { index: 0 },
240 };
241 assert_eq!(canonical_origin(&wsh_of(inner)), None);
242 }
243
244 #[test]
245 fn bare_sh_at_n_returns_none() {
246 let inner = Node {
248 tag: Tag::PkK,
249 body: Body::KeyArg { index: 0 },
250 };
251 assert_eq!(canonical_origin(&sh_of(inner)), None);
252 }
253
254 #[test]
255 fn wsh_with_miniscript_body_returns_none() {
256 let inner = Node {
259 tag: Tag::OrD,
260 body: Body::Children(vec![
261 Node {
262 tag: Tag::PkK,
263 body: Body::KeyArg { index: 0 },
264 },
265 Node {
266 tag: Tag::PkH,
267 body: Body::KeyArg { index: 1 },
268 },
269 ]),
270 };
271 assert_eq!(canonical_origin(&wsh_of(inner)), None);
272 }
273
274 #[test]
275 fn tr_shape_disambiguation_pair_returns_different_verdicts() {
276 let keypath = tr_keypath(0);
279 let with_tree = tr_with_taptree(0);
280 assert!(canonical_origin(&keypath).is_some());
281 assert_eq!(canonical_origin(&with_tree), None);
282 }
283}