Skip to content

Commit 21f3b21

Browse files
committed
refactor: upgrade package to avoid ci error and add verification for v1
TICKET: WP-7080
1 parent f31f558 commit 21f3b21

File tree

4 files changed

+90
-74
lines changed

4 files changed

+90
-74
lines changed

modules/sdk-coin-icp/src/icp.ts

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
SignTransactionOptions,
2121
VerifyTransactionOptions,
2222
verifyMPCWalletAddress,
23+
UnexpectedAddressError,
2324
} from '@bitgo/sdk-core';
2425
import { coins, NetworkType, BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
2526
import { Principal } from '@dfinity/principal';
@@ -146,9 +147,15 @@ export class Icp extends BaseCoin {
146147
/**
147148
* Verify that an address belongs to this wallet.
148149
*
150+
* For wallet version 1 (memo-based): The address format is `rootAddress?memoId=X`.
151+
* We extract the root address and verify it against the commonKeychain at index 0.
152+
*
153+
* For wallet version 2+: The address is derived directly from the commonKeychain
154+
* at the specified index.
155+
*
149156
* @param {TssVerifyIcpAddressOptions} params - Verification parameters
150157
* @returns {Promise<boolean>} True if address belongs to wallet
151-
* @throws {InvalidAddressError} If address format is invalid
158+
* @throws {InvalidAddressError} If address format is invalid or doesn't match derived address
152159
* @throws {Error} If invalid wallet version or missing parameters
153160
*/
154161
async isWalletAddress(params: TssVerifyIcpAddressOptions): Promise<boolean> {
@@ -158,63 +165,38 @@ export class Icp extends BaseCoin {
158165
throw new InvalidAddressError(`invalid address: ${address}`);
159166
}
160167

161-
if (walletVersion === 1) {
162-
return this.verifyMemoBasedAddress(address, rootAddress);
163-
}
168+
let addressToVerify = address;
169+
const parsedIndex = typeof params.index === 'string' ? parseInt(params.index, 10) : params.index;
164170

165-
return this.verifyKeyDerivedAddress(params, address, rootAddress);
166-
}
167-
168-
/**
169-
* Verifies a memo-based address for wallet version 1.
170-
*
171-
* @param {string} address - The full address to verify (must include memoId)
172-
* @param {string | undefined} rootAddress - The wallet's root address
173-
* @returns {boolean} True if the address is valid
174-
* @throws {Error} If rootAddress is missing or memoId is missing
175-
*/
176-
private verifyMemoBasedAddress(address: string, rootAddress: string | undefined): boolean {
177-
if (!rootAddress) {
178-
throw new Error('rootAddress is required for wallet version 1');
179-
}
180-
const extractedRootAddress = utils.validateMemoAndReturnRootAddress(address);
181-
if (extractedRootAddress === address) {
182-
throw new Error('memoId is required for wallet version 1 addresses');
183-
}
184-
185-
return extractedRootAddress?.toLowerCase() === rootAddress.toLowerCase();
186-
}
187-
188-
/**
189-
* Verifies a key-derived address using MPC wallet verification.
190-
*
191-
* @param {TssVerifyIcpAddressOptions} params - Verification parameters
192-
* @param {string} address - The full address to verify
193-
* @param {string | undefined} rootAddress - The wallet's root address
194-
* @returns {Promise<boolean>} True if the address matches the derived address
195-
* @throws {Error} If keychains are missing or address doesn't match
196-
*/
197-
private async verifyKeyDerivedAddress(
198-
params: TssVerifyIcpAddressOptions,
199-
address: string,
200-
rootAddress: string | undefined
201-
): Promise<boolean> {
202-
const { index } = params;
203-
const parsedIndex = typeof index === 'string' ? parseInt(index, 10) : index;
204-
205-
const isVerifyingRootAddress = rootAddress && address.toLowerCase() === rootAddress.toLowerCase();
206-
if (isVerifyingRootAddress && parsedIndex !== 0) {
207-
throw new Error(`Root address verification requires index 0, but got index ${index}`);
171+
if (walletVersion === 1) {
172+
if (!rootAddress) {
173+
throw new Error('rootAddress is required for wallet version 1');
174+
}
175+
const extractedRootAddress = utils.validateMemoAndReturnRootAddress(address);
176+
if (!extractedRootAddress || extractedRootAddress === address) {
177+
throw new Error('memoId is required for wallet version 1 addresses');
178+
}
179+
if (extractedRootAddress.toLowerCase() !== rootAddress.toLowerCase()) {
180+
throw new UnexpectedAddressError(
181+
`address validation failure: expected ${rootAddress} but got ${extractedRootAddress}`
182+
);
183+
}
184+
addressToVerify = rootAddress;
185+
} else if (rootAddress && address.toLowerCase() === rootAddress.toLowerCase() && parsedIndex !== 0) {
186+
throw new Error(`Root address verification requires index 0, but got index ${params.index}`);
208187
}
209188

189+
const indexToVerify = walletVersion === 1 ? 0 : params.index;
210190
const result = await verifyMPCWalletAddress(
211-
{ ...params, keyCurve: 'secp256k1' },
191+
{ ...params, address: addressToVerify, index: indexToVerify, keyCurve: 'secp256k1' },
212192
this.isValidAddress.bind(this),
213193
(pubKey) => utils.getAddressFromPublicKey(pubKey)
214194
);
215195

216196
if (!result) {
217-
throw new InvalidAddressError(`invalid address: ${address}`);
197+
throw new UnexpectedAddressError(
198+
`address validation failure: address ${addressToVerify} is not a wallet address`
199+
);
218200
}
219201

220202
return true;

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

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -238,76 +238,88 @@ describe('Internet computer', function () {
238238
};
239239

240240
describe('Wallet VersionKey 1', () => {
241+
let keychains;
242+
243+
before(function () {
244+
keychains = [
245+
{ commonKeychain: addressVerificationData.commonKeychain },
246+
{ commonKeychain: addressVerificationData.commonKeychain },
247+
{ commonKeychain: addressVerificationData.commonKeychain },
248+
];
249+
});
250+
241251
it('should verify a valid memo-based address', async function () {
242-
const rootAddress = testData.Accounts.account1.address;
252+
const rootAddress = addressVerificationData.rootAddress;
243253
const addressWithMemo = `${rootAddress}?memoId=123`;
244254

245255
const params = {
246256
address: addressWithMemo,
247257
rootAddress: rootAddress,
248258
walletVersion: 1,
249-
keychains: [],
250-
index: 123,
259+
keychains: keychains,
260+
index: 0,
251261
};
252262

253263
const result = await basecoin.isWalletAddress(params);
254264
result.should.equal(true);
255265
});
256266

257267
it('should verify address with memoId=0', async function () {
258-
const rootAddress = testData.Accounts.account1.address;
268+
const rootAddress = addressVerificationData.rootAddress;
259269
const addressWithMemo = `${rootAddress}?memoId=0`;
260270

261271
const params = {
262272
address: addressWithMemo,
263273
rootAddress: rootAddress,
264274
walletVersion: 1,
265-
keychains: [],
275+
keychains: keychains,
266276
index: 0,
267277
};
268278

269279
const result = await basecoin.isWalletAddress(params);
270280
result.should.equal(true);
271281
});
272282

273-
it('should fail when base address does not match root address', async function () {
274-
const rootAddress = testData.Accounts.account1.address;
283+
it('should fail when extracted root does not match provided rootAddress param', async function () {
284+
const rootAddress = addressVerificationData.rootAddress;
275285
const differentAddress = testData.Accounts.account2.address;
276286
const addressWithMemo = `${differentAddress}?memoId=123`;
277287

278288
const params = {
279289
address: addressWithMemo,
280290
rootAddress: rootAddress,
281291
walletVersion: 1,
282-
keychains: [],
283-
index: 123,
292+
keychains: keychains,
293+
index: 0,
284294
};
285295

286-
const result = await basecoin.isWalletAddress(params);
287-
result.should.equal(false);
296+
// The extracted root (differentAddress) doesn't match provided rootAddress
297+
await basecoin
298+
.isWalletAddress(params)
299+
.should.be.rejectedWith(`address validation failure: expected ${rootAddress} but got ${differentAddress}`);
288300
});
289301

290302
it('should throw error when rootAddress is missing for wallet version 1', async function () {
291-
const address = `${testData.Accounts.account1.address}?memoId=123`;
303+
const address = `${addressVerificationData.rootAddress}?memoId=123`;
292304

293305
const params = {
294306
address: address,
295307
walletVersion: 1,
296-
keychains: [],
297-
index: 123,
308+
keychains: keychains,
309+
index: 0,
298310
};
299311

300312
await basecoin.isWalletAddress(params).should.be.rejectedWith('rootAddress is required for wallet version 1');
301313
});
302314

303315
it('should throw error when memoId is missing for wallet version 1', async function () {
304-
const rootAddress = testData.Accounts.account1.address;
316+
const rootAddress = addressVerificationData.rootAddress;
305317

306318
const params = {
307319
address: rootAddress,
308320
rootAddress: rootAddress,
309321
walletVersion: 1,
310-
keychains: [],
322+
keychains: keychains,
311323
index: 0,
312324
};
313325

@@ -317,21 +329,40 @@ describe('Internet computer', function () {
317329
});
318330

319331
it('should handle large memoId values', async function () {
320-
const rootAddress = testData.Accounts.account1.address;
332+
const rootAddress = addressVerificationData.rootAddress;
321333
const largeMemoId = '9007199254740991';
322334
const addressWithMemo = `${rootAddress}?memoId=${largeMemoId}`;
323335

324336
const params = {
325337
address: addressWithMemo,
326338
rootAddress: rootAddress,
327339
walletVersion: 1,
328-
keychains: [],
329-
index: Number(largeMemoId),
340+
keychains: keychains,
341+
index: 0,
330342
};
331343

332344
const result = await basecoin.isWalletAddress(params);
333345
result.should.equal(true);
334346
});
347+
348+
it('should fail when rootAddress does not match commonKeychain derivation', async function () {
349+
// Use a rootAddress that doesn't match what's derived from commonKeychain
350+
const invalidRootAddress = testData.Accounts.account1.address;
351+
const addressWithMemo = `${invalidRootAddress}?memoId=123`;
352+
353+
const params = {
354+
address: addressWithMemo,
355+
rootAddress: invalidRootAddress,
356+
walletVersion: 1,
357+
keychains: keychains,
358+
index: 0,
359+
};
360+
361+
// rootAddress is cryptographically verified against commonKeychain
362+
await basecoin
363+
.isWalletAddress(params)
364+
.should.be.rejectedWith(`address validation failure: address ${invalidRootAddress} is not a wallet address`);
365+
});
335366
});
336367

337368
describe('Wallet VersionKey 2+', () => {
@@ -368,7 +399,9 @@ describe('Internet computer', function () {
368399
walletVersion: 2,
369400
};
370401

371-
await basecoin.isWalletAddress(params).should.be.rejectedWith(`invalid address: ${invalidAddress}`);
402+
await basecoin
403+
.isWalletAddress(params)
404+
.should.be.rejectedWith(`address validation failure: address ${invalidAddress} is not a wallet address`);
372405
});
373406

374407
it('should throw error when keychains is missing', async function () {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@
109109
"**/avalanche/store2": "2.14.4",
110110
"webpack-dev-server": "5.2.1",
111111
"memfs": "4.46.0",
112-
"**/iota-sdk/**/valibot": "1.2.0"
112+
"**/iota-sdk/**/valibot": "1.2.0",
113+
"validator": "13.15.23"
113114
},
114115
"workspaces": [
115116
"modules/*"

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20803,10 +20803,10 @@ validate-npm-package-name@^5.0.0:
2080320803
resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz"
2080420804
integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==
2080520805

20806-
validator@^13.7.0:
20807-
version "13.15.15"
20808-
resolved "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz"
20809-
integrity sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==
20806+
validator@13.15.23, validator@^13.7.0:
20807+
version "13.15.23"
20808+
resolved "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz#59a874f84e4594588e3409ab1edbe64e96d0c62d"
20809+
integrity sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==
2081020810

2081120811
varuint-bitcoin@^1.0.1, varuint-bitcoin@^1.0.4, varuint-bitcoin@^1.1.2:
2081220812
version "1.1.2"

0 commit comments

Comments
 (0)