@@ -3,7 +3,7 @@ import { BIP32Interface, ECPair, ECPairInterface } from "@bitgo/utxo-lib";
33import { getKey } from "@bitgo/utxo-lib/dist/src/testutil" ;
44
55import { DescriptorNode , formatNode } from "../js/ast/index.js" ;
6- import { mockPsbtDefault } from "./psbtFromDescriptor.util.js" ;
6+ import { mockPsbtDefault , PsbtParams } from "./psbtFromDescriptor.util.js" ;
77import { Descriptor , Transaction } from "../js/index.js" ;
88import { toWrappedPsbt } from "./psbt.util.js" ;
99
@@ -38,9 +38,11 @@ function describeSignDescriptor(
3838 {
3939 signBip32 = [ ] ,
4040 signECPair = [ ] ,
41+ psbtParams,
4142 } : {
4243 signBip32 ?: BIP32Interface [ ] [ ] ;
4344 signECPair ?: ECPairInterface [ ] [ ] ;
45+ psbtParams ?: Partial < PsbtParams > ;
4446 } ,
4547) {
4648 describe ( `psbt with descriptor ${ name } ` , function ( ) {
@@ -51,6 +53,7 @@ function describeSignDescriptor(
5153 formatNode ( { wpkh : toKeyWithPath ( external ) } ) ,
5254 "derivable" ,
5355 ) ,
56+ params : psbtParams ,
5457 } ) ;
5558
5659 function getSigResult ( keys : ( BIP32Interface | ECPairInterface ) [ ] ) {
@@ -155,14 +158,48 @@ describeSignDescriptor(
155158 { signECPair : [ [ toECPair ( a ) ] ] } ,
156159) ;
157160
161+ // sBTC taproot: NUMS internal key + deposit leaf (payload_drop + protocol signers key)
162+ // and reclaim leaf (2-of-3 multi_a behind r:older(1)). Reclaim path is the one signable
163+ // by user keys; sequence=1 satisfies older(1).
164+ const SBTC_UNSPENDABLE = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" ;
165+ const SBTC_SIGNERS_KEY = "c9c2312ca406dcb8eed50b829b5292f5fb3e846db0a556af61cc53834ce75421" ;
166+ const SBTC_PAYLOAD_HEX = "0000000000013880051ad206838b7981a116c334e8cb1b950afb73eb54a5" ;
167+ const sbtcDescriptorNode : DescriptorNode = {
168+ tr : [
169+ SBTC_UNSPENDABLE ,
170+ [
171+ { and_v : [ { payload_drop : SBTC_PAYLOAD_HEX } , { pk : SBTC_SIGNERS_KEY } ] } ,
172+ {
173+ and_v : [
174+ { "r:older" : 1 } ,
175+ { multi_a : [ 2 , toKeyWithPath ( a ) , toKeyWithPath ( b ) , toKeyWithPath ( c ) ] } ,
176+ ] ,
177+ } ,
178+ ] ,
179+ ] ,
180+ } ;
181+
182+ describeSignDescriptor ( "P2trSBTC" , fromNodes ( sbtcDescriptorNode , "derivable" ) , {
183+ signBip32 : [
184+ [ a , b ] ,
185+ [ b , a ] ,
186+ ] ,
187+ psbtParams : { sequence : 1 } ,
188+ } ) ;
189+
158190describe ( "WrapPsbt extractTransaction" , function ( ) {
159- function signFinalizeExtract ( descriptor : Descriptor , signKeys : BIP32Interface [ ] ) {
191+ function signFinalizeExtract (
192+ descriptor : Descriptor ,
193+ signKeys : BIP32Interface [ ] ,
194+ psbtParams ?: Partial < PsbtParams > ,
195+ ) {
160196 const psbt = mockPsbtDefault ( {
161197 descriptorSelf : descriptor ,
162198 descriptorOther : Descriptor . fromString (
163199 formatNode ( { wpkh : toKeyWithPath ( external ) } ) ,
164200 "derivable" ,
165201 ) ,
202+ params : psbtParams ,
166203 } ) ;
167204 const wrappedPsbt = toWrappedPsbt ( psbt ) ;
168205 for ( const key of signKeys ) {
@@ -202,6 +239,17 @@ describe("WrapPsbt extractTransaction", function () {
202239 assert . ok ( tx . toBytes ( ) . length > 0 ) ;
203240 } ) ;
204241
242+ it ( "should extract transaction from finalized P2trSBTC PSBT" , function ( ) {
243+ const tx = signFinalizeExtract ( fromNodes ( sbtcDescriptorNode , "derivable" ) , [ a , b ] , {
244+ sequence : 1 ,
245+ } ) ;
246+
247+ assert . strictEqual ( typeof tx . getId ( ) , "string" ) ;
248+ assert . strictEqual ( tx . getId ( ) . length , 64 ) ;
249+ assert . ok ( tx . getVSize ( ) > 0 ) ;
250+ assert . ok ( tx . toBytes ( ) . length > 0 ) ;
251+ } ) ;
252+
205253 it ( "should produce consistent txid across repeated calls" , function ( ) {
206254 const tx = signFinalizeExtract (
207255 fromNodes (
0 commit comments