diff --git a/modules/abstract-substrate/src/abstractSubstrateCoin.ts b/modules/abstract-substrate/src/abstractSubstrateCoin.ts index 728eab274a..0bfab281b2 100644 --- a/modules/abstract-substrate/src/abstractSubstrateCoin.ts +++ b/modules/abstract-substrate/src/abstractSubstrateCoin.ts @@ -5,7 +5,6 @@ import { EDDSAMethods, EDDSAMethodTypes, KeyPair, - MethodNotImplementedError, MPCAlgorithm, MPCConsolidationRecoveryOptions, MPCRecoveryOptions, @@ -20,7 +19,9 @@ import { ParseTransactionOptions, RecoveryTxRequest, SignedTransaction, - VerifyAddressOptions, + TssVerifyAddressOptions, + UnexpectedAddressError, + verifyEddsaTssWalletAddress, VerifyTransactionOptions, } from '@bitgo/sdk-core'; import { CoinFamily, BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; @@ -110,8 +111,18 @@ export class SubstrateCoin extends BaseCoin { } /** @inheritDoc **/ - isWalletAddress(params: VerifyAddressOptions): Promise { - throw new MethodNotImplementedError(); + async isWalletAddress(params: TssVerifyAddressOptions): Promise { + const isValid = await verifyEddsaTssWalletAddress( + params, + (addr) => this.isValidAddress(addr), + (pubKey) => this.getAddressFromPublicKey(pubKey) + ); + + if (!isValid) { + throw new UnexpectedAddressError(`address validation failure: ${params.address} is not a wallet address`); + } + + return true; } /** @inheritDoc **/ diff --git a/modules/sdk-coin-tao/test/unit/tao.ts b/modules/sdk-coin-tao/test/unit/tao.ts index ce5411187e..4d00533d70 100644 --- a/modules/sdk-coin-tao/test/unit/tao.ts +++ b/modules/sdk-coin-tao/test/unit/tao.ts @@ -11,6 +11,15 @@ describe('Tao:', function () { let bitgo: TestBitGoAPI; let baseCoin; + // Test data from wallet 694042b5efbee757e47ec2771cf58a45 + const isWalletAddressTestData = { + commonKeychain: + '6e2235aee215f3909b42bf67c360f5bc6ba7087cbf0ed5ba841dd044ae7c3051722f9be974e8e79fa6c4c93d109dbc618672e36571925df0b5a7c5b015bcd382', + rootAddress: '5GU55E8X2YVSpd4LApeJR5RsXwQ8AnPdjM37Qz1drLTKh7as', + receiveAddress: '5EN6LFnhFRtWiwZfFncqBxaNUabTASKnSxoDt2zQvpVX4qVy', + receiveAddressIndex: 1, + }; + before(function () { bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); bitgo.safeRegister('tao', Tao.createInstance); @@ -19,6 +28,84 @@ describe('Tao:', function () { baseCoin = bitgo.coin('ttao') as Ttao; }); + describe('isWalletAddress', function () { + it('should verify root address (index 0)', async function () { + const keychains = [ + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + ]; + + const result = await baseCoin.isWalletAddress({ + address: isWalletAddressTestData.rootAddress, + keychains, + index: 0, + }); + + result.should.be.true(); + }); + + it('should verify receive address (index > 0)', async function () { + const keychains = [ + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + ]; + + const result = await baseCoin.isWalletAddress({ + address: isWalletAddressTestData.receiveAddress, + keychains, + index: isWalletAddressTestData.receiveAddressIndex, + }); + + result.should.be.true(); + }); + + it('should throw for address mismatch', async function () { + const keychains = [ + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + ]; + + await baseCoin + .isWalletAddress({ + address: isWalletAddressTestData.receiveAddress, + keychains, + index: 0, // Wrong index for this address + }) + .should.be.rejectedWith( + `address validation failure: ${isWalletAddressTestData.receiveAddress} is not a wallet address` + ); + }); + + it('should throw for invalid address', async function () { + const keychains = [ + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + { commonKeychain: isWalletAddressTestData.commonKeychain }, + ]; + + await baseCoin + .isWalletAddress({ + address: 'invalidaddress', + keychains, + index: 0, + }) + .should.be.rejectedWith('invalid address: invalidaddress'); + }); + + it('should throw for missing keychains', async function () { + await baseCoin + .isWalletAddress({ + address: isWalletAddressTestData.rootAddress, + keychains: [], + index: 0, + }) + .should.be.rejectedWith('missing required param keychains'); + }); + }); + describe.skip('Recover Transactions:', function () { const sandBox = sinon.createSandbox(); const recoveryDestination = '5FJ18ywfrWuRifNyc8aPwQ5ium19Fefwmx18H4XYkDc36F2A';