use crate::origin_path::{OriginPath, PathComponent};
use crate::tag::Tag;
use crate::tree::{Body, Node};
fn mk_origin(components: &[(bool, u32)]) -> OriginPath {
OriginPath {
components: components
.iter()
.map(|&(hardened, value)| PathComponent { hardened, value })
.collect(),
}
}
pub(crate) fn is_wsh_inner_multi(tag: Tag) -> bool {
matches!(tag, Tag::Multi | Tag::SortedMulti)
}
pub fn canonical_origin(tree: &Node) -> Option<OriginPath> {
match (&tree.tag, &tree.body) {
(Tag::Pkh, Body::KeyArg { .. }) => Some(mk_origin(&[(true, 44), (true, 0), (true, 0)])),
(Tag::Wpkh, Body::KeyArg { .. }) => Some(mk_origin(&[(true, 84), (true, 0), (true, 0)])),
(Tag::Tr, Body::Tr { tree: None, .. }) => {
Some(mk_origin(&[(true, 86), (true, 0), (true, 0)]))
}
(Tag::Tr, Body::Tr { tree: Some(_), .. }) => None,
(Tag::Wsh, Body::Children(children))
if children.len() == 1 && is_wsh_inner_multi(children[0].tag) =>
{
Some(mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)]))
}
(Tag::Sh, Body::Children(children)) if children.len() == 1 => {
let inner = &children[0];
if inner.tag == Tag::Wsh {
if let Body::Children(grand) = &inner.body {
if grand.len() == 1 && is_wsh_inner_multi(grand[0].tag) {
return Some(mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)]));
}
}
}
None
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tree::{Body, Node};
fn pkh_at(n: u8) -> Node {
Node {
tag: Tag::Pkh,
body: Body::KeyArg { index: n },
}
}
fn wpkh_at(n: u8) -> Node {
Node {
tag: Tag::Wpkh,
body: Body::KeyArg { index: n },
}
}
fn tr_keypath(n: u8) -> Node {
Node {
tag: Tag::Tr,
body: Body::Tr {
is_nums: false,
key_index: n,
tree: None,
},
}
}
fn tr_with_taptree(n: u8) -> Node {
Node {
tag: Tag::Tr,
body: Body::Tr {
is_nums: false,
key_index: n,
tree: Some(Box::new(Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 1 },
})),
},
}
}
fn multi_2of3() -> Node {
Node {
tag: Tag::Multi,
body: Body::MultiKeys {
k: 2,
indices: vec![0, 1, 2],
},
}
}
fn sortedmulti_2of3() -> Node {
Node {
tag: Tag::SortedMulti,
body: Body::MultiKeys {
k: 2,
indices: vec![0, 1, 2],
},
}
}
fn wsh_of(inner: Node) -> Node {
Node {
tag: Tag::Wsh,
body: Body::Children(vec![inner]),
}
}
fn sh_of(inner: Node) -> Node {
Node {
tag: Tag::Sh,
body: Body::Children(vec![inner]),
}
}
#[test]
fn pkh_at_n_returns_bip44_origin() {
let got = canonical_origin(&pkh_at(0)).unwrap();
assert_eq!(got, mk_origin(&[(true, 44), (true, 0), (true, 0)]));
}
#[test]
fn wpkh_at_n_returns_bip84_origin() {
let got = canonical_origin(&wpkh_at(0)).unwrap();
assert_eq!(got, mk_origin(&[(true, 84), (true, 0), (true, 0)]));
}
#[test]
fn tr_keypath_only_returns_bip86_origin() {
let got = canonical_origin(&tr_keypath(0)).unwrap();
assert_eq!(got, mk_origin(&[(true, 86), (true, 0), (true, 0)]));
}
#[test]
fn tr_with_taptree_returns_none() {
assert_eq!(canonical_origin(&tr_with_taptree(0)), None);
}
#[test]
fn wsh_multi_returns_bip48_type_2() {
let got = canonical_origin(&wsh_of(multi_2of3())).unwrap();
assert_eq!(
got,
mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)])
);
}
#[test]
fn wsh_sortedmulti_returns_bip48_type_2() {
let got = canonical_origin(&wsh_of(sortedmulti_2of3())).unwrap();
assert_eq!(
got,
mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 2)])
);
}
#[test]
fn sh_wsh_multi_returns_bip48_type_1() {
let got = canonical_origin(&sh_of(wsh_of(multi_2of3()))).unwrap();
assert_eq!(
got,
mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)])
);
}
#[test]
fn sh_wsh_sortedmulti_returns_bip48_type_1() {
let got = canonical_origin(&sh_of(wsh_of(sortedmulti_2of3()))).unwrap();
assert_eq!(
got,
mk_origin(&[(true, 48), (true, 0), (true, 0), (true, 1)])
);
}
#[test]
fn sh_sortedmulti_legacy_returns_none() {
assert_eq!(canonical_origin(&sh_of(sortedmulti_2of3())), None);
}
#[test]
fn sh_multi_legacy_returns_none() {
assert_eq!(canonical_origin(&sh_of(multi_2of3())), None);
}
#[test]
fn bare_wsh_at_n_returns_none() {
let inner = Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 0 },
};
assert_eq!(canonical_origin(&wsh_of(inner)), None);
}
#[test]
fn bare_sh_at_n_returns_none() {
let inner = Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 0 },
};
assert_eq!(canonical_origin(&sh_of(inner)), None);
}
#[test]
fn wsh_with_miniscript_body_returns_none() {
let inner = Node {
tag: Tag::OrD,
body: Body::Children(vec![
Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 0 },
},
Node {
tag: Tag::PkH,
body: Body::KeyArg { index: 1 },
},
]),
};
assert_eq!(canonical_origin(&wsh_of(inner)), None);
}
#[test]
fn tr_shape_disambiguation_pair_returns_different_verdicts() {
let keypath = tr_keypath(0);
let with_tree = tr_with_taptree(0);
assert!(canonical_origin(&keypath).is_some());
assert_eq!(canonical_origin(&with_tree), None);
}
}