From 69f3583b6ed1221951789959aaffe554cc0b0710 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Tue, 2 Dec 2025 16:20:38 -0500 Subject: [PATCH 1/3] feat: address verification for apt TICKET: WP-7081 --- modules/sdk-coin-apt/src/apt.ts | 42 +++++++++++++++-- modules/sdk-coin-apt/test/unit/apt.ts | 68 ++++++++++----------------- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/modules/sdk-coin-apt/src/apt.ts b/modules/sdk-coin-apt/src/apt.ts index 6091583b85..9f95ccb75e 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, + TssVerifyAddressOptions, VerifyTransactionOptions, + verifyMPCWalletAddress, } from '@bitgo/sdk-core'; import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; import { KeyPair as AptKeyPair, TransactionBuilderFactory } from './lib'; @@ -30,6 +31,11 @@ export interface AptParseTransactionOptions extends ParseTransactionOptions { txHex: string; } +export interface TssVerifyAptAddressOptions extends TssVerifyAddressOptions { + address: string; + rootAddress?: string; +} + export class Apt extends BaseCoin { protected readonly _staticsCoin: Readonly; protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly) { @@ -120,12 +126,38 @@ export class Apt extends BaseCoin { return true; } - async isWalletAddress(params: VerifyAddressOptions): Promise { - const { address: newAddress } = params; + /** + * Verify that an address belongs to this wallet. + * + * @param {TssVerifyAptAddressOptions} params - Verification parameters + * @returns {Promise} True if address belongs to wallet + * @throws {InvalidAddressError} If address format is invalid or doesn't match derived address + */ + async isWalletAddress(params: TssVerifyAptAddressOptions): Promise { + const { address, rootAddress } = params; - if (!this.isValidAddress(newAddress)) { - throw new InvalidAddressError(`invalid address: ${newAddress}`); + if (!this.isValidAddress(address)) { + throw new InvalidAddressError(`invalid address: ${address}`); } + + const isVerifyingRootAddress = rootAddress && address.toLowerCase() === rootAddress.toLowerCase(); + if (isVerifyingRootAddress) { + const index = typeof params.index === 'string' ? parseInt(params.index, 10) : params.index; + if (index !== 0) { + throw new Error(`Root address verification requires index 0, but got index ${params.index}.`); + } + } + + const result = await verifyMPCWalletAddress( + { ...params, keyCurve: 'ed25519' }, + this.isValidAddress.bind(this), + (pubKey) => utils.getAddressFromPublicKey(pubKey) + ); + + if (!result) { + throw new InvalidAddressError(`invalid address: ${address}`); + } + return true; } diff --git a/modules/sdk-coin-apt/test/unit/apt.ts b/modules/sdk-coin-apt/test/unit/apt.ts index f7d8d03ff6..3b7d72e862 100644 --- a/modules/sdk-coin-apt/test/unit/apt.ts +++ b/modules/sdk-coin-apt/test/unit/apt.ts @@ -280,42 +280,20 @@ describe('APT:', function () { }); describe('Address Validation', () => { - let keychains; - let commonKeychain; - - before(function () { - commonKeychain = - '19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781'; - keychains = [ - { - id: '6424c353eaf78d000766e95949868468', - source: 'user', - type: 'tss', - commonKeychain: - '19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781', - encryptedPrv: - '{"iv":"cZd5i7L4RxtwrALW2rK7UA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"5zgoH1Bd3Fw=","ct":"9vVlnXFRtrM9FVEo+d2chbGHlM9lFZemueBuAs3BIkPo33Fo7jzwwNK/kIWkEyg+NmEBd5IaqAS157nvvvwzzsmMWlQdUz9qbmXNv3pg987cXFR08exS+4uhwP1YNOjJTRvRNcO9ZqHb46d4fmyJ/yC9/susCge7r/EsbaN5C3afv1dzybuq912FwaQElZLYYp5BICudFOMZ9k0UDMfKM/PMDkH7WexoGHr9GKq/bgCH2B39TZZyHKU6Uy47lXep2s6h0DrMwHOrnmiL3DZjOj88Ynvphlzxuo4eOlD2UHia2+nvIaISYs29Pr0DAvREutchvcBpExj1kWWPv7hQYrv8F0NAdatsbWl3w+xKyfiMKo1USlrwyJviypGtQtXOJyw0XPN0rv2+L5lW8BbjpzHfYYN13fJTedlGTFhhkzVtbbPAKE02kx7zCJcjYaiexdSTsrDLScYNT9/Jhdt27KpsooehwVohLfSKz4vbFfRu2MPZw3/+c/hfiJNgtz6esWbnxGrcE8U2IwPYCaK+Ghk4DcqWNIni59RI5B5kAsQOToII40qPN510uTgxBSPO7q7MHgkxdd4CqBq+ojr9j0P7oao8E5Y+CBDJrojDoCh1oCCDW9vo2dXlVcD8SIbw7U/9AfvEbA4xyE/5md1M7CIwLnWs2Ynv0YtaKoqhdS9x6FmHlMDhN/DKHinrwmowtrTT82fOkpO5g9saSmgU7Qy3gLt8t+VwdEyeFeQUKRSyci8qgqXQaZIg4+aXgaSOnlCFMtmB8ekYxEhTY5uzRfrNgS4s1QeqFBpNtUF+Ydi297pbVXnJoXAN+SVWd80GCx+yI2dpVC89k3rOWK9WeyqlnzuLJWp2RIOB9cdW8GFv/fN+QAJpYeVxOE4+nZDsKnsj8nKcg9t4Dlx1G6gLM1/Vq9YxNLbuzuRC0asUYvdMnoMvszmpm++TxndYisgNYscpZSoz7wvcazJNEPfhPVjEkd6tUUuN4GM35H0DmKCUQNT+a6B6hmHlTZvjxiyGAg5bY59hdjvJ+22QduazlEEC6LI3HrA7uK0TpplWzS1tCIFvTMUhj65DEZmNJ2+ZY9bQ4vsMf+DRR3OOG4t+DMlNfjOd3zNv3QoY95BjfWpryFwPzDq7bCP67JDsoj7j2TY5FRSrRkD77H0Ewlux2cWfjRTwcMHcdQxxuV0OP0aNjGDjybFN"}', - }, - { - id: '6424c353eaf78d000766e96137d4404b', - source: 'backup', - type: 'tss', - commonKeychain: - '19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781', - encryptedPrv: - '{"iv":"vi0dPef/Rx7kG/pRySQi6Q==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"9efhQsiEvVs=","ct":"Gw6atvf6gxKzsjtl3xseipO3rAxp1mAz7Yu1ihFsi5/lf2vMZegApgZx+pyILFS9KKLHbNF3U6WgSYdrr2t4vzdLsXkH1WIxfHS+cd2C5N59yADZDnPJBT6pv/IRvaYelP0Ck3nIYQ2hSMm8op+VOWC/SzHeh7slYDqwEHTGan0Wigfvk1yRd7CCJTaEAomnc/4eFi2NY3X3gt/3opy9IAgknnwUFohn96EWpEQ0F6pbzH/Z8VF6gF+DUcrrByAxExUPnHQZiFk3YHU/vVV4FxBU/mVAE8xBsBn5ul5e5SUMPfc7TBuJWv4BByTNg9xDShF/91Yx2nbfUm5d9QmM8lpKgzzQvcK8POAPk87gRCuKnsGh5vNS0UppkHc+ocfzRQlGA6jze7QyyQO0rMj5Ly8kWjwk2vISvKYHYS1NR7VU549UIXo7NXjatunKSc3+IreoRUHIshiaLg6hl+pxCCuc0qQ43V0mdIfCjTN8gkGWLNk8R7tAGPz9jyapQPcPEGHgEz0ATIi6yMNWCsibS2eLiE1uVEJONoM4lk6FPl3Q2CHbW2MeEbqjY8hbaw18mNb2xSBH/Fwpiial+Tvi2imqgnCO4ZpO9bllKftZPcQy0stN+eGBlb5ufyflKkDSiChHYroGjEpmiFicdde48cJszF52uKNnf1q67fA9/S2FAHQab3EXojxH2Gbk+kkV2h/TYKFFZSWC3vi4e8mO+vjMUcR0AdsgPFyEIz0SCGuba3CnTLNdEuZwsauAeHkx2vUTnRgJPVgNeeuXmsVG76Sy2ggJHuals0Hj8U2Xda0qO1RuFfoCWfss9wn6HGRwPPkhSB/8oNguAqmRVGKkd8Zwt3IvrTd9fk0/rFFDJKGz7WyNHkYgUmNiGcItD12v0jx7FZ52EJzl3Av1RyJUQK18+8EYPh3SGiU9dt7VX0aF0uo6JouKhOeldUvMP+AugQz8fUclwTQsbboVg27Yxo0DyATVwThW5a56R6Qf5ZiQJluFuzs5y98rq0S5q046lE6o3vVmJpEdwjeSCJoET5CL4nTgkXyWvhm4eB8u/e66l3o0qbaSx8q9YYmT9EpRcl5TP4ThLBKETYdzVvg4exjQfektMatk5EyUpEIhZPXh5vXpJZesdfO9LJ8zTaHBsBjDPU7cdNgQMbebpataRi8A0el2/IJXl+E+olgAz5zC4i2O1Q=="}', - }, - { - id: '6424c353eaf78d000766e9510b125fba', - source: 'bitgo', - type: 'tss', - commonKeychain: - '19bdfe2a4b498a05511381235a8892d54267807c4a3f654e310b938b8b424ff4adedbe92f4c146de641c67508a961324c8504cdf8e0c0acbb68d6104ccccd781', - verifiedVssProof: true, - isBitGo: true, - }, - ]; - }); + // Real test data from tapt wallet 692f53f35a6e3f25a18c4eb8af016fdd + const testData = { + commonKeychain: + '6a724c11eafea4209704c35e6ee3e1fba80d2a40860d873bbe5981de636c9cf6ade77e6fdd4388889ee93d7eaa737ab584edb57cc0cc15b2899380348d6e482c', + rootAddress: '0x0598b31aa77176dbaba25306404aa8131218068df58bf0b7eec13f57053fd5a7', + receiveAddress: '0xdc169725dd6d9a07ee255b17087b4079e8a80850c895c65b62c0ef6de740d37a', + receiveAddressIndex: 1, + }; + + const keychains = [ + { commonKeychain: testData.commonKeychain }, + { commonKeychain: testData.commonKeychain }, + { commonKeychain: testData.commonKeychain }, + ]; it('should return true when validating a well formatted address prefixed with 0x', async function () { const address = '0xf941ae3cbe5645dccc15da8346b533f7f91f202089a5521653c062b2ff10b304'; @@ -332,19 +310,23 @@ describe('APT:', function () { basecoin.isValidAddress(address).should.equal(false); }); - it('should return true for isWalletAddress with valid address for index 4', async function () { - const newAddress = '0x8b3c7807730d75792dd6c49732cf9f014d6984a9c77d386bdb1072a9e537d8d8'; - const index = 4; + it('should verify a valid receive address', async function () { + const params = { + address: testData.receiveAddress, + rootAddress: testData.rootAddress, + keychains, + index: testData.receiveAddressIndex, + }; - const params = { commonKeychain, address: newAddress, index, keychains }; - (await basecoin.isWalletAddress(params)).should.equal(true); + const result = await basecoin.isWalletAddress(params); + result.should.equal(true); }); it('should throw error for isWalletAddress when keychains is missing', async function () { const address = '0x2959bfc3fdb7dc23fed8deba2fafb70f3e606a59'; const index = 0; - const params = { commonKeychain, address, index }; + const params = { commonKeychain: testData.commonKeychain, address, index }; await assert.rejects(async () => basecoin.isWalletAddress(params)); }); @@ -352,7 +334,7 @@ describe('APT:', function () { const wrongAddress = 'badAddress'; const index = 0; - const params = { commonKeychain, address: wrongAddress, index }; + const params = { commonKeychain: testData.commonKeychain, address: wrongAddress, index }; await assert.rejects(async () => basecoin.isWalletAddress(params), { message: `invalid address: ${wrongAddress}`, }); From 642fce1d3c03df684583e50109d95dbdf99b5e3d Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Tue, 2 Dec 2025 16:25:02 -0500 Subject: [PATCH 2/3] docs: remove useless comment TICKET: WP-7081 --- modules/sdk-coin-apt/test/unit/apt.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/sdk-coin-apt/test/unit/apt.ts b/modules/sdk-coin-apt/test/unit/apt.ts index 3b7d72e862..03f07a96dd 100644 --- a/modules/sdk-coin-apt/test/unit/apt.ts +++ b/modules/sdk-coin-apt/test/unit/apt.ts @@ -280,7 +280,6 @@ describe('APT:', function () { }); describe('Address Validation', () => { - // Real test data from tapt wallet 692f53f35a6e3f25a18c4eb8af016fdd const testData = { commonKeychain: '6a724c11eafea4209704c35e6ee3e1fba80d2a40860d873bbe5981de636c9cf6ade77e6fdd4388889ee93d7eaa737ab584edb57cc0cc15b2899380348d6e482c', From aa87e45d87d43ceb01677126ce754011434311c3 Mon Sep 17 00:00:00 2001 From: danielzhao122 Date: Wed, 17 Dec 2025 16:05:53 -0500 Subject: [PATCH 3/3] refactor: simplify TICKET: WP-7081 --- modules/sdk-coin-apt/src/apt.ts | 41 ++++++++------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/modules/sdk-coin-apt/src/apt.ts b/modules/sdk-coin-apt/src/apt.ts index 9f95ccb75e..d18694a596 100644 --- a/modules/sdk-coin-apt/src/apt.ts +++ b/modules/sdk-coin-apt/src/apt.ts @@ -3,7 +3,6 @@ import { BaseCoin, BaseTransaction, BitGoBase, - InvalidAddressError, KeyPair, MPCAlgorithm, MultisigType, @@ -15,8 +14,9 @@ import { SignedTransaction, SignTransactionOptions, TssVerifyAddressOptions, + UnexpectedAddressError, + verifyEddsaTssWalletAddress, VerifyTransactionOptions, - verifyMPCWalletAddress, } from '@bitgo/sdk-core'; import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; import { KeyPair as AptKeyPair, TransactionBuilderFactory } from './lib'; @@ -31,11 +31,6 @@ export interface AptParseTransactionOptions extends ParseTransactionOptions { txHex: string; } -export interface TssVerifyAptAddressOptions extends TssVerifyAddressOptions { - address: string; - rootAddress?: string; -} - export class Apt extends BaseCoin { protected readonly _staticsCoin: Readonly; protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly) { @@ -129,33 +124,17 @@ export class Apt extends BaseCoin { /** * Verify that an address belongs to this wallet. * - * @param {TssVerifyAptAddressOptions} params - Verification parameters - * @returns {Promise} True if address belongs to wallet - * @throws {InvalidAddressError} If address format is invalid or doesn't match derived address + * @param params - Verification parameters including address, keychains, and index + * @returns True if address belongs to wallet + * @throws UnexpectedAddressError if address doesn't match derived address */ - async isWalletAddress(params: TssVerifyAptAddressOptions): Promise { - const { address, rootAddress } = params; - - if (!this.isValidAddress(address)) { - throw new InvalidAddressError(`invalid address: ${address}`); - } - - const isVerifyingRootAddress = rootAddress && address.toLowerCase() === rootAddress.toLowerCase(); - if (isVerifyingRootAddress) { - const index = typeof params.index === 'string' ? parseInt(params.index, 10) : params.index; - if (index !== 0) { - throw new Error(`Root address verification requires index 0, but got index ${params.index}.`); - } - } - - const result = await verifyMPCWalletAddress( - { ...params, keyCurve: 'ed25519' }, - this.isValidAddress.bind(this), - (pubKey) => utils.getAddressFromPublicKey(pubKey) + async isWalletAddress(params: TssVerifyAddressOptions): Promise { + const isValid = await verifyEddsaTssWalletAddress(params, this.isValidAddress.bind(this), (pubKey) => + utils.getAddressFromPublicKey(pubKey) ); - if (!result) { - throw new InvalidAddressError(`invalid address: ${address}`); + if (!isValid) { + throw new UnexpectedAddressError(); } return true;