Skip to content

Commit eabcc69

Browse files
committed
feat: enhance keyless wallet functionality with new methods and improved ID generation
- Introduced a new method to save keyless wallet data to the database in ServiceKeylessWallet, enhancing data persistence. - Updated keyless wallet pack generation to use a new method for creating pack set IDs, improving UUID handling. - Added a utility function to build keyless wallet account IDs, streamlining account management. - Refactored tests to accommodate changes in pack structure and ensure consistency across keyless wallet operations.
1 parent 8cd6d32 commit eabcc69

File tree

5 files changed

+97
-52
lines changed

5 files changed

+97
-52
lines changed

packages/kit-bg/src/services/ServiceKeylessWallet/ServiceKeylessWallet.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import stringUtils from '@onekeyhq/shared/src/utils/stringUtils';
2828
import timerUtils from '@onekeyhq/shared/src/utils/timerUtils';
2929
import { EPrimeTransferDataType } from '@onekeyhq/shared/types/prime/primeTransferTypes';
3030

31+
import localDb from '../../dbs/local/localDb';
3132
import { primePersistAtom } from '../../states/jotai/atoms';
3233
import { devSettingsPersistAtom } from '../../states/jotai/atoms/devSettings';
3334
import ServiceBase from '../ServiceBase';
@@ -149,11 +150,17 @@ class ServiceKeylessWallet extends ServiceBase {
149150
const wallet = await keylessWalletUtils.generateKeylessWalletPacks({
150151
userInfo,
151152
mnemonicInfo,
152-
packSetId: stringUtils.generateUUID(),
153+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
153154
});
154155
return wallet;
155156
}
156157

158+
@backgroundMethod()
159+
async saveKeylessWalletToDB({ packSetId }: { packSetId: string }) {
160+
// localDb.createHDWallet()
161+
void this.backgroundApi.serviceAccount.createHDWallet;
162+
}
163+
157164
@backgroundMethod()
158165
@toastIfError()
159166
async restoreKeylessWallet(params: {
@@ -178,6 +185,7 @@ class ServiceKeylessWallet extends ServiceBase {
178185
// Recover mnemonic from any 2 of 3 packs
179186
if (deviceKeyPack && authKeyPack) {
180187
checkPackSetId(deviceKeyPack, authKeyPack);
188+
181189
return keylessWalletUtils.restoreFromDeviceAndAuth({
182190
deviceKeyPack,
183191
authKeyPack,
@@ -193,13 +201,12 @@ class ServiceKeylessWallet extends ServiceBase {
193201
}
194202
if (authKeyPack && cloudKeyPack) {
195203
checkPackSetId(authKeyPack, cloudKeyPack);
196-
const cloudAccountInfo =
197-
await this.backgroundApi.serviceCloudBackupV2.getCloudAccountInfo();
198-
const cloudKeyUserId = cloudAccountInfo.userId;
204+
// const cloudAccountInfo =
205+
// await this.backgroundApi.serviceCloudBackupV2.getCloudAccountInfo();
206+
// const cloudKeyUserId = cloudAccountInfo.userId;
199207
return keylessWalletUtils.restoreFromAuthAndCloud({
200208
authKeyPack,
201209
cloudKeyPack,
202-
cloudKeyUserId,
203210
});
204211
}
205212

packages/shared/src/keylessWallet/keylessWalletUtils.test.ts

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import keylessWalletUtils from './keylessWalletUtils';
77

88
import type {
99
IKeylessWalletPacks,
10+
IKeylessWalletRestoredData,
1011
IKeylessWalletUserInfo,
1112
} from './keylessWalletTypes';
1213

@@ -89,6 +90,13 @@ function printPacksDiff(
8990
}
9091
}
9192

93+
// Helper function to extract IKeylessWalletPacks from either type
94+
function getPacks(
95+
packs: IKeylessWalletPacks | IKeylessWalletRestoredData,
96+
): IKeylessWalletPacks {
97+
return 'packs' in packs ? packs.packs : packs;
98+
}
99+
92100
describe('keylessWalletUtils', () => {
93101
describe('generateKeylessMnemonic and restoreMnemonicFromShareKey', () => {
94102
it('should generate mnemonic with 3 share keys', async () => {
@@ -225,7 +233,7 @@ describe('keylessWalletUtils', () => {
225233
const packs = await keylessWalletUtils.generateKeylessWalletPacks({
226234
userInfo: mockUserInfo,
227235
mnemonicInfo,
228-
packSetId: stringUtils.generateUUID(),
236+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
229237
});
230238

231239
// Verify mnemonic info is preserved
@@ -252,7 +260,7 @@ describe('keylessWalletUtils', () => {
252260
{
253261
userInfo: mockUserInfo,
254262
mnemonicInfo,
255-
packSetId: stringUtils.generateUUID(),
263+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
256264
},
257265
);
258266

@@ -263,18 +271,18 @@ describe('keylessWalletUtils', () => {
263271
});
264272

265273
// Step 3: Verify mnemonic is restored correctly
266-
expect(restoredPacks.mnemonic).toBe(originalPacks.mnemonic);
274+
expect(restoredPacks.packs.mnemonic).toBe(originalPacks.mnemonic);
267275

268276
// Step 4: Verify user info fields are preserved in packs
269-
expect(restoredPacks.deviceKeyPack.cloudKeyProvider).toBe(
277+
expect(restoredPacks.packs.deviceKeyPack.cloudKeyProvider).toBe(
270278
mockUserInfo.cloudKeyProvider,
271279
);
272280

273281
// Step 5: Verify comparable fields are equal (excluding encrypted and re-generated keys)
274-
if (!isPacksEqual(originalPacks, restoredPacks)) {
275-
printPacksDiff(originalPacks, restoredPacks);
282+
if (!isPacksEqual(originalPacks, restoredPacks.packs)) {
283+
printPacksDiff(originalPacks, restoredPacks.packs);
276284
}
277-
expect(isPacksEqual(originalPacks, restoredPacks)).toBe(true);
285+
expect(isPacksEqual(originalPacks, restoredPacks.packs)).toBe(true);
278286
});
279287

280288
it('should restore from restoredPacks cloudPack + devicePack consistently', async () => {
@@ -284,7 +292,7 @@ describe('keylessWalletUtils', () => {
284292
{
285293
userInfo: mockUserInfo,
286294
mnemonicInfo,
287-
packSetId: stringUtils.generateUUID(),
295+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
288296
},
289297
);
290298

@@ -299,15 +307,20 @@ describe('keylessWalletUtils', () => {
299307
const restoredFromDeviceAndCloud =
300308
await keylessWalletUtils.restoreFromDeviceAndCloud({
301309
deviceKeyPack: originalPacks.deviceKeyPack,
302-
cloudKeyPack: restoredFromDeviceAndAuth.cloudKeyPack,
310+
cloudKeyPack: restoredFromDeviceAndAuth.packs.cloudKeyPack,
303311
});
304312

305313
// Step 4: Verify mnemonic is the same
306-
expect(restoredFromDeviceAndCloud.mnemonic).toBe(originalPacks.mnemonic);
314+
expect(restoredFromDeviceAndCloud.packs.mnemonic).toBe(
315+
originalPacks.mnemonic,
316+
);
307317

308318
// Step 5: Compare comparable fields between the two restored packs
309319
expect(
310-
isPacksEqual(restoredFromDeviceAndAuth, restoredFromDeviceAndCloud),
320+
isPacksEqual(
321+
restoredFromDeviceAndAuth.packs,
322+
restoredFromDeviceAndCloud.packs,
323+
),
311324
).toBe(true);
312325
});
313326

@@ -318,7 +331,7 @@ describe('keylessWalletUtils', () => {
318331
{
319332
userInfo: mockUserInfo,
320333
mnemonicInfo,
321-
packSetId: stringUtils.generateUUID(),
334+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
322335
},
323336
);
324337

@@ -333,16 +346,20 @@ describe('keylessWalletUtils', () => {
333346
const restoredFromAuthAndCloud =
334347
await keylessWalletUtils.restoreFromAuthAndCloud({
335348
authKeyPack: originalPacks.authKeyPack,
336-
cloudKeyPack: restoredFromDeviceAndAuth.cloudKeyPack,
337-
cloudKeyUserId: mockUserInfo.cloudKeyUserId,
349+
cloudKeyPack: restoredFromDeviceAndAuth.packs.cloudKeyPack,
338350
});
339351

340352
// Step 4: Verify mnemonic is the same
341-
expect(restoredFromAuthAndCloud.mnemonic).toBe(originalPacks.mnemonic);
353+
expect(restoredFromAuthAndCloud.packs.mnemonic).toBe(
354+
originalPacks.mnemonic,
355+
);
342356

343357
// Step 5: Compare comparable fields between the two restored packs
344358
expect(
345-
isPacksEqual(restoredFromDeviceAndAuth, restoredFromAuthAndCloud),
359+
isPacksEqual(
360+
restoredFromDeviceAndAuth.packs,
361+
restoredFromAuthAndCloud.packs,
362+
),
346363
).toBe(true);
347364
});
348365

@@ -353,7 +370,7 @@ describe('keylessWalletUtils', () => {
353370
{
354371
userInfo: mockUserInfo,
355372
mnemonicInfo,
356-
packSetId: stringUtils.generateUUID(),
373+
packSetId: keylessWalletUtils.generateKeylessWalletPackSetId(),
357374
},
358375
);
359376

@@ -374,7 +391,6 @@ describe('keylessWalletUtils', () => {
374391
await keylessWalletUtils.restoreFromAuthAndCloud({
375392
authKeyPack: originalPacks.authKeyPack,
376393
cloudKeyPack: originalPacks.cloudKeyPack,
377-
cloudKeyUserId: mockUserInfo.cloudKeyUserId,
378394
});
379395

380396
// Step 3: Collect all pack sources
@@ -387,50 +403,56 @@ describe('keylessWalletUtils', () => {
387403

388404
// Step 4: All packs should have the same mnemonic and be equal
389405
for (const source of allPackSources) {
390-
expect(source.packs.mnemonic).toBe(originalPacks.mnemonic);
406+
const packs = getPacks(source.packs);
407+
expect(packs.mnemonic).toBe(originalPacks.mnemonic);
391408
}
392409
for (let i = 0; i < allPackSources.length; i += 1) {
393410
for (let j = i + 1; j < allPackSources.length; j += 1) {
394-
expect(
395-
isPacksEqual(allPackSources[i].packs, allPackSources[j].packs),
396-
).toBe(true);
411+
const packsI = getPacks(allPackSources[i].packs);
412+
const packsJ = getPacks(allPackSources[j].packs);
413+
expect(isPacksEqual(packsI, packsJ)).toBe(true);
397414
}
398415
}
399416

400417
// Step 5: Test all combinations for restoreFromDeviceAndAuth (4x4=16 combinations)
401418
for (const deviceSource of allPackSources) {
402419
for (const authSource of allPackSources) {
420+
const devicePacks = getPacks(deviceSource.packs);
421+
const authPacks = getPacks(authSource.packs);
403422
const restored = await keylessWalletUtils.restoreFromDeviceAndAuth({
404-
deviceKeyPack: deviceSource.packs.deviceKeyPack,
405-
authKeyPack: authSource.packs.authKeyPack,
423+
deviceKeyPack: devicePacks.deviceKeyPack,
424+
authKeyPack: authPacks.authKeyPack,
406425
});
407-
expect(restored.mnemonic).toBe(originalPacks.mnemonic);
408-
expect(isPacksEqual(restored, originalPacks)).toBe(true);
426+
expect(restored.packs.mnemonic).toBe(originalPacks.mnemonic);
427+
expect(isPacksEqual(restored.packs, originalPacks)).toBe(true);
409428
}
410429
}
411430

412431
// Step 6: Test all combinations for restoreFromDeviceAndCloud (4x4=16 combinations)
413432
for (const deviceSource of allPackSources) {
414433
for (const cloudSource of allPackSources) {
434+
const devicePacks = getPacks(deviceSource.packs);
435+
const cloudPacks = getPacks(cloudSource.packs);
415436
const restored = await keylessWalletUtils.restoreFromDeviceAndCloud({
416-
deviceKeyPack: deviceSource.packs.deviceKeyPack,
417-
cloudKeyPack: cloudSource.packs.cloudKeyPack,
437+
deviceKeyPack: devicePacks.deviceKeyPack,
438+
cloudKeyPack: cloudPacks.cloudKeyPack,
418439
});
419-
expect(restored.mnemonic).toBe(originalPacks.mnemonic);
420-
expect(isPacksEqual(restored, originalPacks)).toBe(true);
440+
expect(restored.packs.mnemonic).toBe(originalPacks.mnemonic);
441+
expect(isPacksEqual(restored.packs, originalPacks)).toBe(true);
421442
}
422443
}
423444

424445
// Step 7: Test all combinations for restoreFromAuthAndCloud (4x4=16 combinations)
425446
for (const authSource of allPackSources) {
426447
for (const cloudSource of allPackSources) {
448+
const authPacks = getPacks(authSource.packs);
449+
const cloudPacks = getPacks(cloudSource.packs);
427450
const restored = await keylessWalletUtils.restoreFromAuthAndCloud({
428-
authKeyPack: authSource.packs.authKeyPack,
429-
cloudKeyPack: cloudSource.packs.cloudKeyPack,
430-
cloudKeyUserId: mockUserInfo.cloudKeyUserId,
451+
authKeyPack: authPacks.authKeyPack,
452+
cloudKeyPack: cloudPacks.cloudKeyPack,
431453
});
432-
expect(restored.mnemonic).toBe(originalPacks.mnemonic);
433-
expect(isPacksEqual(restored, originalPacks)).toBe(true);
454+
expect(restored.packs.mnemonic).toBe(originalPacks.mnemonic);
455+
expect(isPacksEqual(restored.packs, originalPacks)).toBe(true);
434456
}
435457
}
436458
});

packages/shared/src/keylessWallet/keylessWalletUtils.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ function deriveCloudKeyPwd(
7474

7575
function deriveAuthKeyPwd(
7676
authKeyPwdSlice: string,
77-
_cloudKeyUserId: string, // TODO remove this param, 假如 cloudKeyUserId 会导致切换 Cloud 存储后,其余的旧 deviceKey 都失效
77+
// TODO remove this param, 假如 cloudKeyUserId 会导致切换 Cloud 存储后,其余的旧 deviceKey 都失效
78+
// _cloudKeyUserId: string,
7879
): Promise<string> {
7980
return deriveKeyPwd({
8081
pwdSlice: authKeyPwdSlice,
@@ -190,12 +191,22 @@ async function generateKeylessMnemonic(): Promise<IKeylessMnemonicInfo> {
190191
};
191192
}
192193

194+
function generateKeylessWalletPackSetId(): string {
195+
return stringUtils.generateUUID({ removeDashes: true });
196+
}
197+
193198
async function generateKeylessWalletPacks(params: {
194199
userInfo: IKeylessWalletUserInfo;
195200
mnemonicInfo: IKeylessMnemonicInfo;
196201
packSetId: string;
197202
}): Promise<IKeylessWalletPacks> {
198203
const { userInfo, mnemonicInfo, packSetId } = params;
204+
// Validate the packSetId with regex: must match UUID (v4) without dashes (32 lowercase hex characters)
205+
if (!/^[0-9a-f]{32}$/.test(packSetId)) {
206+
throw new OneKeyLocalError(
207+
'Invalid packSetId: must be a 32-character lowercase hex string (UUID with dashes removed)',
208+
);
209+
}
199210
const { onekeyIdEmail, onekeyIdUserId, cloudKeyProvider, cloudKeyUserId } =
200211
userInfo;
201212

@@ -241,7 +252,7 @@ async function generateKeylessWalletPacks(params: {
241252
// 4. Derive Passwords
242253
const deviceKeyPwd = await deriveDeviceKeyPwd(deviceKeyPwdSlice);
243254
const cloudKeyPwd = await deriveCloudKeyPwd(cloudKeyPwdSlice, onekeyIdUserId);
244-
const authKeyPwd = await deriveAuthKeyPwd(authKeyPwdSlice, cloudKeyUserId);
255+
const authKeyPwd = await deriveAuthKeyPwd(authKeyPwdSlice);
245256

246257
// 5. Hash Passwords (for storage/verification, spec has *Hash fields)
247258
const deviceKeyPwdHash = await hashPassword(deviceKeyPwd);
@@ -544,22 +555,16 @@ async function restoreFromDeviceAndCloud(params: {
544555
async function restoreFromAuthAndCloud(params: {
545556
authKeyPack: IAuthKeyPack;
546557
cloudKeyPack: ICloudKeyPack;
547-
cloudKeyUserId: string;
548558
}): Promise<IKeylessWalletRestoredData> {
549559
// Step 1: Get cloudKeyUserId to derive authKeyPwd
550-
const { authKeyPack, cloudKeyPack, cloudKeyUserId } = params;
551-
if (!cloudKeyUserId) {
552-
throw new OneKeyLocalError(
553-
'restoreFromAuthAndCloud ERROR: cloudKeyUserId is required',
554-
);
555-
}
560+
const { authKeyPack, cloudKeyPack } = params;
556561

557562
// Step 2: Use authKeyPwdSlice from CloudKeyPack to derive authKeyPwd
558563
const authKeyPwdSlice = cloudKeyPack.authKeyPwdSlice;
559564
if (!authKeyPwdSlice) {
560565
throw new OneKeyLocalError('CloudKeyPack does not contain authKeyPwdSlice');
561566
}
562-
const authKeyPwd = await deriveAuthKeyPwd(authKeyPwdSlice, cloudKeyUserId);
567+
const authKeyPwd = await deriveAuthKeyPwd(authKeyPwdSlice);
563568

564569
// Step 3: Decrypt AuthKeyPack to get authKey
565570
const authKeyPackData = await decryptPackData<IAuthKeyPackEncryptedData>({
@@ -635,6 +640,7 @@ async function restoreFromAuthAndCloud(params: {
635640

636641
export default {
637642
getShareXCoordinate,
643+
generateKeylessWalletPackSetId,
638644
generateKeylessWalletPacks,
639645
restoreFromDeviceAndAuth,
640646
restoreFromDeviceAndCloud,

packages/shared/src/utils/accountUtils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,17 @@ function isEnabledBtcFreshAddress({
912912
return false;
913913
}
914914

915+
function buildKeylessWalletAccountId({
916+
sharePackSetId,
917+
}: {
918+
sharePackSetId: string;
919+
}) {
920+
return `${WALLET_TYPE_HD}-keyless--${sharePackSetId}`;
921+
}
922+
915923
export default {
916924
URL_ACCOUNT_ID,
925+
buildKeylessWalletAccountId,
917926
buildAccountValueKey,
918927
parseAccountValueKey,
919928
buildUtxoAddressRelPath,

packages/shared/src/utils/miscUtils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import type {
55
IDBUtxoAccount,
66
} from '@onekeyhq/kit-bg/src/dbs/local/types';
77

8-
export function generateUUID() {
9-
return uuid.v4() as string;
8+
export function generateUUID(options?: { removeDashes?: boolean }) {
9+
const uuidString = uuid.v4() as string;
10+
return options?.removeDashes ? uuidString.replace(/-/g, '') : uuidString;
1011
}
1112

1213
export function generateLocalIndexedIdFunc() {

0 commit comments

Comments
 (0)