Skip to content
Draft
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
104 changes: 16 additions & 88 deletions modules/abstract-utxo/src/abstractUtxoCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { randomBytes } from 'crypto';

import _ from 'lodash';
import * as utxolib from '@bitgo/utxo-lib';
import { BIP32, fixedScriptWallet } from '@bitgo/wasm-utxo';
import { BIP32, fixedScriptWallet, hasPsbtMagic } from '@bitgo/wasm-utxo';
import { bitgo, getMainnet } from '@bitgo/utxo-lib';
import {
AddressCoinSpecific,
Expand Down Expand Up @@ -86,13 +86,8 @@ import {
UtxoCoinNameMainnet,
} from './names';
import { assertFixedScriptWalletAddress } from './address/fixedScript';
import { isSdkBackend, ParsedTransaction, SdkBackend } from './transaction/types';
import {
decodeDescriptorPsbt,
decodePsbtWith,
encodeTransaction,
stringToBufferTryFormats,
} from './transaction/decode';
import { ParsedTransaction } from './transaction/types';
import { decodeDescriptorPsbt, decodePsbt, encodeTransaction, stringToBufferTryFormats } from './transaction/decode';
import { fetchKeychains, toBip32Triple, UtxoKeychain } from './keychains';
import { verifyKeySignature, verifyUserPublicKey } from './verifyKey';
import { getPolicyForEnv } from './descriptor/validatePolicy';
Expand Down Expand Up @@ -250,7 +245,6 @@ export interface ExplainTransactionOptions<TNumber extends number | bigint = num
feeInfo?: string;
pubs?: Triple<string>;
customChangeXpubs?: Triple<string>;
decodeWith?: SdkBackend;
}

export interface DecoratedExplainTransactionOptions<TNumber extends number | bigint = number>
Expand All @@ -263,7 +257,6 @@ export type UtxoNetwork = utxolib.Network;
export interface TransactionPrebuild<TNumber extends number | bigint = number> extends BaseTransactionPrebuild {
txInfo?: TransactionInfo<TNumber>;
blockHeight?: number;
decodeWith?: SdkBackend;
/**
* PSBT-lite hex present only in pending approval flows, where another user's send fixed the format.
* Not set in regular /tx/build responses (where the caller controls the build parameters).
Expand Down Expand Up @@ -331,7 +324,6 @@ type UtxoBaseSignTransactionOptions<TNumber extends number | bigint = number> =
walletId?: string;
txHex: string;
txInfo?: TransactionInfo<TNumber>;
decodeWith?: SdkBackend;
};
/** xpubs triple for wallet (user, backup, bitgo). Required only when txPrebuild.txHex is not a PSBT */
pubs?: Triple<string>;
Expand Down Expand Up @@ -420,10 +412,7 @@ export interface SignPsbtResponse {
psbt: string;
}

export abstract class AbstractUtxoCoin
extends BaseCoin
implements Musig2Participant<utxolib.bitgo.UtxoPsbt>, Musig2Participant<fixedScriptWallet.BitGoPsbt>
{
export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Participant<fixedScriptWallet.BitGoPsbt> {
abstract name: UtxoCoinName;

/**
Expand All @@ -437,23 +426,11 @@ export abstract class AbstractUtxoCoin

public readonly amountType: 'number' | 'bigint';

protected supportedTxFormats: { psbt: boolean; legacy: boolean } = {
psbt: true,
legacy: false,
};

protected supportedSdkBackends: { utxolib: boolean; 'wasm-utxo': boolean } = {
utxolib: false,
'wasm-utxo': true,
};

protected constructor(bitgo: BitGoBase, amountType: 'number' | 'bigint' = 'number') {
super(bitgo);
this.amountType = amountType;
}

defaultSdkBackend: SdkBackend = 'wasm-utxo';

/**
* @deprecated - will be removed when we drop support for utxolib
* Use `name` property instead.
Expand Down Expand Up @@ -627,65 +604,29 @@ export abstract class AbstractUtxoCoin
return utxolib.bitgo.createTransactionFromHex<TNumber>(hex, this.network, this.amountType);
}

decodeTransaction<TNumber extends number | bigint>(
input: Buffer | string,
decodeWith: SdkBackend = this.defaultSdkBackend
): DecodedTransaction<TNumber> {
if (typeof input === 'string') {
const buffer = stringToBufferTryFormats(input, ['hex', 'base64']);
return this.decodeTransaction(buffer, decodeWith);
}

if (utxolib.bitgo.isPsbt(input)) {
if (this.supportedSdkBackends[decodeWith] !== true) {
throw new Error(`SDK support for decodeWith=${decodeWith} is not available on this environment.`);
}

if (!this.supportedTxFormats.psbt) {
throw new ErrorDeprecatedTxFormat('psbt');
}
return decodePsbtWith(input, this.name, decodeWith);
} else {
// Legacy format transactions are deprecated. This will be an unconditional error in the future.
if (!this.supportedTxFormats.legacy) {
throw new ErrorDeprecatedTxFormat('legacy');
}
if (decodeWith !== 'utxolib') {
console.error('received decodeWith hint %s, ignoring for legacy transaction', decodeWith);
}
return utxolib.bitgo.createTransactionFromBuffer(input, this.network, {
amountType: this.amountType,
});
decodeTransaction(input: Buffer | string): fixedScriptWallet.BitGoPsbt {
const buffer = typeof input === 'string' ? stringToBufferTryFormats(input, ['hex', 'base64']) : input;
if (!hasPsbtMagic(buffer)) {
throw new ErrorDeprecatedTxFormat('legacy');
}
return decodePsbt(buffer, this.name);
}

decodeTransactionAsPsbt(input: Buffer | string): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt {
const decoded = this.decodeTransaction(input);
if (decoded instanceof fixedScriptWallet.BitGoPsbt || decoded instanceof utxolib.bitgo.UtxoPsbt) {
return decoded;
}
throw new Error('expected psbt but got transaction');
decodeTransactionAsPsbt(input: Buffer | string): fixedScriptWallet.BitGoPsbt {
return this.decodeTransaction(input);
}

decodeTransactionFromPrebuild<TNumber extends number | bigint>(prebuild: {
decodeTransactionFromPrebuild(prebuild: {
txHex?: string;
txBase64?: string;
/** See TransactionPrebuild.txHexPsbt — only present in pending approval flows. */
txHexPsbt?: string;
decodeWith?: string;
}): DecodedTransaction<TNumber> {
}): fixedScriptWallet.BitGoPsbt {
const string = prebuild.txHexPsbt ?? prebuild.txHex ?? prebuild.txBase64;
if (!string) {
throw new Error('missing required txHex or txBase64 property');
}
let { decodeWith } = prebuild;
if (decodeWith !== undefined) {
if (typeof decodeWith !== 'string' || !isSdkBackend(decodeWith)) {
console.error('decodeWith %s is not a valid value, using default', decodeWith);
decodeWith = undefined;
}
}
return this.decodeTransaction(string, decodeWith);
return this.decodeTransaction(string);
}

/**
Expand Down Expand Up @@ -837,26 +778,13 @@ export abstract class AbstractUtxoCoin
* @param psbt all MuSig2 inputs should contain user MuSig2 nonce
* @param walletId
*/
async getMusig2Nonces(psbt: utxolib.bitgo.UtxoPsbt, walletId: string): Promise<utxolib.bitgo.UtxoPsbt>;
async getMusig2Nonces(psbt: fixedScriptWallet.BitGoPsbt, walletId: string): Promise<fixedScriptWallet.BitGoPsbt>;
async getMusig2Nonces<T extends utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt>(
psbt: T,
walletId: string
): Promise<T>;
async getMusig2Nonces<T extends utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt>(
psbt: T,
walletId: string
): Promise<T> {
async getMusig2Nonces(psbt: fixedScriptWallet.BitGoPsbt, walletId: string): Promise<fixedScriptWallet.BitGoPsbt> {
const buffer = encodeTransaction(psbt);
const response = await this.bitgo
.post(this.url('/wallet/' + walletId + '/tx/signpsbt'))
.send({ psbt: buffer.toString('hex') })
.result();
if (psbt instanceof utxolib.bitgo.UtxoPsbt) {
return decodePsbtWith(response.psbt, this.name, 'utxolib') as T;
} else {
return decodePsbtWith(response.psbt, this.name, 'wasm-utxo') as T;
}
return decodePsbt(response.psbt, this.name);
}

/**
Expand Down
1 change: 0 additions & 1 deletion modules/abstract-utxo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export * from './config';
export * from './names';
export * from './recovery';
export * from './transaction/fixedScript/replayProtection';
export * from './transaction/fixedScript/signLegacyTransaction';
export * from './unspent';

export { UtxoWallet } from './wallet';
Expand Down
46 changes: 5 additions & 41 deletions modules/abstract-utxo/src/transaction/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fixedScriptWallet, hasPsbtMagic, Psbt as WasmPsbt, utxolibCompat } from

import { getNetworkFromCoinName, UtxoCoinName } from '../names';

import { SdkBackend, BitGoPsbt } from './types';
import { BitGoPsbt } from './types';

type BufferEncoding = 'hex' | 'base64';

Expand Down Expand Up @@ -31,39 +31,11 @@ function toNetworkName(coinName: UtxoCoinName): utxolibCompat.UtxolibName {
return networkName;
}

export function decodePsbtWith(
psbt: string | Buffer,
coinName: UtxoCoinName,
backend: 'utxolib'
): utxolib.bitgo.UtxoPsbt;
export function decodePsbtWith(
psbt: string | Buffer,
coinName: UtxoCoinName,
backend: 'wasm-utxo'
): fixedScriptWallet.BitGoPsbt;
export function decodePsbtWith(
psbt: string | Buffer,
coinName: UtxoCoinName,
backend: SdkBackend
): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt;
export function decodePsbtWith(
psbt: string | Buffer,
coinName: UtxoCoinName,
backend: SdkBackend
): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt {
export function decodePsbt(psbt: string | Buffer, coinName: UtxoCoinName): 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(coinName));
}
}

export function decodePsbt(psbt: string | Buffer, coinName: UtxoCoinName): BitGoPsbt {
return decodePsbtWith(psbt, coinName, 'wasm-utxo');
return fixedScriptWallet.BitGoPsbt.fromBytes(psbt, toNetworkName(coinName));
}

export type PrebuildLike = {
Expand All @@ -90,14 +62,6 @@ export function decodeDescriptorPsbt(prebuild: PrebuildLike): WasmPsbt {
return WasmPsbt.deserialize(bytes);
}

export function encodeTransaction(
transaction: utxolib.bitgo.UtxoTransaction<bigint | number> | utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt
): Buffer {
if (transaction instanceof utxolib.bitgo.UtxoTransaction) {
return transaction.toBuffer();
} else if (transaction instanceof utxolib.bitgo.UtxoPsbt) {
return transaction.toBuffer();
} else {
return Buffer.from(transaction.serialize());
}
export function encodeTransaction(transaction: fixedScriptWallet.BitGoPsbt): Buffer {
return Buffer.from(transaction.serialize());
}
25 changes: 6 additions & 19 deletions modules/abstract-utxo/src/transaction/explainTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as utxolib from '@bitgo/utxo-lib';
import { fixedScriptWallet, Psbt as WasmPsbt } from '@bitgo/wasm-utxo';
import { isTriple, IWallet, Triple } from '@bitgo/sdk-core';

Expand All @@ -9,11 +8,7 @@ import { UtxoCoinName } from '../names';
import type { Unspent } from '../unspent';

import { getReplayProtectionPubkeys } from './fixedScript/replayProtection';
import type {
TransactionExplanationUtxolibLegacy,
TransactionExplanationUtxolibPsbt,
TransactionExplanationWasm,
} from './fixedScript/explainTransaction';
import type { TransactionExplanationUtxolibPsbt, TransactionExplanationWasm } from './fixedScript/explainTransaction';
import * as fixedScript from './fixedScript';
import * as descriptor from './descriptor';

Expand All @@ -22,16 +17,15 @@ import * as descriptor from './descriptor';
* change amounts, and transaction outputs.
*/
export function explainTx<TNumber extends number | bigint>(
tx: utxolib.bitgo.UtxoTransaction<TNumber> | utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt | WasmPsbt,
tx: fixedScriptWallet.BitGoPsbt | WasmPsbt,
params: {
wallet?: IWallet;
pubs?: string[];
customChangeXpubs?: Triple<string>;
txInfo?: { unspents?: Unspent<TNumber>[] };
changeInfo?: fixedScript.ChangeAddressInfo[];
},
coinName: UtxoCoinName
): TransactionExplanationUtxolibLegacy | TransactionExplanationUtxolibPsbt | TransactionExplanationWasm {
): TransactionExplanationUtxolibPsbt | TransactionExplanationWasm {
if (params.wallet && isDescriptorWallet(params.wallet)) {
if (!(tx instanceof WasmPsbt)) {
throw new Error('descriptor wallets require PSBT format transactions');
Expand All @@ -43,19 +37,12 @@ export function explainTx<TNumber extends number | bigint>(
const descriptors = getDescriptorMapFromWallet(params.wallet, walletKeys, getPolicyForEnv(params.wallet.bitgo.env));
return descriptor.explainPsbt(tx, descriptors, coinName);
}
if (tx instanceof utxolib.bitgo.UtxoPsbt) {
return fixedScript.explainPsbt(tx, { ...params, customChangePubs: params.customChangeXpubs }, coinName);
} else if (tx instanceof fixedScriptWallet.BitGoPsbt) {
if (tx instanceof fixedScriptWallet.BitGoPsbt) {
const pubs = params.pubs;
if (!pubs) {
throw new Error('pub triple is required');
}
const walletXpubs: Triple<string> | undefined =
pubs instanceof utxolib.bitgo.RootWalletKeys
? (pubs.triple.map((k) => k.neutered().toBase58()) as Triple<string>)
: isTriple(pubs)
? (pubs as Triple<string>)
: undefined;
const walletXpubs: Triple<string> | undefined = isTriple(pubs) ? (pubs as Triple<string>) : undefined;
if (!walletXpubs) {
throw new Error('pub triple must be valid triple or RootWalletKeys');
}
Expand All @@ -68,6 +55,6 @@ export function explainTx<TNumber extends number | bigint>(
} else if (tx instanceof WasmPsbt) {
throw new Error('descriptor Psbt is only supported for descriptor wallets');
} else {
return fixedScript.explainLegacyTx(tx, params, coinName);
throw new Error('unsupported transaction type');
}
}
Loading
Loading