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