From bdf723ce51d245e97b217941a21b02084b2473cf Mon Sep 17 00:00:00 2001 From: David Kaplan Date: Fri, 8 Dec 2023 12:59:53 -0500 Subject: [PATCH 1/2] feat(abstract-utxo): enable segwit override when flagged TICKET: BTC-721 --- modules/abstract-utxo/src/abstractUtxoCoin.ts | 22 ++++++++++++++++--- modules/abstract-utxo/src/sign.ts | 19 +++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 96a62a50d2..73dfca456e 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -247,6 +247,11 @@ type UtxoBaseSignTransactionOptions = * When false, creates half-signed transaction with placeholder signatures. */ isLastSignature?: boolean; + /** + * If true, allows signing a non-segwit input with a witnessUtxo instead requiring a previous + * transaction (nonWitnessUtxo) + */ + allowNonSegwitSigningWithoutPrevTx?: boolean; }; export type SignTransactionOptions = UtxoBaseSignTransactionOptions & @@ -1164,13 +1169,21 @@ export abstract class AbstractUtxoCoin extends BaseCoin { return signerKeychain; }; + const setSignerMusigNonceWithOverride = ( + psbt: utxolib.bitgo.UtxoPsbt, + signerKeychain: utxolib.BIP32Interface, + nonSegwitOverride: boolean + ) => { + utxolib.bitgo.withUnsafeNonSegwit(psbt, () => psbt.setAllInputsMusig2NonceHD(signerKeychain), nonSegwitOverride); + }; + let signerKeychain: utxolib.BIP32Interface | undefined; if (tx instanceof bitgo.UtxoPsbt && isTxWithKeyPathSpendInput) { switch (params.signingStep) { case 'signerNonce': signerKeychain = getSignerKeychain(); - tx.setAllInputsMusig2NonceHD(signerKeychain); + setSignerMusigNonceWithOverride(tx, signerKeychain, !!params.allowNonSegwitSigningWithoutPrevTx); AbstractUtxoCoin.PSBT_CACHE.set(tx.getUnsignedTx().getId(), tx); return { txHex: tx.toHex() }; case 'cosignerNonce': @@ -1191,7 +1204,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin { // this instance is not an external signer assert(txPrebuild.walletId, 'walletId is required for MuSig2 bitgo nonce'); signerKeychain = getSignerKeychain(); - tx.setAllInputsMusig2NonceHD(signerKeychain); + setSignerMusigNonceWithOverride(tx, signerKeychain, !!params.allowNonSegwitSigningWithoutPrevTx); const response = await this.signPsbt(tx.toHex(), txPrebuild.walletId); tx.combine(bitgo.createPsbtFromHex(response.psbt, this.network)); break; @@ -1214,7 +1227,10 @@ export abstract class AbstractUtxoCoin extends BaseCoin { let signedTransaction: bitgo.UtxoTransaction | bitgo.UtxoPsbt; if (tx instanceof bitgo.UtxoPsbt) { - signedTransaction = signAndVerifyPsbt(tx, signerKeychain, { isLastSignature }); + signedTransaction = signAndVerifyPsbt(tx, signerKeychain, { + isLastSignature, + allowNonSegwitSigningWithoutPrevTx: params.allowNonSegwitSigningWithoutPrevTx, + }); } else { if (tx.ins.length !== txPrebuild.txInfo?.unspents?.length) { throw new Error('length of unspents array should equal to the number of transaction inputs'); diff --git a/modules/abstract-utxo/src/sign.ts b/modules/abstract-utxo/src/sign.ts index 89f6c40e5b..9d406ec95d 100644 --- a/modules/abstract-utxo/src/sign.ts +++ b/modules/abstract-utxo/src/sign.ts @@ -63,7 +63,10 @@ export class TransactionSigningError e export function signAndVerifyPsbt( psbt: utxolib.bitgo.UtxoPsbt, signerKeychain: utxolib.BIP32Interface, - { isLastSignature }: { isLastSignature: boolean } + { + isLastSignature, + allowNonSegwitSigningWithoutPrevTx, + }: { isLastSignature: boolean; allowNonSegwitSigningWithoutPrevTx?: boolean } ): utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction { const txInputs = psbt.txInputs; const outputIds: string[] = []; @@ -83,7 +86,11 @@ export function signAndVerifyPsbt( } try { - psbt.signInputHD(inputIndex, signerKeychain); + utxolib.bitgo.withUnsafeNonSegwit( + psbt, + () => psbt.signInputHD(inputIndex, signerKeychain), + !!allowNonSegwitSigningWithoutPrevTx + ); debug('Successfully signed input %d of %d', inputIndex + 1, psbt.data.inputs.length); } catch (e) { return new InputSigningError(inputIndex, { id: outputId }, e); @@ -105,7 +112,13 @@ export function signAndVerifyPsbt( const outputId = outputIds[inputIndex]; try { - if (!psbt.validateSignaturesOfInputHD(inputIndex, signerKeychain)) { + if ( + !utxolib.bitgo.withUnsafeNonSegwit( + psbt, + () => psbt.validateSignaturesOfInputHD(inputIndex, signerKeychain), + !!allowNonSegwitSigningWithoutPrevTx + ) + ) { return new InputSigningError(inputIndex, { id: outputId }, new Error(`invalid signature`)); } } catch (e) { From 2bcdaf01953daf68734e96a0046cf69f85a602f1 Mon Sep 17 00:00:00 2001 From: David Kaplan Date: Fri, 8 Dec 2023 13:00:35 -0500 Subject: [PATCH 2/2] feat(sdk-core): flag to do segwit override for bulk consolidations TICKET: BTC-721 --- modules/sdk-core/src/bitgo/wallet/wallet.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index eb3cb42d1c..0de3d93b09 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -688,6 +688,9 @@ export class Wallet implements IWallet { assert(k.pub); return k.pub; }), + // Building PSBTs with the bulk flag does not include the previous transaction for non-segwit inputs. + // Manually override the signing and validating to not fail. + allowNonSegwitSigningWithoutPrevTx: !!params.bulk, }; const txPrebuilds = Array.isArray(buildResponse) ? buildResponse : [buildResponse];