diff --git a/modules/abstract-utxo/src/transaction/explainTransaction.ts b/modules/abstract-utxo/src/transaction/explainTransaction.ts index 57c3b0b14b..35711b5651 100644 --- a/modules/abstract-utxo/src/transaction/explainTransaction.ts +++ b/modules/abstract-utxo/src/transaction/explainTransaction.ts @@ -6,7 +6,7 @@ import { getDescriptorMapFromWallet, isDescriptorWallet } from '../descriptor'; import { toBip32Triple } from '../keychains'; import { getPolicyForEnv } from '../descriptor/validatePolicy'; -import { getReplayProtectionOutputScripts } from './fixedScript/replayProtection'; +import { getReplayProtectionPubkeys } from './fixedScript/replayProtection'; import type { TransactionExplanationUtxolibLegacy, TransactionExplanationUtxolibPsbt, @@ -63,7 +63,7 @@ export function explainTx( } return fixedScript.explainPsbtWasm(tx, walletXpubs, { replayProtection: { - outputScripts: getReplayProtectionOutputScripts(network), + publicKeys: getReplayProtectionPubkeys(network), }, }); } else { diff --git a/modules/abstract-utxo/src/transaction/fixedScript/explainPsbtWasm.ts b/modules/abstract-utxo/src/transaction/fixedScript/explainPsbtWasm.ts index 0f884a3fd5..40f7a659c1 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/explainPsbtWasm.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/explainPsbtWasm.ts @@ -44,7 +44,7 @@ export function explainPsbtWasm( params: { replayProtection: { checkSignature?: boolean; - outputScripts: Buffer[]; + publicKeys: Buffer[]; }; customChangeWalletXpubs?: Triple; } diff --git a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts index 96bf1c9113..58b61d23d6 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts @@ -1,23 +1,45 @@ -import * as wasmUtxo from '@bitgo/wasm-utxo'; import * as utxolib from '@bitgo/utxo-lib'; +import { utxolibCompat } from '@bitgo/wasm-utxo'; -export function getReplayProtectionAddresses(network: utxolib.Network): string[] { +export const pubkeyProd = Buffer.from('0255b9f71ac2c78fffd83e3e37b9e17ae70d5437b7f56d0ed2e93b7de08015aa59', 'hex'); + +export const pubkeyTestnet = Buffer.from('0219da48412c2268865fe8c126327d1b12eee350a3b69eb09e3323cc9a11828945', 'hex'); + +export function getReplayProtectionPubkeys(network: utxolib.Network): Buffer[] { switch (network) { case utxolib.networks.bitcoincash: case utxolib.networks.bitcoinsv: - return ['33p1q7mTGyeM5UnZERGiMcVUkY12SCsatA']; - case utxolib.networks.bitcoincashTestnet: + return [pubkeyProd]; case utxolib.networks.bitcoinsvTestnet: - return ['2MuMnPoSDgWEpNWH28X2nLtYMXQJCyT61eY']; + case utxolib.networks.bitcoincashTestnet: + return [pubkeyTestnet]; } - return []; } -export function getReplayProtectionOutputScripts(network: utxolib.Network): Buffer[] { - return getReplayProtectionAddresses(network).map((address) => - Buffer.from(wasmUtxo.utxolibCompat.toOutputScript(address, network)) - ); +// sh(pk(pubkeyProd)) +// 33p1q7mTGyeM5UnZERGiMcVUkY12SCsatA +// bitcoincash:pqt5x9w0m6z0f3znjkkx79wl3l7ywrszesemp8xgpf +const replayProtectionScriptsProd = [Buffer.from('a914174315cfde84f4c45395ac6f15df8ffc470e02cc87', 'hex')]; +// sh(pk(pubkeyTestnet)) +// 2MuMnPoSDgWEpNWH28X2nLtYMXQJCyT61eY +// bchtest:pqtjmnzwqffkrk2349g3cecfwwjwxusvnq87n07cal +const replayProtectionScriptsTestnet = [Buffer.from('a914172dcc4e025361d951a9511c670973a4e3720c9887', 'hex')]; + +export function getReplayProtectionAddresses( + network: utxolib.Network, + format: 'default' | 'cashaddr' = 'default' +): string[] { + switch (network) { + case utxolib.networks.bitcoincash: + case utxolib.networks.bitcoinsv: + return replayProtectionScriptsProd.map((script) => utxolibCompat.fromOutputScript(script, network, format)); + case utxolib.networks.bitcoinsvTestnet: + case utxolib.networks.bitcoincashTestnet: + return replayProtectionScriptsTestnet.map((script) => utxolibCompat.fromOutputScript(script, network, format)); + default: + return []; + } } export function isReplayProtectionUnspent( diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts index d38467a81e..822ea0a7c6 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts @@ -65,7 +65,7 @@ function describeTransactionWith(acidTest: testutil.AcidTest) { const wasmExplanation = explainPsbtWasm(wasmPsbt, walletXpubs, { replayProtection: { - outputScripts: [acidTest.getReplayProtectionOutputScript()], + publicKeys: [acidTest.getReplayProtectionPublicKey()], }, }); @@ -95,7 +95,7 @@ function describeTransactionWith(acidTest: testutil.AcidTest) { it('returns custom change outputs when parameter is set', function () { const wasmExplanation = explainPsbtWasm(wasmPsbt, walletXpubs, { replayProtection: { - outputScripts: [acidTest.getReplayProtectionOutputScript()], + publicKeys: [acidTest.getReplayProtectionPublicKey()], }, customChangeWalletXpubs, }); diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts index c1404d2666..f5db82dccd 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts @@ -134,7 +134,7 @@ function describeParseTransactionWith( acidTest.rootWalletKeys.triple.map((k) => k.neutered().toBase58()) as Triple, { replayProtection: { - outputScripts: [acidTest.getReplayProtectionOutputScript()], + publicKeys: [acidTest.getReplayProtectionPublicKey()], }, } ); diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts new file mode 100644 index 0000000000..f8f2a62b83 --- /dev/null +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts @@ -0,0 +1,28 @@ +import assert from 'node:assert/strict'; + +import * as utxolib from '@bitgo/utxo-lib'; +import { Descriptor, utxolibCompat } from '@bitgo/wasm-utxo'; + +import { + getReplayProtectionAddresses, + pubkeyProd, + pubkeyTestnet, +} from '../../../../src/transaction/fixedScript/replayProtection'; + +function createReplayProtectionOutputScript(pubkey: Buffer): Buffer { + const descriptor = Descriptor.fromString(`sh(pk(${pubkey.toString('hex')}))`, 'definite'); + return Buffer.from(descriptor.scriptPubkey()); +} + +describe('replayProtection', function () { + it('should have scriptPubKeys that match descriptor computation', function () { + for (const pubkey of [pubkeyProd, pubkeyTestnet]) { + const network = pubkey === pubkeyProd ? utxolib.networks.bitcoincash : utxolib.networks.bitcoincashTestnet; + const expectedScript = createReplayProtectionOutputScript(pubkey); + const actualAddresses = getReplayProtectionAddresses(network); + assert.equal(actualAddresses.length, 1); + const actualScript = Buffer.from(utxolibCompat.toOutputScript(actualAddresses[0], network)); + assert.deepStrictEqual(actualScript, expectedScript); + } + }); +}); diff --git a/modules/utxo-lib/src/testutil/psbt.ts b/modules/utxo-lib/src/testutil/psbt.ts index 7e4bbb185a..0a6285257b 100644 --- a/modules/utxo-lib/src/testutil/psbt.ts +++ b/modules/utxo-lib/src/testutil/psbt.ts @@ -340,8 +340,12 @@ export class AcidTest { return `${networkName} ${this.signStage} ${this.txFormat}`; } + getReplayProtectionPublicKey(): Buffer { + return this.rootWalletKeys.user.publicKey; + } + getReplayProtectionOutputScript(): Buffer { - const { scriptPubKey } = createOutputScriptP2shP2pk(this.rootWalletKeys.user.publicKey); + const { scriptPubKey } = createOutputScriptP2shP2pk(this.getReplayProtectionPublicKey()); assert(scriptPubKey); return scriptPubKey; }