Skip to content

Commit b949138

Browse files
Merge pull request #74 from BitGo/BTC-2656.bump-rust-miniscript-branch
feat(wasm-utxo): update rust-miniscript to v13.0.0-opdrop with API compatibility
2 parents 8f4c723 + 2ffc911 commit b949138

File tree

10 files changed

+238
-197
lines changed

10 files changed

+238
-197
lines changed

packages/wasm-utxo/Cargo.lock

Lines changed: 139 additions & 182 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/wasm-utxo/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ all = "warn"
1616
[dependencies]
1717
wasm-bindgen = "0.2"
1818
js-sys = "0.3"
19-
miniscript = { git = "https://github.com/BitGo/rust-miniscript", tag = "miniscript-12.3.4-opdrop" }
19+
miniscript = { git = "https://github.com/BitGo/rust-miniscript", tag = "miniscript-13.0.0-opdrop" }
2020
bech32 = "0.11"
2121
musig2 = { version = "0.3.1", default-features = false, features = ["k256"] }
2222
getrandom = { version = "0.2", features = ["js"] }

packages/wasm-utxo/deny.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
multiple-versions = "deny"
44
# Highlight bech32 specifically (ensures it's caught if duplicated)
55
highlight = "all"
6+
# Skip hex-conservative duplicate (miniscript uses 1.0.1 directly, bitcoin uses 0.2.2 via bitcoin_hashes)
7+
skip = [{ crate = "[email protected]" }]
68

79
# Allow git sources (needed for miniscript)
810
[sources]
@@ -13,9 +15,9 @@ allow-git = ["https://github.com/BitGo/rust-miniscript"]
1315
allow = [
1416
"MIT",
1517
"Apache-2.0",
18+
"Apache-2.0 WITH LLVM-exception",
1619
"CC0-1.0",
1720
"MITNFA",
18-
"Unicode-DFS-2016",
1921
"Unicode-3.0",
2022
"BSD-3-Clause",
2123
"Unlicense",

packages/wasm-utxo/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ impl From<miniscript::Error> for WasmUtxoError {
3232
}
3333
}
3434

35-
impl From<miniscript::descriptor::ConversionError> for WasmUtxoError {
36-
fn from(err: miniscript::descriptor::ConversionError) -> Self {
35+
impl From<miniscript::descriptor::NonDefiniteKeyError> for WasmUtxoError {
36+
fn from(err: miniscript::descriptor::NonDefiniteKeyError) -> Self {
3737
WasmUtxoError::StringError(err.to_string())
3838
}
3939
}

packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/checksigverify.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl ScriptP2tr {
8888
}
8989

9090
pub fn output_script(&self) -> ScriptBuf {
91-
let output_key = self.spend_info.output_key().to_inner();
91+
let output_key = self.spend_info.output_key().to_x_only_public_key();
9292

9393
Builder::new()
9494
.push_int(1)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ impl WrapMiniscript {
7575
let script = bitcoin::Script::from_bytes(script);
7676
match context_type {
7777
"tap" => Ok(WrapMiniscript::from(
78-
Miniscript::<XOnlyPublicKey, Tap>::parse(script).map_err(WasmUtxoError::from)?,
78+
Miniscript::<XOnlyPublicKey, Tap>::decode(script).map_err(WasmUtxoError::from)?,
7979
)),
8080
"segwitv0" => Ok(WrapMiniscript::from(
81-
Miniscript::<PublicKey, Segwitv0>::parse(script).map_err(WasmUtxoError::from)?,
81+
Miniscript::<PublicKey, Segwitv0>::decode(script).map_err(WasmUtxoError::from)?,
8282
)),
8383
"legacy" => Ok(WrapMiniscript::from(
84-
Miniscript::<PublicKey, Legacy>::parse(script).map_err(WasmUtxoError::from)?,
84+
Miniscript::<PublicKey, Legacy>::decode(script).map_err(WasmUtxoError::from)?,
8585
)),
8686
_ => Err(WasmUtxoError::new("Invalid context type")),
8787
}

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: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,19 +235,34 @@ impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for WshInner<Pk> {
235235

236236
impl<Pk: MiniscriptKey + TryIntoJsValue> TryIntoJsValue for Tr<Pk> {
237237
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
238-
Ok(js_arr!(self.internal_key(), self.tap_tree()))
238+
let tap_tree_js: JsValue = match self.tap_tree() {
239+
Some(tree) => tree.try_to_js_value()?,
240+
None => JsValue::NULL,
241+
};
242+
Ok(js_arr!(self.internal_key(), tap_tree_js))
239243
}
240244
}
241245

242-
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> {
243249
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
244250
match self {
245-
TapTree::Tree { left, right, .. } => js_obj!("Tree" => js_arr!(left, right)),
246-
TapTree::Leaf(ms) => ms.try_to_js_value(),
251+
RecursiveTapTree::Tree { left, right } => js_obj!("Tree" => js_arr!(left, right)),
252+
RecursiveTapTree::Leaf(ms) => ms.try_to_js_value(),
247253
}
248254
}
249255
}
250256

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()
263+
}
264+
}
265+
251266
impl TryIntoJsValue for DescriptorPublicKey {
252267
fn try_to_js_value(&self) -> Result<JsValue, WasmUtxoError> {
253268
match self {
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)