Skip to content

Commit 758f05f

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): add backwards-compatible RecursiveTapTree
Implements a RecursiveTapTree type to maintain compatibility with previous API after the upstream rust-miniscript library removed the recursive tap tree structure in favor of a flat representation. Issue: BTC-2656 Co-authored-by: llm-git <[email protected]>
1 parent a7cbd75 commit 758f05f

File tree

4 files changed

+85
-14
lines changed

4 files changed

+85
-14
lines changed

packages/wasm-utxo/src/wasm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod ecpair;
55
mod fixed_script_wallet;
66
mod miniscript;
77
mod psbt;
8+
mod recursive_tap_tree;
89
mod replay_protection;
910
mod try_from_js_value;
1011
mod try_into_js_value;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use miniscript::descriptor::TapTree;
2+
use miniscript::{Miniscript, MiniscriptKey};
3+
4+
use crate::error::WasmUtxoError;
5+
6+
/// The recursive tap tree was removed from rust-miniscript in https://github.com/rust-bitcoin/rust-miniscript/pull/808
7+
/// Our API is somewhat dependent on it and providing backwards compatibility is easier than rewriting everything.
8+
pub enum RecursiveTapTree<Pk: MiniscriptKey> {
9+
Tree {
10+
left: Box<RecursiveTapTree<Pk>>,
11+
right: Box<RecursiveTapTree<Pk>>,
12+
},
13+
Leaf(Miniscript<Pk, miniscript::Tap>),
14+
}
15+
16+
impl<Pk: MiniscriptKey + Clone> TryFrom<&TapTree<Pk>> for RecursiveTapTree<Pk> {
17+
type Error = WasmUtxoError;
18+
19+
fn try_from(tree: &TapTree<Pk>) -> Result<Self, Self::Error> {
20+
use std::sync::Arc;
21+
22+
// Collect leaves with depths (miniscript() returns Arc<Miniscript>)
23+
let leaves: Vec<(u8, Arc<Miniscript<Pk, miniscript::Tap>>)> = tree
24+
.leaves()
25+
.map(|item| (item.depth(), item.miniscript().clone()))
26+
.collect();
27+
28+
if leaves.is_empty() {
29+
return Err(WasmUtxoError::new("Empty tap tree"));
30+
}
31+
32+
// Stack-based reconstruction: process leaves left-to-right,
33+
// combining siblings at the same depth into Tree nodes
34+
let mut stack: Vec<(u8, RecursiveTapTree<Pk>)> = Vec::new();
35+
36+
for (depth, ms) in leaves {
37+
// Clone the Miniscript from the Arc
38+
stack.push((depth, RecursiveTapTree::Leaf((*ms).clone())));
39+
40+
// Combine nodes at the same depth
41+
while stack.len() >= 2 {
42+
let len = stack.len();
43+
if stack[len - 2].0 != stack[len - 1].0 {
44+
break;
45+
}
46+
47+
let (_, right) = stack.pop().unwrap();
48+
let (d, left) = stack.pop().unwrap();
49+
50+
stack.push((
51+
d - 1,
52+
RecursiveTapTree::Tree {
53+
left: Box::new(left),
54+
right: Box::new(right),
55+
},
56+
));
57+
}
58+
}
59+
60+
if stack.len() != 1 {
61+
return Err(WasmUtxoError::new("Invalid tap tree structure"));
62+
}
63+
64+
Ok(stack.pop().unwrap().1)
65+
}
66+
}

packages/wasm-utxo/src/wasm/try_into_js_value.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,19 +243,23 @@ impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for Tr<Pk> {
243243
}
244244
}
245245

246-
impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for TapTree<Pk> {
246+
use super::recursive_tap_tree::RecursiveTapTree;
247+
248+
impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for RecursiveTapTree<Pk> {
247249
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
248-
// TapTree is now a flat representation of leaves with depths
249-
// Convert to an array of {depth, miniscript} objects
250-
let arr = Array::new();
251-
for item in self.leaves() {
252-
let leaf_obj = js_obj!(
253-
"depth" => item.depth() as u32,
254-
"miniscript" => item.miniscript()
255-
)?;
256-
arr.push(&leaf_obj);
250+
match self {
251+
RecursiveTapTree::Tree { left, right } => js_obj!("Tree" => js_arr!(left, right)),
252+
RecursiveTapTree::Leaf(ms) => ms.try_to_js_value(),
257253
}
258-
Ok(arr.into())
254+
}
255+
}
256+
257+
impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for TapTree<Pk>
258+
where
259+
Pk: Clone,
260+
{
261+
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
262+
RecursiveTapTree::try_from(self)?.try_to_js_value()
259263
}
260264
}
261265

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"descriptor": "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#wf36a0pg",
2+
"descriptor": "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)#v0p5w8jl",
33
"wasmNode": {
44
"Pkh": {
5-
"Single": "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"
5+
"Single": "[deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"
66
}
77
},
88
"ast": {
9-
"pkh": "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"
9+
"pkh": "[deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"
1010
}
1111
}

0 commit comments

Comments
 (0)