|
1 | 1 | import * as assert from "assert"; |
2 | 2 | import * as crypto from "crypto"; |
3 | 3 | 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"; |
5 | 5 |
|
6 | 6 | // sBTC protocol uses two taproot script leaves: |
7 | 7 | // 1. Deposit leaf: allows the signers to spend with a protocol payload |
@@ -154,4 +154,95 @@ describe("sBTC taproot descriptor", function () { |
154 | 154 | assert.strictEqual(Buffer.from(scriptPubkeyBytes).toString("hex"), SCRIPT_PUBKEY_HEX); |
155 | 155 | }); |
156 | 156 | }); |
| 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 | + }); |
157 | 248 | }); |
0 commit comments