@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +13 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
  4. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
  5. package/__tests__/device-lifecycle-events.test.ts +19 -0
  6. package/__tests__/deviceUploadNft.test.ts +68 -0
  7. package/__tests__/evmSignTypedData.test.ts +42 -0
  8. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
  9. package/__tests__/open-wallet-session.test.ts +144 -37
  10. package/__tests__/pro2HostAssetPackage.test.ts +58 -0
  11. package/__tests__/protocol-v2-resources.test.ts +7 -4
  12. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
  13. package/__tests__/protocol-v2.test.ts +720 -51
  14. package/dist/api/FirmwareUpdateV4.d.ts +4 -0
  15. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  16. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  17. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  18. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  19. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  20. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  21. package/dist/api/firmware/getBinary.d.ts +2 -2
  22. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  23. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  24. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  25. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  26. package/dist/core/index.d.ts +1 -0
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/device/Device.d.ts +1 -1
  29. package/dist/device/Device.d.ts.map +1 -1
  30. package/dist/device/DeviceConnector.d.ts +1 -1
  31. package/dist/device/DeviceConnector.d.ts.map +1 -1
  32. package/dist/events/ui-request.d.ts +3 -0
  33. package/dist/events/ui-request.d.ts.map +1 -1
  34. package/dist/index.d.ts +21 -5
  35. package/dist/index.js +2288 -1439
  36. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  37. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
  38. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  40. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  41. package/dist/types/settings.d.ts +2 -2
  42. package/dist/types/settings.d.ts.map +1 -1
  43. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  44. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  45. package/dist/utils/patch.d.ts +1 -1
  46. package/dist/utils/patch.d.ts.map +1 -1
  47. package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
  48. package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
  49. package/package.json +4 -4
  50. package/src/api/FirmwareUpdateV4.ts +434 -75
  51. package/src/api/OpenWalletSession.ts +39 -9
  52. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
  53. package/src/api/evm/EVMSignTypedData.ts +1 -0
  54. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  55. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  56. package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
  57. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +27 -8
  58. package/src/api/protocol-v2/helpers.ts +8 -0
  59. package/src/core/index.ts +130 -14
  60. package/src/data/messages/messages-protocol-v2.json +1541 -1332
  61. package/src/device/Device.ts +3 -1
  62. package/src/device/DeviceConnector.ts +7 -3
  63. package/src/events/ui-request.ts +3 -0
  64. package/src/protocols/protocol-v2/resources.ts +9 -1
  65. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
  66. package/src/protocols/protocol-v2/walletSession.ts +88 -50
  67. package/src/types/settings.ts +4 -2
  68. package/src/utils/deviceFeaturesUtils.ts +4 -0
  69. package/src/utils/pro2HostAssetPackage.ts +354 -0
@@ -142,13 +142,12 @@ describe('openWalletSession', () => {
142
142
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
143
143
  passphrase: '',
144
144
  on_device: false,
145
- });
146
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
147
145
  seed_domains: [],
148
146
  });
147
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
149
148
  });
150
149
 
151
- test('opens the standard wallet directly when passphrase is disabled', async () => {
150
+ test('selects the Main PIN before opening the standard wallet when passphrase is disabled', async () => {
152
151
  const typedCall = jest.fn((request: string) => {
153
152
  if (request === 'ProtocolInfoRequest') {
154
153
  return { message: { version: 2 } };
@@ -167,14 +166,43 @@ describe('openWalletSession', () => {
167
166
 
168
167
  await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
169
168
 
169
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
170
+ source: 'wallet-session-coordinator',
171
+ reason: 'open-wallet',
172
+ deviceOnly: true,
173
+ });
170
174
  expect(typedCall).not.toHaveBeenCalledWith(
171
175
  'DeviceSessionAskPassphrase',
172
176
  'Success',
173
177
  expect.anything()
174
178
  );
175
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
176
- seed_domains: [],
179
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
180
+ });
181
+
182
+ test('reuses a Main PIN selected by the current preflight when passphrase is disabled', async () => {
183
+ const typedCall = jest.fn((request: string) => {
184
+ if (request === 'ProtocolInfoRequest') {
185
+ return { message: { version: 2 } };
186
+ }
187
+ if (request === 'DeviceSessionGet') {
188
+ return {
189
+ message: {
190
+ btc_test_address: 'standard-state',
191
+ session_id: 'standard-session',
192
+ },
193
+ };
194
+ }
195
+ throw new Error(`Unexpected request: ${request}`);
196
+ });
197
+ const device = createDevice({ passphraseProtection: false, typedCall });
198
+
199
+ await getProtocolV2WalletSession(device as any, {
200
+ onlyMainPin: true,
201
+ mainPinSelected: true,
177
202
  });
203
+
204
+ expect(device.unlockDevice).not.toHaveBeenCalled();
205
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
178
206
  });
179
207
 
180
208
  test('does not treat an Attach PIN DeviceStatus as the Main wallet', async () => {
@@ -301,10 +329,9 @@ describe('openWalletSession', () => {
301
329
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
302
330
  passphrase: 'host hidden wallet',
303
331
  on_device: false,
304
- });
305
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
306
332
  seed_domains: [],
307
333
  });
334
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
308
335
  });
309
336
 
310
337
  test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
@@ -421,10 +448,9 @@ describe('openWalletSession', () => {
421
448
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
422
449
  passphrase: 'host hidden wallet',
423
450
  on_device: false,
424
- });
425
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
426
451
  seed_domains: [],
427
452
  });
453
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
428
454
  });
429
455
 
430
456
  test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
@@ -698,9 +724,7 @@ describe('openWalletSession', () => {
698
724
  });
699
725
  expect(promptPassphrase).not.toHaveBeenCalled();
700
726
  expect(device.unlockDevice).not.toHaveBeenCalled();
701
- expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
702
- seed_domains: [],
703
- });
727
+ expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
704
728
  expect(device.commands.typedCall).not.toHaveBeenCalledWith(
705
729
  'DeviceSessionAskPassphrase',
706
730
  'Success',
@@ -796,10 +820,9 @@ describe('openWalletSession', () => {
796
820
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
797
821
  passphrase: 'host hidden wallet',
798
822
  on_device: false,
799
- });
800
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
801
823
  seed_domains: [],
802
824
  });
825
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
803
826
  expect(promptPassphrase).toHaveBeenCalled();
804
827
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
805
828
  });
@@ -871,7 +894,6 @@ describe('openWalletSession', () => {
871
894
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
872
895
  session_id: 'known-session',
873
896
  btc_test_address: 'hidden-state',
874
- seed_domains: [],
875
897
  });
876
898
  expect(promptPassphrase).not.toHaveBeenCalled();
877
899
  });
@@ -1054,10 +1076,9 @@ describe('openWalletSession', () => {
1054
1076
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1055
1077
  passphrase: '',
1056
1078
  on_device: false,
1057
- });
1058
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1059
1079
  seed_domains: [],
1060
1080
  });
1081
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1061
1082
  expect(promptPassphrase).not.toHaveBeenCalled();
1062
1083
  expect(device.passphraseState).toBeUndefined();
1063
1084
  });
@@ -1114,6 +1135,68 @@ describe('openWalletSession', () => {
1114
1135
  expect(device.getDeviceState).toHaveBeenCalledTimes(2);
1115
1136
  });
1116
1137
 
1138
+ test('does not restrict a locked hidden-wallet selection to the Main PIN', async () => {
1139
+ const typedCall = jest
1140
+ .fn()
1141
+ .mockResolvedValueOnce({ message: { version: 2 } })
1142
+ .mockResolvedValueOnce({ message: {} })
1143
+ .mockResolvedValueOnce({
1144
+ message: {
1145
+ btc_test_address: 'hidden-state',
1146
+ session_id: 'hidden-session',
1147
+ },
1148
+ });
1149
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
1150
+ const method = new OpenWalletSession({
1151
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
1152
+ });
1153
+ method.init();
1154
+ const device = createDevice({ typedCall, promptPassphrase });
1155
+ device.features.unlocked = false;
1156
+ device.getDeviceState = jest
1157
+ .fn()
1158
+ .mockResolvedValueOnce({
1159
+ identity: { deviceId: undefined },
1160
+ status: { unlocked: false, passphraseProtection: true },
1161
+ })
1162
+ .mockResolvedValueOnce({
1163
+ identity: { deviceId: 'device-1' },
1164
+ status: {
1165
+ unlocked: true,
1166
+ passphraseProtection: true,
1167
+ unlockedAttachPin: false,
1168
+ },
1169
+ })
1170
+ .mockResolvedValueOnce({
1171
+ identity: { deviceId: 'device-1' },
1172
+ status: {
1173
+ unlocked: true,
1174
+ passphraseProtection: true,
1175
+ unlockedAttachPin: false,
1176
+ },
1177
+ });
1178
+ device.unlockDevice = jest.fn().mockImplementation(() => {
1179
+ device.features.unlocked = true;
1180
+ return Promise.resolve(device.features);
1181
+ });
1182
+ method.device = device as any;
1183
+
1184
+ await expect(method.run()).resolves.toMatchObject({
1185
+ walletType: 'hidden',
1186
+ passphraseState: 'hidden-state',
1187
+ });
1188
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
1189
+ source: 'unlock-coordinator',
1190
+ reason: 'device-locked',
1191
+ deviceOnly: true,
1192
+ method: 'openWalletSession',
1193
+ });
1194
+ expect(device.unlockDevice).not.toHaveBeenCalledWith(
1195
+ DeviceSessionPinType.Main,
1196
+ expect.anything()
1197
+ );
1198
+ });
1199
+
1117
1200
  test('switches from Attach PIN to Main PIN before opening the standard wallet', async () => {
1118
1201
  const typedCall = jest
1119
1202
  .fn()
@@ -1164,9 +1247,7 @@ describe('openWalletSession', () => {
1164
1247
  reason: 'open-wallet',
1165
1248
  deviceOnly: true,
1166
1249
  });
1167
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1168
- seed_domains: [],
1169
- });
1250
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1170
1251
  });
1171
1252
 
1172
1253
  test('selects a hidden wallet without exposing the internal device session', async () => {
@@ -1199,15 +1280,45 @@ describe('openWalletSession', () => {
1199
1280
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1200
1281
  passphrase: 'host hidden wallet',
1201
1282
  on_device: false,
1202
- });
1203
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1204
1283
  seed_domains: [],
1205
1284
  });
1285
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1206
1286
  expect(promptPassphrase).toHaveBeenCalled();
1207
1287
  expect(device.passphraseState).toBeUndefined();
1208
1288
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
1209
1289
  });
1210
1290
 
1291
+ test('reuses the fresh unlock and wallet-selection statuses', async () => {
1292
+ const typedCall = jest
1293
+ .fn()
1294
+ .mockResolvedValueOnce({ message: { version: 2 } })
1295
+ .mockResolvedValueOnce({ message: {} })
1296
+ .mockResolvedValueOnce({
1297
+ message: {
1298
+ btc_test_address: 'hidden-state',
1299
+ session_id: 'hidden-session',
1300
+ },
1301
+ });
1302
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
1303
+ const method = new OpenWalletSession({
1304
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
1305
+ });
1306
+ method.init();
1307
+ method.protocolV2UnlockContext = {
1308
+ preflightCompleted: true,
1309
+ preflightStatusRefreshed: true,
1310
+ };
1311
+ const device = createDevice({ typedCall, promptPassphrase });
1312
+ method.device = device as any;
1313
+
1314
+ await expect(method.run()).resolves.toMatchObject({
1315
+ walletType: 'hidden',
1316
+ passphraseState: 'hidden-state',
1317
+ });
1318
+
1319
+ expect(device.getDeviceState.mock.calls).toEqual([[], []]);
1320
+ });
1321
+
1211
1322
  test('gets the prepared session after host passphrase entry', async () => {
1212
1323
  const typedCall = jest
1213
1324
  .fn()
@@ -1230,10 +1341,9 @@ describe('openWalletSession', () => {
1230
1341
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1231
1342
  passphrase: 'host hidden wallet',
1232
1343
  on_device: false,
1233
- });
1234
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1235
1344
  seed_domains: [],
1236
1345
  });
1346
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1237
1347
  });
1238
1348
 
1239
1349
  test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
@@ -1278,10 +1388,9 @@ describe('openWalletSession', () => {
1278
1388
  });
1279
1389
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1280
1390
  on_device: true,
1281
- });
1282
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
1283
1391
  seed_domains: [],
1284
1392
  });
1393
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1285
1394
  expect(device.createProtocolV2UiPhaseMetadata).toHaveBeenNthCalledWith(
1286
1395
  2,
1287
1396
  'passphrase-on-device',
@@ -1332,10 +1441,9 @@ describe('openWalletSession', () => {
1332
1441
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1333
1442
  passphrase: 'host hidden wallet',
1334
1443
  on_device: false,
1335
- });
1336
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {
1337
1444
  seed_domains: [],
1338
1445
  });
1446
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1339
1447
  });
1340
1448
 
1341
1449
  test('normalizes a Unicode Host passphrase before sending it to Pro2 firmware', async () => {
@@ -1357,6 +1465,7 @@ describe('openWalletSession', () => {
1357
1465
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1358
1466
  passphrase: 'cafe\u0301',
1359
1467
  on_device: false,
1468
+ seed_domains: [],
1360
1469
  });
1361
1470
  });
1362
1471
 
@@ -1380,6 +1489,7 @@ describe('openWalletSession', () => {
1380
1489
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1381
1490
  passphrase,
1382
1491
  on_device: false,
1492
+ seed_domains: [],
1383
1493
  });
1384
1494
  });
1385
1495
 
@@ -1415,9 +1525,7 @@ describe('openWalletSession', () => {
1415
1525
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin, {
1416
1526
  emitUiEvent: false,
1417
1527
  });
1418
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1419
- seed_domains: [],
1420
- });
1528
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1421
1529
  });
1422
1530
 
1423
1531
  test('uses the complete Attach PIN wire flow without a main PIN unlock', async () => {
@@ -1446,7 +1554,7 @@ describe('openWalletSession', () => {
1446
1554
  ['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
1447
1555
  ['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.AttachToPin }],
1448
1556
  ['DeviceStatusGet', 'DeviceStatus', {}],
1449
- ['DeviceSessionGet', 'DeviceSession', { seed_domains: [] }],
1557
+ ['DeviceSessionGet', 'DeviceSession', {}],
1450
1558
  ]);
1451
1559
  expect(device.commands.typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
1452
1560
  type: DeviceSessionPinType.Main,
@@ -1658,7 +1766,6 @@ describe('openWalletSession', () => {
1658
1766
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1659
1767
  session_id: 'known-session',
1660
1768
  btc_test_address: 'hidden-state',
1661
- seed_domains: [],
1662
1769
  });
1663
1770
  expect(promptPassphrase).not.toHaveBeenCalled();
1664
1771
  });
@@ -1718,7 +1825,6 @@ describe('openWalletSession', () => {
1718
1825
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1719
1826
  session_id: 'known-session',
1720
1827
  btc_test_address: 'hidden-state',
1721
- seed_domains: [],
1722
1828
  });
1723
1829
  const sessionGetCall = typedCall.mock.calls.findIndex(
1724
1830
  ([requestName]) => requestName === 'DeviceSessionGet'
@@ -1816,7 +1922,6 @@ describe('openWalletSession', () => {
1816
1922
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1817
1923
  session_id: 'expired-session',
1818
1924
  btc_test_address: 'hidden-state',
1819
- seed_domains: [],
1820
1925
  });
1821
1926
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
1822
1927
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
@@ -1854,7 +1959,6 @@ describe('openWalletSession', () => {
1854
1959
  });
1855
1960
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1856
1961
  btc_test_address: 'hidden-state',
1857
- seed_domains: [],
1858
1962
  });
1859
1963
  expect(promptPassphrase).not.toHaveBeenCalled();
1860
1964
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
@@ -1889,11 +1993,14 @@ describe('openWalletSession', () => {
1889
1993
  walletType: 'hidden',
1890
1994
  passphraseState: 'hidden-state',
1891
1995
  });
1892
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1996
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1997
+ passphrase: 'host hidden wallet',
1998
+ on_device: false,
1893
1999
  seed_domains: [
1894
2000
  DeviceSessionSeedDomain.SeedDomain_Standard,
1895
2001
  ...(deriveCardano ? [DeviceSessionSeedDomain.SeedDomain_Cardano] : []),
1896
2002
  ],
1897
2003
  });
2004
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1898
2005
  });
1899
2006
  });
@@ -0,0 +1,58 @@
1
+ import { sha3_512 } from '@noble/hashes/sha3';
2
+
3
+ import {
4
+ buildPro2HostAssetPackage,
5
+ supportsPro2HostAssetPackage,
6
+ } from '../src/utils/pro2HostAssetPackage';
7
+
8
+ describe('Pro2 host asset package', () => {
9
+ test('builds the unsigned RESOURCE container and LZ4-blocked archive expected by firmware', () => {
10
+ const raw = new TextEncoder().encode('123456789');
11
+ const packageData = buildPro2HostAssetPackage([{ name: 'wallpaper.bin', data: raw }]);
12
+ const headerSize = 0x5f90;
13
+ const payload = packageData.subarray(headerSize);
14
+ const packageView = new DataView(
15
+ packageData.buffer,
16
+ packageData.byteOffset,
17
+ packageData.byteLength
18
+ );
19
+
20
+ expect(packageView.getUint32(0, true)).toBe(0x50504b4f);
21
+ expect(packageView.getUint32(4, true)).toBe(1);
22
+ expect(packageView.getUint32(8, true)).toBe(0x43534552);
23
+ expect(packageView.getUint32(0x0c, true)).toBe(headerSize);
24
+ expect(packageView.getUint32(0x10, true)).toBe(1);
25
+ expect(packageView.getUint32(0x14, true)).toBe(payload.byteLength);
26
+ expect(packageData.subarray(0x200, 0x240)).toEqual(sha3_512(payload));
27
+ expect(packageData.subarray(0x240, 0x280)).toEqual(sha3_512(packageData.subarray(0, 0x240)));
28
+ expect(packageData[0x400]).toBe(0);
29
+ expect(packageView.getUint32(0x408, true)).toBe(0x71717171);
30
+
31
+ const archiveView = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
32
+ expect(archiveView.getUint32(0, true)).toBe(0x52414b4f);
33
+ expect(archiveView.getUint32(4, true)).toBe(1);
34
+ expect(archiveView.getUint16(8, true)).toBe(1);
35
+ expect(new TextDecoder().decode(payload.subarray(43, 56))).toBe('wallpaper.bin');
36
+ expect(archiveView.getUint32(42 + 0x100, true)).toBe(340);
37
+ expect(archiveView.getUint32(42 + 0x104, true)).toBe(raw.byteLength);
38
+ expect(archiveView.getUint32(42 + 0x10c, true)).toBe(0xcbf43926);
39
+ expect(payload[42 + 0x114]).toBe(1);
40
+
41
+ const compressedOffset = archiveView.getUint32(42 + 0x100, true);
42
+ expect(archiveView.getUint16(compressedOffset, true)).toBe(1);
43
+ expect(archiveView.getUint16(compressedOffset + 2, true)).toBe(12);
44
+ expect(archiveView.getUint32(compressedOffset + 4, true)).toBe(0);
45
+ expect(archiveView.getUint32(compressedOffset + 8, true)).toBe(10);
46
+ expect(payload.subarray(compressedOffset + 12)).toEqual(new Uint8Array([0x90, ...raw]));
47
+ });
48
+
49
+ test.each([
50
+ ['1.0.0', false],
51
+ ['1.0.1-beta.1', false],
52
+ ['1.0.1', true],
53
+ ['1.1.0', true],
54
+ [undefined, false],
55
+ ])('selects package uploads for firmware %s', (firmwareVersion, expected) => {
56
+ expect(supportsPro2HostAssetPackage(firmwareVersion)).toBe(expected);
57
+ });
58
+ });
@@ -66,8 +66,6 @@ const createRemoteConfig = (): RemoteConfigResponse =>
66
66
  {
67
67
  required: false,
68
68
  version: [1, 0, 0],
69
- url: 'https://example.com/pro2.okpkg',
70
- fingerprint: 'a'.repeat(64),
71
69
  changelog: { 'zh-CN': 'Pro2', 'en-US': 'Pro2' },
72
70
  resources: { source: resourceSource },
73
71
  },
@@ -79,8 +77,6 @@ const createRemoteConfig = (): RemoteConfigResponse =>
79
77
  {
80
78
  required: false,
81
79
  version: [1, 0, 0],
82
- url: 'https://example.com/neo.okpkg',
83
- fingerprint: 'b'.repeat(64),
84
80
  changelog: { 'zh-CN': 'Neo', 'en-US': 'Neo' },
85
81
  resources: { source: neoResourceSource },
86
82
  },
@@ -109,6 +105,11 @@ describe('Pro2 resource configuration', () => {
109
105
  expect(
110
106
  isProtocolV2ResourceArchiveEntryName('bundles/firmware_logo-pro2-prod_resource-signed.okpkg')
111
107
  ).toBe(true);
108
+ expect(
109
+ isProtocolV2ResourceArchiveEntryName(
110
+ 'pro2-prod_resource/bundles/firmware_logo-pro2-prod_resource-signed.okpkg'
111
+ )
112
+ ).toBe(true);
112
113
  expect(
113
114
  isProtocolV2ResourceArchiveEntryName(
114
115
  '__MACOSX/pro2-prod_resource 2/bundles/._firmware_logo-pro2-prod_resource-signed.okpkg'
@@ -119,6 +120,8 @@ describe('Pro2 resource configuration', () => {
119
120
  'bundles/._firmware_logo-pro2-prod_resource-signed.okpkg'
120
121
  )
121
122
  ).toBe(false);
123
+ expect(isProtocolV2ResourceArchiveEntryName('pro2-prod_resource/manifest.json')).toBe(false);
124
+ expect(isProtocolV2ResourceArchiveEntryName('pro2-prod_resource/.DS_Store')).toBe(false);
122
125
  });
123
126
 
124
127
  test('reads the version, hashes, and direct device path from a RESC package', () => {
@@ -151,8 +151,42 @@ describe('Protocol V2 unlock semantics', () => {
151
151
  });
152
152
 
153
153
  expect(method.unlockPolicy).toBe('unlock-before-run');
154
- expect(device.unlockDevice).toHaveBeenCalledTimes(1);
154
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, expect.any(Object));
155
155
  expect(run).toHaveBeenCalledTimes(1);
156
+ expect(method.protocolV2UnlockContext).toMatchObject({
157
+ preflightCompleted: true,
158
+ preflightStatusRefreshed: true,
159
+ preflightMainPinSelected: true,
160
+ });
161
+ });
162
+
163
+ test('allows either PIN type when pre-unlocking hidden-wallet selection', async () => {
164
+ const method = new OpenWalletSession({
165
+ id: 1,
166
+ payload: { method: 'openWalletSession', mode: 'select-hidden' },
167
+ });
168
+ method.init();
169
+ method.run = jest.fn().mockResolvedValue({ walletType: 'hidden' }) as any;
170
+ const features = { unlocked: false };
171
+ const device = {
172
+ features,
173
+ commands: {
174
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
175
+ },
176
+ isProtocolV2: () => true,
177
+ isBootloader: () => false,
178
+ isRomloader: () => false,
179
+ updateProtocolV2Status: jest.fn(() => features),
180
+ unlockDevice: jest.fn().mockImplementation(() => {
181
+ features.unlocked = true;
182
+ return Promise.resolve(features);
183
+ }),
184
+ };
185
+
186
+ await runMethodWithUnlockPolicy(method, device as any);
187
+
188
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, expect.any(Object));
189
+ expect(method.protocolV2UnlockContext?.preflightMainPinSelected).toBe(false);
156
190
  });
157
191
 
158
192
  test('pre-unlocks an unregistered wallet business method', async () => {