From e0e262f99a7c38c01fb19b80515d5cdc5521b36d Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Thu, 2 Apr 2026 13:24:02 +0200 Subject: [PATCH] feat(abstract-utxo): add writeSignedWith option to control WASM metadata Add writeSignedWith parameter to signing functions to allow callers to opt-in to storing WASM version metadata in PSBTs. Defaults to false to maintain backwards compatibility. Issue: BTC-2992 Co-authored-by: llm-git --- modules/abstract-utxo/src/abstractUtxoCoin.ts | 2 ++ .../transaction/fixedScript/signPsbtWasm.ts | 21 ++++++++++++++++--- .../fixedScript/signTransaction.ts | 16 +++++++++----- .../src/transaction/signTransaction.ts | 1 + 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 8e88a4a9be..58567ba7f7 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -344,6 +344,8 @@ type UtxoBaseSignTransactionOptions = * When false, return finalized PSBT. Useful for testing to keep transactions in PSBT format. */ extractTransaction?: boolean; + /** When true, embeds WASM-UTXO version metadata into the signed PSBT. Defaults to false. */ + writeSignedWith?: boolean; }; export type SignTransactionOptions = UtxoBaseSignTransactionOptions & diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts index 98178803fe..6b61f8cedf 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts @@ -1,6 +1,6 @@ import assert from 'assert'; -import { BIP32, bip32, ECPair, fixedScriptWallet } from '@bitgo/wasm-utxo'; +import { BIP32, bip32, ECPair, fixedScriptWallet, getWasmUtxoVersion } from '@bitgo/wasm-utxo'; import { toWasmBIP32 } from '../../wasmUtil'; @@ -37,7 +37,8 @@ export function signAndVerifyPsbtWasm( tx: fixedScriptWallet.BitGoPsbt, signerKeychain: bip32.BIP32Interface | BIP32, rootWalletKeys: fixedScriptWallet.RootWalletKeys, - replayProtection: ReplayProtectionKeys + replayProtection: ReplayProtectionKeys, + { writeSignedWith = false }: { writeSignedWith?: boolean } = {} ): fixedScriptWallet.BitGoPsbt { const wasmSigner = toWasmBIP32(signerKeychain); @@ -74,6 +75,17 @@ export function signAndVerifyPsbtWasm( throw new TransactionSigningError([], verifyErrors); } + if (writeSignedWith) { + const versionInfo = getWasmUtxoVersion(); + const versionPayload = new TextEncoder().encode( + JSON.stringify({ + version: versionInfo.version, + gitHash: versionInfo.gitHash, + }) + ); + tx.setKV({ type: 'bitgo', subtype: fixedScriptWallet.BitGoKeySubtype.WasmUtxoSignedWith }, versionPayload); + } + return tx; } @@ -86,6 +98,7 @@ export async function signPsbtWithMusig2ParticipantWasm( replayProtection: ReplayProtectionKeys; signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined; walletId: string | undefined; + writeSignedWith?: boolean; } ): Promise { const wasmSigner = signerKeychain ? toWasmBIP32(signerKeychain) : undefined; @@ -135,5 +148,7 @@ export async function signPsbtWithMusig2ParticipantWasm( } assert(signerKeychain); - return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection); + return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection, { + writeSignedWith: params.writeSignedWith, + }); } diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts index 493832be6f..23961037da 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts @@ -23,32 +23,36 @@ export function signAndVerifyPsbt( psbt: utxolib.bitgo.UtxoPsbt, signerKeychain: bip32.BIP32Interface | BIP32, rootWalletKeys: fixedScriptWallet.RootWalletKeys | undefined, - replayProtection: ReplayProtectionKeys | undefined + replayProtection: ReplayProtectionKeys | undefined, + options?: { writeSignedWith?: boolean } ): utxolib.bitgo.UtxoPsbt; export function signAndVerifyPsbt( psbt: fixedScriptWallet.BitGoPsbt, signerKeychain: bip32.BIP32Interface | BIP32, rootWalletKeys: fixedScriptWallet.RootWalletKeys, - replayProtection: ReplayProtectionKeys + replayProtection: ReplayProtectionKeys, + options?: { writeSignedWith?: boolean } ): fixedScriptWallet.BitGoPsbt; export function signAndVerifyPsbt( psbt: utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt, signerKeychain: bip32.BIP32Interface | BIP32, rootWalletKeys: fixedScriptWallet.RootWalletKeys, - replayProtection: ReplayProtectionKeys + replayProtection: ReplayProtectionKeys, + options?: { writeSignedWith?: boolean } ): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt; export function signAndVerifyPsbt( psbt: utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt, signerKeychain: bip32.BIP32Interface | BIP32, rootWalletKeys: fixedScriptWallet.RootWalletKeys | undefined, - replayProtection: ReplayProtectionKeys | undefined + replayProtection: ReplayProtectionKeys | undefined, + options: { writeSignedWith?: boolean } = {} ): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt { if (psbt instanceof bitgo.UtxoPsbt) { return signAndVerifyPsbtUtxolib(psbt, toUtxolibBIP32(signerKeychain)); } assert(rootWalletKeys, 'rootWalletKeys required for wasm-utxo signing'); assert(replayProtection, 'replayProtection required for wasm-utxo signing'); - return signAndVerifyPsbtWasm(psbt, signerKeychain, rootWalletKeys, replayProtection); + return signAndVerifyPsbtWasm(psbt, signerKeychain, rootWalletKeys, replayProtection, options); } export async function signTransaction< @@ -69,6 +73,7 @@ export async function signTransaction< cosignerPub: string | undefined; /** When true (default), extract finalized PSBT to legacy transaction format. When false, return finalized PSBT. */ extractTransaction?: boolean; + writeSignedWith?: boolean; } ): Promise< utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction | fixedScriptWallet.BitGoPsbt | Buffer @@ -115,6 +120,7 @@ export async function signTransaction< }, signingStep: params.signingStep, walletId: params.walletId, + writeSignedWith: params.writeSignedWith, } ); if (isLastSignature) { diff --git a/modules/abstract-utxo/src/transaction/signTransaction.ts b/modules/abstract-utxo/src/transaction/signTransaction.ts index ebc487aa2c..75c76f4443 100644 --- a/modules/abstract-utxo/src/transaction/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/signTransaction.ts @@ -79,6 +79,7 @@ export async function signTransaction( pubs: params.pubs, cosignerPub: params.cosignerPub, extractTransaction: params.extractTransaction, + writeSignedWith: params.writeSignedWith, }); // Convert half-signed PSBT to legacy format when the caller explicitly requested txFormat: 'legacy'