From 5ff36300bb8d2c2a69c44e38f853d1953b97fe43 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 19 Nov 2025 12:03:29 -0500 Subject: [PATCH 1/4] feat: wallet ownership validation for eth-like, ada, apt, vet, icp ticket: WP-6461 --- .../src/abstractEthLikeNewCoins.ts | 47 ++- modules/sdk-coin-ada/src/ada.ts | 13 +- modules/sdk-coin-ada/test/unit/ada.ts | 80 ++++ modules/sdk-coin-apt/src/apt.ts | 16 +- modules/sdk-coin-apt/test/unit/apt.ts | 2 +- modules/sdk-coin-eth/test/unit/eth.ts | 382 +++++++++++------- modules/sdk-coin-icp/src/icp.ts | 33 +- modules/sdk-coin-icp/test/unit/icp.ts | 88 ++++ modules/sdk-coin-vet/src/vet.ts | 21 +- modules/sdk-coin-vet/test/unit/vet.ts | 81 ++++ .../sdk-core/src/bitgo/baseCoin/iBaseCoin.ts | 15 +- .../bitgo/utils/tss/addressVerification.ts | 27 +- modules/sdk-core/src/index.ts | 1 + 13 files changed, 612 insertions(+), 194 deletions(-) diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index be9a301d33..bc27ac28ab 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -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'; @@ -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 = { @@ -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 { + async isWalletAddress(params: VerifyEthAddressOptions | TssVerifyEthAddressOptions): Promise { const ethUtil = optionalDeps.ethUtil; let expectedAddress; let actualAddress; - const { address, coinSpecific, baseAddress, impliedForwarderVersion = coinSpecific?.forwarderVersion } = params; + const { address, impliedForwarderVersion } = params; 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 (impliedForwarderVersion === 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) => { + const derivedPublicKey = Buffer.from(pubKey, 'hex').subarray(0, 33).toString('hex'); + return new KeyPairLib({ pub: derivedPublicKey }).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; @@ -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); - 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)); diff --git a/modules/sdk-coin-ada/src/ada.ts b/modules/sdk-coin-ada/src/ada.ts index bcc952f66f..759115d73d 100644 --- a/modules/sdk-coin-ada/src/ada.ts +++ b/modules/sdk-coin-ada/src/ada.ts @@ -12,7 +12,6 @@ import { SignedTransaction, SignTransactionOptions as BaseSignTransactionOptions, TransactionExplanation, - VerifyAddressOptions, VerifyTransactionOptions, EDDSAMethods, EDDSAMethodTypes, @@ -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'; @@ -161,12 +162,18 @@ export class Ada extends BaseCoin { return true; } - async isWalletAddress(params: VerifyAddressOptions): Promise { + async isWalletAddress(params: TssVerifyAddressOptions): Promise { 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 */ diff --git a/modules/sdk-coin-ada/test/unit/ada.ts b/modules/sdk-coin-ada/test/unit/ada.ts index 37f8f8ea7a..301a841ef9 100644 --- a/modules/sdk-coin-ada/test/unit/ada.ts +++ b/modules/sdk-coin-ada/test/unit/ada.ts @@ -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}`); + }); + }); }); diff --git a/modules/sdk-coin-apt/src/apt.ts b/modules/sdk-coin-apt/src/apt.ts index 6091583b85..e31d37aa84 100644 --- a/modules/sdk-coin-apt/src/apt.ts +++ b/modules/sdk-coin-apt/src/apt.ts @@ -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'; @@ -120,13 +121,16 @@ export class Apt extends BaseCoin { return true; } - async isWalletAddress(params: VerifyAddressOptions): Promise { - const { address: newAddress } = params; + async isWalletAddress(params: TssVerifyAddressOptions): Promise { + 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) => { + return utils.getAddressFromPublicKey(publicKey.slice(0, 64)); + }); } async parseTransaction(params: AptParseTransactionOptions): Promise { diff --git a/modules/sdk-coin-apt/test/unit/apt.ts b/modules/sdk-coin-apt/test/unit/apt.ts index f7d8d03ff6..c4c2324722 100644 --- a/modules/sdk-coin-apt/test/unit/apt.ts +++ b/modules/sdk-coin-apt/test/unit/apt.ts @@ -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 }; diff --git a/modules/sdk-coin-eth/test/unit/eth.ts b/modules/sdk-coin-eth/test/unit/eth.ts index d0f2f82774..9679c70394 100644 --- a/modules/sdk-coin-eth/test/unit/eth.ts +++ b/modules/sdk-coin-eth/test/unit/eth.ts @@ -23,8 +23,10 @@ import { Teth, TransactionBuilder, TransferBuilder, + TssVerifyEthAddressOptions, UnsignedBuilConsolidation, UnsignedSweepTxMPCv2, + VerifyEthAddressOptions, } from '../../src'; import { EthereumNetwork } from '@bitgo/statics'; import assert from 'assert'; @@ -662,182 +664,250 @@ describe('ETH:', function () { }); describe('Address Verification', function () { - it('should verify an address generated using forwarder version 0', async function () { - const coin = bitgo.coin('teth') as Teth; + describe('isWalletAddress', function () { + it('should verify an address generated using forwarder version 0', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + id: '6127bff4ecd84c0006cd9a0e5ccdc36f', + chain: 0, + index: 3174, + coin: 'teth', + lastNonce: 0, + wallet: '598f606cd8fc24710d2ebadb1d9459bb', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + nonce: -1, + updateTime: '2021-08-26T16:23:16.563Z', + txCount: 0, + pendingChainInitialization: true, + creationFailure: [], + pendingDeployment: false, + forwarderVersion: 0, + }, + } as unknown as VerifyEthAddressOptions; - const params = { - id: '6127bff4ecd84c0006cd9a0e5ccdc36f', - chain: 0, - index: 3174, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-26T16:23:16.563Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - pendingDeployment: false, - forwarderVersion: 0, - }, - }; + const isWalletAddr = await coin.isWalletAddress(params); + isWalletAddr.should.equal(true); + }); - const isAddressVerified = await coin.verifyAddress(params as any); - isAddressVerified.should.equal(true); - }); + it('should verify an address generated using forwarder version 1', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + id: '61250217c8c02b000654b15e7af6f618', + address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', + chain: 0, + index: 3162, + coin: 'teth', + lastNonce: 0, + wallet: '598f606cd8fc24710d2ebadb1d9459bb', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + nonce: -1, + updateTime: '2021-08-24T14:28:39.841Z', + txCount: 0, + pendingChainInitialization: true, + creationFailure: [], + salt: '0xc5a', + pendingDeployment: true, + forwarderVersion: 1, + }, + }; - it('should verify an address generated using forwarder version 1', async function () { - const coin = bitgo.coin('teth') as Teth; + const isWalletAddr = await coin.isWalletAddress(params as any); + isWalletAddr.should.equal(true); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-24T14:28:39.841Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - salt: '0xc5a', - pendingDeployment: true, - forwarderVersion: 1, - }, - }; + it('should reject when actual address differs from expected address', async function () { + const coin = bitgo.coin('teth') as Teth; - const isAddressVerified = await coin.verifyAddress(params); - isAddressVerified.should.equal(true); - }); + const params = { + address: '0x28904591f735994f050804fda3b61b813b16e04c', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + salt: '0xc5a', + forwarderVersion: 1, + }, + } as unknown as VerifyEthAddressOptions; - it('should reject address verification if coinSpecific field is not an object', async function () { - const coin = bitgo.coin('teth') as Teth; + await assert.rejects(async () => coin.isWalletAddress(params), UnexpectedAddressError); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', - }; + it('should reject if coinSpecific field is not an object', async function () { + const coin = bitgo.coin('teth') as Teth; - assert.rejects(async () => coin.verifyAddress(params), InvalidAddressVerificationObjectPropertyError); - }); + const params = { + address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + }; - it('should reject address verification when an actual address is different from expected address', async function () { - const coin = bitgo.coin('teth') as Teth; + await assert.rejects( + async () => coin.isWalletAddress(params as any), + InvalidAddressVerificationObjectPropertyError + ); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0x28904591f735994f050804fda3b61b813b16e04c', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-24T14:28:39.841Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - salt: '0xc5a', - pendingDeployment: true, - forwarderVersion: 1, - }, - }; + it('should reject if the derived address is in invalid format', async function () { + const coin = bitgo.coin('teth') as Teth; - assert.rejects(async () => coin.verifyAddress(params), UnexpectedAddressError); - }); + const params = { + address: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + salt: '0xc5a', + forwarderVersion: 1, + }, + } as unknown as VerifyEthAddressOptions; - it('should reject address verification if the derived address is in invalid format', async function () { - const coin = bitgo.coin('teth') as Teth; + await assert.rejects(async () => coin.isWalletAddress(params), InvalidAddressError); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-24T14:28:39.841Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - salt: '0xc5a', - pendingDeployment: true, - forwarderVersion: 1, - }, - }; + it('should reject if base address is undefined', async function () { + const coin = bitgo.coin('teth') as Teth; - assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError); - }); + const params = { + address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', + coinSpecific: { + salt: '0xc5a', + forwarderVersion: 1, + }, + }; - it('should reject address verification if base address is undefined', async function () { - const coin = bitgo.coin('teth') as Teth; + await assert.rejects(async () => coin.isWalletAddress(params as any), InvalidAddressError); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-24T14:28:39.841Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - salt: '0xc5a', - pendingDeployment: true, - forwarderVersion: 1, - }, - }; + it('should reject if base address is in invalid format', async function () { + const coin = bitgo.coin('teth') as Teth; - assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError); - }); + const params = { + address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', + baseAddress: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf', + coinSpecific: { + salt: '0xc5a', + forwarderVersion: 1, + }, + } as unknown as VerifyEthAddressOptions; - it('should reject address verification if base address is in invalid format', async function () { - const coin = bitgo.coin('teth') as Teth; + await assert.rejects(async () => coin.isWalletAddress(params), InvalidAddressError); + }); - const params = { - id: '61250217c8c02b000654b15e7af6f618', - address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d', - chain: 0, - index: 3162, - coin: 'teth', - lastNonce: 0, - wallet: '598f606cd8fc24710d2ebadb1d9459bb', - baseAddress: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf', - coinSpecific: { - nonce: -1, - updateTime: '2021-08-24T14:28:39.841Z', - txCount: 0, - pendingChainInitialization: true, - creationFailure: [], - salt: '0xc5a', - pendingDeployment: true, - forwarderVersion: 1, - }, - }; + describe('MPC wallet addresses', function () { + const commonKeychain = + '03f9c2fb2e5a8b78a44f5d1e4f906f8e3d7a0e6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9e8d7c6b5a4' + + '93827160594857463728190a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'; + const keychains = [ + { pub: 'user_pub', commonKeychain }, + { pub: 'backup_pub', commonKeychain }, + { pub: 'bitgo_pub', commonKeychain }, + ]; + + it('should verify an MPC wallet address with forwarder version 3', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 3, + }, + keychains, + index: 0, + walletVersion: 3, + } as unknown as TssVerifyEthAddressOptions; + + const isWalletAddr = await coin.isWalletAddress(params); + isWalletAddr.should.equal(true); + }); + + it('should verify an MPC wallet address with forwarder version 5', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 5, + }, + keychains, + index: 0, + walletVersion: 6, + } as unknown as TssVerifyEthAddressOptions; + + const isWalletAddr = await coin.isWalletAddress(params); + isWalletAddr.should.equal(true); + }); + + it('should reject MPC wallet address with wrong address', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + address: '0x0000000000000000000000000000000000000001', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 3, + }, + keychains, + index: 0, + walletVersion: 3, + } as unknown as TssVerifyEthAddressOptions; + + const isWalletAddr = await coin.isWalletAddress(params); + isWalletAddr.should.equal(false); + }); + + it('should reject MPC wallet address with invalid address format', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + address: '0xinvalid', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 3, + }, + keychains, + index: 0, + walletVersion: 3, + } as unknown as TssVerifyEthAddressOptions; + + await assert.rejects(async () => coin.isWalletAddress(params), InvalidAddressError); + }); - assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError); + it('should reject if keychains are missing for MPC wallet', async function () { + const coin = bitgo.coin('teth') as Teth; + + const params = { + address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 3, + }, + index: 0, + walletVersion: 3, + }; + + await assert.rejects(async () => coin.isWalletAddress(params as any), Error); + }); + + it('should reject if commonKeychain is missing for MPC wallet', async function () { + const coin = bitgo.coin('teth') as Teth; + + const invalidKeychains = [{ pub: 'user_pub' }, { pub: 'backup_pub' }, { pub: 'bitgo_pub' }]; + + const params = { + address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', + coinSpecific: { + forwarderVersion: 3, + }, + keychains: invalidKeychains, + index: 0, + walletVersion: 3, + } as unknown as TssVerifyEthAddressOptions; + + await assert.rejects(async () => coin.isWalletAddress(params), Error); + }); + }); }); }); diff --git a/modules/sdk-coin-icp/src/icp.ts b/modules/sdk-coin-icp/src/icp.ts index 0ff9eb3746..4b9873e422 100644 --- a/modules/sdk-coin-icp/src/icp.ts +++ b/modules/sdk-coin-icp/src/icp.ts @@ -8,6 +8,7 @@ import { Ecdsa, ECDSAUtils, Environments, + InvalidAddressError, KeyPair, MPCAlgorithm, MultisigType, @@ -19,6 +20,7 @@ import { SignTransactionOptions, TssVerifyAddressOptions, VerifyTransactionOptions, + verifyMPCWalletAddress, } from '@bitgo/sdk-core'; import { coins, BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; import { Principal } from '@dfinity/principal'; @@ -46,6 +48,7 @@ import { TransactionBuilderFactory } from './lib/transactionBuilderFactory'; import utils from './lib/utils'; import { auditEcdsaPrivateKey } from '@bitgo/sdk-lib-mpc'; import { IcpAgent } from './lib/icpAgent'; +import { KeyPair as IcpKeyPair } from './lib/keyPair'; /** * Class representing the Internet Computer (ICP) coin. @@ -141,7 +144,20 @@ export class Icp extends BaseCoin { } async isWalletAddress(params: TssVerifyAddressOptions): Promise { - return this.isValidAddress(params.address); + const { address } = params; + + if (!this.isValidAddress(address)) { + throw new InvalidAddressError(`invalid address: ${address}`); + } + + return verifyMPCWalletAddress( + { ...params, keyCurve: 'secp256k1' }, + this.isValidAddress.bind(this), + (publicKey: string) => { + const compressedPublicKey = Buffer.from(publicKey, 'hex').subarray(0, 33).toString('hex'); + return this.getAddressFromPublicKeySync(compressedPublicKey); + } + ); } async parseTransaction(params: ParseTransactionOptions): Promise { @@ -213,6 +229,21 @@ export class Icp extends BaseCoin { return utils.getAddressFromPublicKey(hexEncodedPublicKey); } + /** + * Synchronously derives an ICP address from a public key. + * Used for address verification in isWalletAddress. + * @param hexEncodedPublicKey - The public key in hex format + * @returns The derived ICP address + */ + private getAddressFromPublicKeySync(hexEncodedPublicKey: string): string { + if (!utils.isValidPublicKey(hexEncodedPublicKey)) { + throw new Error('Invalid hex-encoded public key format.'); + } + const compressedKey = utils.compressPublicKey(hexEncodedPublicKey); + const keyPair = new IcpKeyPair({ pub: compressedKey }); + return keyPair.getAddress(); + } + /** @inheritDoc **/ protected getPublicNodeUrl(): string { return Environments[this.bitgo.getEnv()].icpNodeUrl; diff --git a/modules/sdk-coin-icp/test/unit/icp.ts b/modules/sdk-coin-icp/test/unit/icp.ts index a328a397fa..626f6e5804 100644 --- a/modules/sdk-coin-icp/test/unit/icp.ts +++ b/modules/sdk-coin-icp/test/unit/icp.ts @@ -233,4 +233,92 @@ describe('Internet computer', function () { .should.rejectedWith('generated signableHex is not equal to params.signableHex'); }); }); + + describe('isWalletAddress', function () { + let keychains; + const commonKeychain = + '036b38ca5e63e9800b5040af498eb6e9a9c77e244ac2858edafa4bd0926a635731c3fabde9007a5771e93621d9fcb1c879660208dc79cc609fe8ddd189f7a955ab'; + + before(function () { + keychains = [ + { + id: '691ddce39d0505f43dd931570e6bd7cf', + source: 'user', + type: 'tss', + commonKeychain, + }, + { + id: '691ddce3b1a977aaf2465acca73b2aef', + source: 'backup', + type: 'tss', + commonKeychain, + }, + { + id: '691ddce38e366afce3f35b0778c79858', + source: 'bitgo', + type: 'tss', + commonKeychain, + isBitGo: true, + }, + ]; + }); + + it('should verify TSS address derivation', async function () { + const validAddress = 'fd3eaed3e2064bd30ab497e22e8ac5a0dcadd81fa5353879dbab64e259ec70c0'; + 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 = 'c3d30f404955975adaba89f2e1ebc75c1f44a6a204578afce8f3780d64fe252e'; + 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 = 'fd3eaed3e2064bd30ab497e22e8ac5a0dcadd81fa5353879dbab64e259ec70c0'; + const index = 0; + + const params = { address: validAddress, index }; + await assert.rejects(async () => basecoin.isWalletAddress(params), { + message: 'missing required param keychains', + }); + }); + + it('should throw error when commonKeychain is missing', async function () { + const validAddress = 'fd3eaed3e2064bd30ab497e22e8ac5a0dcadd81fa5353879dbab64e259ec70c0'; + const index = 0; + const invalidKeychains = [ + { + id: '691ddce39d0505f43dd931570e6bd7cf', + source: 'user', + type: 'tss', + }, + ]; + + const params = { address: validAddress, index, keychains: invalidKeychains }; + await assert.rejects(async () => basecoin.isWalletAddress(params), { + message: '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 assert.rejects( + async () => basecoin.isWalletAddress(params), + (err: Error) => { + return err.message.includes('invalid address'); + } + ); + }); + }); }); diff --git a/modules/sdk-coin-vet/src/vet.ts b/modules/sdk-coin-vet/src/vet.ts index 2c57c644a2..7d98568d65 100644 --- a/modules/sdk-coin-vet/src/vet.ts +++ b/modules/sdk-coin-vet/src/vet.ts @@ -20,7 +20,6 @@ import { SignedTransaction, SignTransactionOptions, TokenTransferRecipientParams, - VerifyAddressOptions, VerifyTransactionOptions, TokenType, Ecdsa, @@ -28,6 +27,8 @@ import { Environments, BaseBroadcastTransactionOptions, BaseBroadcastTransactionResult, + TssVerifyAddressOptions, + verifyMPCWalletAddress, } from '@bitgo/sdk-core'; import * as mpc from '@bitgo/sdk-lib-mpc'; import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; @@ -153,13 +154,21 @@ export class Vet extends BaseCoin { return true; } - async isWalletAddress(params: VerifyAddressOptions): Promise { - const { address: newAddress } = params; + async isWalletAddress(params: TssVerifyAddressOptions): Promise { + 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 verifyMPCWalletAddress( + { ...params, keyCurve: 'secp256k1' }, + this.isValidAddress.bind(this), + (publicKey: string) => { + const compressedPublicKey = Buffer.from(publicKey, 'hex').subarray(0, 33).toString('hex'); + return new EthKeyPair({ pub: compressedPublicKey }).getAddress(); + } + ); } async parseTransaction(params: VetParseTransactionOptions): Promise { diff --git a/modules/sdk-coin-vet/test/unit/vet.ts b/modules/sdk-coin-vet/test/unit/vet.ts index 9385d7f449..adaf63dee0 100644 --- a/modules/sdk-coin-vet/test/unit/vet.ts +++ b/modules/sdk-coin-vet/test/unit/vet.ts @@ -228,6 +228,36 @@ describe('Vechain', function () { }); describe('address validation', () => { + let keychains; + let commonKeychain; + + before(function () { + commonKeychain = + '0366ccc33b9011c31e25945606a50b9e6f12bb2f6397537d527ebc00f4b35a38c151c95abefec2e3f730aadca93f573e40f320c64d46e46604159411721bb31288'; + + keychains = [ + { + id: '691ce9e4d69474994294a012cd266267', + source: 'user', + type: 'tss', + commonKeychain, + }, + { + id: '691ce9e4193ef7977228c769ced07e35', + source: 'backup', + type: 'tss', + commonKeychain, + }, + { + id: '691ce9e481112facbfb48cc8a1e5a3a5', + source: 'bitgo', + type: 'tss', + commonKeychain, + isBitGo: true, + }, + ]; + }); + it('should return true when validating a well formatted address prefixed with 0x', async function () { const address = testData.addresses.validAddresses[0]; basecoin.isValidAddress(address).should.equal(true); @@ -237,5 +267,56 @@ describe('Vechain', function () { const address = 'wrongaddress'; basecoin.isValidAddress(address).should.equal(false); }); + + describe('isWalletAddress', function () { + it('should verify TSS address derivation', async function () { + const address = '0xb3ba0f4ebbc8fcb307192c39311fc33872724f01'; + const index = 0; + + const params = { address, index, keychains }; + const result = await basecoin.isWalletAddress(params); + assert.equal(result, true); + }); + + it('should return false when address does not match derived address', async function () { + const wrongAddress = testData.addresses.validAddresses[1]; + const index = 0; + + const params = { address: wrongAddress, index, keychains }; + const result = await basecoin.isWalletAddress(params); + assert.equal(result, false); + }); + + it('should throw error when keychains is missing', async function () { + const address = testData.addresses.validAddresses[0]; + const index = 0; + + const params = { address, index }; + await basecoin.isWalletAddress(params).should.be.rejectedWith('missing required param keychains'); + }); + + it('should throw error when commonKeychain is missing', async function () { + const address = testData.addresses.validAddresses[0]; + const index = 0; + const invalidKeychains = [ + { + id: 'test-user', + source: 'user', + type: 'tss', + }, + ]; + + const params = { address, 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 = 'badAddress'; + const index = 0; + + const params = { address: invalidAddress, index, keychains }; + await basecoin.isWalletAddress(params).should.be.rejectedWith(`invalid address: ${invalidAddress}`); + }); + }); }); }); diff --git a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts index ac0925ee1b..136630361b 100644 --- a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts +++ b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts @@ -169,12 +169,23 @@ export interface TssVerifyAddressOptions { * For MPC wallets, the commonKeychain (combined public key from MPC key generation) * should be identical across all keychains (user, backup, bitgo). */ - keychains: Keychain[]; + keychains: Pick[]; /** * Derivation index for the address. * Used to derive child addresses from the root keychain via HD derivation path: m/{index} */ - index: string; + index: number | string; +} + +export function isTssVerifyAddressOptions( + params: T +): params is T & TssVerifyAddressOptions { + return !!( + 'keychains' in params && + 'index' in params && + 'address' in params && + params.keychains?.some((kc) => 'commonKeychain' in kc && !!kc.commonKeychain) + ); } export interface TransactionParams { diff --git a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts index cc42b95f14..d9322bc4a3 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts @@ -1,3 +1,4 @@ +import { Ecdsa } from 'modules/sdk-core/src/account-lib/mpc'; import { TssVerifyAddressOptions } from '../../baseCoin/iBaseCoin'; import { InvalidAddressError } from '../../errors'; import { EDDSAMethods } from '../../tss'; @@ -42,6 +43,27 @@ export async function verifyEddsaTssWalletAddress( params: TssVerifyAddressOptions, isValidAddress: (address: string) => boolean, getAddressFromPublicKey: (publicKey: string) => string +): Promise { + return verifyMPCWalletAddress({ ...params, keyCurve: 'ed25519' }, isValidAddress, getAddressFromPublicKey); +} + +/** + * Verifies if an address belongs to a wallet using ECDSA TSS MPC derivation. + * This is a common implementation for ECDSA-based MPC coins (ETH, BTC, etc.) + * + * @param params - Verification options including keychains, address, and derivation index + * @param isValidAddress - Coin-specific function to validate address format + * @param getAddressFromPublicKey - Coin-specific function to convert public key to address + * @returns true if the address matches the derived address, false otherwise + * @throws {InvalidAddressError} if the address is invalid + * @throws {Error} if required parameters are missing or invalid + */ +export async function verifyMPCWalletAddress( + params: TssVerifyAddressOptions & { + keyCurve: 'secp256k1' | 'ed25519'; + }, + isValidAddress: (address: string) => boolean, + getAddressFromPublicKey: (publicKey: string) => string ): Promise { const { keychains, address, index } = params; @@ -49,11 +71,10 @@ export async function verifyEddsaTssWalletAddress( throw new InvalidAddressError(`invalid address: ${address}`); } + const MPC = params.keyCurve === 'secp256k1' ? new Ecdsa() : await EDDSAMethods.getInitializedMpcInstance(); const commonKeychain = extractCommonKeychain(keychains); - - const MPC = await EDDSAMethods.getInitializedMpcInstance(); const derivationPath = 'm/' + index; - const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64); + const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath); const expectedAddress = getAddressFromPublicKey(derivedPublicKey); return address === expectedAddress; diff --git a/modules/sdk-core/src/index.ts b/modules/sdk-core/src/index.ts index e10fc32e2a..1b07844be5 100644 --- a/modules/sdk-core/src/index.ts +++ b/modules/sdk-core/src/index.ts @@ -10,6 +10,7 @@ import { EcdsaUtils } from './bitgo/utils/tss/ecdsa/ecdsa'; export { EcdsaUtils }; import { EcdsaMPCv2Utils } from './bitgo/utils/tss/ecdsa/ecdsaMPCv2'; export { EcdsaMPCv2Utils }; +export { verifyEddsaTssWalletAddress, verifyMPCWalletAddress } from './bitgo/utils/tss/addressVerification'; export { GShare, SignShare, YShare } from './account-lib/mpc/tss/eddsa/types'; export { TssEcdsaStep1ReturnMessage, TssEcdsaStep2ReturnMessage } from './bitgo/tss/types'; export { SShare } from './bitgo/tss/ecdsa/types'; From 2cc762f1b442e02db02d13c34b15729a3b3f3a95 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 19 Nov 2025 12:47:42 -0500 Subject: [PATCH 2/4] fix: slice pub to 32 bytes for isWalletAddress for iota TICKET: WP-6461 --- modules/sdk-coin-iota/src/iota.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/sdk-coin-iota/src/iota.ts b/modules/sdk-coin-iota/src/iota.ts index ee4c577c32..57e93955e5 100644 --- a/modules/sdk-coin-iota/src/iota.ts +++ b/modules/sdk-coin-iota/src/iota.ts @@ -136,7 +136,10 @@ export class Iota extends BaseCoin { return verifyEddsaTssWalletAddress( params, (address) => this.isValidAddress(address), - (publicKey) => utils.getAddressFromPublicKey(publicKey) + (publicKey) => { + const publicKeyOnly = Buffer.from(publicKey, 'hex').subarray(0, 32).toString('hex'); + return utils.getAddressFromPublicKey(publicKeyOnly); + } ); } From e68aa9778305aa3207f449d9a37b94da951a8ed9 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 19 Nov 2025 13:58:39 -0500 Subject: [PATCH 3/4] fix: slice address during verifyEddsaTssWalletAddress TICKET: WP-6461 --- modules/sdk-coin-apt/src/apt.ts | 6 +++--- modules/sdk-coin-iota/src/iota.ts | 5 +---- .../sdk-core/src/bitgo/utils/tss/addressVerification.ts | 7 ++++++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/sdk-coin-apt/src/apt.ts b/modules/sdk-coin-apt/src/apt.ts index e31d37aa84..2004a338b7 100644 --- a/modules/sdk-coin-apt/src/apt.ts +++ b/modules/sdk-coin-apt/src/apt.ts @@ -128,9 +128,9 @@ export class Apt extends BaseCoin { throw new InvalidAddressError(`invalid address: ${address}`); } - return verifyEddsaTssWalletAddress(params, this.isValidAddress.bind(this), (publicKey: string) => { - return utils.getAddressFromPublicKey(publicKey.slice(0, 64)); - }); + return verifyEddsaTssWalletAddress(params, this.isValidAddress.bind(this), (publicKey: string) => + utils.getAddressFromPublicKey(publicKey) + ); } async parseTransaction(params: AptParseTransactionOptions): Promise { diff --git a/modules/sdk-coin-iota/src/iota.ts b/modules/sdk-coin-iota/src/iota.ts index 57e93955e5..ee4c577c32 100644 --- a/modules/sdk-coin-iota/src/iota.ts +++ b/modules/sdk-coin-iota/src/iota.ts @@ -136,10 +136,7 @@ export class Iota extends BaseCoin { return verifyEddsaTssWalletAddress( params, (address) => this.isValidAddress(address), - (publicKey) => { - const publicKeyOnly = Buffer.from(publicKey, 'hex').subarray(0, 32).toString('hex'); - return utils.getAddressFromPublicKey(publicKeyOnly); - } + (publicKey) => utils.getAddressFromPublicKey(publicKey) ); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts index d9322bc4a3..25f5a54e46 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts @@ -75,7 +75,12 @@ export async function verifyMPCWalletAddress( const commonKeychain = extractCommonKeychain(keychains); const derivationPath = 'm/' + index; const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath); - const expectedAddress = getAddressFromPublicKey(derivedPublicKey); + + // secp256k1 expects 33 bytes; ed25519 expects 32 bytes + const publicKeySize = params.keyCurve === 'secp256k1' ? 33 : 32; + const publicKeyOnly = Buffer.from(derivedPublicKey, 'hex').subarray(0, publicKeySize).toString('hex'); + + const expectedAddress = getAddressFromPublicKey(publicKeyOnly); return address === expectedAddress; } From 8534100841937e03fc45713ce734a7b90ab59e7f Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 19 Nov 2025 15:30:28 -0500 Subject: [PATCH 4/4] refactor: fix tests and import TICKET: WP-6461 --- modules/abstract-eth/src/abstractEthLikeNewCoins.ts | 8 ++++---- modules/sdk-coin-eth/test/unit/eth.ts | 4 ++-- .../sdk-core/src/bitgo/utils/tss/addressVerification.ts | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index bc27ac28ab..58d9164bdf 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -2748,20 +2748,20 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { let expectedAddress; let actualAddress; - const { address, impliedForwarderVersion } = params; + const { address, impliedForwarderVersion, coinSpecific } = params; + const forwarderVersion = impliedForwarderVersion ?? coinSpecific?.forwarderVersion; if (address && !this.isValidAddress(address)) { throw new InvalidAddressError(`invalid address: ${address}`); } // Forwarder version 0 addresses cannot be verified because we do not store the nonce value required for address derivation. - if (impliedForwarderVersion === 0) { + 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) => { - const derivedPublicKey = Buffer.from(pubKey, 'hex').subarray(0, 33).toString('hex'); - return new KeyPairLib({ pub: derivedPublicKey }).getAddress(); + return new KeyPairLib({ pub: pubKey }).getAddress(); }); } else { // Verify forwarder receive address diff --git a/modules/sdk-coin-eth/test/unit/eth.ts b/modules/sdk-coin-eth/test/unit/eth.ts index 9679c70394..f689a18577 100644 --- a/modules/sdk-coin-eth/test/unit/eth.ts +++ b/modules/sdk-coin-eth/test/unit/eth.ts @@ -806,7 +806,7 @@ describe('ETH:', function () { const coin = bitgo.coin('teth') as Teth; const params = { - address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + address: '0x01153f3adfe454a72589ca9ef74f013c19e54961', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', coinSpecific: { forwarderVersion: 3, @@ -824,7 +824,7 @@ describe('ETH:', function () { const coin = bitgo.coin('teth') as Teth; const params = { - address: '0x9e7ce8c24d9f76a814e23633e61be7cb8e6e2d5e', + address: '0x01153f3adfe454a72589ca9ef74f013c19e54961', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', coinSpecific: { forwarderVersion: 5, diff --git a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts index 25f5a54e46..4e5fd92080 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts @@ -1,4 +1,4 @@ -import { Ecdsa } from 'modules/sdk-core/src/account-lib/mpc'; +import { Ecdsa } from '../../../account-lib/mpc'; import { TssVerifyAddressOptions } from '../../baseCoin/iBaseCoin'; import { InvalidAddressError } from '../../errors'; import { EDDSAMethods } from '../../tss'; @@ -73,8 +73,7 @@ export async function verifyMPCWalletAddress( const MPC = params.keyCurve === 'secp256k1' ? new Ecdsa() : await EDDSAMethods.getInitializedMpcInstance(); const commonKeychain = extractCommonKeychain(keychains); - const derivationPath = 'm/' + index; - const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath); + const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, 'm/' + index); // secp256k1 expects 33 bytes; ed25519 expects 32 bytes const publicKeySize = params.keyCurve === 'secp256k1' ? 33 : 32;