Skip to content

Commit 96f4834

Browse files
committed
Merge remote-tracking branch 'origin/master' into master-into-rel-latest
2 parents 7044366 + ab43513 commit 96f4834

75 files changed

Lines changed: 4683 additions & 1274 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/prompts/code-review.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ You are an expert code reviewer for the BitGoJS cryptocurrency wallet SDK. Pleas
88
- Proper validation of transaction parameters
99
- Safe handling of private keys and sensitive data
1010

11+
## Internal Information Leakage (Public Repository)
12+
Comments and strings should describe what the code does, not the dev process. Flag in comments, JSDoc, test names, and error/log strings:
13+
- Verification/testing metadata (dates, "dry-run confirmed", "verified/tested on", investigation notes)
14+
- Internal team/system names or codenames (e.g. "by WP"), infra, or tooling
15+
- Internal ticket IDs or links to internal-only docs
16+
- Rationale on how/why a change was made rather than code behavior
17+
18+
For each, suggest a behavior-only rewrite.
19+
1120
## Code Quality & Architecture
1221
- Adherence to BitGoJS coding standards and patterns
1322
- TypeScript type safety and interface compliance
@@ -35,8 +44,9 @@ You are an expert code reviewer for the BitGoJS cryptocurrency wallet SDK. Pleas
3544

3645
Please provide constructive feedback focusing on:
3746
1. Critical issues that must be addressed
38-
2. Suggestions for improvement
39-
3. Questions about design decisions
40-
4. Acknowledgment of good practices
47+
2. Internal-information leaks in comments or strings (must be removed before merge)
48+
3. Suggestions for improvement
49+
4. Questions about design decisions
50+
5. Acknowledgment of good practices
4151

4252
Be thorough but concise, and explain the reasoning behind your suggestions.

.github/workflows/npmjs-release.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,13 @@ jobs:
236236
run: |
237237
yarn install --frozen-lockfile
238238
239+
- name: Install retry
240+
uses: BitGo/install-github-release-binary@v2
241+
with:
242+
targets: EricCrosson/retry@v1.4.8:sha256-d207746ff0eda67c706df25e88c02520f0cf3172279eb8eec8224fb0d3558911
243+
239244
- name: Run yarn audit
240-
run: |
241-
yarn run audit-high
245+
run: retry --up-to 2x --every 3s -- yarn run audit-high --retry-on-network-failure
242246

243247
- name: Run dependency check
244248
run: |

.github/workflows/publish.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ jobs:
3636
- name: Install BitGoJS
3737
run: sfw yarn install --with-frozen-lockfile
3838

39+
- name: Install retry
40+
uses: BitGo/install-github-release-binary@v2
41+
with:
42+
targets: EricCrosson/retry@v1.4.8:sha256-d207746ff0eda67c706df25e88c02520f0cf3172279eb8eec8224fb0d3558911
43+
3944
- name: Audit Dependencies
40-
run: yarn run improved-yarn-audit --min-severity high
45+
run: retry --up-to 2x --every 3s -- yarn run improved-yarn-audit --min-severity high --retry-on-network-failure
4146

4247
- name: Set Environment Variable for Alpha
4348
if: github.ref != 'refs/heads/master' # only publish changes if on feature branches

modules/abstract-utxo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
},
7979
"devDependencies": {
8080
"@bitgo/sdk-test": "^9.1.50",
81+
"@bitgo/utxo-lib": "^11.22.1",
8182
"mocha": "^10.2.0"
8283
},
8384
"gitHead": "18e460ddf02de2dbf13c2aa243478188fb539f0c"

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 34 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import assert from 'assert';
22
import { randomBytes } from 'crypto';
33

44
import _ from 'lodash';
5-
import * as utxolib from '@bitgo/utxo-lib';
6-
import { BIP32, fixedScriptWallet, hasPsbtMagic } from '@bitgo/wasm-utxo';
7-
import { bitgo, getMainnet } from '@bitgo/utxo-lib';
5+
import { address as wasmAddress, BIP32, fixedScriptWallet, hasPsbtMagic } from '@bitgo/wasm-utxo';
86
import {
97
AddressCoinSpecific,
108
BaseCoin,
@@ -62,7 +60,6 @@ import { getReplayProtectionPubkeys, isReplayProtectionUnspent } from './transac
6260
import { supportedCrossChainRecoveries } from './config';
6361
import {
6462
assertValidTransactionRecipient,
65-
DecodedTransaction,
6663
explainTx,
6764
fromExtendedAddressFormat,
6865
isScriptRecipient,
@@ -77,14 +74,7 @@ import {
7774
ErrorImplicitExternalOutputs,
7875
} from './transaction/descriptor/verifyTransaction';
7976
import { assertDescriptorWalletAddress, getDescriptorMapFromWallet, isDescriptorWallet } from './descriptor';
80-
import {
81-
getFullNameFromCoinName,
82-
getMainnetCoinName,
83-
getNetworkFromCoinName,
84-
isMainnetCoin,
85-
UtxoCoinName,
86-
UtxoCoinNameMainnet,
87-
} from './names';
77+
import { getFullNameFromCoinName, getMainnetCoinName, isMainnetCoin, UtxoCoinName, UtxoCoinNameMainnet } from './names';
8878
import { assertFixedScriptWalletAddress } from './address/fixedScript';
8979
import { ParsedTransaction } from './transaction/types';
9080
import { decodeDescriptorPsbt, decodePsbt, encodeTransaction, stringToBufferTryFormats } from './transaction/decode';
@@ -96,7 +86,7 @@ import { isUtxoWalletData, UtxoWallet } from './wallet';
9686
import { isDescriptorWalletData } from './descriptor/descriptorWallet';
9787
import type { Unspent } from './unspent';
9888

99-
import ScriptType2Of3 = utxolib.bitgo.outputScripts.ScriptType2Of3;
89+
type ScriptType2Of3 = 'p2sh' | 'p2shP2wsh' | 'p2wsh' | 'p2tr' | 'p2trMusig2';
10090

10191
export type TxFormat =
10292
// This is a legacy transaction format based around the bitcoinjs-lib serialization of unsigned transactions
@@ -142,28 +132,19 @@ type UtxoCustomSigningFunction<TNumber extends number | bigint> = {
142132
}): Promise<SignedTransaction>;
143133
};
144134

145-
const { isChainCode, scriptTypeForChain, outputScripts } = bitgo;
146-
147135
/**
148136
* Check if a decoded transaction has at least one taproot key path spend (MuSig2) input.
149-
* Works for both utxolib UtxoPsbt and wasm-utxo BitGoPsbt.
150137
*/
151-
function hasKeyPathSpendInput<TNumber extends number | bigint>(
152-
tx: DecodedTransaction<TNumber>,
138+
function hasKeyPathSpendInput(
139+
tx: fixedScriptWallet.BitGoPsbt,
153140
pubs: string[] | undefined,
154141
coinName: UtxoCoinName
155142
): boolean {
156-
if (tx instanceof bitgo.UtxoPsbt) {
157-
return bitgo.isTransactionWithKeyPathSpendInput(tx);
158-
}
159-
if (tx instanceof fixedScriptWallet.BitGoPsbt) {
160-
assert(pubs && isTriple(pubs), 'pub triple is required to check for key path spend inputs in wasm-utxo PSBT');
161-
const rootWalletKeys = fixedScriptWallet.RootWalletKeys.fromXpubs(pubs);
162-
const replayProtection = { publicKeys: getReplayProtectionPubkeys(coinName) };
163-
const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, { replayProtection });
164-
return parsed.inputs.some((input) => input.scriptType === 'p2trMusig2KeyPath');
165-
}
166-
return false;
143+
assert(pubs && isTriple(pubs), 'pub triple is required to check for key path spend inputs in wasm-utxo PSBT');
144+
const rootWalletKeys = fixedScriptWallet.RootWalletKeys.fromXpubs(pubs);
145+
const replayProtection = { publicKeys: getReplayProtectionPubkeys(coinName) };
146+
const parsed = tx.parseTransactionWithWalletKeys(rootWalletKeys, { replayProtection });
147+
return parsed.inputs.some((input) => input.scriptType === 'p2trMusig2KeyPath');
167148
}
168149

169150
/**
@@ -216,8 +197,6 @@ function convertValidationErrorToTxIntentMismatch(
216197

217198
export type { DecodedTransaction } from './transaction/types';
218199

219-
export type RootWalletKeys = bitgo.RootWalletKeys;
220-
221200
export type UtxoCoinSpecific = AddressCoinSpecific | DescriptorAddressCoinSpecific;
222201

223202
export interface VerifyAddressOptions<TCoinSpecific extends UtxoCoinSpecific> extends BaseVerifyAddressOptions {
@@ -252,8 +231,6 @@ export interface DecoratedExplainTransactionOptions<TNumber extends number | big
252231
changeInfo?: { address: string; chain: number; index: number }[];
253232
}
254233

255-
export type UtxoNetwork = utxolib.Network;
256-
257234
export interface TransactionPrebuild<TNumber extends number | bigint = number> extends BaseTransactionPrebuild {
258235
txInfo?: TransactionInfo<TNumber>;
259236
blockHeight?: number;
@@ -335,11 +312,6 @@ type UtxoBaseSignTransactionOptions<TNumber extends number | bigint = number> =
335312
* When false, creates half-signed transaction with placeholder signatures.
336313
*/
337314
isLastSignature?: boolean;
338-
/**
339-
* If true, allows signing a non-segwit input with a witnessUtxo instead requiring a previous
340-
* transaction (nonWitnessUtxo)
341-
*/
342-
allowNonSegwitSigningWithoutPrevTx?: boolean;
343315
/**
344316
* When true, the signed transaction will be converted from PSBT to legacy format before returning.
345317
* Set automatically by presignTransaction() when the caller explicitly requested txFormat: 'legacy'.
@@ -432,14 +404,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
432404
this.amountType = amountType;
433405
}
434406

435-
/**
436-
* @deprecated - will be removed when we drop support for utxolib
437-
* Use `name` property instead.
438-
*/
439-
get network(): utxolib.Network {
440-
return getNetworkFromCoinName(this.name);
441-
}
442-
443407
getChain(): UtxoCoinName {
444408
return this.name;
445409
}
@@ -455,13 +419,8 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
455419
/** Indicates whether the coin supports a block target */
456420
supportsBlockTarget(): boolean {
457421
// FIXME: the SDK does not seem to use this anywhere so it is unclear what the purpose of this method is
458-
switch (getMainnet(this.network)) {
459-
case utxolib.networks.bitcoin:
460-
case utxolib.networks.dogecoin:
461-
return true;
462-
default:
463-
return false;
464-
}
422+
const mainnet = getMainnetCoinName(this.name);
423+
return mainnet === 'btc' || mainnet === 'doge';
465424
}
466425

467426
sweepWithSendMany(): boolean {
@@ -470,7 +429,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
470429

471430
/** @deprecated */
472431
static get validAddressTypes(): ScriptType2Of3[] {
473-
return [...outputScripts.scriptTypes2Of3];
432+
return ['p2sh', 'p2shP2wsh', 'p2wsh', 'p2tr', 'p2trMusig2'];
474433
}
475434

476435
/**
@@ -508,15 +467,20 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
508467
// At the time of writing, the only additional address format is bch cashaddr.
509468
const anyFormat = (param as { anyFormat: boolean } | undefined)?.anyFormat ?? true;
510469
try {
511-
// Find out if the address is valid for any format. Tries all supported formats by default.
512-
// Throws if address cannot be decoded with any format.
513-
const [format, script] = utxolib.addressFormat.toOutputScriptAndFormat(address, this.network);
514-
// unless anyFormat is set, only 'default' is allowed.
515-
if (!anyFormat && format !== 'default') {
516-
return false;
470+
const script = wasmAddress.toOutputScriptWithCoin(address, this.name);
471+
// Determine which format the input address was in by round-tripping
472+
// through each candidate and checking byte-equality. 'default' is tried
473+
// first so canonical default-format addresses early-exit.
474+
for (const format of ['default', 'cashaddr'] as const) {
475+
try {
476+
if (wasmAddress.fromOutputScriptWithCoin(script, this.name, format) === address) {
477+
return anyFormat || format === 'default';
478+
}
479+
} catch {
480+
// coin doesn't support this format; try the next one
481+
}
517482
}
518-
// make sure that address is in normal representation for given format.
519-
return address === utxolib.addressFormat.fromOutputScriptWithFormat(script, format, this.network);
483+
return false;
520484
} catch (e) {
521485
return false;
522486
}
@@ -596,13 +560,9 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
596560
* @param addressDetails
597561
*/
598562
static inferAddressType(addressDetails: { chain: number }): ScriptType2Of3 | null {
599-
return isChainCode(addressDetails.chain) ? scriptTypeForChain(addressDetails.chain) : null;
600-
}
601-
602-
createTransactionFromHex<TNumber extends number | bigint = number>(
603-
hex: string
604-
): utxolib.bitgo.UtxoTransaction<TNumber> {
605-
return utxolib.bitgo.createTransactionFromHex<TNumber>(hex, this.network, this.amountType);
563+
return fixedScriptWallet.ChainCode.is(addressDetails.chain)
564+
? (fixedScriptWallet.ChainCode.scriptType(addressDetails.chain) as ScriptType2Of3)
565+
: null;
606566
}
607567

608568
decodeTransaction(input: Buffer | string): fixedScriptWallet.BitGoPsbt {
@@ -761,7 +721,7 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
761721
* @returns true iff coin supports spending from unspentType
762722
*/
763723
supportsAddressType(addressType: ScriptType2Of3): boolean {
764-
return utxolib.bitgo.outputScripts.isSupportedScriptType(this.network, addressType);
724+
return fixedScriptWallet.supportsScriptType(this.name, addressType);
765725
}
766726

767727
/** inherited doc */
@@ -774,7 +734,10 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
774734
* @return true iff coin supports spending from chain
775735
*/
776736
supportsAddressChain(chain: number): boolean {
777-
return isChainCode(chain) && this.supportsAddressType(utxolib.bitgo.scriptTypeForChain(chain));
737+
return (
738+
fixedScriptWallet.ChainCode.is(chain) &&
739+
this.supportsAddressType(fixedScriptWallet.ChainCode.scriptType(chain) as ScriptType2Of3)
740+
);
778741
}
779742

780743
keyIdsForSigning(): number[] {
@@ -1063,17 +1026,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin implements Musig2Partici
10631026
}
10641027

10651028
const returnLegacyFormat = (params as Record<string, unknown>).txFormat === 'legacy';
1066-
1067-
// In the case that we have a 'psbt-lite' transaction format, we want to indicate in signing to not fail
1068-
const txHex = (params.txHex ?? params.txPrebuild?.txHex) as string;
1069-
if (
1070-
txHex &&
1071-
utxolib.bitgo.isPsbt(txHex as string) &&
1072-
utxolib.bitgo.isPsbtLite(utxolib.bitgo.createPsbtFromHex(txHex, this.network)) &&
1073-
params.allowNonSegwitSigningWithoutPrevTx === undefined
1074-
) {
1075-
return { ...params, allowNonSegwitSigningWithoutPrevTx: true, returnLegacyFormat };
1076-
}
10771029
return { ...params, returnLegacyFormat };
10781030
}
10791031

modules/abstract-utxo/src/impl/doge/doge.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { BitGoBase, HalfSignedUtxoTransaction, SignedTransaction } from '@bitgo/sdk-core';
2-
import { bitgo } from '@bitgo/utxo-lib';
32

43
import {
54
AbstractUtxoCoin,
@@ -75,10 +74,6 @@ export class Doge extends AbstractUtxoCoin {
7574

7675
/* postProcessPrebuild, isBitGoTaintedUnspent, verifyCustomChangeKeySignatures do not care whether they receive number or bigint */
7776

78-
createTransactionFromHex<TNumber extends number | bigint = bigint>(hex: string): bitgo.UtxoTransaction<TNumber> {
79-
return super.createTransactionFromHex<TNumber>(hex);
80-
}
81-
8277
async parseTransaction<TNumber extends number | bigint = bigint>(
8378
params: ParseTransactionOptions<TNumber>
8479
): /*

modules/abstract-utxo/src/keychains.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import assert from 'assert';
22

33
import * as t from 'io-ts';
4-
import { bitgo } from '@bitgo/utxo-lib';
54
import { IRequestTracer, IWallet, KeyIndices, promiseProps, Triple } from '@bitgo/sdk-core';
65
import { BIP32, bip32, fixedScriptWallet } from '@bitgo/wasm-utxo';
76

@@ -48,10 +47,10 @@ export function toKeychainTriple(keychains: UtxoNamedKeychains): Triple<UtxoKeyc
4847
}
4948

5049
export function toBip32Triple(
51-
keychains: bitgo.RootWalletKeys | UtxoNamedKeychains | Triple<{ pub: string }> | string[]
50+
keychains: fixedScriptWallet.RootWalletKeys | UtxoNamedKeychains | Triple<{ pub: string }> | string[]
5251
): Triple<BIP32> {
53-
if (keychains instanceof bitgo.RootWalletKeys) {
54-
return keychains.triple.map((k) => BIP32.fromBase58(k.toBase58())) as Triple<BIP32>;
52+
if (keychains instanceof fixedScriptWallet.RootWalletKeys) {
53+
return [keychains.userKey(), keychains.backupKey(), keychains.bitgoKey()];
5554
}
5655
if (Array.isArray(keychains)) {
5756
if (keychains.length !== 3) {

0 commit comments

Comments
 (0)