Skip to content

Commit af61422

Browse files
authored
Merge pull request #279 from BitGo/veetragjain/cshld-815-fix-wasm-utxo-to-accept-taproot-descriptors-with
fix(wasm-utxo): enable drop ExtParams in fromStringDetectType
2 parents 40dcbee + d4fb802 commit af61422

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,9 @@ impl WrapDescriptor {
252252
#[wasm_bindgen(js_name = fromStringDetectType, skip_typescript)]
253253
pub fn from_string_detect_type(descriptor: &str) -> Result<WrapDescriptor, WasmUtxoError> {
254254
let secp = Secp256k1::new();
255-
let (descriptor, _key_map) = Descriptor::parse_descriptor(&secp, descriptor)
256-
.map_err(|_| WasmUtxoError::new("Invalid descriptor"))?;
255+
let (descriptor, _key_map) =
256+
Descriptor::parse_descriptor_ext(&secp, descriptor, &ExtParams::sane().drop())
257+
.map_err(|_| WasmUtxoError::new("Invalid descriptor"))?;
257258
if descriptor.has_wildcard() {
258259
WrapDescriptor::from_string_derivable(&secp, &descriptor.to_string())
259260
} else {

packages/wasm-utxo/test/sbtc.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as assert from "assert";
22
import * as crypto from "crypto";
33
import { Descriptor } from "../js/index.js";
4-
import { getUnspendableKey } from "../js/testutils/descriptor/descriptors.js";
4+
import { getDefaultXPubs, getUnspendableKey } from "../js/testutils/descriptor/descriptors.js";
55

66
// sBTC protocol uses two taproot script leaves:
77
// 1. Deposit leaf: allows the signers to spend with a protocol payload
@@ -154,4 +154,95 @@ describe("sBTC taproot descriptor", function () {
154154
assert.strictEqual(Buffer.from(scriptPubkeyBytes).toString("hex"), SCRIPT_PUBKEY_HEX);
155155
});
156156
});
157+
158+
describe("fromStringDetectType with wildcard xpubs", function () {
159+
type GenericKey = { Single: string } | { XPub: string };
160+
type DerivableSbtcNode = {
161+
Tr: [
162+
GenericKey,
163+
{
164+
Tree: [
165+
{ Check: { AndV: [{ PayloadDrop: string }, { PkK: GenericKey }] } },
166+
{
167+
AndV: [
168+
{ Drop: { Older: { relLockTime: number } } },
169+
{ MultiA: [number, ...GenericKey[]] },
170+
];
171+
},
172+
];
173+
},
174+
];
175+
};
176+
177+
const xpubs = getDefaultXPubs();
178+
const path = "0/*";
179+
const depositLeafDerivable =
180+
"c:and_v(payload_drop(" +
181+
"0000000000013880051ad206838b7981a116c334e8cb1b950afb73eb54a5" +
182+
")," +
183+
`pk_k(${xpubs[0]}/${path})` +
184+
")";
185+
const reclaimLeafDerivable =
186+
"and_v(r:older(1),multi_a(2," +
187+
`${xpubs[0]}/${path},${xpubs[1]}/${path},${xpubs[2]}/${path}` +
188+
"))";
189+
const derivableDescriptor = Descriptor.fromStringDetectType(
190+
getSbtcDescriptor(depositLeafDerivable, reclaimLeafDerivable),
191+
);
192+
193+
it("parses as derivable when keys are xpubs with wildcards", () => {
194+
assert.ok(derivableDescriptor);
195+
assert.strictEqual(derivableDescriptor.hasWildcard(), true);
196+
});
197+
198+
it("preserves payload_drop and Drop wrapper in derivable node structure", () => {
199+
const node = derivableDescriptor.node() as DerivableSbtcNode;
200+
const depositLeaf = node.Tr[1].Tree[0];
201+
const reclaimLeaf = node.Tr[1].Tree[1];
202+
203+
assert.strictEqual(
204+
depositLeaf.Check.AndV[0].PayloadDrop,
205+
"0000000000013880051ad206838b7981a116c334e8cb1b950afb73eb54a5",
206+
);
207+
assert.strictEqual(reclaimLeaf.AndV[0].Drop.Older.relLockTime, 1);
208+
// MultiA serializes as [threshold, ...keys]
209+
assert.strictEqual(reclaimLeaf.AndV[1].MultiA[0], 2);
210+
assert.strictEqual(reclaimLeaf.AndV[1].MultiA.length, 4);
211+
});
212+
213+
it("derives at a concrete index and produces a P2TR scriptPubkey", () => {
214+
const derived = derivableDescriptor.atDerivationIndex(0);
215+
assert.strictEqual(derived.hasWildcard(), false);
216+
const scriptPubkey = derived.scriptPubkey();
217+
// P2TR: OP_1 (0x51) OP_PUSHBYTES_32 (0x20) <32-byte x-only key tweak>
218+
assert.strictEqual(scriptPubkey.length, 34);
219+
assert.strictEqual(scriptPubkey[0], 0x51);
220+
assert.strictEqual(scriptPubkey[1], 0x20);
221+
});
222+
});
223+
224+
describe("fromStringDetectType", function () {
225+
const detected = Descriptor.fromStringDetectType(getSbtcDescriptor(DEPOSIT_LEAF, RECLAIM_LEAF));
226+
227+
it("parses sBTC descriptor with payload_drop and r:older", () => {
228+
assert.ok(detected, "Descriptor should parse successfully via fromStringDetectType");
229+
assert.strictEqual(detected.hasWildcard(), false);
230+
});
231+
232+
it("produces the same script pubkey as fromString", () => {
233+
assert.strictEqual(Buffer.from(detected.scriptPubkey()).toString("hex"), SCRIPT_PUBKEY_HEX);
234+
});
235+
236+
it("preserves payload_drop and Drop wrapper in node structure", () => {
237+
const node = detected.node() as SbtcDescriptorNode;
238+
const depositLeaf = node.Tr[1].Tree[0];
239+
const reclaimLeaf = node.Tr[1].Tree[1];
240+
241+
assert.strictEqual(
242+
depositLeaf.Check.AndV[0].PayloadDrop,
243+
"0000000000013880051ad206838b7981a116c334e8cb1b950afb73eb54a5",
244+
);
245+
assert.strictEqual(reclaimLeaf.AndV[0].Drop.Older.relLockTime, 1);
246+
});
247+
});
157248
});

0 commit comments

Comments
 (0)