Skip to content

Commit 3275ae1

Browse files
committed
improvements
1 parent dee5777 commit 3275ae1

4 files changed

Lines changed: 64 additions & 12 deletions

File tree

src/lib/wallet.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,16 @@ export function generateAddressKeypairSOL(
409409
export function generateSolanaPubkeyArray(
410410
xpriv: string,
411411
chain: keyof cryptos,
412+
typeIndex: number,
412413
): string[] {
414+
// typeIndex selects the BIP32 child slot under which we derive the 20
415+
// ed25519 leaves. Consumer wallet passes 0 (receiving). Enterprise
416+
// vaults pass `vault.vaultIndex` so each vault in an org gets a distinct
417+
// pubkey pool — mirrors EVM/UTXO per-vault key separation. Wallet + Key
418+
// must pass the same typeIndex per vault or their multisig PDAs diverge.
413419
const pubkeys: string[] = [];
414420
for (let i = 0; i < 20; i++) {
415-
const { pubKey } = generateAddressKeypairSOL(xpriv, 0, i, chain);
421+
const { pubKey } = generateAddressKeypairSOL(xpriv, typeIndex, i, chain);
416422
pubkeys.push(pubKey);
417423
}
418424
return pubkeys;

src/screens/Home/Home.tsx

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,13 @@ function Home({ navigation }: Props) {
636636
) {
637637
const xprk = CryptoJS.AES.decrypt(xprivKey, pwForEncryption);
638638
const xprivKeyDecrypted = xprk.toString(CryptoJS.enc.Utf8);
639+
// Consumer wallet on-the-fly migration: derive at typeIndex=0
640+
// (receiving slot). Enterprise vaults take a different code path
641+
// (handleVaultXpubAction) which passes vault.vaultIndex.
639642
const keyPubkeys = generateSolanaPubkeyArray(
640643
xprivKeyDecrypted,
641644
chain,
645+
0,
642646
);
643647
xpubKeyDecrypted = JSON.stringify(keyPubkeys);
644648
// Persist the JSON-encoded form back to encrypted storage so
@@ -2318,15 +2322,48 @@ function Home({ navigation }: Props) {
23182322
throw new Error('Unsupported chain: ' + vaultXpubData.chain);
23192323
}
23202324

2321-
// Derive xpub at m/48'/coin'/orgIndex'/scriptType'
2322-
const vaultXpub = getMasterXpub(
2323-
mnemonicPhrase,
2324-
48,
2325-
blockchainConfig.slip,
2326-
vaultXpubData.orgIndex,
2327-
blockchainConfig.scriptType,
2328-
vaultChain,
2329-
);
2325+
// For UTXO/EVM: BIP32 xpub at m/48'/coin'/orgIndex'/scriptType'. Backend
2326+
// derives child pubkeys per addressIndex on demand.
2327+
// For Solana: pre-derive 20 ed25519 pubkeys at /[vaultIndex]/0..19 from
2328+
// the master xpriv and send as JSON array. vaultIndex (from the wallet's
2329+
// relay payload) provides per-vault key separation — mirrors EVM/UTXO
2330+
// behavior where vault.vaultIndex shifts the HD derivation.
2331+
let vaultXpub: string;
2332+
if (
2333+
typeof vaultXpubData.vaultIndex !== 'number' ||
2334+
!Number.isInteger(vaultXpubData.vaultIndex) ||
2335+
vaultXpubData.vaultIndex < 0
2336+
) {
2337+
throw new Error(
2338+
'vaultXpub request missing valid vaultIndex (wallet must send a non-negative integer)',
2339+
);
2340+
}
2341+
const solVaultTypeIndex = vaultXpubData.vaultIndex;
2342+
if (blockchainConfig.chainType === 'sol') {
2343+
const solVaultXpriv = getMasterXpriv(
2344+
mnemonicPhrase,
2345+
48,
2346+
blockchainConfig.slip,
2347+
vaultXpubData.orgIndex,
2348+
blockchainConfig.scriptType,
2349+
vaultChain,
2350+
);
2351+
const pubkeys = generateSolanaPubkeyArray(
2352+
solVaultXpriv,
2353+
vaultChain,
2354+
solVaultTypeIndex,
2355+
);
2356+
vaultXpub = JSON.stringify(pubkeys);
2357+
} else {
2358+
vaultXpub = getMasterXpub(
2359+
mnemonicPhrase,
2360+
48,
2361+
blockchainConfig.slip,
2362+
vaultXpubData.orgIndex,
2363+
blockchainConfig.scriptType,
2364+
vaultChain,
2365+
);
2366+
}
23302367

23312368
// Sign the keyXpub with identity key for verification
23322369
const { xprivKey: idXprivKey } = identityChainState || {};
@@ -2505,6 +2542,11 @@ function Home({ navigation }: Props) {
25052542
typeof solInputDetailsParsed[0]?.addressIndex === 'number'
25062543
? solInputDetailsParsed[0].addressIndex
25072544
: 0;
2545+
// Sign at HD path [vaultIndex][addressIndex]. Mirrors the per-vault
2546+
// xpub flow (generateSolanaPubkeyArray now also derives at
2547+
// typeIndex=vault.vaultIndex), so the wallet's signing pubkey
2548+
// matches the multisig slot pubkey computed from the stored xpub
2549+
// array. Identical to EVM/UTXO per-vault key separation.
25082550
const signingKeypair = generateAddressKeypair(
25092551
vaultXpriv,
25102552
vaultSigningData.vaultIndex,

src/types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,10 @@ interface wkSigningRequest {
599599
interface vaultXpubRequest {
600600
chain: string;
601601
orgIndex: number;
602+
// Per-vault HD typeIndex slot. Solana xpub generation uses this to derive
603+
// a vault-specific 20-pubkey array (mirrors EVM/UTXO per-vault keys).
604+
// Always present — the wallet's relay payload includes it.
605+
vaultIndex: number;
602606
vaultName: string;
603607
orgName: string;
604608
xpubWallet: string;

tests/lib/walletSolana.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe('Solana wallet lib (key device)', () => {
4848

4949
describe('generateSolanaPubkeyArray', () => {
5050
test('produces 20 distinct base58 pubkeys', () => {
51-
const arr = generateSolanaPubkeyArray(xprivWallet, 'solDevnet');
51+
const arr = generateSolanaPubkeyArray(xprivWallet, 'solDevnet', 0);
5252
expect(arr).toHaveLength(20);
5353
expect(new Set(arr).size).toBe(20);
5454
for (const pk of arr) {
@@ -57,7 +57,7 @@ describe('Solana wallet lib (key device)', () => {
5757
});
5858

5959
test('matches generateAddressKeypairSOL element-by-element', () => {
60-
const arr = generateSolanaPubkeyArray(xprivWallet, 'solDevnet');
60+
const arr = generateSolanaPubkeyArray(xprivWallet, 'solDevnet', 0);
6161
for (let i = 0; i < arr.length; i += 7) {
6262
const { pubKey } = generateAddressKeypairSOL(
6363
xprivWallet,

0 commit comments

Comments
 (0)