Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/abstract-utxo/src/transaction/explainTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -63,7 +63,7 @@ export function explainTx<TNumber extends number | bigint>(
}
return fixedScript.explainPsbtWasm(tx, walletXpubs, {
replayProtection: {
outputScripts: getReplayProtectionOutputScripts(network),
publicKeys: getReplayProtectionPubkeys(network),
},
});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function explainPsbtWasm(
params: {
replayProtection: {
checkSignature?: boolean;
outputScripts: Buffer[];
publicKeys: Buffer[];
};
customChangeWalletXpubs?: Triple<string>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
import * as wasmUtxo from '@bitgo/wasm-utxo';
import * as utxolib from '@bitgo/utxo-lib';
import { Descriptor, utxolibCompat } from '@bitgo/wasm-utxo';

export function getReplayProtectionAddresses(network: utxolib.Network): string[] {
// 33p1q7mTGyeM5UnZERGiMcVUkY12SCsatA
// bitcoincash:pqt5x9w0m6z0f3znjkkx79wl3l7ywrszesemp8xgpf
const pubkeyProd = Buffer.from('0255b9f71ac2c78fffd83e3e37b9e17ae70d5437b7f56d0ed2e93b7de08015aa59', 'hex');

// 2MuMnPoSDgWEpNWH28X2nLtYMXQJCyT61eY
// bchtest:pqtjmnzwqffkrk2349g3cecfwwjwxusvnq87n07cal
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))
);
export function createReplayProtectionOutputScript(pubkey: Buffer): Buffer {
const descriptor = Descriptor.fromString(`sh(pk(${pubkey.toString('hex')}))`, 'definite');
return Buffer.from(descriptor.scriptPubkey());
}

const replayProtectionScriptsProd = [createReplayProtectionOutputScript(pubkeyProd)];
const replayProtectionScriptsTestnet = [createReplayProtectionOutputScript(pubkeyTestnet)];

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<TNumber extends number | bigint>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function describeTransactionWith(acidTest: testutil.AcidTest) {

const wasmExplanation = explainPsbtWasm(wasmPsbt, walletXpubs, {
replayProtection: {
outputScripts: [acidTest.getReplayProtectionOutputScript()],
publicKeys: [acidTest.getReplayProtectionPublicKey()],
},
});

Expand Down Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function describeParseTransactionWith(
acidTest.rootWalletKeys.triple.map((k) => k.neutered().toBase58()) as Triple<string>,
{
replayProtection: {
outputScripts: [acidTest.getReplayProtectionOutputScript()],
publicKeys: [acidTest.getReplayProtectionPublicKey()],
},
}
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import assert from 'node:assert/strict';

import * as utxolib from '@bitgo/utxo-lib';

import {
getReplayProtectionPubkeys,
getReplayProtectionAddresses,
} from '../../../../src/transaction/fixedScript/replayProtection';

describe('replayProtection', function () {
for (const network of utxolib.getNetworkList()) {
const networkName = utxolib.getNetworkName(network);
assert(networkName, 'network name is required');

describe(`${networkName}`, function () {
if (
utxolib.getMainnet(network) === utxolib.networks.bitcoincash ||
utxolib.getMainnet(network) === utxolib.networks.bitcoinsv
) {
it('should have keys that correspond to addresses via p2shP2pk', function () {
const actualAddressesDefault = getReplayProtectionAddresses(network, 'default');

switch (network) {
case utxolib.networks.bitcoincash:
case utxolib.networks.bitcoinsv:
assert.deepStrictEqual(actualAddressesDefault, ['33p1q7mTGyeM5UnZERGiMcVUkY12SCsatA']);
break;
case utxolib.networks.bitcoincashTestnet:
case utxolib.networks.bitcoinsvTestnet:
assert.deepStrictEqual(actualAddressesDefault, ['2MuMnPoSDgWEpNWH28X2nLtYMXQJCyT61eY']);
break;
default:
throw new Error(`illegal state`);
}

if (utxolib.getMainnet(network) !== utxolib.networks.bitcoincash) {
return;
}

const actualAddressesCashaddr = getReplayProtectionAddresses(network, 'cashaddr');
switch (network) {
case utxolib.networks.bitcoincash:
assert.deepStrictEqual(actualAddressesCashaddr, [
'bitcoincash:pqt5x9w0m6z0f3znjkkx79wl3l7ywrszesemp8xgpf',
]);
break;
case utxolib.networks.bitcoincashTestnet:
assert.deepStrictEqual(actualAddressesCashaddr, ['bchtest:pqtjmnzwqffkrk2349g3cecfwwjwxusvnq87n07cal']);
break;
default:
throw new Error(`illegal state`);
}
});
} else {
it('should have no replay protection', function () {
assert.deepEqual(getReplayProtectionPubkeys(network), []);
assert.deepEqual(getReplayProtectionAddresses(network), []);
});
}
});
}
});
6 changes: 5 additions & 1 deletion modules/utxo-lib/src/testutil/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading