Skip to content
Open
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
47 changes: 31 additions & 16 deletions modules/abstract-eth/src/abstractEthLikeNewCoins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import {
VerifyAddressOptions as BaseVerifyAddressOptions,
VerifyTransactionOptions,
Wallet,
verifyMPCWalletAddress,
TssVerifyAddressOptions,
isTssVerifyAddressOptions,
} from '@bitgo/sdk-core';
import { getDerivationPath } from '@bitgo/sdk-lib-mpc';
import { bip32 } from '@bitgo/secp256k1';
Expand Down Expand Up @@ -401,9 +404,12 @@ export interface EthConsolidationRecoveryOptions {
export interface VerifyEthAddressOptions extends BaseVerifyAddressOptions {
baseAddress: string;
coinSpecific: EthAddressCoinSpecifics;
forwarderVersion: number;
forwarderVersion?: number;
walletVersion?: number;
}

export type TssVerifyEthAddressOptions = TssVerifyAddressOptions & VerifyEthAddressOptions;

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

export const optionalDeps = {
Expand Down Expand Up @@ -2736,32 +2742,41 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
* @throws {UnexpectedAddressError}
* @returns {boolean} True iff address is a wallet address
*/
async isWalletAddress(params: VerifyEthAddressOptions): Promise<boolean> {
async isWalletAddress(params: VerifyEthAddressOptions | TssVerifyEthAddressOptions): Promise<boolean> {
const ethUtil = optionalDeps.ethUtil;

let expectedAddress;
let actualAddress;

const { address, coinSpecific, baseAddress, impliedForwarderVersion = coinSpecific?.forwarderVersion } = params;
const { address, impliedForwarderVersion, coinSpecific } = params;
const forwarderVersion = impliedForwarderVersion ?? coinSpecific?.forwarderVersion;

if (address && !this.isValidAddress(address)) {
throw new InvalidAddressError(`invalid address: ${address}`);
}

// base address is required to calculate the salt which is used in calculateForwarderV1Address method
if (_.isUndefined(baseAddress) || !this.isValidAddress(baseAddress)) {
throw new InvalidAddressError('invalid base address');
// Forwarder version 0 addresses cannot be verified because we do not store the nonce value required for address derivation.
if (forwarderVersion === 0) {
return true;
}
// Verify MPC wallet address for wallet version 3 and 6
if (isTssVerifyAddressOptions(params) && params.walletVersion !== 5) {
return verifyMPCWalletAddress({ ...params, keyCurve: 'secp256k1' }, this.isValidAddress, (pubKey) => {
return new KeyPairLib({ pub: pubKey }).getAddress();
});
} else {
// Verify forwarder receive address
const { coinSpecific, baseAddress } = params;

if (!_.isObject(coinSpecific)) {
throw new InvalidAddressVerificationObjectPropertyError(
'address validation failure: coinSpecific field must be an object'
);
}
if (_.isUndefined(baseAddress) || !this.isValidAddress(baseAddress)) {
throw new InvalidAddressError('invalid base address');
}

if (!_.isObject(coinSpecific)) {
throw new InvalidAddressVerificationObjectPropertyError(
'address validation failure: coinSpecific field must be an object'
);
}

if (impliedForwarderVersion === 0 || impliedForwarderVersion === 3 || impliedForwarderVersion === 5) {
return true;
} else {
const ethNetwork = this.getNetwork();
const forwarderFactoryAddress = ethNetwork?.forwarderFactoryAddress as string;
const forwarderImplementationAddress = ethNetwork?.forwarderImplementationAddress as string;
Expand Down Expand Up @@ -3056,7 +3071,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
}
const typedDataRaw = JSON.parse(typedData.typedDataRaw);
const sanitizedData = TypedDataUtils.sanitizeData(typedDataRaw as unknown as TypedMessage<any>);
const parts = [Buffer.from('1901', 'hex')];
const parts: Buffer[] = [Buffer.from('1901', 'hex')];
const eip712Domain = 'EIP712Domain';
parts.push(TypedDataUtils.hashStruct(eip712Domain, sanitizedData.domain, sanitizedData.types, version));

Expand Down
13 changes: 10 additions & 3 deletions modules/sdk-coin-ada/src/ada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
SignedTransaction,
SignTransactionOptions as BaseSignTransactionOptions,
TransactionExplanation,
VerifyAddressOptions,
VerifyTransactionOptions,
EDDSAMethods,
EDDSAMethodTypes,
Expand All @@ -32,6 +31,8 @@ import {
MultisigType,
multisigTypes,
AuditDecryptedKeyParams,
TssVerifyAddressOptions,
verifyEddsaTssWalletAddress,
} from '@bitgo/sdk-core';
import { KeyPair as AdaKeyPair, Transaction, TransactionBuilderFactory, Utils } from './lib';
import { BaseCoin as StaticsBaseCoin, CoinFamily, coins } from '@bitgo/statics';
Expand Down Expand Up @@ -161,12 +162,18 @@ export class Ada extends BaseCoin {
return true;
}

async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
async isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
const { address } = params;
if (!this.isValidAddress(address)) {
throw new InvalidAddressError(`Invalid Cardano Address: ${address}`);
}
return true;

const addressFormat = this.getChain() === 'ada' ? AddressFormat.mainnet : AddressFormat.testnet;

return verifyEddsaTssWalletAddress(params, this.isValidAddress.bind(this), (publicKey: string) => {
const adaKeyPair = new AdaKeyPair({ pub: publicKey.slice(0, 64) });
return adaKeyPair.getAddress(addressFormat);
});
}

/** @inheritDoc */
Expand Down
80 changes: 80 additions & 0 deletions modules/sdk-coin-ada/test/unit/ada.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,4 +898,84 @@ describe('ADA', function () {
.should.be.rejectedWith('tx outputs does not match with expected address');
});
});

describe('isWalletAddress', function () {
let keychains;
const commonKeychain =
'0c012a54ac4bcbfc322ed71f3fcba85b993f4de0377e211f8e52e539571a7b397b63db46d8f20218019681c45ff7d4ef86b4d7ee8c4dac0d8b69b7b966962258';

before(function () {
keychains = [
{
id: '691ce22d193ef7977224cef7f2c5736f',
source: 'user',
type: 'tss',
commonKeychain,
},
{
id: '691ce22d193ef7977224cefac9e54e33',
source: 'backup',
type: 'tss',
commonKeychain,
},
{
id: '691ce22d193ef7977224cef23e73b113',
source: 'bitgo',
type: 'tss',
commonKeychain,
isBitGo: true,
},
];
});

it('should verify TSS address derivation', async function () {
const validAddress = 'addr_test1vrdswnnph3gh9pm9zsd2r7k9p2rek7927xqzs48cdtph5rq3fxtjf';
const index = 0;

const params = { address: validAddress, index, keychains };
const res = await basecoin.isWalletAddress(params);
assert.equal(res, true);
});

it('should return false when address does not match derived address', async function () {
const wrongAddress =
'addr_test1qqnnvptrc3rec64q2n9jh572ncu5wvdtt8uvg4g3aj96s5dwu9nj70mlahzglm9939uevupsmj8dcdqv25d5n5r8vw8sn7prey';
const index = 0;

const params = { address: wrongAddress, index, keychains };
const res = await basecoin.isWalletAddress(params);
assert.equal(res, false);
});

it('should throw error when keychains is missing', async function () {
const validAddress = 'addr_test1vr8rakm66rcfv4fcxqykg5lf0yv7lsyk9mvapx369jpvtcgfcuk7f';
const index = 0;

const params = { address: validAddress, index };
await basecoin.isWalletAddress(params).should.be.rejectedWith('missing required param keychains');
});

it('should throw error when commonKeychain is missing', async function () {
const validAddress = 'addr_test1vr8rakm66rcfv4fcxqykg5lf0yv7lsyk9mvapx369jpvtcgfcuk7f';
const index = 0;
const invalidKeychains = [
{
id: 'test-user',
source: 'user',
type: 'tss',
},
];

const params = { address: validAddress, index, keychains: invalidKeychains };
await basecoin.isWalletAddress(params).should.be.rejectedWith('missing required param commonKeychain');
});

it('should throw error when address is invalid', async function () {
const invalidAddress = 'invalid-address';
const index = 0;

const params = { address: invalidAddress, index, keychains };
await basecoin.isWalletAddress(params).should.be.rejectedWith(`Invalid Cardano Address: ${invalidAddress}`);
});
});
});
16 changes: 10 additions & 6 deletions modules/sdk-coin-apt/src/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import {
PrebuildTransactionWithIntentOptions,
SignedTransaction,
SignTransactionOptions,
VerifyAddressOptions,
VerifyTransactionOptions,
TssVerifyAddressOptions,
verifyEddsaTssWalletAddress,
} from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
import { KeyPair as AptKeyPair, TransactionBuilderFactory } from './lib';
Expand Down Expand Up @@ -120,13 +121,16 @@ export class Apt extends BaseCoin {
return true;
}

async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
const { address: newAddress } = params;
async isWalletAddress(params: TssVerifyAddressOptions): Promise<boolean> {
const { address } = params;

if (!this.isValidAddress(newAddress)) {
throw new InvalidAddressError(`invalid address: ${newAddress}`);
if (!this.isValidAddress(address)) {
throw new InvalidAddressError(`invalid address: ${address}`);
}
return true;

return verifyEddsaTssWalletAddress(params, this.isValidAddress.bind(this), (publicKey: string) =>
utils.getAddressFromPublicKey(publicKey)
);
}

async parseTransaction(params: AptParseTransactionOptions): Promise<ParsedTransaction> {
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-coin-apt/test/unit/apt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ describe('APT:', function () {
});

it('should return true for isWalletAddress with valid address for index 4', async function () {
const newAddress = '0x8b3c7807730d75792dd6c49732cf9f014d6984a9c77d386bdb1072a9e537d8d8';
const newAddress = '0x3d7a55c4f55702b0a57f0228060c78dcf612d157108d77487d1fbed45d8f656a';
const index = 4;

const params = { commonKeychain, address: newAddress, index, keychains };
Expand Down
Loading