@@ -3,10 +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
7- import { bip322Fixtures } from './fixtures/bip322/fixtures' ;
8- import { getUtxoCoin } from './util' ;
9-
8+ import { explainPsbtWasm } from '../../src/transaction/fixedScript' ;
109import {
1110 BIP322MessageBroadcastable ,
1211 BIP322MessageInfo ,
@@ -20,20 +19,22 @@ function createTestWalletKeys(seed: string): {
2019 xpubs : Triple < string > ;
2120 xprivs : Triple < string > ;
2221} {
23- const keys = utxolib . testutil . getKeyTriple ( seed ) ;
22+ const keys = getKeyTriple ( seed ) ;
2423 return {
2524 xpubs : keys . map ( ( k ) => k . neutered ( ) . toBase58 ( ) ) as Triple < string > ,
2625 xprivs : keys . map ( ( k ) => k . toBase58 ( ) ) as Triple < string > ,
2726 } ;
2827}
2928
3029function getDerivedPubkeys ( seed : string , chain : number , index : number ) : Triple < string > {
31- const keys = utxolib . testutil . getKeyTriple ( seed ) ;
32- 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 > ;
3334}
3435
3536function getAddress ( walletKeys : fixedScriptWallet . RootWalletKeys , chain : number , index : number ) : string {
36- return fixedScriptWallet . address ( walletKeys , chain , index , utxolib . networks . bitcoin ) ;
37+ return fixedScriptWallet . address ( walletKeys , chain , index , 'btc' ) ;
3738}
3839
3940describe ( 'BIP322' , function ( ) {
@@ -379,8 +380,8 @@ describe('BIP322', function () {
379380 scriptId : { chain, index } ,
380381 rootWalletKeys : walletKeys ,
381382 } ) ;
382- psbt . sign ( 0 , BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
383- psbt . sign ( 0 , BIP32 . fromBase58 ( xprivs [ 2 ] ) ) ;
383+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 0 ] ) ) ;
384+ psbt . sign ( BIP32 . fromBase58 ( xprivs [ 2 ] ) ) ;
384385
385386 const pubkeys = getDerivedPubkeys ( seed , chain , index ) ;
386387 const address = getAddress ( walletKeys , chain , index ) ;
@@ -407,76 +408,99 @@ describe('BIP322', function () {
407408 } ) ;
408409
409410 describe ( 'BIP322 Proof' , function ( ) {
410- const coin = getUtxoCoin ( 'btc' ) ;
411- const pubs = bip322Fixtures . valid . rootWalletKeys . triple . map ( ( b ) => b . neutered ( ) . toBase58 ( ) ) as Triple < string > ;
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 ) ;
412416
413- it ( 'should successfully run with a user nonce' , async function ( ) {
414- const psbtHex = bip322Fixtures . valid . userNonce ;
415- const result = await coin . explainTransaction ( { txHex : psbtHex , pubs } ) ;
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 {
416424 assert . strictEqual ( result . outputAmount , '0' ) ;
417425 assert . strictEqual ( result . changeAmount , '0' ) ;
418426 assert . strictEqual ( result . outputs . length , 1 ) ;
419427 assert . strictEqual ( result . outputs [ 0 ] . address , 'scriptPubKey:6a' ) ;
420428 assert . strictEqual ( result . fee , '0' ) ;
421- assert . ok ( 'signatures' in result ) ;
422- assert . strictEqual ( result . signatures , 0 ) ;
429+ for ( const input of result . inputs ) {
430+ const signerCount = Object . values ( input . signedBy ) . filter ( Boolean ) . length ;
431+ assert . strictEqual ( signerCount , expectedSignerCount ) ;
432+ }
423433 assert . ok ( result . messages ) ;
424- result . messages ?. forEach ( ( obj ) => {
434+ for ( const obj of result . messages ?? [ ] ) {
425435 assert . ok ( obj . address ) ;
426- assert . ok ( obj . message ) ;
427- assert . strictEqual ( obj . message , bip322Fixtures . valid . message ) ;
428- } ) ;
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 ) ;
429443 } ) ;
430444
431- it ( 'should successfully run with a user signature' , async function ( ) {
432- const psbtHex = bip322Fixtures . valid . userSignature ;
433- const result = await coin . explainTransaction ( { txHex : psbtHex , pubs } ) ;
434- assert . strictEqual ( result . outputAmount , '0' ) ;
435- assert . strictEqual ( result . changeAmount , '0' ) ;
436- assert . strictEqual ( result . outputs . length , 1 ) ;
437- assert . strictEqual ( result . outputs [ 0 ] . address , 'scriptPubKey:6a' ) ;
438- assert . strictEqual ( result . fee , '0' ) ;
439- assert . ok ( 'signatures' in result ) ;
440- assert . strictEqual ( result . signatures , 1 ) ;
441- assert . ok ( result . messages ) ;
442- result . messages ?. forEach ( ( obj ) => {
443- assert . ok ( obj . address ) ;
444- assert . ok ( obj . message ) ;
445- assert . strictEqual ( obj . message , bip322Fixtures . valid . message ) ;
446- } ) ;
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 ) ;
447449 } ) ;
448450
449- it ( 'should successfully run with a hsm signature' , async function ( ) {
450- const psbtHex = bip322Fixtures . valid . hsmSignature ;
451- const result = await coin . explainTransaction ( { txHex : psbtHex , pubs } ) ;
452- assert . strictEqual ( result . outputAmount , '0' ) ;
453- assert . strictEqual ( result . changeAmount , '0' ) ;
454- assert . strictEqual ( result . outputs . length , 1 ) ;
455- assert . strictEqual ( result . outputs [ 0 ] . address , 'scriptPubKey:6a' ) ;
456- assert . strictEqual ( result . fee , '0' ) ;
457- assert . ok ( 'signatures' in result ) ;
458- assert . strictEqual ( result . signatures , 2 ) ;
459- assert . ok ( result . messages ) ;
460- result . messages ?. forEach ( ( obj ) => {
461- assert . ok ( obj . address ) ;
462- assert . ok ( obj . message ) ;
463- assert . strictEqual ( obj . message , bip322Fixtures . valid . message ) ;
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 ( ) {
461+ 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' } ,
464475 } ) ;
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' ) ) ;
465491 } ) ;
466492 } ) ;
467493
468- describe ( 'utxolib verification stack - wasm-utxo respects input.sighashType' , function ( ) {
469- // This test verifies that wasm-utxo correctly respects the input.sighashType field
470- // when creating musig2 partial signatures.
471- //
472- // Previously (before fix), wasm-utxo would always create signatures with SIGHASH_DEFAULT (0)
473- // regardless of the input.sighashType field, causing validation to fail.
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
474499 //
475- // Now (after fix), wasm-utxo reads input.sighashType and creates signatures with the
476- // correct sighash type, allowing validation to succeed.
500+ // This ensures that wasm-utxo and utxolib generate compatible BIP322 proofs.
477501
478- it ( 'should validate signatures when wasm-utxo respects input.sighashType ' , function ( ) {
479- const seed = 'p2trMusig2_sighash_test ' ;
502+ it ( 'should sign utxolib-created BIP322 PSBT and validate with utxolib ' , function ( ) {
503+ const seed = 'p2trMusig2_utxolib_compat_test ' ;
480504 const { xprivs } = createTestWalletKeys ( seed ) ;
481505
482506 // Create utxolib RootWalletKeys for utxo-core PSBT construction
@@ -485,37 +509,34 @@ describe('BIP322', function () {
485509 // p2trMusig2 external chain code
486510 const chain = utxolib . bitgo . getExternalChainCode ( 'p2trMusig2' ) ;
487511 const index = 0 ;
488- const messageText = 'BIP322 sighash test' ;
512+ const messageText = 'BIP322 utxolib interop test' ;
489513
490514 // Create BIP322 PSBT using utxo-core
491515 const psbt = coreBip322 . createBaseToSignPsbt ( utxolibRootWalletKeys , utxolib . networks . bitcoin ) ;
492- coreBip322 . addBip322InputWithChainAndIndex ( psbt , messageText , utxolibRootWalletKeys , { chain, index } ) ;
493-
494- // Note: utxo-core sets sighashType: Transaction.SIGHASH_ALL (1) for BIP322 inputs
495- const SIGHASH_ALL = 1 ;
496- assert . strictEqual ( psbt . data . inputs [ 0 ] . sighashType , SIGHASH_ALL ) ;
516+ coreBip322 . addBip322InputWithChainAndIndex ( psbt , messageText , utxolibRootWalletKeys , {
517+ chain,
518+ index,
519+ } ) ;
497520
498- // Convert to wasm-utxo PSBT for cosigning
521+ // Convert to wasm-utxo PSBT for signing
499522 const wasmPsbt = fixedScriptWallet . BitGoPsbt . fromBytes ( psbt . toBuffer ( ) , 'btc' ) ;
500523
501524 // Generate musig2 nonces and sign with wasm-utxo
502- // wasm-utxo now respects input.sighashType and creates signatures with SIGHASH_ALL
503525 const userKey = BIP32 . fromBase58 ( xprivs [ 0 ] ) ;
504526 const bitgoKey = BIP32 . fromBase58 ( xprivs [ 2 ] ) ;
505527
506528 wasmPsbt . generateMusig2Nonces ( userKey ) ;
507529 wasmPsbt . generateMusig2Nonces ( bitgoKey ) ;
508- wasmPsbt . sign ( 0 , userKey ) ;
509- wasmPsbt . sign ( 0 , bitgoKey ) ;
530+ wasmPsbt . sign ( userKey ) ;
531+ wasmPsbt . sign ( bitgoKey ) ;
510532
511533 // Convert back to utxolib PSBT for validation
512534 const signedPsbt = utxolib . bitgo . createPsbtFromBuffer (
513535 Buffer . from ( wasmPsbt . serialize ( ) ) ,
514536 utxolib . networks . bitcoin
515537 ) ;
516538
517- // Validation should succeed because wasm-utxo now creates signatures
518- // with the correct sighash type (SIGHASH_ALL) matching input.sighashType
539+ // Validation should succeed - wasm-utxo signatures are compatible with utxolib
519540 const validationResult = utxolib . bitgo . getSignatureValidationArrayPsbt ( signedPsbt , utxolibRootWalletKeys ) ;
520541
521542 // Verify that both user (index 0) and bitgo (index 2) signatures are valid
0 commit comments