Skip to content

Commit a298876

Browse files
committed
feat: address verification for substrate coins (tao, polyx)
TICKET: WP-7086
1 parent edc099f commit a298876

File tree

2 files changed

+153
-3
lines changed

2 files changed

+153
-3
lines changed

modules/abstract-substrate/src/abstractSubstrateCoin.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
2+
AddressCoinSpecific,
23
AuditDecryptedKeyParams,
34
BaseCoin,
45
BitGoBase,
56
EDDSAMethods,
67
EDDSAMethodTypes,
78
KeyPair,
8-
MethodNotImplementedError,
99
MPCAlgorithm,
1010
MPCConsolidationRecoveryOptions,
1111
MPCRecoveryOptions,
@@ -20,6 +20,8 @@ import {
2020
ParseTransactionOptions,
2121
RecoveryTxRequest,
2222
SignedTransaction,
23+
TssVerifyAddressOptions,
24+
verifyEddsaTssWalletAddress,
2325
VerifyAddressOptions,
2426
VerifyTransactionOptions,
2527
} from '@bitgo/sdk-core';
@@ -34,6 +36,13 @@ import { ApiPromise } from '@polkadot/api';
3436

3537
export const DEFAULT_SCAN_FACTOR = 20;
3638

39+
export interface SubstrateVerifyAddressOptions extends VerifyAddressOptions {
40+
index?: number | string;
41+
coinSpecific?: AddressCoinSpecific & {
42+
index?: number | string;
43+
};
44+
}
45+
3746
export class SubstrateCoin extends BaseCoin {
3847
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
3948
readonly MAX_VALIDITY_DURATION = 2400;
@@ -110,8 +119,31 @@ export class SubstrateCoin extends BaseCoin {
110119
}
111120

112121
/** @inheritDoc **/
113-
isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
114-
throw new MethodNotImplementedError();
122+
async isWalletAddress(params: SubstrateVerifyAddressOptions): Promise<boolean> {
123+
const { address, keychains } = params;
124+
125+
const index = Number(params.index ?? params.coinSpecific?.index);
126+
if (isNaN(index) || index < 0) {
127+
throw new Error('Invalid or missing index. index must be a non-negative number.');
128+
}
129+
130+
const tssParams: TssVerifyAddressOptions = {
131+
address,
132+
keychains: keychains as TssVerifyAddressOptions['keychains'],
133+
index,
134+
};
135+
136+
const isValid = await verifyEddsaTssWalletAddress(
137+
tssParams,
138+
(addr) => this.isValidAddress(addr),
139+
(pubKey) => this.getAddressFromPublicKey(pubKey)
140+
);
141+
142+
if (!isValid) {
143+
throw new Error(`Address verification failed: address ${address} is not a wallet address at index ${index}`);
144+
}
145+
146+
return true;
115147
}
116148

117149
/** @inheritDoc **/

modules/sdk-coin-tao/test/unit/tao.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ describe('Tao:', function () {
1111
let bitgo: TestBitGoAPI;
1212
let baseCoin;
1313

14+
// Test data from wallet 694042b5efbee757e47ec2771cf58a45
15+
const isWalletAddressTestData = {
16+
commonKeychain:
17+
'6e2235aee215f3909b42bf67c360f5bc6ba7087cbf0ed5ba841dd044ae7c3051722f9be974e8e79fa6c4c93d109dbc618672e36571925df0b5a7c5b015bcd382',
18+
rootAddress: '5GU55E8X2YVSpd4LApeJR5RsXwQ8AnPdjM37Qz1drLTKh7as',
19+
receiveAddress: '5EN6LFnhFRtWiwZfFncqBxaNUabTASKnSxoDt2zQvpVX4qVy',
20+
receiveAddressIndex: 1,
21+
};
22+
1423
before(function () {
1524
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' });
1625
bitgo.safeRegister('tao', Tao.createInstance);
@@ -19,6 +28,115 @@ describe('Tao:', function () {
1928
baseCoin = bitgo.coin('ttao') as Ttao;
2029
});
2130

31+
describe('isWalletAddress', function () {
32+
it('should verify root address (index 0)', async function () {
33+
const keychains = [
34+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
35+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
36+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
37+
];
38+
39+
const result = await baseCoin.isWalletAddress({
40+
address: isWalletAddressTestData.rootAddress,
41+
keychains,
42+
index: 0,
43+
});
44+
45+
result.should.be.true();
46+
});
47+
48+
it('should verify receive address (index > 0)', async function () {
49+
const keychains = [
50+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
51+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
52+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
53+
];
54+
55+
const result = await baseCoin.isWalletAddress({
56+
address: isWalletAddressTestData.receiveAddress,
57+
keychains,
58+
index: isWalletAddressTestData.receiveAddressIndex,
59+
});
60+
61+
result.should.be.true();
62+
});
63+
64+
it('should throw for address mismatch', async function () {
65+
const keychains = [
66+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
67+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
68+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
69+
];
70+
71+
await baseCoin
72+
.isWalletAddress({
73+
address: isWalletAddressTestData.receiveAddress,
74+
keychains,
75+
index: 0, // Wrong index for this address
76+
})
77+
.should.be.rejectedWith(/Address verification failed/);
78+
});
79+
80+
it('should throw for missing index', async function () {
81+
const keychains = [
82+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
83+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
84+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
85+
];
86+
87+
await baseCoin
88+
.isWalletAddress({
89+
address: isWalletAddressTestData.rootAddress,
90+
keychains,
91+
})
92+
.should.be.rejectedWith(/Invalid or missing index/);
93+
});
94+
95+
it('should throw for invalid address', async function () {
96+
const keychains = [
97+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
98+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
99+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
100+
];
101+
102+
await baseCoin
103+
.isWalletAddress({
104+
address: 'invalidaddress',
105+
keychains,
106+
index: 0,
107+
})
108+
.should.be.rejectedWith(/invalid address/);
109+
});
110+
111+
it('should throw for missing keychains', async function () {
112+
await baseCoin
113+
.isWalletAddress({
114+
address: isWalletAddressTestData.rootAddress,
115+
keychains: [],
116+
index: 0,
117+
})
118+
.should.be.rejectedWith(/missing required param keychains/);
119+
});
120+
121+
it('should accept index from coinSpecific', async function () {
122+
const keychains = [
123+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
124+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
125+
{ commonKeychain: isWalletAddressTestData.commonKeychain },
126+
];
127+
128+
const result = await baseCoin.isWalletAddress({
129+
address: isWalletAddressTestData.receiveAddress,
130+
keychains,
131+
coinSpecific: {
132+
index: isWalletAddressTestData.receiveAddressIndex,
133+
},
134+
});
135+
136+
result.should.be.true();
137+
});
138+
});
139+
22140
describe.skip('Recover Transactions:', function () {
23141
const sandBox = sinon.createSandbox();
24142
const recoveryDestination = '5FJ18ywfrWuRifNyc8aPwQ5ium19Fefwmx18H4XYkDc36F2A';

0 commit comments

Comments
 (0)