Skip to content
Merged
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
26 changes: 15 additions & 11 deletions modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -80,6 +80,7 @@ import {
getFullNameFromCoinName,
getMainnetCoinName,
getNetworkFromCoinName,
isTestnetCoin,
UtxoCoinName,
UtxoCoinNameMainnet,
} from './names';
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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))
);
Expand All @@ -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',
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -862,7 +866,7 @@ export abstract class AbstractUtxoCoin
* @returns {boolean}
*/
isBitGoTaintedUnspent<TNumber extends number | bigint>(unspent: Unspent<TNumber>): boolean {
return isReplayProtectionUnspent<TNumber>(unspent, this.network);
return isReplayProtectionUnspent(unspent, this.name);
}

/**
Expand All @@ -873,7 +877,7 @@ export abstract class AbstractUtxoCoin
override async explainTransaction<TNumber extends number | bigint = number>(
params: ExplainTransactionOptions<TNumber>
): Promise<TransactionExplanation> {
return explainTx(this.decodeTransactionFromPrebuild(params), params, this.network);
return explainTx(this.decodeTransactionFromPrebuild(params), params, this.name);
}

/**
Expand Down Expand Up @@ -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';
}

Expand Down
23 changes: 13 additions & 10 deletions modules/abstract-utxo/src/address/fixedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<string>,
chain: bitgo.ChainCode,
index: number,
Expand All @@ -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);
}

Expand All @@ -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;
Expand Down Expand Up @@ -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`);
Expand All @@ -120,7 +123,7 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS
}

return generateAddressWithChainAndIndex(
network,
coinName,
keychains.map((k) => k.pub) as Triple<string>,
derivationChain,
derivationIndex,
Expand All @@ -133,7 +136,7 @@ type Keychain = {
};

export function assertFixedScriptWalletAddress(
network: utxolib.Network,
coinName: UtxoCoinName,
{
chain,
index,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -15,7 +16,7 @@ class DescriptorAddressMismatchError extends Error {
}

export function assertDescriptorWalletAddress(
network: utxolib.Network,
coinName: UtxoCoinName,
params: VerifyAddressOptions<UtxoCoinSpecific>,
descriptors: DescriptorMap
): void {
Expand All @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions modules/abstract-utxo/src/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
}
Expand All @@ -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');
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNetworkFromChain } from '../names';
import { UtxoCoinName } from '../names';

import { OfflineVaultSignable } from './OfflineVaultSignable';
import { DescriptorTransaction, getTransactionExplanationFromPsbt } from './descriptor';
Expand All @@ -19,12 +19,12 @@ export interface TransactionExplanation<TFee> {
};
}

export function getTransactionExplanation(coin: string, tx: unknown): TransactionExplanation<string> {
export function getTransactionExplanation(coinName: UtxoCoinName, tx: unknown): TransactionExplanation<string> {
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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) })],
Expand All @@ -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' });
Expand All @@ -44,8 +46,9 @@ export function getHalfSignedPsbt(

export function getTransactionExplanationFromPsbt(
tx: DescriptorTransaction,
network: utxolib.Network
coinName: UtxoCoinName
): TransactionExplanation<string> {
const network = getNetworkFromCoinName(coinName);
const psbt = utxolib.bitgo.createPsbtDecode(tx.coinSpecific.txHex, network);
const descriptorMap = getDescriptorsFromDescriptorTransaction(tx);
const { outputs, changeOutputs, fee } = explainPsbt(psbt, descriptorMap);
Expand Down
13 changes: 7 additions & 6 deletions modules/abstract-utxo/src/recovery/backupKeyRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -110,21 +111,21 @@ 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
* @returns The formatted address (with cashaddr prefix stripped for BCH/BCHA)
*/
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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Loading