Skip to content

Commit e6c34ab

Browse files
committed
Merge commit 'b28bbf26ecb13db38b5569b1b4c6bf26726b00fe' into rel/latest
2 parents 47a5dc1 + b28bbf2 commit e6c34ab

79 files changed

Lines changed: 2490 additions & 288 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.

.husky/commit-msg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
rootDir=$(pwd)
4+
cd "$rootDir"
5+
6+
yarn check-commits

commitlint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ module.exports = {
6767
'VL-',
6868
'WIN-',
6969
'WP-',
70+
'WAL-',
71+
'WCN-',
72+
'WCI-',
7073
'COIN-',
7174
'FIAT-',
7275
'ME-',

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ interface EthTransactionParams extends TransactionParams {
359359
hop?: boolean;
360360
prebuildTx?: PrebuildTransactionResult;
361361
tokenName?: string;
362+
feeToken?: string;
362363
}
363364

364365
export interface VerifyEthTransactionOptions extends VerifyTransactionOptions {

modules/abstract-lightning/src/abstractLightningCoin.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
AuditDecryptedKeyParams,
33
BaseCoin,
44
BitGoBase,
5+
InvalidAddressError,
56
KeyPair,
67
ParsedTransaction,
78
ParseTransactionOptions,
@@ -15,16 +16,20 @@ import * as utxolib from '@bitgo/utxo-lib';
1516
import { randomBytes } from 'crypto';
1617
import { bip32 } from '@bitgo/utxo-lib';
1718

19+
export interface LightningVerifyAddressOptions extends VerifyAddressOptions {
20+
walletId: string;
21+
}
22+
1823
export abstract class AbstractLightningCoin extends BaseCoin {
1924
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
20-
private readonly _network: utxolib.Network;
25+
protected readonly network: utxolib.Network;
2126
protected constructor(bitgo: BitGoBase, network: utxolib.Network, staticsCoin?: Readonly<StaticsBaseCoin>) {
2227
super(bitgo);
2328
if (!staticsCoin) {
2429
throw new Error('missing required constructor parameter staticsCoin');
2530
}
2631
this._staticsCoin = staticsCoin;
27-
this._network = network;
32+
this.network = network;
2833
}
2934

3035
getBaseFactor(): number {
@@ -35,8 +40,24 @@ export abstract class AbstractLightningCoin extends BaseCoin {
3540
throw new Error('Method not implemented.');
3641
}
3742

38-
isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
39-
throw new Error('Method not implemented.');
43+
async isWalletAddress(params: LightningVerifyAddressOptions): Promise<boolean> {
44+
const { address, walletId } = params;
45+
46+
if (!this.isValidAddress(address)) {
47+
throw new InvalidAddressError(`invalid address: ${address}`);
48+
}
49+
50+
// Node pubkeys are valid addresses but not wallet addresses
51+
if (/^(02|03)[0-9a-fA-F]{64}$/.test(address)) {
52+
return false;
53+
}
54+
55+
try {
56+
await this.bitgo.get(this.url(`/wallet/${walletId}/address/${encodeURIComponent(address)}`)).result();
57+
return true;
58+
} catch (e) {
59+
return false;
60+
}
4061
}
4162

4263
parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
@@ -58,11 +79,23 @@ export abstract class AbstractLightningCoin extends BaseCoin {
5879
}
5980

6081
isValidPub(pub: string): boolean {
61-
throw new Error('Method not implemented.');
82+
try {
83+
return bip32.fromBase58(pub).isNeutered();
84+
} catch (e) {
85+
return false;
86+
}
6287
}
6388

6489
isValidAddress(address: string): boolean {
65-
throw new Error('Method not implemented.');
90+
if (/^(02|03)[0-9a-fA-F]{64}$/.test(address)) {
91+
return true;
92+
}
93+
try {
94+
const script = utxolib.address.toOutputScript(address, this.network);
95+
return address === utxolib.address.fromOutputScript(script, this.network);
96+
} catch (e) {
97+
return false;
98+
}
6699
}
67100

68101
signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {

modules/abstract-utxo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"@bitgo/utxo-core": "^1.35.2",
6767
"@bitgo/utxo-lib": "^11.22.0",
6868
"@bitgo/utxo-ord": "^1.28.1",
69-
"@bitgo/wasm-utxo": "^2.1.0",
69+
"@bitgo/wasm-utxo": "^4.1.0",
7070
"@types/lodash": "^4.14.121",
7171
"@types/superagent": "4.1.15",
7272
"bignumber.js": "^9.0.2",

modules/abstract-utxo/src/abstractUtxoCoin.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ExtraPrebuildParamsOptions,
1414
HalfSignedUtxoTransaction,
1515
IBaseCoin,
16+
isBolt11Invoice,
1617
InvalidAddressDerivationPropertyError,
1718
InvalidAddressError,
1819
IRequestTracer,
@@ -344,6 +345,8 @@ type UtxoBaseSignTransactionOptions<TNumber extends number | bigint = number> =
344345
* When false, return finalized PSBT. Useful for testing to keep transactions in PSBT format.
345346
*/
346347
extractTransaction?: boolean;
348+
/** When true, embeds WASM-UTXO version metadata into the signed PSBT. Defaults to false. */
349+
writeSignedWith?: boolean;
347350
};
348351

349352
export type SignTransactionOptions<TNumber extends number | bigint = number> = UtxoBaseSignTransactionOptions<TNumber> &
@@ -489,11 +492,24 @@ export abstract class AbstractUtxoCoin
489492
* @param address
490493
* @param param
491494
*/
492-
isValidAddress(address: string, param?: { anyFormat: boolean } | /* legacy parameter */ boolean): boolean {
495+
isValidAddress(
496+
address: string,
497+
param?: { anyFormat?: boolean; allowLightning?: boolean } | /* legacy parameter */ boolean
498+
): boolean {
493499
if (typeof param === 'boolean' && param) {
494500
throw new Error('deprecated');
495501
}
496502

503+
const allowLightning = (param as { allowLightning?: boolean } | undefined)?.allowLightning ?? false;
504+
if (allowLightning) {
505+
if (/^(02|03)[0-9a-fA-F]{64}$/.test(address)) {
506+
return true;
507+
}
508+
if (isBolt11Invoice(address)) {
509+
return true;
510+
}
511+
}
512+
497513
// By default, allow all address formats.
498514
// At the time of writing, the only additional address format is bch cashaddr.
499515
const anyFormat = (param as { anyFormat: boolean } | undefined)?.anyFormat ?? true;

modules/abstract-utxo/src/transaction/fixedScript/signPsbtWasm.ts

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

3-
import { BIP32, bip32, ECPair, fixedScriptWallet } from '@bitgo/wasm-utxo';
3+
import { BIP32, bip32, ECPair, fixedScriptWallet, getWasmUtxoVersion } from '@bitgo/wasm-utxo';
44

55
import { toWasmBIP32 } from '../../wasmUtil';
66

@@ -37,7 +37,8 @@ export function signAndVerifyPsbtWasm(
3737
tx: fixedScriptWallet.BitGoPsbt,
3838
signerKeychain: bip32.BIP32Interface | BIP32,
3939
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
40-
replayProtection: ReplayProtectionKeys
40+
replayProtection: ReplayProtectionKeys,
41+
{ writeSignedWith = false }: { writeSignedWith?: boolean } = {}
4142
): fixedScriptWallet.BitGoPsbt {
4243
const wasmSigner = toWasmBIP32(signerKeychain);
4344

@@ -74,6 +75,17 @@ export function signAndVerifyPsbtWasm(
7475
throw new TransactionSigningError([], verifyErrors);
7576
}
7677

78+
if (writeSignedWith) {
79+
const versionInfo = getWasmUtxoVersion();
80+
const versionPayload = new TextEncoder().encode(
81+
JSON.stringify({
82+
version: versionInfo.version,
83+
gitHash: versionInfo.gitHash,
84+
})
85+
);
86+
tx.setKV({ type: 'bitgo', subtype: fixedScriptWallet.BitGoKeySubtype.WasmUtxoSignedWith }, versionPayload);
87+
}
88+
7789
return tx;
7890
}
7991

@@ -86,6 +98,7 @@ export async function signPsbtWithMusig2ParticipantWasm(
8698
replayProtection: ReplayProtectionKeys;
8799
signingStep: 'signerNonce' | 'cosignerNonce' | 'signerSignature' | undefined;
88100
walletId: string | undefined;
101+
writeSignedWith?: boolean;
89102
}
90103
): Promise<fixedScriptWallet.BitGoPsbt> {
91104
const wasmSigner = signerKeychain ? toWasmBIP32(signerKeychain) : undefined;
@@ -135,5 +148,7 @@ export async function signPsbtWithMusig2ParticipantWasm(
135148
}
136149

137150
assert(signerKeychain);
138-
return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection);
151+
return signAndVerifyPsbtWasm(tx, signerKeychain, rootWalletKeys, params.replayProtection, {
152+
writeSignedWith: params.writeSignedWith,
153+
});
139154
}

modules/abstract-utxo/src/transaction/fixedScript/signTransaction.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,36 @@ export function signAndVerifyPsbt(
2323
psbt: utxolib.bitgo.UtxoPsbt,
2424
signerKeychain: bip32.BIP32Interface | BIP32,
2525
rootWalletKeys: fixedScriptWallet.RootWalletKeys | undefined,
26-
replayProtection: ReplayProtectionKeys | undefined
26+
replayProtection: ReplayProtectionKeys | undefined,
27+
options?: { writeSignedWith?: boolean }
2728
): utxolib.bitgo.UtxoPsbt;
2829
export function signAndVerifyPsbt(
2930
psbt: fixedScriptWallet.BitGoPsbt,
3031
signerKeychain: bip32.BIP32Interface | BIP32,
3132
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
32-
replayProtection: ReplayProtectionKeys
33+
replayProtection: ReplayProtectionKeys,
34+
options?: { writeSignedWith?: boolean }
3335
): fixedScriptWallet.BitGoPsbt;
3436
export function signAndVerifyPsbt(
3537
psbt: utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt,
3638
signerKeychain: bip32.BIP32Interface | BIP32,
3739
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
38-
replayProtection: ReplayProtectionKeys
40+
replayProtection: ReplayProtectionKeys,
41+
options?: { writeSignedWith?: boolean }
3942
): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt;
4043
export function signAndVerifyPsbt(
4144
psbt: utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt,
4245
signerKeychain: bip32.BIP32Interface | BIP32,
4346
rootWalletKeys: fixedScriptWallet.RootWalletKeys | undefined,
44-
replayProtection: ReplayProtectionKeys | undefined
47+
replayProtection: ReplayProtectionKeys | undefined,
48+
options: { writeSignedWith?: boolean } = {}
4549
): utxolib.bitgo.UtxoPsbt | fixedScriptWallet.BitGoPsbt {
4650
if (psbt instanceof bitgo.UtxoPsbt) {
4751
return signAndVerifyPsbtUtxolib(psbt, toUtxolibBIP32(signerKeychain));
4852
}
4953
assert(rootWalletKeys, 'rootWalletKeys required for wasm-utxo signing');
5054
assert(replayProtection, 'replayProtection required for wasm-utxo signing');
51-
return signAndVerifyPsbtWasm(psbt, signerKeychain, rootWalletKeys, replayProtection);
55+
return signAndVerifyPsbtWasm(psbt, signerKeychain, rootWalletKeys, replayProtection, options);
5256
}
5357

5458
export async function signTransaction<
@@ -69,6 +73,7 @@ export async function signTransaction<
6973
cosignerPub: string | undefined;
7074
/** When true (default), extract finalized PSBT to legacy transaction format. When false, return finalized PSBT. */
7175
extractTransaction?: boolean;
76+
writeSignedWith?: boolean;
7277
}
7378
): Promise<
7479
utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint | number> | fixedScriptWallet.BitGoPsbt | Buffer
@@ -115,6 +120,7 @@ export async function signTransaction<
115120
},
116121
signingStep: params.signingStep,
117122
walletId: params.walletId,
123+
writeSignedWith: params.writeSignedWith,
118124
}
119125
);
120126
if (isLastSignature) {

modules/abstract-utxo/src/transaction/signTransaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export async function signTransaction<TNumber extends number | bigint>(
7979
pubs: params.pubs,
8080
cosignerPub: params.cosignerPub,
8181
extractTransaction: params.extractTransaction,
82+
writeSignedWith: params.writeSignedWith,
8283
});
8384

8485
// Convert half-signed PSBT to legacy format when the caller explicitly requested txFormat: 'legacy'

modules/abstract-utxo/test/unit/coins.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as utxolib from '@bitgo/utxo-lib';
44

55
import { getMainnetCoinName, utxoCoinsMainnet, utxoCoinsTestnet } from '../../src/names';
66

7-
import { getNetworkForCoinName, getUtxoCoinForNetwork, utxoCoins } from './util';
7+
import { getNetworkForCoinName, getUtxoCoinForNetwork, getUtxoCoin, utxoCoins } from './util';
88

99
describe('utxoCoins', function () {
1010
it('has expected chain/network values for items', function () {
@@ -76,6 +76,74 @@ describe('utxoCoins', function () {
7676
);
7777
});
7878

79+
describe('isValidAddress with allowLightning', function () {
80+
const btc = getUtxoCoin('btc');
81+
const tbtc = getUtxoCoin('tbtc');
82+
const bch = getUtxoCoin('bch');
83+
84+
it('should reject node pubkeys and invoices without allowLightning', function () {
85+
assert.strictEqual(
86+
btc.isValidAddress('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
87+
false
88+
);
89+
assert.strictEqual(btc.isValidAddress('lnbc1500n1pj0ggavpp5example'), false);
90+
});
91+
92+
it('should accept node pubkeys with allowLightning', function () {
93+
assert.strictEqual(
94+
btc.isValidAddress('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619', {
95+
allowLightning: true,
96+
}),
97+
true
98+
);
99+
assert.strictEqual(
100+
tbtc.isValidAddress('03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad', {
101+
allowLightning: true,
102+
}),
103+
true
104+
);
105+
});
106+
107+
it('should reject invalid node pubkeys even with allowLightning', function () {
108+
// wrong prefix
109+
assert.strictEqual(
110+
btc.isValidAddress('04eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619', {
111+
allowLightning: true,
112+
}),
113+
false
114+
);
115+
// too short
116+
assert.strictEqual(
117+
btc.isValidAddress('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368', {
118+
allowLightning: true,
119+
}),
120+
false
121+
);
122+
});
123+
124+
it('should accept bolt11 invoices with allowLightning', function () {
125+
assert.strictEqual(btc.isValidAddress('lnbc1500n1pj0ggavpp5example', { allowLightning: true }), true);
126+
assert.strictEqual(tbtc.isValidAddress('lntb1500n1pj0ggavpp5example', { allowLightning: true }), true);
127+
});
128+
129+
it('should reject non-bolt11 strings with allowLightning', function () {
130+
assert.strictEqual(btc.isValidAddress('lnxyz1500n1pj0ggavpp5example', { allowLightning: true }), false);
131+
assert.strictEqual(btc.isValidAddress('not-an-address', { allowLightning: true }), false);
132+
});
133+
134+
it('should still accept regular bitcoin addresses with allowLightning', function () {
135+
assert.strictEqual(btc.isValidAddress('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', { allowLightning: true }), true);
136+
});
137+
138+
it('should not accept lightning addresses for non-btc coins without allowLightning', function () {
139+
assert.strictEqual(
140+
bch.isValidAddress('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
141+
false
142+
);
143+
assert.strictEqual(bch.isValidAddress('lnbc1500n1pj0ggavpp5example'), false);
144+
});
145+
});
146+
79147
it('getMainnetCoinName returns correct mainnet coin name', function () {
80148
// Mainnet coins return themselves
81149
for (const coin of utxoCoinsMainnet) {

0 commit comments

Comments
 (0)