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
34 changes: 21 additions & 13 deletions modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,8 @@ import {
} from './recovery';
import { getReplayProtectionPubkeys, isReplayProtectionUnspent } from './transaction/fixedScript/replayProtection';
import { supportedCrossChainRecoveries } from './config';
import {
assertValidTransactionRecipient,
explainTx,
fromExtendedAddressFormat,
isScriptRecipient,
parseTransaction,
verifyTransaction,
} from './transaction';
import { explainTx, parseTransaction, verifyTransaction } from './transaction';
import { AddressCodec } from './transaction/recipient';
import type { TransactionExplanation } from './transaction/fixedScript/explainTransaction';
import { Musig2Participant } from './transaction/fixedScript/musig2';
import {
Expand Down Expand Up @@ -517,7 +511,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
if (address === undefined) {
return recipient; // Already { script, amount } — pass through unchanged
}
return { ...rest, ...fromExtendedAddressFormat(address) };
return { ...rest, ...AddressCodec.fromExtendedAddressFormat(address) };
})
: params.recipients;
}
Expand All @@ -538,8 +532,8 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
}

checkRecipient(recipient: { address?: string; amount: number | string }): void {
assertValidTransactionRecipient(recipient);
if (recipient.address && !isScriptRecipient(recipient.address)) {
AddressCodec.assertValidTransactionRecipient(recipient);
if (recipient.address && !AddressCodec.isScriptRecipient(recipient.address)) {
super.checkRecipient({ address: recipient.address, amount: recipient.amount });
}
}
Expand Down Expand Up @@ -602,7 +596,14 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
async parseTransaction<TNumber extends number | bigint = number>(
params: ParseTransactionOptions<TNumber>
): Promise<ParsedTransaction<TNumber>> {
return parseTransaction(this, params);
return this.parseTransactionWithAddressCodec(params, new AddressCodec(this.name));
}

protected parseTransactionWithAddressCodec<TNumber extends number | bigint>(
params: ParseTransactionOptions<TNumber>,
addressCodec: AddressCodec
): Promise<ParsedTransaction<TNumber>> {
return parseTransaction(this, params, addressCodec);
}

/**
Expand Down Expand Up @@ -637,9 +638,16 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
*/
async verifyTransaction<TNumber extends number | bigint = number>(
params: VerifyTransactionOptions<TNumber>
): Promise<boolean> {
return this.verifyTransactionWithAddressCodec(params, new AddressCodec(this.name));
}

protected async verifyTransactionWithAddressCodec<TNumber extends number | bigint>(
params: VerifyTransactionOptions<TNumber>,
addressCodec: AddressCodec
): Promise<boolean> {
try {
return await verifyTransaction(this, this.bitgo, params);
return await verifyTransaction(this, this.bitgo, params, addressCodec);
} catch (error) {
if (error instanceof AggregateValidationError) {
const txExplanation = await TxIntentMismatchError.tryGetTxExplanation(
Expand Down
4 changes: 2 additions & 2 deletions modules/abstract-utxo/src/impl/bch/bch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { address as wasmAddress } from '@bitgo/wasm-utxo';

import { AbstractUtxoCoin } from '../../abstractUtxoCoin';
import { UtxoCoinName } from '../../names';
import { isScriptRecipient } from '../../transaction';
import { AddressCodec } from '../../transaction';

export class Bch extends AbstractUtxoCoin {
readonly name: UtxoCoinName = 'bch';
Expand All @@ -29,7 +29,7 @@ export class Bch extends AbstractUtxoCoin {
* @returns {*} address string
*/
canonicalAddress(address: string, version: unknown = 'base58'): string {
if (isScriptRecipient(address)) {
if (AddressCodec.isScriptRecipient(address)) {
return address;
}

Expand Down
44 changes: 22 additions & 22 deletions modules/abstract-utxo/src/transaction/descriptor/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import { BaseOutput, BaseParsedTransaction, BaseParsedTransactionOutputs } from
import { getKeySignatures, toBip32Triple, UtxoNamedKeychains } from '../../keychains';
import { getDescriptorMapFromWallet, getPolicyForEnv } from '../../descriptor';
import { IDescriptorWallet } from '../../descriptor/descriptorWallet';
import { fromExtendedAddressFormatToScript, toExtendedAddressFormat } from '../recipient';
import { AddressCodec } from '../recipient';
import { outputDifferencesWithExpected, OutputDifferenceWithExpected } from '../outputDifference';
import { UtxoCoinName } from '../../names';
import { decodeDescriptorPsbt } from '../decode';

function sumValues(arr: { value: bigint }[]): bigint {
Expand All @@ -21,11 +20,11 @@ export type RecipientOutput = Omit<ParsedOutput, 'value'> & {
value: bigint | 'max';
};

function toRecipientOutput(recipient: ITransactionRecipient, coinName: UtxoCoinName): RecipientOutput {
function toRecipientOutput(recipient: ITransactionRecipient, addressCodec: AddressCodec): RecipientOutput {
return {
address: recipient.address,
value: recipient.amount === 'max' ? 'max' : BigInt(recipient.amount),
script: fromExtendedAddressFormatToScript(recipient.address, coinName),
script: addressCodec.fromExtendedAddressFormatToScript(recipient.address),
scriptId: undefined, // Recipients are external outputs
};
}
Expand All @@ -40,9 +39,9 @@ function parseOutputsWithPsbt(
psbt: Psbt,
descriptorMap: descriptorWallet.DescriptorMap,
recipientOutputs: RecipientOutput[],
coinName: UtxoCoinName
addressCodec: AddressCodec
): ParsedOutputs {
const parsed = descriptorWallet.parse(psbt, descriptorMap, coinName);
const parsed = descriptorWallet.parse(psbt, descriptorMap, addressCodec.coinName);
const outputs: ParsedOutput[] = parsed.outputs.map((output) => ({
...output,
script: Buffer.from(output.script),
Expand All @@ -56,15 +55,15 @@ function parseOutputsWithPsbt(
};
}

function toBaseOutputs(outputs: ParsedOutput[], coinName: UtxoCoinName): BaseOutput<bigint>[];
function toBaseOutputs(outputs: RecipientOutput[], coinName: UtxoCoinName): BaseOutput<bigint | 'max'>[];
function toBaseOutputs(outputs: ParsedOutput[], addressCodec: AddressCodec): BaseOutput<bigint>[];
function toBaseOutputs(outputs: RecipientOutput[], addressCodec: AddressCodec): BaseOutput<bigint | 'max'>[];
function toBaseOutputs(
outputs: (ParsedOutput | RecipientOutput)[],
coinName: UtxoCoinName
addressCodec: AddressCodec
): BaseOutput<bigint | 'max'>[] {
return outputs.map(
(o): BaseOutput<bigint | 'max'> => ({
address: toExtendedAddressFormat(o.script, coinName),
address: addressCodec.toExtendedAddressFormat(o.script),
amount: o.value === 'max' ? 'max' : BigInt(o.value),
external: o.scriptId === undefined,
})
Expand All @@ -75,36 +74,36 @@ export type ParsedOutputsBigInt = BaseParsedTransactionOutputs<bigint, BaseOutpu

function toBaseParsedTransactionOutputs(
{ outputs, changeOutputs, explicitOutputs, implicitOutputs, missingOutputs }: ParsedOutputs,
coinName: UtxoCoinName
addressCodec: AddressCodec
): ParsedOutputsBigInt {
const explicitExternalOutputs = explicitOutputs.filter((o) => o.scriptId === undefined);
const implicitExternalOutputs = implicitOutputs.filter((o) => o.scriptId === undefined);
return {
outputs: toBaseOutputs(outputs, coinName),
changeOutputs: toBaseOutputs(changeOutputs, coinName),
explicitExternalOutputs: toBaseOutputs(explicitExternalOutputs, coinName),
outputs: toBaseOutputs(outputs, addressCodec),
changeOutputs: toBaseOutputs(changeOutputs, addressCodec),
explicitExternalOutputs: toBaseOutputs(explicitExternalOutputs, addressCodec),
explicitExternalSpendAmount: sumValues(explicitExternalOutputs),
implicitExternalOutputs: toBaseOutputs(implicitExternalOutputs, coinName),
implicitExternalOutputs: toBaseOutputs(implicitExternalOutputs, addressCodec),
implicitExternalSpendAmount: sumValues(implicitExternalOutputs),
missingOutputs: toBaseOutputs(missingOutputs, coinName),
missingOutputs: toBaseOutputs(missingOutputs, addressCodec),
};
}

export function toBaseParsedTransactionOutputsFromPsbt(
psbt: Psbt | Uint8Array,
descriptorMap: descriptorWallet.DescriptorMap,
recipients: ITransactionRecipient[],
coinName: UtxoCoinName
addressCodec: AddressCodec
): ParsedOutputsBigInt {
const wasmPsbt = psbt instanceof Psbt ? psbt : Psbt.deserialize(psbt);
return toBaseParsedTransactionOutputs(
parseOutputsWithPsbt(
wasmPsbt,
descriptorMap,
recipients.map((r) => toRecipientOutput(r, coinName)),
coinName
recipients.map((r) => toRecipientOutput(r, addressCodec)),
addressCodec
),
coinName
addressCodec
);
}

Expand All @@ -116,7 +115,8 @@ export type ParsedDescriptorTransaction<TAmount extends number | bigint> = BaseP
export function parse(
coin: AbstractUtxoCoin,
wallet: IDescriptorWallet,
params: ParseTransactionOptions<number | bigint>
params: ParseTransactionOptions<number | bigint>,
addressCodec: AddressCodec = new AddressCodec(coin.name)
): ParsedDescriptorTransaction<bigint> {
if (params.txParams.allowExternalChangeAddress) {
throw new Error('allowExternalChangeAddress is not supported for descriptor wallets');
Expand All @@ -136,7 +136,7 @@ export function parse(
const walletKeys = toBip32Triple(keychains);
const descriptorMap = getDescriptorMapFromWallet(wallet, walletKeys, getPolicyForEnv(params.wallet.bitgo.env));
return {
...toBaseParsedTransactionOutputsFromPsbt(wasmPsbt, descriptorMap, recipients, coin.name),
...toBaseParsedTransactionOutputsFromPsbt(wasmPsbt, descriptorMap, recipients, addressCodec),
keychains,
keySignatures: getKeySignatures(wallet) ?? {},
customChange: undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AbstractUtxoCoin, ParseTransactionOptions } from '../../abstractUtxoCoin';
import { BaseOutput, BaseParsedTransaction } from '../types';
import { IDescriptorWallet } from '../../descriptor/descriptorWallet';
import { AddressCodec } from '../recipient';

import { parse, ParsedDescriptorTransaction } from './parse';

Expand Down Expand Up @@ -76,9 +77,10 @@ export function parsedDescriptorTransactionToTNumber<TAmount extends number | bi
export function parseToAmountType<TAmount extends number | bigint>(
coin: AbstractUtxoCoin,
wallet: IDescriptorWallet,
params: ParseTransactionOptions<TAmount>
params: ParseTransactionOptions<TAmount>,
addressCodec: AddressCodec = new AddressCodec(coin.name)
): BaseParsedTransaction<TAmount, BaseOutput<string>> {
return parsedDescriptorTransactionToTNumber<TAmount, BaseOutput<string>>(parse(coin, wallet, params), {
return parsedDescriptorTransactionToTNumber<TAmount, BaseOutput<string>>(parse(coin, wallet, params, addressCodec), {
amountTypeAggregate: coin.amountType,
amountTypeBaseOutput: 'string',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Psbt, descriptorWallet } from '@bitgo/wasm-utxo';

import { AbstractUtxoCoin, VerifyTransactionOptions } from '../../abstractUtxoCoin';
import { BaseOutput, BaseParsedTransactionOutputs } from '../types';
import { UtxoCoinName } from '../../names';
import { AddressCodec } from '../recipient';
import { decodeDescriptorPsbt } from '../decode';

import { toBaseParsedTransactionOutputsFromPsbt } from './parse';
Expand Down Expand Up @@ -54,9 +54,9 @@ export function assertValidTransaction(
psbt: Psbt | Uint8Array,
descriptors: descriptorWallet.DescriptorMap,
recipients: ITransactionRecipient[],
coinName: UtxoCoinName
addressCodec: AddressCodec
): void {
assertExpectedOutputDifference(toBaseParsedTransactionOutputsFromPsbt(psbt, descriptors, recipients, coinName));
assertExpectedOutputDifference(toBaseParsedTransactionOutputsFromPsbt(psbt, descriptors, recipients, addressCodec));
}

/**
Expand All @@ -74,7 +74,8 @@ export function assertValidTransaction(
export async function verifyTransaction<TNumber extends number | bigint>(
coin: AbstractUtxoCoin,
params: VerifyTransactionOptions<TNumber>,
descriptorMap: descriptorWallet.DescriptorMap
descriptorMap: descriptorWallet.DescriptorMap,
addressCodec: AddressCodec = new AddressCodec(coin.name)
): Promise<boolean> {
let psbt: Psbt;
try {
Expand All @@ -94,13 +95,13 @@ export async function verifyTransaction<TNumber extends number | bigint>(
);
}

assertValidTransaction(psbt, descriptorMap, params.txParams.recipients ?? [], coin.name);
assertValidTransaction(psbt, descriptorMap, params.txParams.recipients ?? [], addressCodec);

const parsedOutputs = toBaseParsedTransactionOutputsFromPsbt(
psbt,
descriptorMap,
params.txParams.recipients ?? [],
coin.name
addressCodec
);

if (params.txParams.qr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

import { AbstractUtxoCoin } from '../../abstractUtxoCoin';
import { Output, FixedScriptWalletOutput } from '../types';
import { fromExtendedAddressFormatToScript } from '../recipient';
import type { AddressCodec } from '../recipient';

const debug = debugLib('bitgo:v2:parseoutput');

Expand Down Expand Up @@ -199,6 +199,7 @@ export interface ParseOutputOptions {
verification: VerificationOptions;
keychainArray: Triple<{ pub: string }>;
wallet: IWallet;
addressCodec: AddressCodec;
txParams: {
recipients: ITransactionRecipient[];
changeAddress?: string;
Expand All @@ -214,6 +215,7 @@ export async function parseOutput({
verification,
keychainArray,
wallet,
addressCodec,
txParams,
customChange,
reqId,
Expand Down Expand Up @@ -280,9 +282,9 @@ export async function parseOutput({
*/
if (txParams.recipients !== undefined && txParams.recipients.length > RECIPIENT_THRESHOLD) {
const isCurrentAddressInRecipients = txParams.recipients.some((recipient) =>
fromExtendedAddressFormatToScript(recipient.address, coin.name).equals(
fromExtendedAddressFormatToScript(currentAddress, coin.name)
)
addressCodec
.fromExtendedAddressFormatToScript(recipient.address)
.equals(addressCodec.fromExtendedAddressFormatToScript(currentAddress))
);

if (isCurrentAddressInRecipients) {
Expand Down
Loading
Loading