@@ -3,7 +3,9 @@ import assert from 'assert';
33import * as utxolib from '@bitgo/utxo-lib' ;
44import { bip322 as coreBip322 } from '@bitgo/utxo-core' ;
55import { bip322 as wasmBip322 , fixedScriptWallet , BIP32 , type Triple } from '@bitgo/wasm-utxo' ;
6+ import { getKeyTriple } from '@bitgo/wasm-utxo/testutils' ;
67
8+ import { explainPsbtWasm } from '../../src/transaction/fixedScript' ;
79import {
810 BIP322MessageBroadcastable ,
911 BIP322MessageInfo ,
@@ -17,20 +19,22 @@ function createTestWalletKeys(seed: string): {
1719 xpubs : Triple < string > ;
1820 xprivs : Triple < string > ;
1921} {
20- const keys = utxolib . testutil . getKeyTriple ( seed ) ;
22+ const keys = getKeyTriple ( seed ) ;
2123 return {
2224 xpubs : keys . map ( ( k ) => k . neutered ( ) . toBase58 ( ) ) as Triple < string > ,
2325 xprivs : keys . map ( ( k ) => k . toBase58 ( ) ) as Triple < string > ,
2426 } ;
2527}
2628
2729function getDerivedPubkeys ( seed : string , chain : number , index : number ) : Triple < string > {
28- const keys = utxolib . testutil . getKeyTriple ( seed ) ;
29- return keys . map ( ( k ) => k . derivePath ( `m/0/0/${ chain } /${ index } ` ) . publicKey . toString ( 'hex' ) ) as Triple < string > ;
30+ const keys = getKeyTriple ( seed ) ;
31+ return keys . map ( ( k ) =>
32+ Buffer . from ( k . derivePath ( `m/0/0/${ chain } /${ index } ` ) . publicKey ) . toString ( 'hex' )
33+ ) as Triple < string > ;
3034}
3135
3236function getAddress ( walletKeys : fixedScriptWallet . RootWalletKeys , chain : number , index : number ) : string {
33- return fixedScriptWallet . address ( walletKeys , chain , index , utxolib . networks . bitcoin ) ;
37+ return fixedScriptWallet . address ( walletKeys , chain , index , 'btc' ) ;
3438}
3539
3640describe ( 'BIP322' , function ( ) {
@@ -376,8 +380,8 @@ describe('BIP322', function () {
376380 scriptId : { chain, index } ,
377381 rootWalletKeys : walletKeys ,
378382 } ) ;
379- psbt . sign ( 0 , BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
380- psbt . sign ( 0 , BIP32 . fromBase58 ( xprivs [ 2 ] ) ) ;
383+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
384+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 2 ] ) ) ;
381385
382386 const pubkeys = getDerivedPubkeys ( seed , chain , index ) ;
383387 const address = getAddress ( walletKeys , chain , index ) ;
@@ -403,18 +407,100 @@ describe('BIP322', function () {
403407 } ) ;
404408 } ) ;
405409
406- describe ( 'utxolib verification stack - wasm-utxo respects input.sighashType' , function ( ) {
407- // This test verifies that wasm-utxo correctly respects the input.sighashType field
408- // when creating musig2 partial signatures.
409- //
410- // Previously (before fix), wasm-utxo would always create signatures with SIGHASH_DEFAULT (0)
411- // regardless of the input.sighashType field, causing validation to fail.
412- //
413- // Now (after fix), wasm-utxo reads input.sighashType and creates signatures with the
414- // correct sighash type, allowing validation to succeed.
410+ describe ( 'BIP322 Proof' , function ( ) {
411+ const message = 'I can believe it is not butter' ;
412+ const chain = 10 ;
413+ const index = 0 ;
414+ const { xpubs, xprivs } = createTestWalletKeys ( 'bip322-proof' ) ;
415+ const walletKeys = fixedScriptWallet . RootWalletKeys . from ( xpubs ) ;
416+
417+ function createUnsignedPsbt ( ) : fixedScriptWallet . BitGoPsbt {
418+ const psbt = fixedScriptWallet . BitGoPsbt . createEmpty ( 'btc' , walletKeys , { version : 0 } ) ;
419+ wasmBip322 . addBip322Input ( psbt , { message, scriptId : { chain, index } , rootWalletKeys : walletKeys } ) ;
420+ return psbt ;
421+ }
422+
423+ function assertCommon ( result : ReturnType < typeof explainPsbtWasm > , expectedSignerCount : number ) : void {
424+ assert . strictEqual ( result . outputAmount , '0' ) ;
425+ assert . strictEqual ( result . changeAmount , '0' ) ;
426+ assert . strictEqual ( result . outputs . length , 1 ) ;
427+ assert . strictEqual ( result . outputs [ 0 ] . address , 'scriptPubKey:6a' ) ;
428+ assert . strictEqual ( result . fee , '0' ) ;
429+ for ( const input of result . inputs ) {
430+ const signerCount = Object . values ( input . signedBy ) . filter ( Boolean ) . length ;
431+ assert . strictEqual ( signerCount , expectedSignerCount ) ;
432+ }
433+ assert . ok ( result . messages ) ;
434+ for ( const obj of result . messages ?? [ ] ) {
435+ assert . ok ( obj . address ) ;
436+ assert . strictEqual ( obj . message , message ) ;
437+ }
438+ }
439+
440+ it ( 'should successfully run with a user nonce' , function ( ) {
441+ const psbt = createUnsignedPsbt ( ) ;
442+ assertCommon ( explainPsbtWasm ( psbt , walletKeys , { replayProtection : { publicKeys : [ ] } } ) , 0 ) ;
443+ } ) ;
415444
416- it ( 'should validate signatures when wasm-utxo respects input.sighashType' , function ( ) {
445+ it ( 'should successfully run with a user signature' , function ( ) {
446+ const psbt = createUnsignedPsbt ( ) ;
447+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
448+ assertCommon ( explainPsbtWasm ( psbt , walletKeys , { replayProtection : { publicKeys : [ ] } } ) , 1 ) ;
449+ } ) ;
450+
451+ it ( 'should successfully run with a hsm signature' , function ( ) {
452+ const psbt = createUnsignedPsbt ( ) ;
453+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
454+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 2 ] ) ) ;
455+ assertCommon ( explainPsbtWasm ( psbt , walletKeys , { replayProtection : { publicKeys : [ ] } } ) , 2 ) ;
456+ } ) ;
457+ } ) ;
458+
459+ describe ( 'p2trMusig2 BIP322 signing' , function ( ) {
460+ it ( 'should produce verifiable musig2 signatures' , function ( ) {
417461 const seed = 'p2trMusig2_sighash_test' ;
462+ const { xpubs, xprivs } = createTestWalletKeys ( seed ) ;
463+ const walletKeys = fixedScriptWallet . RootWalletKeys . from ( xpubs ) ;
464+
465+ const chain = 40 ; // p2trMusig2 external
466+ const index = 0 ;
467+ const messageText = 'BIP322 sighash test' ;
468+
469+ const psbt = fixedScriptWallet . BitGoPsbt . createEmpty ( 'btc' , walletKeys , { version : 0 } ) ;
470+ wasmBip322 . addBip322Input ( psbt , {
471+ message : messageText ,
472+ scriptId : { chain, index } ,
473+ rootWalletKeys : walletKeys ,
474+ signPath : { signer : 'user' , cosigner : 'bitgo' } ,
475+ } ) ;
476+
477+ const userKey = BIP32 . fromBase58 ( xprivs [ 0 ] ) ;
478+ const bitgoKey = BIP32 . fromBase58 ( xprivs [ 2 ] ) ;
479+ psbt . generateMusig2Nonces ( userKey ) ;
480+ psbt . generateMusig2Nonces ( bitgoKey ) ;
481+ psbt . sign ( userKey ) ;
482+ psbt . sign ( bitgoKey ) ;
483+
484+ const signers = wasmBip322 . verifyBip322PsbtInput ( psbt , 0 , {
485+ message : messageText ,
486+ scriptId : { chain, index } ,
487+ rootWalletKeys : walletKeys ,
488+ } ) ;
489+ assert . ok ( signers . includes ( 'user' ) ) ;
490+ assert . ok ( signers . includes ( 'bitgo' ) ) ;
491+ } ) ;
492+ } ) ;
493+
494+ describe ( 'utxolib interoperability - wasm-utxo can verify utxolib-generated BIP322 proofs' , function ( ) {
495+ // This test verifies cross-library compatibility:
496+ // 1. utxo-core (utxolib) creates a BIP322 PSBT
497+ // 2. wasm-utxo signs it with musig2
498+ // 3. utxo-core validates the wasm-utxo signatures
499+ //
500+ // This ensures that wasm-utxo and utxolib generate compatible BIP322 proofs.
501+
502+ it ( 'should sign utxolib-created BIP322 PSBT and validate with utxolib' , function ( ) {
503+ const seed = 'p2trMusig2_utxolib_compat_test' ;
418504 const { xprivs } = createTestWalletKeys ( seed ) ;
419505
420506 // Create utxolib RootWalletKeys for utxo-core PSBT construction
@@ -423,37 +509,34 @@ describe('BIP322', function () {
423509 // p2trMusig2 external chain code
424510 const chain = utxolib . bitgo . getExternalChainCode ( 'p2trMusig2' ) ;
425511 const index = 0 ;
426- const messageText = 'BIP322 sighash test' ;
512+ const messageText = 'BIP322 utxolib interop test' ;
427513
428514 // Create BIP322 PSBT using utxo-core
429515 const psbt = coreBip322 . createBaseToSignPsbt ( utxolibRootWalletKeys , utxolib . networks . bitcoin ) ;
430- coreBip322 . addBip322InputWithChainAndIndex ( psbt , messageText , utxolibRootWalletKeys , { chain, index } ) ;
431-
432- // Note: utxo-core sets sighashType: Transaction.SIGHASH_ALL (1) for BIP322 inputs
433- const SIGHASH_ALL = 1 ;
434- assert . strictEqual ( psbt . data . inputs [ 0 ] . sighashType , SIGHASH_ALL ) ;
516+ coreBip322 . addBip322InputWithChainAndIndex ( psbt , messageText , utxolibRootWalletKeys , {
517+ chain,
518+ index,
519+ } ) ;
435520
436- // Convert to wasm-utxo PSBT for cosigning
521+ // Convert to wasm-utxo PSBT for signing
437522 const wasmPsbt = fixedScriptWallet . BitGoPsbt . fromBytes ( psbt . toBuffer ( ) , 'btc' ) ;
438523
439524 // Generate musig2 nonces and sign with wasm-utxo
440- // wasm-utxo now respects input.sighashType and creates signatures with SIGHASH_ALL
441525 const userKey = BIP32 . fromBase58 ( xprivs [ 0 ] ) ;
442526 const bitgoKey = BIP32 . fromBase58 ( xprivs [ 2 ] ) ;
443527
444528 wasmPsbt . generateMusig2Nonces ( userKey ) ;
445529 wasmPsbt . generateMusig2Nonces ( bitgoKey ) ;
446- wasmPsbt . sign ( 0 , userKey ) ;
447- wasmPsbt . sign ( 0 , bitgoKey ) ;
530+ wasmPsbt . sign ( userKey ) ;
531+ wasmPsbt . sign ( bitgoKey ) ;
448532
449533 // Convert back to utxolib PSBT for validation
450534 const signedPsbt = utxolib . bitgo . createPsbtFromBuffer (
451535 Buffer . from ( wasmPsbt . serialize ( ) ) ,
452536 utxolib . networks . bitcoin
453537 ) ;
454538
455- // Validation should succeed because wasm-utxo now creates signatures
456- // with the correct sighash type (SIGHASH_ALL) matching input.sighashType
539+ // Validation should succeed - wasm-utxo signatures are compatible with utxolib
457540 const validationResult = utxolib . bitgo . getSignatureValidationArrayPsbt ( signedPsbt , utxolibRootWalletKeys ) ;
458541
459542 // Verify that both user (index 0) and bitgo (index 2) signatures are valid
0 commit comments