From 23b0f15bec5e6547cbe1cd423b6a9787a2887f2b Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 12 Jan 2026 12:01:23 +0100 Subject: [PATCH 1/2] feat(abstract-utxo): simplify replay protection unspent check Simplify the signature of isReplayProtectionUnspent to accept any object with an address property instead of requiring a full utxolib unspent. Issue: BTC-2916 Co-authored-by: llm-git --- .../src/transaction/fixedScript/replayProtection.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts index 58b61d23d6..fbf58bfcb5 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts @@ -42,9 +42,6 @@ export function getReplayProtectionAddresses( } } -export function isReplayProtectionUnspent( - u: utxolib.bitgo.Unspent, - network: utxolib.Network -): boolean { +export function isReplayProtectionUnspent(u: { address: string }, network: utxolib.Network): boolean { return getReplayProtectionAddresses(network).includes(u.address); } From bd9ace7d0ba8950d12f3eb74bbe61883d2d65a53 Mon Sep 17 00:00:00 2001 From: Otto Allmendinger Date: Mon, 12 Jan 2026 13:39:57 +0100 Subject: [PATCH 2/2] feat(abstract-utxo): use coin name instead of network object This commit refactors the code to use coin name strings instead of utxolib network objects when interacting with addresses, descriptors, and other coin-specific functionality. This makes the code more maintainable and helps in the transition to wasm-utxo. Also adds utility functions isTestnetCoin and isMainnetCoin to replace the utxolib functions. Issue: BTC-2916 Co-authored-by: llm-git --- modules/abstract-utxo/src/abstractUtxoCoin.ts | 26 +++++++----- .../abstract-utxo/src/address/fixedScript.ts | 23 ++++++----- .../assertDescriptorWalletAddress.ts | 4 +- modules/abstract-utxo/src/names.ts | 8 ++++ .../offlineVault/OfflineVaultHalfSigned.ts | 7 ++-- .../offlineVault/TransactionExplanation.ts | 6 +-- .../offlineVault/descriptor/transaction.ts | 7 +++- .../src/recovery/backupKeyRecovery.ts | 13 +++--- .../src/recovery/crossChainRecovery.ts | 13 +++--- modules/abstract-utxo/src/recovery/psbt.ts | 9 ++-- .../abstract-utxo/src/transaction/decode.ts | 18 ++++---- .../src/transaction/descriptor/explainPsbt.ts | 10 +++-- .../src/transaction/descriptor/parse.ts | 33 +++++++-------- .../descriptor/verifyTransaction.ts | 7 ++-- .../src/transaction/explainTransaction.ts | 9 ++-- .../src/transaction/fetchInputs.ts | 4 +- .../fixedScript/explainTransaction.ts | 41 +++++++++++-------- .../fixedScript/parseTransaction.ts | 4 +- .../fixedScript/replayProtection.ts | 32 ++++++++------- .../fixedScript/signLegacyTransaction.ts | 9 +++- .../fixedScript/signTransaction.ts | 8 ++-- .../fixedScript/verifyTransaction.ts | 2 +- .../transaction/getPayGoVerificationPubkey.ts | 10 ++--- .../src/transaction/recipient.ts | 10 +++-- .../src/transaction/signTransaction.ts | 2 +- modules/abstract-utxo/test/unit/address.ts | 16 ++++---- .../test/unit/customChangeWallet.ts | 2 +- .../test/unit/prebuildAndSign.ts | 2 +- .../test/unit/recovery/crossChainRecovery.ts | 12 +++--- .../abstract-utxo/test/unit/transaction.ts | 10 ++--- .../test/unit/transaction/descriptor/parse.ts | 2 +- .../transaction/fixedScript/explainPsbt.ts | 7 +++- .../unit/transaction/fixedScript/parsePsbt.ts | 4 +- .../fixedScript/replayProtection.ts | 4 +- .../unit/transaction/fixedScript/signPsbt.ts | 4 +- .../abstract-utxo/test/unit/util/unspents.ts | 4 +- 36 files changed, 220 insertions(+), 162 deletions(-) diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 485667728f..2e7c01065f 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -4,7 +4,7 @@ import { randomBytes } from 'crypto'; import _ from 'lodash'; import * as utxolib from '@bitgo/utxo-lib'; import { bip32 } from '@bitgo/secp256k1'; -import { bitgo, getMainnet, isMainnet, isTestnet } from '@bitgo/utxo-lib'; +import { bitgo, getMainnet, isMainnet } from '@bitgo/utxo-lib'; import { AddressCoinSpecific, BaseCoin, @@ -80,6 +80,7 @@ import { getFullNameFromCoinName, getMainnetCoinName, getNetworkFromCoinName, + isTestnetCoin, UtxoCoinName, UtxoCoinNameMainnet, } from './names'; @@ -387,7 +388,10 @@ export abstract class AbstractUtxoCoin this.amountType = amountType; } - /** @deprecated - will be removed when we drop support for utxolib */ + /** + * @deprecated - will be removed when we drop support for utxolib + * Use `name` property instead. + */ get network(): utxolib.Network { return getNetworkFromCoinName(this.name); } @@ -546,7 +550,7 @@ export abstract class AbstractUtxoCoin } if (utxolib.bitgo.isPsbt(input)) { - return decodePsbtWith(input, this.network, decodeWith); + return decodePsbtWith(input, this.name, decodeWith); } else { if (decodeWith !== 'utxolib') { console.error('received decodeWith hint %s, ignoring for legacy transaction', decodeWith); @@ -688,7 +692,7 @@ export abstract class AbstractUtxoCoin throw new Error('keychains must be a triple'); } assertDescriptorWalletAddress( - this.network, + this.name, params, getDescriptorMapFromWallet(wallet, toBip32Triple(keychains), getPolicyForEnv(this.bitgo.env)) ); @@ -705,7 +709,7 @@ export abstract class AbstractUtxoCoin throw new Error('missing required param keychains'); } - assertFixedScriptWalletAddress(this.network, { + assertFixedScriptWalletAddress(this.name, { address, keychains, format: params.format ?? 'base58', @@ -763,9 +767,9 @@ export abstract class AbstractUtxoCoin .send({ psbt: buffer.toString('hex') }) .result(); if (psbt instanceof utxolib.bitgo.UtxoPsbt) { - return decodePsbtWith(response.psbt, this.network, 'utxolib') as T; + return decodePsbtWith(response.psbt, this.name, 'utxolib') as T; } else { - return decodePsbtWith(response.psbt, this.network, 'wasm-utxo') as T; + return decodePsbtWith(response.psbt, this.name, 'wasm-utxo') as T; } } @@ -862,7 +866,7 @@ export abstract class AbstractUtxoCoin * @returns {boolean} */ isBitGoTaintedUnspent(unspent: Unspent): boolean { - return isReplayProtectionUnspent(unspent, this.network); + return isReplayProtectionUnspent(unspent, this.name); } /** @@ -873,7 +877,7 @@ export abstract class AbstractUtxoCoin override async explainTransaction( params: ExplainTransactionOptions ): Promise { - return explainTx(this.decodeTransactionFromPrebuild(params), params, this.network); + return explainTx(this.decodeTransactionFromPrebuild(params), params, this.name); } /** @@ -968,14 +972,14 @@ export abstract class AbstractUtxoCoin getDefaultTxFormat(wallet: Wallet, requestedFormat?: TxFormat): TxFormat | undefined { // If format is explicitly requested, use it if (requestedFormat !== undefined) { - if (isTestnet(this.network) && requestedFormat === 'legacy') { + if (isTestnetCoin(this.name) && requestedFormat === 'legacy') { throw new ErrorDeprecatedTxFormat(requestedFormat); } return requestedFormat; } - if (isTestnet(this.network)) { + if (isTestnetCoin(this.name)) { return 'psbt-lite'; } diff --git a/modules/abstract-utxo/src/address/fixedScript.ts b/modules/abstract-utxo/src/address/fixedScript.ts index 2325553add..7aa8d5aaac 100644 --- a/modules/abstract-utxo/src/address/fixedScript.ts +++ b/modules/abstract-utxo/src/address/fixedScript.ts @@ -12,10 +12,11 @@ import { isTriple, Triple, } from '@bitgo/sdk-core'; -import * as utxolib from '@bitgo/utxo-lib'; import { bitgo } from '@bitgo/utxo-lib'; import * as wasmUtxo from '@bitgo/wasm-utxo'; +import { getNetworkFromCoinName, UtxoCoinName } from '../names'; + type ScriptType2Of3 = bitgo.outputScripts.ScriptType2Of3; export interface FixedScriptAddressCoinSpecific { @@ -37,12 +38,13 @@ interface GenerateFixedScriptAddressOptions extends GenerateAddressOptions { keychains: { pub: string }[]; } -function supportsAddressType(network: utxolib.Network, addressType: ScriptType2Of3): boolean { - return utxolib.bitgo.outputScripts.isSupportedScriptType(network, addressType); +function supportsAddressType(coinName: UtxoCoinName, addressType: ScriptType2Of3): boolean { + const network = getNetworkFromCoinName(coinName); + return bitgo.outputScripts.isSupportedScriptType(network, addressType); } export function generateAddressWithChainAndIndex( - network: utxolib.Network, + coinName: UtxoCoinName, keychains: bitgo.RootWalletKeys | Triple, chain: bitgo.ChainCode, index: number, @@ -51,6 +53,7 @@ export function generateAddressWithChainAndIndex( // Convert CreateAddressFormat to AddressFormat for wasm-utxo // 'base58' -> 'default', 'cashaddr' -> 'cashaddr' const wasmFormat = format === 'base58' ? 'default' : format; + const network = getNetworkFromCoinName(coinName); return wasmUtxo.fixedScriptWallet.address(keychains, chain, index, network, wasmFormat); } @@ -66,7 +69,7 @@ export function generateAddressWithChainAndIndex( * @param params.bech32 {boolean} Deprecated * @returns {string} The generated address */ -export function generateAddress(network: utxolib.Network, params: GenerateFixedScriptAddressOptions): string { +export function generateAddress(coinName: UtxoCoinName, params: GenerateFixedScriptAddressOptions): string { let derivationIndex = 0; if (_.isInteger(params.index) && (params.index as number) > 0) { derivationIndex = params.index as number; @@ -94,11 +97,11 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS const addressType = params.addressType || convertFlagsToAddressType(); - if (addressType !== utxolib.bitgo.scriptTypeForChain(derivationChain)) { + if (addressType !== bitgo.scriptTypeForChain(derivationChain)) { throw new AddressTypeChainMismatchError(addressType, derivationChain); } - if (!supportsAddressType(network, addressType)) { + if (!supportsAddressType(coinName, addressType)) { switch (addressType) { case 'p2sh': throw new Error(`internal error: p2sh should always be supported`); @@ -120,7 +123,7 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS } return generateAddressWithChainAndIndex( - network, + coinName, keychains.map((k) => k.pub) as Triple, derivationChain, derivationIndex, @@ -133,7 +136,7 @@ type Keychain = { }; export function assertFixedScriptWalletAddress( - network: utxolib.Network, + coinName: UtxoCoinName, { chain, index, @@ -160,7 +163,7 @@ export function assertFixedScriptWalletAddress( throw new Error('missing required param keychains'); } - const expectedAddress = generateAddress(network, { + const expectedAddress = generateAddress(coinName, { format, addressType: addressType as ScriptType2Of3, keychains, diff --git a/modules/abstract-utxo/src/descriptor/assertDescriptorWalletAddress.ts b/modules/abstract-utxo/src/descriptor/assertDescriptorWalletAddress.ts index cc05e572bc..f297adbb66 100644 --- a/modules/abstract-utxo/src/descriptor/assertDescriptorWalletAddress.ts +++ b/modules/abstract-utxo/src/descriptor/assertDescriptorWalletAddress.ts @@ -5,6 +5,7 @@ import { Descriptor } from '@bitgo/wasm-utxo'; import { DescriptorMap } from '@bitgo/utxo-core/descriptor'; import { UtxoCoinSpecific, VerifyAddressOptions } from '../abstractUtxoCoin'; +import { getNetworkFromCoinName, UtxoCoinName } from '../names'; class DescriptorAddressMismatchError extends Error { constructor(descriptor: Descriptor, index: number, derivedAddress: string, expectedAddress: string) { @@ -15,7 +16,7 @@ class DescriptorAddressMismatchError extends Error { } export function assertDescriptorWalletAddress( - network: utxolib.Network, + coinName: UtxoCoinName, params: VerifyAddressOptions, descriptors: DescriptorMap ): void { @@ -34,6 +35,7 @@ export function assertDescriptorWalletAddress( ); } const derivedScript = Buffer.from(descriptor.atDerivationIndex(params.index).scriptPubkey()); + const network = getNetworkFromCoinName(coinName); const derivedAddress = utxolib.address.fromOutputScript(derivedScript, network); if (params.address !== derivedAddress) { throw new DescriptorAddressMismatchError(descriptor, params.index, derivedAddress, params.address); diff --git a/modules/abstract-utxo/src/names.ts b/modules/abstract-utxo/src/names.ts index 0c3c158d68..555797ae3c 100644 --- a/modules/abstract-utxo/src/names.ts +++ b/modules/abstract-utxo/src/names.ts @@ -193,3 +193,11 @@ export function getFullNameFromCoinName(coinName: UtxoCoinName): string { export function getFullNameFromNetwork(n: utxolib.Network): string { return getFullNameFromCoinName(getCoinName(n)); } + +export function isTestnetCoin(coinName: UtxoCoinName): boolean { + return isUtxoCoinNameTestnet(coinName); +} + +export function isMainnetCoin(coinName: UtxoCoinName): boolean { + return isUtxoCoinNameMainnet(coinName); +} diff --git a/modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts b/modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts index e7c8d624e8..d61b2b7906 100644 --- a/modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts +++ b/modules/abstract-utxo/src/offlineVault/OfflineVaultHalfSigned.ts @@ -2,7 +2,7 @@ import { BIP32Interface, bip32 } from '@bitgo/secp256k1'; import * as utxolib from '@bitgo/utxo-lib'; import { BaseCoin } from '@bitgo/sdk-core'; -import { getNetworkFromChain } from '../names'; +import { UtxoCoinName } from '../names'; import { OfflineVaultSignable } from './OfflineVaultSignable'; import { DescriptorTransaction, getHalfSignedPsbt } from './descriptor'; @@ -16,12 +16,11 @@ function createHalfSignedFromPsbt(psbt: utxolib.Psbt): OfflineVaultHalfSigned { } export function createHalfSigned( - coin: string, + coinName: UtxoCoinName, prv: string | BIP32Interface, derivationId: string, tx: unknown ): OfflineVaultHalfSigned { - const network = getNetworkFromChain(coin); if (typeof prv === 'string') { prv = bip32.fromBase58(prv); } @@ -30,7 +29,7 @@ export function createHalfSigned( throw new Error('unsupported transaction type'); } if (DescriptorTransaction.is(tx)) { - return createHalfSignedFromPsbt(getHalfSignedPsbt(tx, prv, network)); + return createHalfSignedFromPsbt(getHalfSignedPsbt(tx, prv, coinName)); } throw new Error('unsupported transaction type'); } diff --git a/modules/abstract-utxo/src/offlineVault/TransactionExplanation.ts b/modules/abstract-utxo/src/offlineVault/TransactionExplanation.ts index 8e7996b645..2b5daa4fb3 100644 --- a/modules/abstract-utxo/src/offlineVault/TransactionExplanation.ts +++ b/modules/abstract-utxo/src/offlineVault/TransactionExplanation.ts @@ -1,4 +1,4 @@ -import { getNetworkFromChain } from '../names'; +import { UtxoCoinName } from '../names'; import { OfflineVaultSignable } from './OfflineVaultSignable'; import { DescriptorTransaction, getTransactionExplanationFromPsbt } from './descriptor'; @@ -19,12 +19,12 @@ export interface TransactionExplanation { }; } -export function getTransactionExplanation(coin: string, tx: unknown): TransactionExplanation { +export function getTransactionExplanation(coinName: UtxoCoinName, tx: unknown): TransactionExplanation { if (!OfflineVaultSignable.is(tx)) { throw new Error('not a signable transaction'); } if (DescriptorTransaction.is(tx)) { - return getTransactionExplanationFromPsbt(tx, getNetworkFromChain(coin)); + return getTransactionExplanationFromPsbt(tx, coinName); } throw new Error('unsupported transaction type'); diff --git a/modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts b/modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts index a62795cd37..223335e9fb 100644 --- a/modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts +++ b/modules/abstract-utxo/src/offlineVault/descriptor/transaction.ts @@ -11,6 +11,7 @@ import { } from '../../descriptor/validatePolicy'; import { explainPsbt, signPsbt } from '../../transaction/descriptor'; import { TransactionExplanation } from '../TransactionExplanation'; +import { getNetworkFromCoinName, UtxoCoinName } from '../../names'; export const DescriptorTransaction = t.intersection( [OfflineVaultSignable, t.type({ descriptors: t.array(NamedDescriptor) })], @@ -34,8 +35,9 @@ export function getDescriptorsFromDescriptorTransaction(tx: DescriptorTransactio export function getHalfSignedPsbt( tx: DescriptorTransaction, prv: utxolib.BIP32Interface, - network: utxolib.Network + coinName: UtxoCoinName ): utxolib.Psbt { + const network = getNetworkFromCoinName(coinName); const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network); const descriptorMap = getDescriptorsFromDescriptorTransaction(tx); signPsbt(psbt, descriptorMap, prv, { onUnknownInput: 'throw' }); @@ -44,8 +46,9 @@ export function getHalfSignedPsbt( export function getTransactionExplanationFromPsbt( tx: DescriptorTransaction, - network: utxolib.Network + coinName: UtxoCoinName ): TransactionExplanation { + const network = getNetworkFromCoinName(coinName); const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network); const descriptorMap = getDescriptorsFromDescriptorTransaction(tx); const { outputs, changeOutputs, fee } = explainPsbt(psbt, descriptorMap); diff --git a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts index a35f82ab37..8cc494f322 100644 --- a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts +++ b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts @@ -18,6 +18,7 @@ import { signAndVerifyPsbt } from '../transaction/fixedScript/signTransaction'; import { generateAddressWithChainAndIndex } from '../address'; import { encodeTransaction } from '../transaction/decode'; import { getReplayProtectionPubkeys } from '../transaction/fixedScript/replayProtection'; +import { isTestnetCoin, UtxoCoinName } from '../names'; import { forCoin, RecoveryProvider } from './RecoveryProvider'; import { MempoolApi } from './mempoolApi'; @@ -110,7 +111,7 @@ export interface RecoverParams { /** * Generate an address and format it for API queries * @param coin - The coin instance - * @param network - The network to use + * @param coinName - The coin name * @param walletKeys - The wallet keys * @param chain - The chain code * @param addrIndex - The address index @@ -118,13 +119,13 @@ export interface RecoverParams { */ function getFormattedAddress( coin: AbstractUtxoCoin, - network: utxolib.Network, + coinName: UtxoCoinName, walletKeys: RootWalletKeys, chain: ChainCode, addrIndex: number ): string { const format = coin.getChain() === 'bch' || coin.getChain() === 'bcha' ? 'cashaddr' : undefined; - const address = generateAddressWithChainAndIndex(network, walletKeys, chain, addrIndex, format); + const address = generateAddressWithChainAndIndex(coinName, walletKeys, chain, addrIndex, format); // Blockchair uses cashaddr format when querying the API for address information. Strip the prefix for BCH/BCHA. return format === 'cashaddr' ? address.split(':')[1] : address; @@ -154,7 +155,7 @@ async function queryBlockchainUnspentsPath( } async function gatherUnspents(addrIndex: number) { - const formattedAddress = getFormattedAddress(coin, coin.network, walletKeys, chain, addrIndex); + const formattedAddress = getFormattedAddress(coin, coin.name, walletKeys, chain, addrIndex); const addrInfo = await recoveryProvider.getAddressInfo(formattedAddress); // we use txCount here because it implies usage - having tx'es means the addr was generated and used if (addrInfo.txCount === 0) { @@ -370,7 +371,7 @@ export async function backupKeyRecovery( } // Use wasm-utxo for testnet coins only, utxolib for mainnet - const backend: PsbtBackend = utxolib.isTestnet(coin.network) ? 'wasm-utxo' : 'utxolib'; + const backend: PsbtBackend = isTestnetCoin(coin.name) ? 'wasm-utxo' : 'utxolib'; let psbt = createBackupKeyRecoveryPsbt( coin.getChain(), walletKeys, @@ -394,7 +395,7 @@ export async function backupKeyRecovery( } const rootWalletKeysWasm = fixedScriptWallet.RootWalletKeys.from(walletKeys); - const replayProtection = { publicKeys: getReplayProtectionPubkeys(coin.network) }; + const replayProtection = { publicKeys: getReplayProtectionPubkeys(coin.name) }; // Sign with user key first psbt = signAndVerifyPsbt(psbt, walletKeys.user, rootWalletKeysWasm, replayProtection); diff --git a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts index f510715204..c33e8af81d 100644 --- a/modules/abstract-utxo/src/recovery/crossChainRecovery.ts +++ b/modules/abstract-utxo/src/recovery/crossChainRecovery.ts @@ -7,7 +7,7 @@ import { decrypt } from '@bitgo/sdk-api'; import { AbstractUtxoCoin, TransactionInfo } from '../abstractUtxoCoin'; import { signAndVerifyPsbt } from '../transaction/fixedScript/signTransaction'; -import { getNetworkFromCoinName } from '../names'; +import { getNetworkFromCoinName, isTestnetCoin, UtxoCoinName } from '../names'; import { encodeTransaction } from '../transaction/decode'; import { getReplayProtectionPubkeys } from '../transaction/fixedScript/replayProtection'; import { toTNumber } from '../tnumber'; @@ -131,10 +131,11 @@ export async function isWalletAddress(wallet: IWallet | WalletV1, address: strin * stores addresses in the 3... format while the LTC blockchain returns addresses in M... format. * * @param address - LTC address to convert - * @param network - The Litecoin network + * @param coinName - The coin name (e.g. 'ltc', 'tltc') * @returns The address in legacy 3... format, or the original address if it's not a P2SH address */ -export function convertLtcAddressToLegacyFormat(address: string, network: utxolib.Network): string { +export function convertLtcAddressToLegacyFormat(address: string, coinName: UtxoCoinName): string { + const network = getNetworkFromCoinName(coinName); try { // Try to decode as bech32 - these don't need conversion utxolib.address.fromBech32(address); @@ -189,7 +190,7 @@ async function getAllRecoveryOutputs( // When LTC is sent to a BTC address, the LTC blockchain returns M... addresses // but the BTC wallet stores addresses in 3... format. if (!isWalletOwned && coin.getFamily() === 'ltc') { - const legacyAddress = convertLtcAddressToLegacyFormat(output.address, coin.network); + const legacyAddress = convertLtcAddressToLegacyFormat(output.address, coin.name); if (legacyAddress !== output.address) { isWalletOwned = await isWalletAddress(wallet, legacyAddress); } @@ -501,7 +502,7 @@ export async function recoverCrossChain( params.sourceCoin.getChain(), walletKeys, @@ -523,7 +524,7 @@ export async function recoverCrossChain = utxolib.bitgo.WalletUnspent; @@ -28,12 +28,13 @@ export function isTaprootChain(chain: ChainCode): boolean { } /** - * Convert utxolib Network to wasm-utxo network name + * Convert coin name to wasm-utxo network name */ -export function toNetworkName(network: utxolib.Network): utxolibCompat.UtxolibName { +export function toNetworkName(coinName: UtxoCoinName): utxolibCompat.UtxolibName { + const network = getNetworkFromCoinName(coinName); const networkName = utxolib.getNetworkName(network); if (!networkName) { - throw new Error(`Invalid network`); + throw new Error(`Invalid coinName: ${coinName}`); } return networkName; } diff --git a/modules/abstract-utxo/src/transaction/decode.ts b/modules/abstract-utxo/src/transaction/decode.ts index feccd9de4d..10e63221cd 100644 --- a/modules/abstract-utxo/src/transaction/decode.ts +++ b/modules/abstract-utxo/src/transaction/decode.ts @@ -1,6 +1,8 @@ import * as utxolib from '@bitgo/utxo-lib'; import { fixedScriptWallet, utxolibCompat } from '@bitgo/wasm-utxo'; +import { getNetworkFromCoinName, UtxoCoinName } from '../names'; + import { SdkBackend } from './types'; type BufferEncoding = 'hex' | 'base64'; @@ -20,41 +22,43 @@ export function stringToBufferTryFormats(input: string, formats: BufferEncoding[ throw new Error('input must be a valid hex or base64 string'); } -function toNetworkName(network: utxolib.Network): utxolibCompat.UtxolibName { +function toNetworkName(coinName: UtxoCoinName): utxolibCompat.UtxolibName { + const network = getNetworkFromCoinName(coinName); const networkName = utxolib.getNetworkName(network); if (!networkName) { - throw new Error(`Invalid network: ${network}`); + throw new Error(`Invalid coinName: ${coinName}`); } return networkName; } export function decodePsbtWith( psbt: string | Buffer, - network: utxolib.Network, + coinName: UtxoCoinName, backend: 'utxolib' ): utxolib.bitgo.UtxoPsbt; export function decodePsbtWith( psbt: string | Buffer, - network: utxolib.Network, + coinName: UtxoCoinName, backend: 'wasm-utxo' ): fixedScriptWallet.BitGoPsbt; export function decodePsbtWith( psbt: string | Buffer, - network: utxolib.Network, + coinName: UtxoCoinName, backend: SdkBackend ): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt; export function decodePsbtWith( psbt: string | Buffer, - network: utxolib.Network, + coinName: UtxoCoinName, backend: SdkBackend ): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt { if (typeof psbt === 'string') { psbt = Buffer.from(psbt, 'hex'); } if (backend === 'utxolib') { + const network = getNetworkFromCoinName(coinName); return utxolib.bitgo.createPsbtFromBuffer(psbt, network); } else { - return fixedScriptWallet.BitGoPsbt.fromBytes(psbt, toNetworkName(network)); + return fixedScriptWallet.BitGoPsbt.fromBytes(psbt, toNetworkName(coinName)); } } diff --git a/modules/abstract-utxo/src/transaction/descriptor/explainPsbt.ts b/modules/abstract-utxo/src/transaction/descriptor/explainPsbt.ts index 1976cc0d5d..a7b277579d 100644 --- a/modules/abstract-utxo/src/transaction/descriptor/explainPsbt.ts +++ b/modules/abstract-utxo/src/transaction/descriptor/explainPsbt.ts @@ -4,10 +4,11 @@ import * as coreDescriptors from '@bitgo/utxo-core/descriptor'; import { toExtendedAddressFormat } from '../recipient'; import type { TransactionExplanationDescriptor } from '../fixedScript/explainTransaction'; +import { getCoinName, UtxoCoinName } from '../../names'; -function toRecipient(output: coreDescriptors.ParsedOutput, network: utxolib.Network): ITransactionRecipient { +function toRecipient(output: coreDescriptors.ParsedOutput, coinName: UtxoCoinName): ITransactionRecipient { return { - address: toExtendedAddressFormat(output.script, network), + address: toExtendedAddressFormat(output.script, coinName), amount: output.value.toString(), }; } @@ -41,14 +42,15 @@ export function explainPsbt( const changeOutputs = outputs.filter((o) => o.scriptId !== undefined); const fee = sumValues(inputs) - sumValues(outputs); const inputSignatures = getInputSignatures(psbt); + const coinName = getCoinName(psbt.network); return { inputSignatures, signatures: inputSignatures.reduce((a, b) => Math.min(a, b), Infinity), locktime: psbt.locktime, id: psbt.getUnsignedTx().getId(), - outputs: externalOutputs.map((o) => toRecipient(o, psbt.network)), + outputs: externalOutputs.map((o) => toRecipient(o, coinName)), outputAmount: sumValues(externalOutputs).toString(), - changeOutputs: changeOutputs.map((o) => toRecipient(o, psbt.network)), + changeOutputs: changeOutputs.map((o) => toRecipient(o, coinName)), changeAmount: sumValues(changeOutputs).toString(), fee: fee.toString(), }; diff --git a/modules/abstract-utxo/src/transaction/descriptor/parse.ts b/modules/abstract-utxo/src/transaction/descriptor/parse.ts index 3d3afdfbac..62cc2252e6 100644 --- a/modules/abstract-utxo/src/transaction/descriptor/parse.ts +++ b/modules/abstract-utxo/src/transaction/descriptor/parse.ts @@ -9,6 +9,7 @@ import { getDescriptorMapFromWallet, getPolicyForEnv } from '../../descriptor'; import { IDescriptorWallet } from '../../descriptor/descriptorWallet'; import { fromExtendedAddressFormatToScript, toExtendedAddressFormat } from '../recipient'; import { outputDifferencesWithExpected, OutputDifferenceWithExpected } from '../outputDifference'; +import { UtxoCoinName } from '../../names'; type ParsedOutput = coreDescriptors.ParsedOutput; @@ -16,11 +17,11 @@ export type RecipientOutput = Omit & { value: bigint | 'max'; }; -function toRecipientOutput(recipient: ITransactionRecipient, network: utxolib.Network): RecipientOutput { +function toRecipientOutput(recipient: ITransactionRecipient, coinName: UtxoCoinName): RecipientOutput { return { address: recipient.address, value: recipient.amount === 'max' ? 'max' : BigInt(recipient.amount), - script: fromExtendedAddressFormatToScript(recipient.address, network), + script: fromExtendedAddressFormatToScript(recipient.address, coinName), }; } @@ -49,15 +50,15 @@ function sumValues(arr: { value: bigint }[]): bigint { return arr.reduce((sum, e) => sum + e.value, BigInt(0)); } -function toBaseOutputs(outputs: ParsedOutput[], network: utxolib.Network): BaseOutput[]; -function toBaseOutputs(outputs: RecipientOutput[], network: utxolib.Network): BaseOutput[]; +function toBaseOutputs(outputs: ParsedOutput[], coinName: UtxoCoinName): BaseOutput[]; +function toBaseOutputs(outputs: RecipientOutput[], coinName: UtxoCoinName): BaseOutput[]; function toBaseOutputs( outputs: (ParsedOutput | RecipientOutput)[], - network: utxolib.Network + coinName: UtxoCoinName ): BaseOutput[] { return outputs.map( (o): BaseOutput => ({ - address: toExtendedAddressFormat(o.script, network), + address: toExtendedAddressFormat(o.script, coinName), amount: o.value === 'max' ? 'max' : BigInt(o.value), external: o.scriptId === undefined, }) @@ -68,18 +69,18 @@ export type ParsedOutputsBigInt = BaseParsedTransactionOutputs o.scriptId === undefined); const implicitExternalOutputs = implicitOutputs.filter((o) => o.scriptId === undefined); return { - outputs: toBaseOutputs(outputs, network), - changeOutputs: toBaseOutputs(changeOutputs, network), - explicitExternalOutputs: toBaseOutputs(explicitExternalOutputs, network), + outputs: toBaseOutputs(outputs, coinName), + changeOutputs: toBaseOutputs(changeOutputs, coinName), + explicitExternalOutputs: toBaseOutputs(explicitExternalOutputs, coinName), explicitExternalSpendAmount: sumValues(explicitExternalOutputs), - implicitExternalOutputs: toBaseOutputs(implicitExternalOutputs, network), + implicitExternalOutputs: toBaseOutputs(implicitExternalOutputs, coinName), implicitExternalSpendAmount: sumValues(implicitExternalOutputs), - missingOutputs: toBaseOutputs(missingOutputs, network), + missingOutputs: toBaseOutputs(missingOutputs, coinName), }; } @@ -87,15 +88,15 @@ export function toBaseParsedTransactionOutputsFromPsbt( psbt: utxolib.bitgo.UtxoPsbt, descriptorMap: coreDescriptors.DescriptorMap, recipients: ITransactionRecipient[], - network: utxolib.Network + coinName: UtxoCoinName ): ParsedOutputsBigInt { return toBaseParsedTransactionOutputs( parseOutputsWithPsbt( psbt, descriptorMap, - recipients.map((r) => toRecipientOutput(r, psbt.network)) + recipients.map((r) => toRecipientOutput(r, coinName)) ), - network + coinName ); } @@ -130,7 +131,7 @@ export function parse( const walletKeys = toBip32Triple(keychains); const descriptorMap = getDescriptorMapFromWallet(wallet, walletKeys, getPolicyForEnv(params.wallet.bitgo.env)); return { - ...toBaseParsedTransactionOutputsFromPsbt(psbt, descriptorMap, recipients, psbt.network), + ...toBaseParsedTransactionOutputsFromPsbt(psbt, descriptorMap, recipients, coin.name), keychains, keySignatures: getKeySignatures(wallet) ?? {}, customChange: undefined, diff --git a/modules/abstract-utxo/src/transaction/descriptor/verifyTransaction.ts b/modules/abstract-utxo/src/transaction/descriptor/verifyTransaction.ts index ca163570be..3774cfc068 100644 --- a/modules/abstract-utxo/src/transaction/descriptor/verifyTransaction.ts +++ b/modules/abstract-utxo/src/transaction/descriptor/verifyTransaction.ts @@ -4,6 +4,7 @@ import { DescriptorMap } from '@bitgo/utxo-core/descriptor'; import { AbstractUtxoCoin, VerifyTransactionOptions } from '../../abstractUtxoCoin'; import { BaseOutput, BaseParsedTransactionOutputs } from '../types'; +import { UtxoCoinName } from '../../names'; import { toBaseParsedTransactionOutputsFromPsbt } from './parse'; @@ -53,9 +54,9 @@ export function assertValidTransaction( psbt: utxolib.bitgo.UtxoPsbt, descriptors: DescriptorMap, recipients: ITransactionRecipient[], - network: utxolib.Network + coinName: UtxoCoinName ): void { - assertExpectedOutputDifference(toBaseParsedTransactionOutputsFromPsbt(psbt, descriptors, recipients, network)); + assertExpectedOutputDifference(toBaseParsedTransactionOutputsFromPsbt(psbt, descriptors, recipients, coinName)); } /** @@ -90,7 +91,7 @@ export async function verifyTransaction( ); } - assertValidTransaction(tx, descriptorMap, params.txParams.recipients ?? [], tx.network); + assertValidTransaction(tx, descriptorMap, params.txParams.recipients ?? [], coin.name); return true; } diff --git a/modules/abstract-utxo/src/transaction/explainTransaction.ts b/modules/abstract-utxo/src/transaction/explainTransaction.ts index 35711b5651..df88abc868 100644 --- a/modules/abstract-utxo/src/transaction/explainTransaction.ts +++ b/modules/abstract-utxo/src/transaction/explainTransaction.ts @@ -5,6 +5,7 @@ import { isTriple, IWallet, Triple } from '@bitgo/sdk-core'; import { getDescriptorMapFromWallet, isDescriptorWallet } from '../descriptor'; import { toBip32Triple } from '../keychains'; import { getPolicyForEnv } from '../descriptor/validatePolicy'; +import { UtxoCoinName } from '../names'; import { getReplayProtectionPubkeys } from './fixedScript/replayProtection'; import type { @@ -27,7 +28,7 @@ export function explainTx( txInfo?: { unspents?: utxolib.bitgo.Unspent[] }; changeInfo?: fixedScript.ChangeAddressInfo[]; }, - network: utxolib.Network + coinName: UtxoCoinName ): TransactionExplanationUtxolibLegacy | TransactionExplanationUtxolibPsbt | TransactionExplanationWasm { if (params.wallet && isDescriptorWallet(params.wallet)) { if (tx instanceof utxolib.bitgo.UtxoPsbt) { @@ -46,7 +47,7 @@ export function explainTx( throw new Error('legacy transactions are not supported for descriptor wallets'); } if (tx instanceof utxolib.bitgo.UtxoPsbt) { - return fixedScript.explainPsbt(tx, params, network); + return fixedScript.explainPsbt(tx, params, coinName); } else if (tx instanceof fixedScriptWallet.BitGoPsbt) { const pubs = params.pubs; if (!pubs) { @@ -63,10 +64,10 @@ export function explainTx( } return fixedScript.explainPsbtWasm(tx, walletXpubs, { replayProtection: { - publicKeys: getReplayProtectionPubkeys(network), + publicKeys: getReplayProtectionPubkeys(coinName), }, }); } else { - return fixedScript.explainLegacyTx(tx, params, network); + return fixedScript.explainLegacyTx(tx, params, coinName); } } diff --git a/modules/abstract-utxo/src/transaction/fetchInputs.ts b/modules/abstract-utxo/src/transaction/fetchInputs.ts index 79fb6b28da..01459430a4 100644 --- a/modules/abstract-utxo/src/transaction/fetchInputs.ts +++ b/modules/abstract-utxo/src/transaction/fetchInputs.ts @@ -2,14 +2,16 @@ import * as utxolib from '@bitgo/utxo-lib'; import { BitGoBase, IRequestTracer } from '@bitgo/sdk-core'; import { AbstractUtxoCoin, TransactionPrebuild } from '../abstractUtxoCoin'; +import { getNetworkFromCoinName, UtxoCoinName } from '../names'; /** * Get the inputs for a psbt from a prebuild. */ export function getPsbtTxInputs( psbtArg: string | utxolib.bitgo.UtxoPsbt, - network: utxolib.Network + coinName: UtxoCoinName ): { address: string; value: bigint; valueString: string }[] { + const network = getNetworkFromCoinName(coinName); const psbt = psbtArg instanceof utxolib.bitgo.UtxoPsbt ? psbtArg : utxolib.bitgo.createPsbtFromHex(psbtArg, network); const txInputs = psbt.txInputs; return psbt.data.inputs.map((input, index) => { diff --git a/modules/abstract-utxo/src/transaction/fixedScript/explainTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/explainTransaction.ts index 4c1625a6b3..81d4292eca 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/explainTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/explainTransaction.ts @@ -10,6 +10,7 @@ import type { Output, FixedScriptWalletOutput } from '../types'; import { toExtendedAddressFormat } from '../recipient'; import { getPayGoVerificationPubkey } from '../getPayGoVerificationPubkey'; import { toBip32Triple } from '../../keychains'; +import { getNetworkFromCoinName, UtxoCoinName } from '../../names'; // ===== Transaction Explanation Type Definitions ===== @@ -71,13 +72,13 @@ export type ChangeAddressInfo = { function toChangeOutput( txOutput: utxolib.TxOutput, - network: utxolib.Network, + coinName: UtxoCoinName, changeInfo: ChangeAddressInfo[] | undefined ): FixedScriptWalletOutput | undefined { if (!changeInfo) { return undefined; } - const address = toExtendedAddressFormat(txOutput.script, network); + const address = toExtendedAddressFormat(txOutput.script, coinName); const change = changeInfo.find((change) => change.address === address); if (!change) { return undefined; @@ -102,7 +103,7 @@ function explainCommon( customChangeInfo?: ChangeAddressInfo[]; feeInfo?: string; }, - network: utxolib.Network + coinName: UtxoCoinName ) { const displayOrder = ['id', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs']; const changeOutputs: FixedScriptWalletOutput[] = []; @@ -114,16 +115,16 @@ function explainCommon( tx.outs.forEach((currentOutput) => { // Try to encode the script pubkey with an address. If it fails, try to parse it as an OP_RETURN output with the prefix. // If that fails, then it is an unrecognized scriptPubkey and should fail - const currentAddress = toExtendedAddressFormat(currentOutput.script, network); + const currentAddress = toExtendedAddressFormat(currentOutput.script, coinName); const currentAmount = BigInt(currentOutput.value); - const changeOutput = toChangeOutput(currentOutput, network, changeInfo); + const changeOutput = toChangeOutput(currentOutput, coinName, changeInfo); if (changeOutput) { changeOutputs.push(changeOutput); return; } - const customChangeOutput = toChangeOutput(currentOutput, network, customChangeInfo); + const customChangeOutput = toChangeOutput(currentOutput, coinName, customChangeInfo); if (customChangeOutput) { customChangeOutputs.push(customChangeOutput); return; @@ -194,8 +195,9 @@ function getTxInputSignaturesCount( txInfo?: { unspents?: bitgo.Unspent[] }; pubs?: bitgo.RootWalletKeys | string[]; }, - network: utxolib.Network + coinName: UtxoCoinName ) { + const network = getNetworkFromCoinName(coinName); const prevOutputs = params.txInfo?.unspents?.map((u) => bitgo.toOutput(u, network)); const rootWalletKeys = getRootWalletKeys(params); const { unspents = [] } = params.txInfo ?? {}; @@ -273,7 +275,7 @@ function getChangeInfo(psbt: bitgo.UtxoPsbt, walletKeys?: Triple */ function getPayGoVerificationInfo( psbt: bitgo.UtxoPsbt, - network: utxolib.Network + coinName: UtxoCoinName ): { outputIndex: number; verificationPubkey: string } | undefined { let outputIndex: number | undefined = undefined; let address: string | undefined = undefined; @@ -283,12 +285,13 @@ function getPayGoVerificationInfo( } // This pulls the pubkey depending on given network - const verificationPubkey = getPayGoVerificationPubkey(network); + const verificationPubkey = getPayGoVerificationPubkey(coinName); // find which output index that contains the PayGo proof outputIndex = utxocore.paygo.getPayGoAddressProofOutputIndex(psbt); if (outputIndex === undefined || !verificationPubkey) { return undefined; } + const network = getNetworkFromCoinName(coinName); const output = psbt.txOutputs[outputIndex]; address = utxolib.address.fromOutputScript(output.script, network); if (!address) { @@ -304,7 +307,8 @@ function getPayGoVerificationInfo( * @returns An array of objects containing the message and address for each input, * or undefined if no BIP322 messages are found. */ -function getBip322MessageInfoAndVerify(psbt: bitgo.UtxoPsbt, network: utxolib.Network): Bip322Message[] | undefined { +function getBip322MessageInfoAndVerify(psbt: bitgo.UtxoPsbt, coinName: UtxoCoinName): Bip322Message[] | undefined { + const network = getNetworkFromCoinName(coinName); const bip322Messages: { message: string; address: string }[] = []; for (let i = 0; i < psbt.data.inputs.length; i++) { const message = bip322.getBip322ProofMessageAtIndex(psbt, i); @@ -375,7 +379,7 @@ function getBip322MessageInfoAndVerify(psbt: bitgo.UtxoPsbt, network: utxolib.Ne * * @param psbt {bitgo.UtxoPsbt} The PSBT to explain * @param pubs {bitgo.RootWalletKeys | string[]} The public keys to use for the explanation - * @param network {utxolib.Network} The network to use for the explanation + * @param coinName {UtxoCoinName} The coin name to use for the explanation * @param strict {boolean} Whether to throw an error if the PayGo address proof is invalid */ export function explainPsbt( @@ -384,10 +388,11 @@ export function explainPsbt( pubs?: bitgo.RootWalletKeys | string[]; customChangePubs?: bitgo.RootWalletKeys | string[]; }, - network: utxolib.Network, + coinName: UtxoCoinName, { strict = true }: { strict?: boolean } = {} ): TransactionExplanationUtxolibPsbt { - const payGoVerificationInfo = getPayGoVerificationInfo(psbt, network); + const network = getNetworkFromCoinName(coinName); + const payGoVerificationInfo = getPayGoVerificationInfo(psbt, coinName); if (payGoVerificationInfo) { try { utxocore.paygo.verifyPayGoAddressProof( @@ -403,13 +408,13 @@ export function explainPsbt( } } - const messages = getBip322MessageInfoAndVerify(psbt, network); + const messages = getBip322MessageInfoAndVerify(psbt, coinName); const changeInfo = getChangeInfo(psbt); const customChangeInfo = params.customChangePubs ? getChangeInfo(psbt, toBip32Triple(params.customChangePubs)) : undefined; const tx = psbt.getUnsignedTx(); - const common = explainCommon(tx, { ...params, changeInfo, customChangeInfo }, network); + const common = explainCommon(tx, { ...params, changeInfo, customChangeInfo }, coinName); const inputSignaturesCount = getPsbtInputSignaturesCount(psbt, params); // Set fee from subtracting inputs from outputs @@ -442,10 +447,10 @@ export function explainLegacyTx( txInfo?: { unspents?: bitgo.Unspent[] }; changeInfo?: { address: string; chain: number; index: number }[]; }, - network: utxolib.Network + coinName: UtxoCoinName ): TransactionExplanationUtxolibLegacy { - const common = explainCommon(tx, params, network); - const inputSignaturesCount = getTxInputSignaturesCount(tx, params, network); + const common = explainCommon(tx, params, coinName); + const inputSignaturesCount = getTxInputSignaturesCount(tx, params, coinName); return { ...common, inputSignatures: inputSignaturesCount, diff --git a/modules/abstract-utxo/src/transaction/fixedScript/parseTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/parseTransaction.ts index eeaf6167ad..caea6e5fe0 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/parseTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/parseTransaction.ts @@ -179,7 +179,7 @@ export async function parseTransaction( function toComparableOutputsWithExternal(outputs: Output[]): ComparableOutputWithExternal[] { return outputs.map((output) => ({ - script: fromExtendedAddressFormatToScript(output.address, coin.network), + script: fromExtendedAddressFormatToScript(output.address, coin.name), value: output.amount === 'max' ? 'max' : (BigInt(output.amount) as bigint | 'max'), external: output.external, })); @@ -225,7 +225,7 @@ export async function parseTransaction( function toOutputs(outputs: ComparableOutputWithExternal[]): Output[] { return outputs.map((output) => ({ - address: toExtendedAddressFormat(output.script, coin.network), + address: toExtendedAddressFormat(output.script, coin.name), amount: output.value.toString(), external: output.external, })); diff --git a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts index fbf58bfcb5..2f11151b9b 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/replayProtection.ts @@ -1,17 +1,18 @@ -import * as utxolib from '@bitgo/utxo-lib'; import { utxolibCompat } from '@bitgo/wasm-utxo'; +import { getNetworkFromCoinName, UtxoCoinName } from '../../names'; + 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: +export function getReplayProtectionPubkeys(coinName: UtxoCoinName): Buffer[] { + switch (coinName) { + case 'bch': + case 'bsv': return [pubkeyProd]; - case utxolib.networks.bitcoinsvTestnet: - case utxolib.networks.bitcoincashTestnet: + case 'tbsv': + case 'tbch': return [pubkeyTestnet]; } return []; @@ -27,21 +28,22 @@ const replayProtectionScriptsProd = [Buffer.from('a914174315cfde84f4c45395ac6f15 const replayProtectionScriptsTestnet = [Buffer.from('a914172dcc4e025361d951a9511c670973a4e3720c9887', 'hex')]; export function getReplayProtectionAddresses( - network: utxolib.Network, + coinName: UtxoCoinName, format: 'default' | 'cashaddr' = 'default' ): string[] { - switch (network) { - case utxolib.networks.bitcoincash: - case utxolib.networks.bitcoinsv: + const network = getNetworkFromCoinName(coinName); + switch (coinName) { + case 'bch': + case 'bsv': return replayProtectionScriptsProd.map((script) => utxolibCompat.fromOutputScript(script, network, format)); - case utxolib.networks.bitcoinsvTestnet: - case utxolib.networks.bitcoincashTestnet: + case 'tbsv': + case 'tbch': return replayProtectionScriptsTestnet.map((script) => utxolibCompat.fromOutputScript(script, network, format)); default: return []; } } -export function isReplayProtectionUnspent(u: { address: string }, network: utxolib.Network): boolean { - return getReplayProtectionAddresses(network).includes(u.address); +export function isReplayProtectionUnspent(u: { address: string }, coinName: UtxoCoinName): boolean { + return getReplayProtectionAddresses(coinName).includes(u.address); } diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts index c271422e2f..b21b40c0b8 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signLegacyTransaction.ts @@ -6,6 +6,8 @@ import { bitgo } from '@bitgo/utxo-lib'; import { isTriple, Triple } from '@bitgo/sdk-core'; import debugLib from 'debug'; +import { UtxoCoinName } from '../../names'; + import { getReplayProtectionAddresses } from './replayProtection'; import { InputSigningError, TransactionSigningError } from './SigningError'; @@ -25,6 +27,7 @@ type RootWalletKeys = utxolib.bitgo.RootWalletKeys; * @param transaction - wallet transaction (builder) to be signed * @param unspents - transaction unspents * @param walletSigner - signing parameters + * @param coinName - coin name for network-specific logic * @param isLastSignature - Returns full-signed transaction when true. Builds half-signed when false. * @param replayProtectionAddresses - List of replay protection addresses to skip signing */ @@ -32,6 +35,7 @@ export function signAndVerifyWalletTransaction( transaction: utxolib.bitgo.UtxoTransaction | utxolib.bitgo.UtxoTransactionBuilder, unspents: Unspent[], walletSigner: utxolib.bitgo.WalletUnspentSigner, + coinName: UtxoCoinName, { isLastSignature, replayProtectionAddresses, @@ -42,7 +46,7 @@ export function signAndVerifyWalletTransaction( ): utxolib.bitgo.UtxoTransaction { const network = transaction.network as utxolib.Network; if (replayProtectionAddresses === undefined) { - replayProtectionAddresses = getReplayProtectionAddresses(network); + replayProtectionAddresses = getReplayProtectionAddresses(coinName); } const prevOutputs = unspents.map((u) => toOutput(u, network)); @@ -116,6 +120,7 @@ export function signAndVerifyWalletTransaction( export function signLegacyTransaction( tx: utxolib.bitgo.UtxoTransaction, signerKeychain: BIP32Interface | undefined, + coinName: UtxoCoinName, params: { isLastSignature: boolean; signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined; @@ -148,7 +153,7 @@ export function signLegacyTransaction( assert(signerKeychain); const walletSigner = new bitgo.WalletUnspentSigner(keychains, signerKeychain, cosignerKeychain); - return signAndVerifyWalletTransaction(tx, params.txInfo.unspents, walletSigner, { + return signAndVerifyWalletTransaction(tx, params.txInfo.unspents, walletSigner, coinName, { isLastSignature: params.isLastSignature, }) as utxolib.bitgo.UtxoTransaction; } diff --git a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts index 7378f518b3..d4925e43dd 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts @@ -7,6 +7,8 @@ import { bitgo } from '@bitgo/utxo-lib'; import * as utxolib from '@bitgo/utxo-lib'; import { fixedScriptWallet } from '@bitgo/wasm-utxo'; +import { UtxoCoinName } from '../../names'; + import { Musig2Participant } from './musig2'; import { signLegacyTransaction } from './signLegacyTransaction'; import { signPsbtWithMusig2ParticipantUtxolib, signAndVerifyPsbt as signAndVerifyPsbtUtxolib } from './signPsbtUtxolib'; @@ -55,7 +57,7 @@ export async function signTransaction< coin: Musig2Participant | Musig2Participant, tx: T, signerKeychain: BIP32Interface | undefined, - network: utxolib.Network, + coinName: UtxoCoinName, params: { walletId: string | undefined; txInfo: { unspents?: utxolib.bitgo.Unspent[] } | undefined; @@ -101,7 +103,7 @@ export async function signTransaction< rootWalletKeys, { replayProtection: { - publicKeys: getReplayProtectionPubkeys(network), + publicKeys: getReplayProtectionPubkeys(coinName), }, signingStep: params.signingStep, walletId: params.walletId, @@ -114,7 +116,7 @@ export async function signTransaction< return signedPsbt; } - return signLegacyTransaction(tx, signerKeychain, { + return signLegacyTransaction(tx, signerKeychain, coinName, { isLastSignature, signingStep: params.signingStep, txInfo: params.txInfo, diff --git a/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts b/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts index a99dfee178..d9dd99a2f1 100644 --- a/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts +++ b/modules/abstract-utxo/src/transaction/fixedScript/verifyTransaction.ts @@ -174,7 +174,7 @@ export async function verifyTransaction( throw new Error(`txPrebuild.txHex not set`); } const inputs = isPsbt - ? getPsbtTxInputs(txPrebuild.txHex, coin.network).map((v) => ({ + ? getPsbtTxInputs(txPrebuild.txHex, coin.name).map((v) => ({ ...v, value: utxolib.bitgo.toTNumber(v.value, coin.amountType), })) diff --git a/modules/abstract-utxo/src/transaction/getPayGoVerificationPubkey.ts b/modules/abstract-utxo/src/transaction/getPayGoVerificationPubkey.ts index 6712093768..3de6396ac6 100644 --- a/modules/abstract-utxo/src/transaction/getPayGoVerificationPubkey.ts +++ b/modules/abstract-utxo/src/transaction/getPayGoVerificationPubkey.ts @@ -1,4 +1,4 @@ -import * as utxolib from '@bitgo/utxo-lib'; +import { isTestnetCoin, UtxoCoinName } from '../names'; const BITGOPAYGOATTESTATIONPUBKEY = 'xpub6BKRgmCPX5oQiJgJ6Vq6BF8tDvZhwQki5dVVQohckK2ZJXtxj8K6M9pavLwt9piW33hZz17SWmG8QWsjJ1tHdde2Fs5UA3DFbApCtbdaGKn'; @@ -6,13 +6,13 @@ const BITGOPAYGOATTESTATIONPUBKEY = /** * We want to return the verification pubkey from our statics that has our * verification pubkey. - * @param network + * @param coinName * @returns */ -export function getPayGoVerificationPubkey(network: utxolib.Network): string | undefined { - if (utxolib.isTestnet(network)) { +export function getPayGoVerificationPubkey(coinName: UtxoCoinName): string | undefined { + if (isTestnetCoin(coinName)) { return BITGOPAYGOATTESTATIONPUBKEY; - } else if (utxolib.isMainnet(network)) { + } else { return undefined; } } diff --git a/modules/abstract-utxo/src/transaction/recipient.ts b/modules/abstract-utxo/src/transaction/recipient.ts index 4bf2d9d388..a5dee328c2 100644 --- a/modules/abstract-utxo/src/transaction/recipient.ts +++ b/modules/abstract-utxo/src/transaction/recipient.ts @@ -1,5 +1,7 @@ import * as utxolib from '@bitgo/utxo-lib'; +import { getNetworkFromCoinName, UtxoCoinName } from '../names'; + const ScriptRecipientPrefix = 'scriptPubKey:'; /** @@ -22,21 +24,23 @@ export function fromExtendedAddressFormat(extendedAddress: string): { address: s return { address: extendedAddress }; } -export function fromExtendedAddressFormatToScript(extendedAddress: string, network: utxolib.Network): Buffer { +export function fromExtendedAddressFormatToScript(extendedAddress: string, coinName: UtxoCoinName): Buffer { const result = fromExtendedAddressFormat(extendedAddress); if ('script' in result) { return Buffer.from(result.script, 'hex'); } + const network = getNetworkFromCoinName(coinName); return utxolib.addressFormat.toOutputScriptTryFormats(result.address, network); } /** * Convert a script or address to the extended address format. * @param script - * @param network + * @param coinName * @returns if the script is an OP_RETURN script, then it will be prefixed with `scriptPubKey:`, otherwise it will be converted to an address. */ -export function toExtendedAddressFormat(script: Buffer, network: utxolib.Network): string { +export function toExtendedAddressFormat(script: Buffer, coinName: UtxoCoinName): string { + const network = getNetworkFromCoinName(coinName); return script[0] === utxolib.opcodes.OP_RETURN ? `${ScriptRecipientPrefix}${script.toString('hex')}` : utxolib.address.fromOutputScript(script, network); diff --git a/modules/abstract-utxo/src/transaction/signTransaction.ts b/modules/abstract-utxo/src/transaction/signTransaction.ts index 15c2d69949..65d962750a 100644 --- a/modules/abstract-utxo/src/transaction/signTransaction.ts +++ b/modules/abstract-utxo/src/transaction/signTransaction.ts @@ -64,7 +64,7 @@ export async function signTransaction( throw new Error('expected a UtxoPsbt object'); } } else { - const signedTx = await fixedScript.signTransaction(coin, tx, getSignerKeychain(params.prv), coin.network, { + const signedTx = await fixedScript.signTransaction(coin, tx, getSignerKeychain(params.prv), coin.name, { walletId: params.txPrebuild.walletId, txInfo: params.txPrebuild.txInfo, isLastSignature: params.isLastSignature ?? false, diff --git a/modules/abstract-utxo/test/unit/address.ts b/modules/abstract-utxo/test/unit/address.ts index 1fa2964b09..5a4ee10950 100644 --- a/modules/abstract-utxo/test/unit/address.ts +++ b/modules/abstract-utxo/test/unit/address.ts @@ -82,7 +82,7 @@ function run(coin: AbstractUtxoCoin) { const addresses = getParameters().map((p) => { const label = { chain: p.chain === undefined ? 'default' : p.chain }; try { - return [label, generateAddress(coin.network, p)]; + return [label, generateAddress(coin.name, p)]; } catch (e) { return [label, { error: e.message }]; } @@ -94,11 +94,11 @@ function run(coin: AbstractUtxoCoin) { it('validates and verifies generated addresses', function () { getParameters().forEach((p) => { if (p.chain && !coin.supportsAddressChain(p.chain)) { - assert.throws(() => generateAddress(coin.network, p)); + assert.throws(() => generateAddress(coin.name, p)); return; } - const address = generateAddress(coin.network, p); + const address = generateAddress(coin.name, p); coin.isValidAddress(address).should.eql(true); if (address !== address.toUpperCase()) { coin.isValidAddress(address.toUpperCase()).should.eql(false); @@ -110,7 +110,7 @@ function run(coin: AbstractUtxoCoin) { it('defaults to canonical address', function () { getParameters().forEach((p) => { if (!p.chain || coin.supportsAddressChain(p.chain)) { - const address = generateAddress(coin.network, p); + const address = generateAddress(coin.name, p); coin.canonicalAddress(address).should.eql(address); } }); @@ -135,12 +135,12 @@ function run(coin: AbstractUtxoCoin) { const params = { keychains, chain }; // Generate with cashaddr format - const addressCashaddr = generateAddress(coin.network, { ...params, format: 'cashaddr' }); + const addressCashaddr = generateAddress(coin.name, { ...params, format: 'cashaddr' }); coin.isValidAddress(addressCashaddr).should.eql(true); addressCashaddr.should.startWith(expectedPrefix, `cashaddr should start with ${expectedPrefix}`); // Generate with base58 format explicitly - const addressBase58 = generateAddress(coin.network, { ...params, format: 'base58' }); + const addressBase58 = generateAddress(coin.name, { ...params, format: 'base58' }); coin.isValidAddress(addressBase58).should.eql(true); addressBase58.should.not.match(/.*:.*/, 'base58 should not contain colon separator'); @@ -154,8 +154,8 @@ function run(coin: AbstractUtxoCoin) { if (p.chain && (!coin.supportsAddressChain(p.chain) || !otherCoin.supportsAddressChain(p.chain))) { return; } - const address = generateAddress(coin.network, p); - const otherAddress = generateAddress(otherCoin.network, p); + const address = generateAddress(coin.name, p); + const otherAddress = generateAddress(otherCoin.name, p); (address === otherAddress).should.eql(isCompatibleAddress(coin, otherCoin)); coin.isValidAddress(otherAddress).should.eql(isCompatibleAddress(coin, otherCoin)); }); diff --git a/modules/abstract-utxo/test/unit/customChangeWallet.ts b/modules/abstract-utxo/test/unit/customChangeWallet.ts index 1d8a1f49f9..e6a6ca4761 100644 --- a/modules/abstract-utxo/test/unit/customChangeWallet.ts +++ b/modules/abstract-utxo/test/unit/customChangeWallet.ts @@ -41,7 +41,7 @@ describe('Custom Change Wallets', () => { threshold: 2, }; - const changeAddress = generateAddress(coin.network, addressData); + const changeAddress = generateAddress(coin.name, addressData); const changeWalletId = 'changeWalletId'; const stubData = { diff --git a/modules/abstract-utxo/test/unit/prebuildAndSign.ts b/modules/abstract-utxo/test/unit/prebuildAndSign.ts index 6fe553c03f..aacbe59961 100644 --- a/modules/abstract-utxo/test/unit/prebuildAndSign.ts +++ b/modules/abstract-utxo/test/unit/prebuildAndSign.ts @@ -313,7 +313,7 @@ utxoCoins } run(coin, [inputScript, inputScript], 'psbt'); - if (getReplayProtectionAddresses(coin.network).length) { + if (getReplayProtectionAddresses(coin.name).length) { run(coin, ['p2shP2pk', inputScript], 'psbt'); } }); diff --git a/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts b/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts index a52eef1bcc..f44ec6d461 100644 --- a/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts +++ b/modules/abstract-utxo/test/unit/recovery/crossChainRecovery.ts @@ -99,14 +99,14 @@ function run(sourceCoin: AbstractUtxoC const recoveryWalletId = '5abacebe28d72fbd07e0b8cbba0ff39e'; // the address the accidental deposit went to, in both sourceCoin and addressCoin formats const [depositAddressSourceCoin, depositAddressRecoveryCoin] = [sourceCoin, recoveryCoin].map((coin) => ({ - address: generateAddress(coin.network, { keychains: keychainsBase58, chain: 0, index: 0 }), + address: generateAddress(coin.name, { keychains: keychainsBase58, chain: 0, index: 0 }), chain: 0, index: 0, })); const chain = 0; const index = 1; // the address where we want to recover our funds to - const recoveryAddress = generateAddress(sourceCoin.network, { + const recoveryAddress = generateAddress(sourceCoin.name, { keychains: keychainsBase58, chain, index, @@ -318,8 +318,6 @@ describe(`Cross-Chain Recovery getWallet`, async function () { }); describe('convertLtcAddressToLegacyFormat', function () { - const ltcNetwork = utxolib.networks.litecoin; - it('should convert M... P2SH address to 3... legacy format', function () { // These two addresses represent the same underlying script hash: // - MNQ7zkgMsaV67rsjA3JuP59RC5wxRXpwgE is the LTC format (scriptHash 0x32) @@ -327,13 +325,13 @@ describe('convertLtcAddressToLegacyFormat', function () { const ltcAddress = 'MNQ7zkgMsaV67rsjA3JuP59RC5wxRXpwgE'; const expectedLegacyAddress = '3GBygsGPvTdfKMbq4AKZZRu1sPMWPEsBfd'; - const legacyAddress = convertLtcAddressToLegacyFormat(ltcAddress, ltcNetwork); + const legacyAddress = convertLtcAddressToLegacyFormat(ltcAddress, 'ltc'); assert.strictEqual(legacyAddress, expectedLegacyAddress); }); it('should convert MD68PsdheKxcYsrVLyZRXgoSDLnB1MdVtE to legacy format', function () { const address = 'MD68PsdheKxcYsrVLyZRXgoSDLnB1MdVtE'; - const legacyAddress = convertLtcAddressToLegacyFormat(address, ltcNetwork); + const legacyAddress = convertLtcAddressToLegacyFormat(address, 'ltc'); // Should start with '3' (legacy BTC P2SH format) assert.ok(legacyAddress.startsWith('3'), `Expected address to start with '3', got: ${legacyAddress}`); @@ -341,7 +339,7 @@ describe('convertLtcAddressToLegacyFormat', function () { it('should not modify bech32 addresses', function () { const bech32Address = 'ltc1qgrl8zpndsklaa9swgd5vevyxmx5x63vcrl7dk4'; - const result = convertLtcAddressToLegacyFormat(bech32Address, ltcNetwork); + const result = convertLtcAddressToLegacyFormat(bech32Address, 'ltc'); assert.strictEqual(result, bech32Address); }); }); diff --git a/modules/abstract-utxo/test/unit/transaction.ts b/modules/abstract-utxo/test/unit/transaction.ts index 7e228b02e6..20536661fc 100644 --- a/modules/abstract-utxo/test/unit/transaction.ts +++ b/modules/abstract-utxo/test/unit/transaction.ts @@ -176,7 +176,7 @@ describe(`UTXO coin signTransaction`, async function () { const unspentSum = inputs.reduce((prev: bigint, curr) => prev + curr.value, BigInt(0)); const outputs: testutil.Output[] = [{ scriptType: 'p2sh', value: unspentSum - BigInt(1000) }]; const psbt = testutil.constructPsbt(inputs, outputs, coin.network, rootWalletKeys, 'unsigned', { - p2shP2pkKey: getReplayProtectionPubkeys(coin.network)[0], + p2shP2pkKey: getReplayProtectionPubkeys(coin.name)[0], }); for (const v of [false, true]) { @@ -319,7 +319,7 @@ function run( } function getOutputAddress(rootWalletKeys: utxolib.bitgo.RootWalletKeys): string { - return generateAddress(coin.network, { + return generateAddress(coin.name, { keychains: rootWalletKeys.triple.map((k) => ({ pub: k.neutered().toBase58() })), }); } @@ -404,7 +404,7 @@ function run( { address: getOutputAddress(getWalletKeys('test')), value: unspentSum - BigInt(1000) }, ]; const psbt = testutil.constructPsbt(inputs, outputs, coin.network, walletKeys, 'unsigned', { - p2shP2pkKey: getReplayProtectionPubkeys(coin.network)[0], + p2shP2pkKey: getReplayProtectionPubkeys(coin.name)[0], }); utxolib.bitgo.addXpubsToPsbt(psbt, walletKeys); return psbt; @@ -653,13 +653,13 @@ function runTestForCoin(coin: AbstractUtxoCoin) { } run(coin, [type, type], txFormat); - if (getReplayProtectionAddresses(coin.network).length) { + if (getReplayProtectionAddresses(coin.name).length) { run(coin, ['p2shP2pk', type], txFormat); } if (txFormat === 'psbt') { run(coin, [type, type], txFormat, { decodeWith: 'wasm-utxo' }); - if (getReplayProtectionAddresses(coin.network).length) { + if (getReplayProtectionAddresses(coin.name).length) { run(coin, ['p2shP2pk', type], txFormat, { decodeWith: 'wasm-utxo' }); } } diff --git a/modules/abstract-utxo/test/unit/transaction/descriptor/parse.ts b/modules/abstract-utxo/test/unit/transaction/descriptor/parse.ts index ae095a5fee..9287c4fbc5 100644 --- a/modules/abstract-utxo/test/unit/transaction/descriptor/parse.ts +++ b/modules/abstract-utxo/test/unit/transaction/descriptor/parse.ts @@ -78,7 +78,7 @@ describe('parse', function () { psbt, getDescriptorMap('Wsh2Of3', getDefaultXPubs('a')), recipients.map(toBaseOutputString), - psbt.network + 'btc' ); } diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts index 56c885afe5..05b3de54f6 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/explainPsbt.ts @@ -6,6 +6,7 @@ import { fixedScriptWallet, Triple } from '@bitgo/wasm-utxo'; import type { TransactionExplanation } from '../../../../src/transaction/fixedScript/explainTransaction'; import { explainPsbt, explainPsbtWasm } from '../../../../src/transaction/fixedScript'; +import { getCoinName } from '../../../../src/names'; function describeTransactionWith(acidTest: testutil.AcidTest) { describe(`${acidTest.name}`, function () { @@ -17,7 +18,8 @@ function describeTransactionWith(acidTest: testutil.AcidTest) { let refExplanation: TransactionExplanation; before('prepare', function () { psbt = acidTest.createPsbt(); - refExplanation = explainPsbt(psbt, { pubs: acidTest.rootWalletKeys }, acidTest.network, { + const coinName = getCoinName(acidTest.network); + refExplanation = explainPsbt(psbt, { pubs: acidTest.rootWalletKeys }, coinName, { strict: true, }); psbtBytes = psbt.toBuffer(); @@ -41,10 +43,11 @@ function describeTransactionWith(acidTest: testutil.AcidTest) { }); it('reference implementation should support custom change outputs', function () { + const coinName = getCoinName(acidTest.network); const customChangeExplanation = explainPsbt( psbt, { pubs: acidTest.rootWalletKeys, customChangePubs: acidTest.otherWalletKeys }, - acidTest.network, + coinName, { strict: true } ); assert.ok(customChangeExplanation.customChangeOutputs); diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts index 21c1e66e95..818e0471e7 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/parsePsbt.ts @@ -119,7 +119,7 @@ function describeParseTransactionWith( let explanation: TransactionExplanation; if (txFormat === 'psbt') { if (backend === 'utxolib') { - explanation = explainPsbt(psbt, { pubs: acidTest.rootWalletKeys }, acidTest.network, { + explanation = explainPsbt(psbt, { pubs: acidTest.rootWalletKeys }, coinName, { strict: true, }); } else if (backend === 'wasm') { @@ -143,7 +143,7 @@ function describeParseTransactionWith( const pubs = acidTest.rootWalletKeys.triple.map((k) => k.neutered().toBase58()); // Extract change info from PSBT to pass to explainLegacyTx const changeInfo = getChangeInfoFromPsbt(psbt); - explanation = explainLegacyTx(tx, { pubs, changeInfo }, acidTest.network); + explanation = explainLegacyTx(tx, { pubs, changeInfo }, coinName); } else { throw new Error(`Invalid txFormat: ${txFormat}`); } diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts index f8f2a62b83..6af7e42ca3 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/replayProtection.ts @@ -8,6 +8,7 @@ import { pubkeyProd, pubkeyTestnet, } from '../../../../src/transaction/fixedScript/replayProtection'; +import { UtxoCoinName } from '../../../../src/names'; function createReplayProtectionOutputScript(pubkey: Buffer): Buffer { const descriptor = Descriptor.fromString(`sh(pk(${pubkey.toString('hex')}))`, 'definite'); @@ -17,9 +18,10 @@ function createReplayProtectionOutputScript(pubkey: Buffer): Buffer { describe('replayProtection', function () { it('should have scriptPubKeys that match descriptor computation', function () { for (const pubkey of [pubkeyProd, pubkeyTestnet]) { + const coinName: UtxoCoinName = pubkey === pubkeyProd ? 'bch' : 'tbch'; const network = pubkey === pubkeyProd ? utxolib.networks.bitcoincash : utxolib.networks.bitcoincashTestnet; const expectedScript = createReplayProtectionOutputScript(pubkey); - const actualAddresses = getReplayProtectionAddresses(network); + const actualAddresses = getReplayProtectionAddresses(coinName); assert.equal(actualAddresses.length, 1); const actualScript = Buffer.from(utxolibCompat.toOutputScript(actualAddresses[0], network)); assert.deepStrictEqual(actualScript, expectedScript); diff --git a/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts b/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts index 7842f87e13..b7959dc1bd 100644 --- a/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts +++ b/modules/abstract-utxo/test/unit/transaction/fixedScript/signPsbt.ts @@ -11,6 +11,7 @@ import { signPsbtWithMusig2ParticipantWasm, } from '../../../../src/transaction/fixedScript/signPsbtWasm'; import { SdkBackend } from '../../../../src/transaction/types'; +import { getCoinName } from '../../../../src/names'; function getMockCoinUtxolib(keys: utxolib.bitgo.RootWalletKeys): Musig2Participant { return { @@ -101,7 +102,8 @@ function describeSignPsbtWithMusig2Participant( describe(`${acidTest.name} ${decodeWith}`, function () { it('should sign unsigned psbt to halfsigned', async function () { // Create unsigned PSBT - const psbt = decodePsbtWith(acidTest.createPsbt().toBuffer(), acidTest.network, decodeWith); + const coinName = getCoinName(acidTest.network); + const psbt = decodePsbtWith(acidTest.createPsbt().toBuffer(), coinName, decodeWith); let result; if (decodeWith === 'utxolib') { diff --git a/modules/abstract-utxo/test/unit/util/unspents.ts b/modules/abstract-utxo/test/unit/util/unspents.ts index 24ac209f68..674e61c4c7 100644 --- a/modules/abstract-utxo/test/unit/util/unspents.ts +++ b/modules/abstract-utxo/test/unit/util/unspents.ts @@ -3,6 +3,7 @@ import { getSeed } from '@bitgo/sdk-test'; import * as wasmUtxo from '@bitgo/wasm-utxo'; import { getReplayProtectionAddresses } from '../../../src'; +import { getCoinName } from '../../../src/names'; const { scriptTypeForChain, chainCodesP2sh, getExternalChainCode, getInternalChainCode } = utxolib.bitgo; @@ -77,7 +78,8 @@ export function mockUnspentReplayProtection { - const addresses = getReplayProtectionAddresses(network); + const coinName = getCoinName(network); + const addresses = getReplayProtectionAddresses(coinName); if (addresses.length) { const address = addresses[0]; return {