Skip to content

Commit a64baf3

Browse files
committed
chore: use Keychains and extract commonKeychain verfn
TICKET: WP-6378
1 parent e9982fe commit a64baf3

File tree

5 files changed

+62
-94
lines changed

5 files changed

+62
-94
lines changed

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

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ describe('DOT:', function () {
678678
const commonKeychain =
679679
'6d2d5150f6e435dfd9b4f225f2cc29d95ec3b61b34e8bec98693b1a7ffe44cd764f99ee5058838d785c73360ad4f24d78e0255ab2c368c09060b29a9b27f040e';
680680
const index = '3';
681-
const keychains = [{ commonKeychain }];
681+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
682682

683683
const result = await basecoin.isWalletAddress({ keychains, address, index });
684684
result.should.equal(true);
@@ -689,7 +689,7 @@ describe('DOT:', function () {
689689
const wrongKeychain =
690690
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
691691
const index = '3';
692-
const keychains = [{ commonKeychain: wrongKeychain }];
692+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain: wrongKeychain }];
693693

694694
const result = await basecoin.isWalletAddress({ keychains, address, index });
695695
result.should.equal(false);
@@ -700,7 +700,7 @@ describe('DOT:', function () {
700700
const commonKeychain =
701701
'6d2d5150f6e435dfd9b4f225f2cc29d95ec3b61b34e8bec98693b1a7ffe44cd764f99ee5058838d785c73360ad4f24d78e0255ab2c368c09060b29a9b27f040e';
702702
const wrongIndex = '999';
703-
const keychains = [{ commonKeychain }];
703+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
704704

705705
const result = await basecoin.isWalletAddress({ keychains, address, index: wrongIndex });
706706
result.should.equal(false);
@@ -711,35 +711,12 @@ describe('DOT:', function () {
711711
const commonKeychain =
712712
'6d2d5150f6e435dfd9b4f225f2cc29d95ec3b61b34e8bec98693b1a7ffe44cd764f99ee5058838d785c73360ad4f24d78e0255ab2c368c09060b29a9b27f040e';
713713
const index = '3';
714-
const keychains = [{ commonKeychain }];
714+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
715715

716716
await assert.rejects(async () => await basecoin.isWalletAddress({ keychains, address: invalidAddress, index }), {
717717
message: `invalid address: ${invalidAddress}`,
718718
});
719719
});
720-
721-
it('should throw error when keychains are missing', async function () {
722-
const address = '5DxD9nT16GQLrU6aB5pSS5VtxoZbVju3NHUCcawxZyZCTf74';
723-
const index = '3';
724-
725-
await assert.rejects(async () => await basecoin.isWalletAddress({ address, index } as any), {
726-
message: 'missing required param keychains',
727-
});
728-
});
729-
730-
it('should throw error when keychains have different commonKeychains', async function () {
731-
const address = '5DxD9nT16GQLrU6aB5pSS5VtxoZbVju3NHUCcawxZyZCTf74';
732-
const commonKeychain1 =
733-
'6d2d5150f6e435dfd9b4f225f2cc29d95ec3b61b34e8bec98693b1a7ffe44cd764f99ee5058838d785c73360ad4f24d78e0255ab2c368c09060b29a9b27f040e';
734-
const commonKeychain2 =
735-
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
736-
const index = '3';
737-
const keychains = [{ commonKeychain: commonKeychain1 }, { commonKeychain: commonKeychain2 }];
738-
739-
await assert.rejects(async () => await basecoin.isWalletAddress({ keychains, address, index }), {
740-
message: 'all keychains must have the same commonKeychain for MPC coins',
741-
});
742-
});
743720
});
744721

745722
describe('getAddressFromPublicKey', () => {

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

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3360,7 +3360,7 @@ describe('SOL:', function () {
33603360
const commonKeychain =
33613361
'8ea32ecacfc83effbd2e2790ee44fa7c59b4d86c29a12f09fb613d8195f93f4e21875cad3b98adada40c040c54c3569467df41a020881a6184096378701862bd';
33623362
const index = '1';
3363-
const keychains = [{ commonKeychain }];
3363+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
33643364

33653365
const result = await basecoin.isWalletAddress({ keychains, address, index });
33663366
result.should.equal(true);
@@ -3371,7 +3371,7 @@ describe('SOL:', function () {
33713371
const wrongKeychain =
33723372
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
33733373
const index = '1';
3374-
const keychains = [{ commonKeychain: wrongKeychain }];
3374+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain: wrongKeychain }];
33753375

33763376
const result = await basecoin.isWalletAddress({ keychains, address, index });
33773377
result.should.equal(false);
@@ -3382,7 +3382,7 @@ describe('SOL:', function () {
33823382
const commonKeychain =
33833383
'8ea32ecacfc83effbd2e2790ee44fa7c59b4d86c29a12f09fb613d8195f93f4e21875cad3b98adada40c040c54c3569467df41a020881a6184096378701862bd';
33843384
const wrongIndex = '999';
3385-
const keychains = [{ commonKeychain }];
3385+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
33863386

33873387
const result = await basecoin.isWalletAddress({ keychains, address, index: wrongIndex });
33883388
result.should.equal(false);
@@ -3393,35 +3393,12 @@ describe('SOL:', function () {
33933393
const commonKeychain =
33943394
'8ea32ecacfc83effbd2e2790ee44fa7c59b4d86c29a12f09fb613d8195f93f4e21875cad3b98adada40c040c54c3569467df41a020881a6184096378701862bd';
33953395
const index = '1';
3396-
const keychains = [{ commonKeychain }];
3396+
const keychains = [{ id: '1', type: 'tss' as const, commonKeychain }];
33973397

33983398
await assert.rejects(async () => await basecoin.isWalletAddress({ keychains, address: invalidAddress, index }), {
33993399
message: `invalid address: ${invalidAddress}`,
34003400
});
34013401
});
3402-
3403-
it('should throw error when keychains are missing', async function () {
3404-
const address = '7YAesfwPk41VChUgr65bm8FEep7ymWqLSW5rpYB5zZPY';
3405-
const index = '1';
3406-
3407-
await assert.rejects(async () => await basecoin.isWalletAddress({ address, index } as any), {
3408-
message: 'missing required param keychains',
3409-
});
3410-
});
3411-
3412-
it('should throw error when keychains have different commonKeychains', async function () {
3413-
const address = '7YAesfwPk41VChUgr65bm8FEep7ymWqLSW5rpYB5zZPY';
3414-
const commonKeychain1 =
3415-
'8ea32ecacfc83effbd2e2790ee44fa7c59b4d86c29a12f09fb613d8195f93f4e21875cad3b98adada40c040c54c3569467df41a020881a6184096378701862bd';
3416-
const commonKeychain2 =
3417-
'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
3418-
const index = '1';
3419-
const keychains = [{ commonKeychain: commonKeychain1 }, { commonKeychain: commonKeychain2 }];
3420-
3421-
await assert.rejects(async () => await basecoin.isWalletAddress({ keychains, address, index }), {
3422-
message: 'all keychains must have the same commonKeychain for MPC coins',
3423-
});
3424-
});
34253402
});
34263403

34273404
describe('getAddressFromPublicKey', () => {

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

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
MPCTxs,
3131
MPCSweepRecoveryOptions,
3232
AuditDecryptedKeyParams,
33+
extractCommonKeychain,
3334
} from '@bitgo/sdk-core';
3435
import { auditEddsaPrivateKey, getDerivationPath } from '@bitgo/sdk-lib-mpc';
3536
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
@@ -159,29 +160,21 @@ export class Ton extends BaseCoin {
159160
throw new InvalidAddressError(`invalid address: ${newAddress}`);
160161
}
161162

162-
if (!keychains) {
163-
throw new Error('missing required param keychains');
164-
}
165-
166-
for (const keychain of keychains) {
167-
const [address, memoId] = newAddress.split('?memoId=');
168-
const MPC = await EDDSAMethods.getInitializedMpcInstance();
169-
const commonKeychain = keychain.commonKeychain as string;
163+
const [address, memoId] = newAddress.split('?memoId=');
170164

171-
const derivationPath = 'm/' + index;
172-
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);
173-
const expectedAddress = await Utils.default.getAddressFromPublicKey(derivedPublicKey);
165+
// TON supports memoId for address tagging - verify it matches the index
166+
if (memoId) {
167+
return memoId === `${index}`;
168+
}
174169

175-
if (memoId) {
176-
return memoId === `${index}`;
177-
}
170+
const commonKeychain = extractCommonKeychain(keychains);
178171

179-
if (address !== expectedAddress) {
180-
return false;
181-
}
182-
}
172+
const MPC = await EDDSAMethods.getInitializedMpcInstance();
173+
const derivationPath = 'm/' + index;
174+
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);
175+
const expectedAddress = await Utils.default.getAddressFromPublicKey(derivedPublicKey);
183176

184-
return true;
177+
return address === expectedAddress;
185178
}
186179

187180
async parseTransaction(params: TonParseTransactionOptions): Promise<ParsedTransaction> {

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,24 @@ export interface VerifyAddressOptions {
155155
impliedForwarderVersion?: number;
156156
}
157157

158+
/**
159+
* Options for verifying if an address belongs to a TSS/MPC wallet.
160+
* Used for EdDSA-based MPC coins (SOL, DOT, SUI, TON, IOTA, NEAR, etc.)
161+
* to cryptographically verify address derivation without trusting the platform.
162+
*/
158163
export interface TssVerifyAddressOptions {
164+
/** The address to verify */
159165
address: string;
160-
keychains: {
161-
commonKeychain: string;
162-
}[];
163-
chain?: string;
166+
/**
167+
* Keychains containing the commonKeychain for HD derivation.
168+
* For MPC wallets, the commonKeychain (combined public key from MPC key generation)
169+
* should be identical across all keychains (user, backup, bitgo).
170+
*/
171+
keychains: Keychain[];
172+
/**
173+
* Derivation index for the address.
174+
* Used to derive child addresses from the root keychain via HD derivation path: m/{index}
175+
*/
164176
index: string;
165177
}
166178

modules/sdk-core/src/bitgo/utils/tss/addressVerification.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ import { TssVerifyAddressOptions } from '../../baseCoin/iBaseCoin';
22
import { InvalidAddressError } from '../../errors';
33
import { EDDSAMethods } from '../../tss';
44

5+
/**
6+
* Extracts and validates the commonKeychain from keychains array.
7+
* For MPC wallets, all keychains should have the same commonKeychain.
8+
*
9+
* @param keychains - Array of keychains containing commonKeychain
10+
* @returns The validated commonKeychain
11+
* @throws {Error} if keychains are missing, empty, or have mismatched commonKeychains
12+
*/
13+
export function extractCommonKeychain(keychains: TssVerifyAddressOptions['keychains']): string {
14+
if (!keychains?.length) {
15+
throw new Error('missing required param keychains');
16+
}
17+
18+
const commonKeychain = keychains[0].commonKeychain;
19+
if (!commonKeychain) {
20+
throw new Error('missing required param commonKeychain');
21+
}
22+
23+
// Verify all keychains have the same commonKeychain
24+
if (keychains.find((kc) => kc.commonKeychain !== commonKeychain))
25+
throw new Error('all keychains must have the same commonKeychain for MPC coins');
26+
27+
return commonKeychain;
28+
}
29+
530
/**
631
* Verifies if an address belongs to a wallet using EdDSA TSS MPC derivation.
732
* This is a common implementation for EdDSA-based MPC coins (SOL, DOT, SUI, TON, IOTA, etc.)
@@ -24,24 +49,8 @@ export async function verifyEddsaTssWalletAddress(
2449
throw new InvalidAddressError(`invalid address: ${address}`);
2550
}
2651

27-
if (!keychains || keychains.length === 0) {
28-
throw new Error('missing required param keychains');
29-
}
30-
31-
// For MPC coins, commonKeychain should be the same for all keychains
32-
const commonKeychain = keychains[0].commonKeychain as string;
33-
if (!commonKeychain) {
34-
throw new Error('missing required param commonKeychain');
35-
}
36-
37-
// Verify all keychains have the same commonKeychain
38-
for (const keychain of keychains) {
39-
if (keychain.commonKeychain !== commonKeychain) {
40-
throw new Error('all keychains must have the same commonKeychain for MPC coins');
41-
}
42-
}
52+
const commonKeychain = extractCommonKeychain(keychains);
4353

44-
// Only perform derivation once since commonKeychain is the same
4554
const MPC = await EDDSAMethods.getInitializedMpcInstance();
4655
const derivationPath = 'm/' + index;
4756
const derivedPublicKey = MPC.deriveUnhardened(commonKeychain, derivationPath).slice(0, 64);

0 commit comments

Comments
 (0)