diff --git a/modules/abstract-utxo/package.json b/modules/abstract-utxo/package.json index b4265f03af..18d6bd3826 100644 --- a/modules/abstract-utxo/package.json +++ b/modules/abstract-utxo/package.json @@ -67,7 +67,7 @@ "@bitgo/utxo-core": "^1.32.0", "@bitgo/utxo-lib": "^11.20.0", "@bitgo/utxo-ord": "^1.25.0", - "@bitgo/wasm-utxo": "^1.32.0", + "@bitgo/wasm-utxo": "^1.34.0", "@types/lodash": "^4.14.121", "@types/superagent": "4.1.15", "bignumber.js": "^9.0.2", diff --git a/modules/abstract-utxo/test/unit/bip322.ts b/modules/abstract-utxo/test/unit/bip322.ts index e5b0766512..819ac02877 100644 --- a/modules/abstract-utxo/test/unit/bip322.ts +++ b/modules/abstract-utxo/test/unit/bip322.ts @@ -1,6 +1,7 @@ import assert from 'assert'; import * as utxolib from '@bitgo/utxo-lib'; +import { bip322 as coreBip322 } from '@bitgo/utxo-core'; import { bip322 as wasmBip322, fixedScriptWallet, BIP32, type Triple } from '@bitgo/wasm-utxo'; import { @@ -401,4 +402,67 @@ describe('BIP322', function () { ); }); }); + + describe('utxolib verification stack - wasm-utxo respects input.sighashType', function () { + // This test verifies that wasm-utxo correctly respects the input.sighashType field + // when creating musig2 partial signatures. + // + // Previously (before fix), wasm-utxo would always create signatures with SIGHASH_DEFAULT (0) + // regardless of the input.sighashType field, causing validation to fail. + // + // Now (after fix), wasm-utxo reads input.sighashType and creates signatures with the + // correct sighash type, allowing validation to succeed. + + it('should validate signatures when wasm-utxo respects input.sighashType', function () { + const seed = 'p2trMusig2_sighash_test'; + const { xprivs } = createTestWalletKeys(seed); + + // Create utxolib RootWalletKeys for utxo-core PSBT construction + const utxolibRootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple(seed)); + + // p2trMusig2 external chain code + const chain = utxolib.bitgo.getExternalChainCode('p2trMusig2'); + const index = 0; + const messageText = 'BIP322 sighash test'; + + // Create BIP322 PSBT using utxo-core + const psbt = coreBip322.createBaseToSignPsbt(utxolibRootWalletKeys, utxolib.networks.bitcoin); + coreBip322.addBip322InputWithChainAndIndex(psbt, messageText, utxolibRootWalletKeys, { chain, index }); + + // Note: utxo-core sets sighashType: Transaction.SIGHASH_ALL (1) for BIP322 inputs + const SIGHASH_ALL = 1; + assert.strictEqual(psbt.data.inputs[0].sighashType, SIGHASH_ALL); + + // Convert to wasm-utxo PSBT for cosigning + const wasmPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(psbt.toBuffer(), 'btc'); + + // Generate musig2 nonces and sign with wasm-utxo + // wasm-utxo now respects input.sighashType and creates signatures with SIGHASH_ALL + const userKey = BIP32.fromBase58(xprivs[0]); + const bitgoKey = BIP32.fromBase58(xprivs[2]); + + wasmPsbt.generateMusig2Nonces(userKey); + wasmPsbt.generateMusig2Nonces(bitgoKey); + wasmPsbt.sign(0, userKey); + wasmPsbt.sign(0, bitgoKey); + + // Convert back to utxolib PSBT for validation + const signedPsbt = utxolib.bitgo.createPsbtFromBuffer( + Buffer.from(wasmPsbt.serialize()), + utxolib.networks.bitcoin + ); + + // Validation should succeed because wasm-utxo now creates signatures + // with the correct sighash type (SIGHASH_ALL) matching input.sighashType + const validationResult = utxolib.bitgo.getSignatureValidationArrayPsbt(signedPsbt, utxolibRootWalletKeys); + + // Verify that both user (index 0) and bitgo (index 2) signatures are valid + assert.strictEqual(validationResult.length, 1); + const [inputIndex, sigValidation] = validationResult[0]; + assert.strictEqual(inputIndex, 0); + assert.strictEqual(sigValidation[0], true, 'user signature should be valid'); + assert.strictEqual(sigValidation[1], false, 'backup signature should not be present'); + assert.strictEqual(sigValidation[2], true, 'bitgo signature should be valid'); + }); + }); }); diff --git a/modules/utxo-bin/package.json b/modules/utxo-bin/package.json index 9b5a9b8d78..b89797fb9b 100644 --- a/modules/utxo-bin/package.json +++ b/modules/utxo-bin/package.json @@ -31,7 +31,7 @@ "@bitgo/unspents": "^0.51.0", "@bitgo/utxo-core": "^1.32.0", "@bitgo/utxo-lib": "^11.20.0", - "@bitgo/wasm-utxo": "^1.32.0", + "@bitgo/wasm-utxo": "^1.34.0", "@noble/curves": "1.8.1", "archy": "^1.0.0", "bech32": "^2.0.0", diff --git a/modules/utxo-core/package.json b/modules/utxo-core/package.json index 286d78f63c..f6c2c0215d 100644 --- a/modules/utxo-core/package.json +++ b/modules/utxo-core/package.json @@ -81,7 +81,7 @@ "@bitgo/secp256k1": "^1.10.0", "@bitgo/unspents": "^0.51.0", "@bitgo/utxo-lib": "^11.20.0", - "@bitgo/wasm-utxo": "^1.32.0", + "@bitgo/wasm-utxo": "^1.34.0", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", "fast-sha256": "^1.3.0" }, diff --git a/modules/utxo-ord/package.json b/modules/utxo-ord/package.json index ad2444a77d..bf98243ef9 100644 --- a/modules/utxo-ord/package.json +++ b/modules/utxo-ord/package.json @@ -45,7 +45,7 @@ "directory": "modules/utxo-ord" }, "dependencies": { - "@bitgo/wasm-utxo": "^1.32.0" + "@bitgo/wasm-utxo": "^1.34.0" }, "devDependencies": { "@bitgo/utxo-lib": "^11.20.0" diff --git a/modules/utxo-staking/package.json b/modules/utxo-staking/package.json index 693dae7ce9..8589176fd7 100644 --- a/modules/utxo-staking/package.json +++ b/modules/utxo-staking/package.json @@ -63,7 +63,7 @@ "@bitgo/babylonlabs-io-btc-staking-ts": "^3.4.0", "@bitgo/utxo-core": "^1.32.0", "@bitgo/utxo-lib": "^11.20.0", - "@bitgo/wasm-utxo": "^1.32.0", + "@bitgo/wasm-utxo": "^1.34.0", "bip174": "npm:@bitgo-forks/bip174@3.1.0-master.4", "bip322-js": "^2.0.0", "bitcoinjs-lib": "^6.1.7", diff --git a/yarn.lock b/yarn.lock index ad6e4c5bd2..35b27978cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -996,10 +996,10 @@ monocle-ts "^2.3.13" newtype-ts "^0.3.5" -"@bitgo/wasm-utxo@^1.32.0": - version "1.32.0" - resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.32.0.tgz#fc7e7803eb584ba8ad16aeb0a2805d6905d287d3" - integrity sha512-fqUGh8XOrzbPcTxK3lhS9UjqKxx3UaN6L+eS3vocBeWHbQvl6jm9xPPQ+TDkeiUuZdxaj0+7ca4Algt9vyiXHg== +"@bitgo/wasm-utxo@^1.34.0": + version "1.34.0" + resolved "https://registry.npmjs.org/@bitgo/wasm-utxo/-/wasm-utxo-1.34.0.tgz#318bcc0a20acc3f35b9547548ea36506beaab237" + integrity sha512-aJuLQ8fNAWmI213sIwMt9WJfWvsyVAmmyUWnojUdmK2iHwf6tIo4B9gWHoPuWwNYoqc/UJVbK3dDKxnMIUk/bg== "@brandonblack/musig@^0.0.1-alpha.0": version "0.0.1-alpha.1" @@ -3080,10 +3080,10 @@ resolved "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz" integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== +"@isaacs/brace-expansion@5.0.1", "@isaacs/brace-expansion@^5.0.0": + version "5.0.1" + resolved "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz#0ef5a92d91f2fff2a37646ce54da9e5f599f6eff" + integrity sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ== dependencies: "@isaacs/balanced-match" "^4.0.1"