@onekeyfe/hd-core 1.2.2-alpha.4 → 1.2.2-alpha.6

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.
@@ -11,6 +11,13 @@ import {
11
11
  getProtocolV2WalletSession,
12
12
  } from '../src/protocols/protocol-v2/walletSession';
13
13
 
14
+ const STANDARD_SEED_DOMAINS = [DeviceSessionSeedDomain.SeedDomain_Standard];
15
+ const CARDANO_SEED_DOMAINS = [
16
+ DeviceSessionSeedDomain.SeedDomain_Standard,
17
+ DeviceSessionSeedDomain.SeedDomain_Cardano,
18
+ ];
19
+ const standardSessionGet = {};
20
+
14
21
  jest.mock('../src/data/config', () => ({
15
22
  getSDKVersion: jest.fn(() => '1.0.0'),
16
23
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
@@ -142,9 +149,9 @@ describe('openWalletSession', () => {
142
149
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
143
150
  passphrase: '',
144
151
  on_device: false,
145
- seed_domains: [],
152
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
146
153
  });
147
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
154
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
148
155
  });
149
156
 
150
157
  test('selects the Main PIN before opening the standard wallet when passphrase is disabled', async () => {
@@ -176,7 +183,7 @@ describe('openWalletSession', () => {
176
183
  'Success',
177
184
  expect.anything()
178
185
  );
179
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
186
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
180
187
  });
181
188
 
182
189
  test('reuses a Main PIN selected by the current preflight when passphrase is disabled', async () => {
@@ -202,7 +209,7 @@ describe('openWalletSession', () => {
202
209
  });
203
210
 
204
211
  expect(device.unlockDevice).not.toHaveBeenCalled();
205
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
212
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
206
213
  });
207
214
 
208
215
  test('does not treat an Attach PIN DeviceStatus as the Main wallet', async () => {
@@ -329,9 +336,14 @@ describe('openWalletSession', () => {
329
336
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
330
337
  passphrase: 'host hidden wallet',
331
338
  on_device: false,
332
- seed_domains: [],
339
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
333
340
  });
334
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
341
+ expect(typedCall).toHaveBeenNthCalledWith(
342
+ 3,
343
+ 'DeviceSessionGet',
344
+ 'DeviceSession',
345
+ standardSessionGet
346
+ );
335
347
  });
336
348
 
337
349
  test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
@@ -448,9 +460,14 @@ describe('openWalletSession', () => {
448
460
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
449
461
  passphrase: 'host hidden wallet',
450
462
  on_device: false,
451
- seed_domains: [],
463
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
452
464
  });
453
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
465
+ expect(typedCall).toHaveBeenNthCalledWith(
466
+ 3,
467
+ 'DeviceSessionGet',
468
+ 'DeviceSession',
469
+ standardSessionGet
470
+ );
454
471
  });
455
472
 
456
473
  test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
@@ -724,7 +741,11 @@ describe('openWalletSession', () => {
724
741
  });
725
742
  expect(promptPassphrase).not.toHaveBeenCalled();
726
743
  expect(device.unlockDevice).not.toHaveBeenCalled();
727
- expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
744
+ expect(device.commands.typedCall).toHaveBeenCalledWith(
745
+ 'DeviceSessionGet',
746
+ 'DeviceSession',
747
+ standardSessionGet
748
+ );
728
749
  expect(device.commands.typedCall).not.toHaveBeenCalledWith(
729
750
  'DeviceSessionAskPassphrase',
730
751
  'Success',
@@ -820,9 +841,9 @@ describe('openWalletSession', () => {
820
841
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
821
842
  passphrase: 'host hidden wallet',
822
843
  on_device: false,
823
- seed_domains: [],
844
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
824
845
  });
825
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
846
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
826
847
  expect(promptPassphrase).toHaveBeenCalled();
827
848
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
828
849
  });
@@ -894,6 +915,7 @@ describe('openWalletSession', () => {
894
915
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
895
916
  session_id: 'known-session',
896
917
  btc_test_address: 'hidden-state',
918
+ ...standardSessionGet,
897
919
  });
898
920
  expect(promptPassphrase).not.toHaveBeenCalled();
899
921
  });
@@ -1076,9 +1098,9 @@ describe('openWalletSession', () => {
1076
1098
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1077
1099
  passphrase: '',
1078
1100
  on_device: false,
1079
- seed_domains: [],
1101
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1080
1102
  });
1081
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1103
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
1082
1104
  expect(promptPassphrase).not.toHaveBeenCalled();
1083
1105
  expect(device.passphraseState).toBeUndefined();
1084
1106
  });
@@ -1247,7 +1269,7 @@ describe('openWalletSession', () => {
1247
1269
  reason: 'open-wallet',
1248
1270
  deviceOnly: true,
1249
1271
  });
1250
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1272
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
1251
1273
  });
1252
1274
 
1253
1275
  test('selects a hidden wallet without exposing the internal device session', async () => {
@@ -1280,9 +1302,9 @@ describe('openWalletSession', () => {
1280
1302
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1281
1303
  passphrase: 'host hidden wallet',
1282
1304
  on_device: false,
1283
- seed_domains: [],
1305
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1284
1306
  });
1285
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1307
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
1286
1308
  expect(promptPassphrase).toHaveBeenCalled();
1287
1309
  expect(device.passphraseState).toBeUndefined();
1288
1310
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
@@ -1341,9 +1363,9 @@ describe('openWalletSession', () => {
1341
1363
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1342
1364
  passphrase: 'host hidden wallet',
1343
1365
  on_device: false,
1344
- seed_domains: [],
1366
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1345
1367
  });
1346
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1368
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
1347
1369
  });
1348
1370
 
1349
1371
  test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
@@ -1388,9 +1410,14 @@ describe('openWalletSession', () => {
1388
1410
  });
1389
1411
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1390
1412
  on_device: true,
1391
- seed_domains: [],
1413
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1392
1414
  });
1393
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1415
+ expect(typedCall).toHaveBeenNthCalledWith(
1416
+ 3,
1417
+ 'DeviceSessionGet',
1418
+ 'DeviceSession',
1419
+ standardSessionGet
1420
+ );
1394
1421
  expect(device.createProtocolV2UiPhaseMetadata).toHaveBeenNthCalledWith(
1395
1422
  2,
1396
1423
  'passphrase-on-device',
@@ -1441,9 +1468,14 @@ describe('openWalletSession', () => {
1441
1468
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1442
1469
  passphrase: 'host hidden wallet',
1443
1470
  on_device: false,
1444
- seed_domains: [],
1471
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1445
1472
  });
1446
- expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
1473
+ expect(typedCall).toHaveBeenNthCalledWith(
1474
+ 3,
1475
+ 'DeviceSessionGet',
1476
+ 'DeviceSession',
1477
+ standardSessionGet
1478
+ );
1447
1479
  });
1448
1480
 
1449
1481
  test('normalizes a Unicode Host passphrase before sending it to Pro2 firmware', async () => {
@@ -1465,7 +1497,7 @@ describe('openWalletSession', () => {
1465
1497
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1466
1498
  passphrase: 'cafe\u0301',
1467
1499
  on_device: false,
1468
- seed_domains: [],
1500
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1469
1501
  });
1470
1502
  });
1471
1503
 
@@ -1489,7 +1521,7 @@ describe('openWalletSession', () => {
1489
1521
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
1490
1522
  passphrase,
1491
1523
  on_device: false,
1492
- seed_domains: [],
1524
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1493
1525
  });
1494
1526
  });
1495
1527
 
@@ -1525,7 +1557,7 @@ describe('openWalletSession', () => {
1525
1557
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin, {
1526
1558
  emitUiEvent: false,
1527
1559
  });
1528
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1560
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
1529
1561
  });
1530
1562
 
1531
1563
  test('uses the complete Attach PIN wire flow without a main PIN unlock', async () => {
@@ -1554,7 +1586,7 @@ describe('openWalletSession', () => {
1554
1586
  ['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
1555
1587
  ['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.AttachToPin }],
1556
1588
  ['DeviceStatusGet', 'DeviceStatus', {}],
1557
- ['DeviceSessionGet', 'DeviceSession', {}],
1589
+ ['DeviceSessionGet', 'DeviceSession', standardSessionGet],
1558
1590
  ]);
1559
1591
  expect(device.commands.typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
1560
1592
  type: DeviceSessionPinType.Main,
@@ -1766,6 +1798,7 @@ describe('openWalletSession', () => {
1766
1798
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1767
1799
  session_id: 'known-session',
1768
1800
  btc_test_address: 'hidden-state',
1801
+ ...standardSessionGet,
1769
1802
  });
1770
1803
  expect(promptPassphrase).not.toHaveBeenCalled();
1771
1804
  });
@@ -1825,6 +1858,7 @@ describe('openWalletSession', () => {
1825
1858
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1826
1859
  session_id: 'known-session',
1827
1860
  btc_test_address: 'hidden-state',
1861
+ ...standardSessionGet,
1828
1862
  });
1829
1863
  const sessionGetCall = typedCall.mock.calls.findIndex(
1830
1864
  ([requestName]) => requestName === 'DeviceSessionGet'
@@ -1922,6 +1956,7 @@ describe('openWalletSession', () => {
1922
1956
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1923
1957
  session_id: 'expired-session',
1924
1958
  btc_test_address: 'hidden-state',
1959
+ ...standardSessionGet,
1925
1960
  });
1926
1961
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
1927
1962
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
@@ -1959,6 +1994,7 @@ describe('openWalletSession', () => {
1959
1994
  });
1960
1995
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1961
1996
  btc_test_address: 'hidden-state',
1997
+ ...standardSessionGet,
1962
1998
  });
1963
1999
  expect(promptPassphrase).not.toHaveBeenCalled();
1964
2000
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
@@ -1998,9 +2034,13 @@ describe('openWalletSession', () => {
1998
2034
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1999
2035
  passphrase: 'host hidden wallet',
2000
2036
  on_device: false,
2001
- seed_domains: [],
2037
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
2002
2038
  });
2003
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
2039
+ expect(typedCall).toHaveBeenCalledWith(
2040
+ 'DeviceSessionGet',
2041
+ 'DeviceSession',
2042
+ standardSessionGet
2043
+ );
2004
2044
  }
2005
2045
  );
2006
2046
 
@@ -2017,6 +2057,10 @@ describe('openWalletSession', () => {
2017
2057
  message: {
2018
2058
  btc_test_address: 'hidden-state',
2019
2059
  session_id: 'new-session',
2060
+ seed_domains: [
2061
+ DeviceSessionSeedDomain.SeedDomain_Standard,
2062
+ DeviceSessionSeedDomain.SeedDomain_Cardano,
2063
+ ],
2020
2064
  },
2021
2065
  };
2022
2066
  }
@@ -2036,6 +2080,60 @@ describe('openWalletSession', () => {
2036
2080
  passphraseState: 'hidden-state',
2037
2081
  newSession: 'new-session',
2038
2082
  });
2083
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2084
+ passphrase: 'host hidden wallet',
2085
+ on_device: false,
2086
+ seed_domains: CARDANO_SEED_DOMAINS,
2087
+ });
2088
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
2089
+ });
2090
+
2091
+ test('asks Cardano seed domains after Get reports a Standard-only session', async () => {
2092
+ const typedCall = jest.fn((request: string) => {
2093
+ if (request === 'ProtocolInfoRequest') {
2094
+ return { message: { version: 2 } };
2095
+ }
2096
+ if (request === 'DeviceSessionAskPassphrase') {
2097
+ return { message: {} };
2098
+ }
2099
+ if (request === 'DeviceSessionGet') {
2100
+ const hasCardanoAsk = typedCall.mock.calls.some(
2101
+ call =>
2102
+ call[0] === 'DeviceSessionAskPassphrase' &&
2103
+ Array.isArray(call[2]?.seed_domains) &&
2104
+ call[2].seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano)
2105
+ );
2106
+ return {
2107
+ message: {
2108
+ btc_test_address: 'hidden-state',
2109
+ session_id: 'new-session',
2110
+ seed_domains: hasCardanoAsk
2111
+ ? [
2112
+ DeviceSessionSeedDomain.SeedDomain_Standard,
2113
+ DeviceSessionSeedDomain.SeedDomain_Cardano,
2114
+ ]
2115
+ : [DeviceSessionSeedDomain.SeedDomain_Standard],
2116
+ },
2117
+ };
2118
+ }
2119
+ throw new Error(`Unexpected request: ${request}`);
2120
+ });
2121
+ const device = createDevice({
2122
+ typedCall,
2123
+ promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
2124
+ });
2125
+ device.passphraseState = 'hidden-state';
2126
+ deviceWalletSessionStore.set('device-1', 'hidden-state', 'cached-session');
2127
+
2128
+ await getProtocolV2WalletSession(device as any, {
2129
+ expectedPassphraseState: 'hidden-state',
2130
+ deriveCardano: true,
2131
+ });
2132
+
2133
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
2134
+ session_id: 'cached-session',
2135
+ btc_test_address: 'hidden-state',
2136
+ });
2039
2137
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2040
2138
  passphrase: 'host hidden wallet',
2041
2139
  on_device: false,
@@ -2046,7 +2144,267 @@ describe('openWalletSession', () => {
2046
2144
  });
2047
2145
  });
2048
2146
 
2049
- test('does not freeze a V2 session to Standard-only when deriveCardano is false', async () => {
2147
+ test('uses empty AskPassphrase to add Cardano on an Attach PIN session', async () => {
2148
+ const typedCall = jest.fn((request: string) => {
2149
+ if (request === 'ProtocolInfoRequest') {
2150
+ return { message: { version: 2 } };
2151
+ }
2152
+ if (request === 'DeviceSessionAskPassphrase') {
2153
+ return { message: {} };
2154
+ }
2155
+ if (request === 'DeviceSessionGet') {
2156
+ const askedCardano = typedCall.mock.calls.some(
2157
+ call =>
2158
+ call[0] === 'DeviceSessionAskPassphrase' &&
2159
+ Array.isArray(call[2]?.seed_domains) &&
2160
+ call[2].seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano)
2161
+ );
2162
+ return {
2163
+ message: {
2164
+ btc_test_address: 'attach-state',
2165
+ session_id: 'attach-session',
2166
+ seed_domains: askedCardano ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS,
2167
+ },
2168
+ };
2169
+ }
2170
+ throw new Error(`Unexpected request: ${request}`);
2171
+ });
2172
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
2173
+ const device = createDevice({
2174
+ typedCall,
2175
+ promptPassphrase,
2176
+ unlockedAttachPin: true,
2177
+ });
2178
+
2179
+ await expect(
2180
+ getProtocolV2WalletSession(device as any, {
2181
+ readCurrentAttachPinSession: true,
2182
+ deriveCardano: true,
2183
+ })
2184
+ ).resolves.toMatchObject({
2185
+ passphraseState: 'attach-state',
2186
+ newSession: 'attach-session',
2187
+ unlockedAttachPin: true,
2188
+ });
2189
+ expect(promptPassphrase).not.toHaveBeenCalled();
2190
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2191
+ passphrase: '',
2192
+ on_device: false,
2193
+ seed_domains: CARDANO_SEED_DOMAINS,
2194
+ });
2195
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
2196
+ expect(
2197
+ typedCall.mock.calls
2198
+ .filter(call => call[0] === 'DeviceSessionGet')
2199
+ .every(call => !('seed_domains' in (call[2] ?? {})))
2200
+ ).toBe(true);
2201
+ });
2202
+
2203
+ test('uses empty AskPassphrase after selecting Attach PIN for Cardano intent', async () => {
2204
+ const typedCall = jest.fn((request: string) => {
2205
+ if (request === 'ProtocolInfoRequest') {
2206
+ return { message: { version: 2 } };
2207
+ }
2208
+ if (request === 'DeviceSessionAskPassphrase') {
2209
+ return { message: {} };
2210
+ }
2211
+ if (request === 'DeviceSessionGet') {
2212
+ return {
2213
+ message: {
2214
+ btc_test_address: 'attach-state',
2215
+ session_id: 'attach-session',
2216
+ seed_domains: CARDANO_SEED_DOMAINS,
2217
+ },
2218
+ };
2219
+ }
2220
+ throw new Error(`Unexpected request: ${request}`);
2221
+ });
2222
+ const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
2223
+ const device = createDevice({ typedCall, promptPassphrase });
2224
+ device.features.attachToPinEnabled = true;
2225
+
2226
+ await expect(
2227
+ getProtocolV2WalletSession(device as any, {
2228
+ forceWalletSelection: true,
2229
+ deriveCardano: true,
2230
+ })
2231
+ ).resolves.toMatchObject({
2232
+ passphraseState: 'attach-state',
2233
+ newSession: 'attach-session',
2234
+ unlockedAttachPin: true,
2235
+ });
2236
+ expect(promptPassphrase).toHaveBeenCalledTimes(1);
2237
+ expect(device.unlockDevice).toHaveBeenCalledWith(
2238
+ DeviceSessionPinType.AttachToPin,
2239
+ expect.objectContaining({ emitUiEvent: false })
2240
+ );
2241
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2242
+ passphrase: '',
2243
+ on_device: false,
2244
+ seed_domains: CARDANO_SEED_DOMAINS,
2245
+ });
2246
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
2247
+ expect(
2248
+ typedCall.mock.calls
2249
+ .filter(call => call[0] === 'DeviceSessionGet')
2250
+ .every(call => !('seed_domains' in (call[2] ?? {})))
2251
+ ).toBe(true);
2252
+ });
2253
+
2254
+ test('still locks before a passphrase picker after Attach PIN Cardano empty Ask', async () => {
2255
+ const typedCall = jest.fn((request: string) => {
2256
+ if (request === 'ProtocolInfoRequest') {
2257
+ return { message: { version: 2 } };
2258
+ }
2259
+ if (request === 'DeviceSessionAskPassphrase') {
2260
+ return { message: {} };
2261
+ }
2262
+ if (request === 'DeviceSessionGet') {
2263
+ return {
2264
+ message: {
2265
+ btc_test_address: 'attach-state',
2266
+ session_id: 'attach-session',
2267
+ seed_domains: CARDANO_SEED_DOMAINS,
2268
+ },
2269
+ };
2270
+ }
2271
+ throw new Error(`Unexpected request: ${request}`);
2272
+ });
2273
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
2274
+ const device = createDevice({
2275
+ typedCall,
2276
+ promptPassphrase,
2277
+ unlockedAttachPin: true,
2278
+ });
2279
+
2280
+ await getProtocolV2WalletSession(device as any, {
2281
+ readCurrentAttachPinSession: true,
2282
+ deriveCardano: true,
2283
+ });
2284
+ await expect(
2285
+ getProtocolV2WalletSession(device as any, { forceWalletSelection: true })
2286
+ ).rejects.toMatchObject({
2287
+ errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
2288
+ });
2289
+ expect(device.lockDevice).toHaveBeenCalled();
2290
+ expect(promptPassphrase).not.toHaveBeenCalled();
2291
+ });
2292
+
2293
+ test('uses a second Get for Cardano when passphrase protection is off', async () => {
2294
+ const typedCall = jest.fn((request: string) => {
2295
+ if (request === 'ProtocolInfoRequest') {
2296
+ return { message: { version: 2 } };
2297
+ }
2298
+ if (request === 'DeviceSessionGet') {
2299
+ const getCount = typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet').length;
2300
+ return {
2301
+ message: {
2302
+ btc_test_address: 'standard-state',
2303
+ session_id: 'standard-session',
2304
+ seed_domains: getCount > 1 ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS,
2305
+ },
2306
+ };
2307
+ }
2308
+ throw new Error(`Unexpected request: ${request}`);
2309
+ });
2310
+ const device = createDevice({
2311
+ typedCall,
2312
+ passphraseProtection: false,
2313
+ });
2314
+
2315
+ await expect(
2316
+ getProtocolV2WalletSession(device as any, {
2317
+ onlyMainPin: true,
2318
+ deriveCardano: true,
2319
+ })
2320
+ ).resolves.toMatchObject({
2321
+ passphraseState: 'standard-state',
2322
+ newSession: 'standard-session',
2323
+ });
2324
+ expect(typedCall).not.toHaveBeenCalledWith(
2325
+ 'DeviceSessionAskPassphrase',
2326
+ 'Success',
2327
+ expect.anything()
2328
+ );
2329
+ expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(2);
2330
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
2331
+ });
2332
+
2333
+ test('fails closed when Cardano fallback still lacks Cardano', async () => {
2334
+ const typedCall = jest.fn((request: string) => {
2335
+ if (request === 'ProtocolInfoRequest') {
2336
+ return { message: { version: 2 } };
2337
+ }
2338
+ if (request === 'DeviceSessionAskPassphrase') {
2339
+ return { message: {} };
2340
+ }
2341
+ if (request === 'DeviceSessionGet') {
2342
+ return {
2343
+ message: {
2344
+ btc_test_address: 'hidden-state',
2345
+ session_id: 'hidden-session',
2346
+ seed_domains: STANDARD_SEED_DOMAINS,
2347
+ },
2348
+ };
2349
+ }
2350
+ throw new Error(`Unexpected request: ${request}`);
2351
+ });
2352
+ const device = createDevice({
2353
+ typedCall,
2354
+ promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
2355
+ });
2356
+ deviceWalletSessionStore.set('device-1', 'hidden-state', 'cached-session');
2357
+
2358
+ await expect(
2359
+ getProtocolV2WalletSession(device as any, {
2360
+ expectedPassphraseState: 'hidden-state',
2361
+ deriveCardano: true,
2362
+ })
2363
+ ).rejects.toMatchObject({
2364
+ errorCode: HardwareErrorCode.WalletSessionInvalid,
2365
+ });
2366
+ expect(device.clearInternalState).toHaveBeenCalled();
2367
+ });
2368
+
2369
+ test('locks before selecting a passphrase wallet from an Attach PIN session', async () => {
2370
+ const typedCall = jest.fn((request: string) => {
2371
+ if (request === 'ProtocolInfoRequest') {
2372
+ return { message: { version: 2 } };
2373
+ }
2374
+ if (request === 'DeviceStatusGet') {
2375
+ return {
2376
+ message: {
2377
+ device_id: 'device-1',
2378
+ unlocked: true,
2379
+ passphrase_enabled: true,
2380
+ unlocked_by_attach_to_pin: true,
2381
+ },
2382
+ };
2383
+ }
2384
+ throw new Error(`Unexpected request: ${request}`);
2385
+ });
2386
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
2387
+ const device = createDevice({
2388
+ typedCall,
2389
+ promptPassphrase,
2390
+ unlockedAttachPin: true,
2391
+ });
2392
+
2393
+ await expect(
2394
+ getProtocolV2WalletSession(device as any, { forceWalletSelection: true })
2395
+ ).rejects.toMatchObject({
2396
+ errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
2397
+ });
2398
+ expect(device.lockDevice).toHaveBeenCalled();
2399
+ expect(promptPassphrase).not.toHaveBeenCalled();
2400
+ expect(typedCall).not.toHaveBeenCalledWith(
2401
+ 'DeviceSessionAskPassphrase',
2402
+ 'Success',
2403
+ expect.anything()
2404
+ );
2405
+ });
2406
+
2407
+ test('asks Standard-only seed domains when deriveCardano is false', async () => {
2050
2408
  const typedCall = jest.fn((request: string) => {
2051
2409
  if (request === 'ProtocolInfoRequest') {
2052
2410
  return { message: { version: 2 } };
@@ -2076,7 +2434,7 @@ describe('openWalletSession', () => {
2076
2434
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2077
2435
  passphrase: 'host hidden wallet',
2078
2436
  on_device: false,
2079
- seed_domains: [],
2437
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
2080
2438
  });
2081
2439
  });
2082
2440
  });
@@ -7,6 +7,7 @@ import { encode as encodeJpeg } from 'jpeg-js';
7
7
  import {
8
8
  DeviceRebootType,
9
9
  DeviceSessionPinType,
10
+ DeviceSessionSeedDomain,
10
11
  DeviceSettingsPage,
11
12
  DeviceType,
12
13
  } from '@onekeyfe/hd-transport';
@@ -1115,7 +1116,7 @@ describe('Protocol V2 feature adapter', () => {
1115
1116
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1116
1117
  passphrase: '',
1117
1118
  on_device: false,
1118
- seed_domains: [],
1119
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1119
1120
  });
1120
1121
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1121
1122
  device.passphraseState = 'state-1';
@@ -1538,7 +1539,7 @@ describe('Protocol V2 feature adapter', () => {
1538
1539
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
1539
1540
  passphrase: 'host hidden wallet',
1540
1541
  on_device: false,
1541
- seed_domains: [],
1542
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1542
1543
  });
1543
1544
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
1544
1545
  });
@@ -1768,7 +1769,11 @@ describe('Protocol V2 feature adapter', () => {
1768
1769
  [
1769
1770
  'DeviceSessionAskPassphrase',
1770
1771
  'Success',
1771
- { passphrase: 'host hidden wallet', on_device: false, seed_domains: [] },
1772
+ {
1773
+ passphrase: 'host hidden wallet',
1774
+ on_device: false,
1775
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
1776
+ },
1772
1777
  ],
1773
1778
  ['DeviceStatusGet', 'DeviceStatus', {}],
1774
1779
  ['DeviceSessionGet', 'DeviceSession', {}],
@@ -2230,7 +2235,7 @@ describe('Protocol V2 feature adapter', () => {
2230
2235
  firmwareVersion: '4.15.0',
2231
2236
  passphraseProtection: true,
2232
2237
  sessionId: 'feature-session',
2233
- unlockedAttachPin: true,
2238
+ unlockedAttachPin: false,
2234
2239
  };
2235
2240
  const typedCall = jest
2236
2241
  .fn()
@@ -2585,7 +2590,7 @@ describe('Protocol V2 feature adapter', () => {
2585
2590
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
2586
2591
  passphrase: 'host hidden wallet',
2587
2592
  on_device: false,
2588
- seed_domains: [],
2593
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
2589
2594
  });
2590
2595
  expect(typedCall).toHaveBeenLastCalledWith('DeviceSessionGet', 'DeviceSession', {});
2591
2596
  });
@@ -2671,7 +2676,7 @@ describe('Protocol V2 feature adapter', () => {
2671
2676
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2672
2677
  passphrase: 'host hidden wallet',
2673
2678
  on_device: false,
2674
- seed_domains: [],
2679
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
2675
2680
  });
2676
2681
  expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
2677
2682
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
@@ -2780,7 +2785,7 @@ describe('Protocol V2 feature adapter', () => {
2780
2785
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
2781
2786
  passphrase: '',
2782
2787
  on_device: false,
2783
- seed_domains: [],
2788
+ seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
2784
2789
  });
2785
2790
  expect(typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
2786
2791
  expect(typedCall.mock.calls.filter(([request]) => request === 'DeviceStatusGet')).toHaveLength(
package/dist/index.js CHANGED
@@ -39413,6 +39413,11 @@ var nested = {
39413
39413
  btc_test_address: {
39414
39414
  type: "string",
39415
39415
  id: 2
39416
+ },
39417
+ seed_domains: {
39418
+ rule: "repeated",
39419
+ type: "DeviceSessionSeedDomain",
39420
+ id: 3
39416
39421
  }
39417
39422
  }
39418
39423
  },
@@ -41794,12 +41799,14 @@ const negotiateEventlessWalletSession = (device) => __awaiter(void 0, void 0, vo
41794
41799
  yield device.ensureProtocolV2RuntimeContext();
41795
41800
  });
41796
41801
  const getDeviceSession = (device, request) => __awaiter(void 0, void 0, void 0, function* () { return device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request); });
41797
- const buildDeviceSessionSeedDomains = (deriveCardano) => deriveCardano === true
41798
- ? [
41799
- hdTransport.DeviceSessionSeedDomain.SeedDomain_Standard,
41800
- hdTransport.DeviceSessionSeedDomain.SeedDomain_Cardano,
41801
- ]
41802
- : [];
41802
+ const STANDARD_SEED_DOMAINS = [hdTransport.DeviceSessionSeedDomain.SeedDomain_Standard];
41803
+ const CARDANO_SEED_DOMAINS = [
41804
+ hdTransport.DeviceSessionSeedDomain.SeedDomain_Standard,
41805
+ hdTransport.DeviceSessionSeedDomain.SeedDomain_Cardano,
41806
+ ];
41807
+ const buildDeviceSessionSeedDomains = (deriveCardano) => deriveCardano === true ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS;
41808
+ const deviceSessionHasCardano = (message) => Array.isArray(message.seed_domains) &&
41809
+ message.seed_domains.includes(hdTransport.DeviceSessionSeedDomain.SeedDomain_Cardano);
41803
41810
  const buildDeviceSessionGetRequest = ({ sessionId, expectedPassphraseState, } = {}) => (Object.assign(Object.assign({}, (sessionId ? { session_id: sessionId } : {})), (expectedPassphraseState ? { btc_test_address: expectedPassphraseState } : {})));
41804
41811
  const askDevicePassphrase = (device, requestPayload, deriveCardano, onStatusRefreshed) => __awaiter(void 0, void 0, void 0, function* () {
41805
41812
  yield device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', Object.assign(Object.assign({}, requestPayload), { seed_domains: buildDeviceSessionSeedDomains(deriveCardano) }));
@@ -41840,7 +41847,14 @@ const selectDeviceSession = (device, expectedPassphraseState, deriveCardano, onS
41840
41847
  interaction: attachPinInteraction,
41841
41848
  });
41842
41849
  onStatusRefreshed === null || onStatusRefreshed === void 0 ? void 0 : onStatusRefreshed();
41843
- return getDeviceSession(device, buildDeviceSessionGetRequest());
41850
+ if (deriveCardano === true) {
41851
+ yield askDevicePassphrase(device, { passphrase: '', on_device: false }, true, onStatusRefreshed);
41852
+ if (device.features) {
41853
+ device.features.unlockedAttachPin = true;
41854
+ }
41855
+ }
41856
+ const attachPinSession = yield getDeviceSession(device, buildDeviceSessionGetRequest());
41857
+ return Object.assign(attachPinSession, { viaAttachPin: true });
41844
41858
  }
41845
41859
  if (hasHostPassphrase) {
41846
41860
  yield askDevicePassphrase(device, {
@@ -41855,10 +41869,16 @@ const selectDeviceSession = (device, expectedPassphraseState, deriveCardano, onS
41855
41869
  return getDeviceSession(device, buildDeviceSessionGetRequest());
41856
41870
  });
41857
41871
  function getProtocolV2WalletSession(device, options) {
41858
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
41872
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
41859
41873
  return __awaiter(this, void 0, void 0, function* () {
41860
41874
  const forceWalletSelection = (options === null || options === void 0 ? void 0 : options.forceWalletSelection) === true || (options === null || options === void 0 ? void 0 : options.initSession) === true;
41861
41875
  const readCurrentAttachPinSession = (options === null || options === void 0 ? void 0 : options.readCurrentAttachPinSession) === true;
41876
+ const sessionIsAttachPinWallet = (session) => {
41877
+ var _a;
41878
+ return readCurrentAttachPinSession ||
41879
+ (session === null || session === void 0 ? void 0 : session.viaAttachPin) === true ||
41880
+ ((_a = device.features) === null || _a === void 0 ? void 0 : _a.unlockedAttachPin) === true;
41881
+ };
41862
41882
  if (forceWalletSelection) {
41863
41883
  if (options.onlyMainPin) {
41864
41884
  (_a = device.clearStandardInternalState) === null || _a === void 0 ? void 0 : _a.call(device);
@@ -41903,8 +41923,20 @@ function getProtocolV2WalletSession(device, options) {
41903
41923
  device.clearInternalState();
41904
41924
  }
41905
41925
  };
41926
+ const sessionGetRequest = ({ sessionId, expectedPassphraseState: passphraseState, } = {}) => buildDeviceSessionGetRequest({
41927
+ sessionId,
41928
+ expectedPassphraseState: passphraseState,
41929
+ });
41930
+ const askEmptyPassphraseAndGet = ({ deriveCardano, keepAttachPin, } = {}) => __awaiter(this, void 0, void 0, function* () {
41931
+ yield askDevicePassphrase(device, { passphrase: '', on_device: false }, deriveCardano, markWalletStatusRefreshed);
41932
+ if (keepAttachPin && device.features) {
41933
+ device.features.unlockedAttachPin = true;
41934
+ }
41935
+ const session = yield getDeviceSession(device, buildDeviceSessionGetRequest());
41936
+ return keepAttachPin ? Object.assign(session, { viaAttachPin: true }) : session;
41937
+ });
41906
41938
  const rejectMismatchedAttachPinWallet = () => __awaiter(this, void 0, void 0, function* () {
41907
- var _k;
41939
+ var _m;
41908
41940
  const features = yield refreshProtocolV2DeviceStatus(device);
41909
41941
  markWalletStatusRefreshed();
41910
41942
  if (features.unlockedAttachPin !== true) {
@@ -41913,10 +41945,10 @@ function getProtocolV2WalletSession(device, options) {
41913
41945
  try {
41914
41946
  yield device.lockDevice();
41915
41947
  }
41916
- catch (_l) {
41948
+ catch (_o) {
41917
41949
  }
41918
41950
  if (options === null || options === void 0 ? void 0 : options.rejectAttachPinForMainWallet) {
41919
- (_k = device.clearStandardInternalState) === null || _k === void 0 ? void 0 : _k.call(device);
41951
+ (_m = device.clearStandardInternalState) === null || _m === void 0 ? void 0 : _m.call(device);
41920
41952
  device.clearInternalState();
41921
41953
  }
41922
41954
  else {
@@ -41924,6 +41956,16 @@ function getProtocolV2WalletSession(device, options) {
41924
41956
  }
41925
41957
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceCheckUnlockTypeError);
41926
41958
  });
41959
+ const lockAttachPinBeforePassphraseSelection = () => __awaiter(this, void 0, void 0, function* () {
41960
+ var _p;
41961
+ if (readCurrentAttachPinSession || (options === null || options === void 0 ? void 0 : options.onlyMainPin)) {
41962
+ return;
41963
+ }
41964
+ if (((_p = device.features) === null || _p === void 0 ? void 0 : _p.unlockedAttachPin) !== true) {
41965
+ return;
41966
+ }
41967
+ yield rejectMismatchedAttachPinWallet();
41968
+ });
41927
41969
  if ((options === null || options === void 0 ? void 0 : options.onlyMainPin) && options.rejectAttachPinForMainWallet) {
41928
41970
  yield rejectMismatchedAttachPinWallet();
41929
41971
  }
@@ -41940,8 +41982,8 @@ function getProtocolV2WalletSession(device, options) {
41940
41982
  }
41941
41983
  });
41942
41984
  const selectStandardWallet = () => __awaiter(this, void 0, void 0, function* () {
41943
- var _m;
41944
- if (((_m = device.features) === null || _m === void 0 ? void 0 : _m.passphraseProtection) === true) {
41985
+ var _q;
41986
+ if (((_q = device.features) === null || _q === void 0 ? void 0 : _q.passphraseProtection) === true) {
41945
41987
  yield selectMainPin();
41946
41988
  yield askDevicePassphrase(device, {
41947
41989
  passphrase: '',
@@ -41963,7 +42005,7 @@ function getProtocolV2WalletSession(device, options) {
41963
42005
  device.clearInternalState();
41964
42006
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceCheckUnlockTypeError);
41965
42007
  }
41966
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest());
42008
+ response = yield getDeviceSession(device, sessionGetRequest());
41967
42009
  }
41968
42010
  else if (options === null || options === void 0 ? void 0 : options.onlyMainPin) {
41969
42011
  expectedPassphraseState = cachedStandardSession === null || cachedStandardSession === void 0 ? void 0 : cachedStandardSession.passphraseState;
@@ -41972,7 +42014,7 @@ function getProtocolV2WalletSession(device, options) {
41972
42014
  if (options === null || options === void 0 ? void 0 : options.selectMainWalletBeforeRestore) {
41973
42015
  yield selectMainPin();
41974
42016
  }
41975
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest({
42017
+ response = yield getDeviceSession(device, sessionGetRequest({
41976
42018
  sessionId: cachedStandardSession.sessionId,
41977
42019
  expectedPassphraseState,
41978
42020
  }));
@@ -41988,12 +42030,12 @@ function getProtocolV2WalletSession(device, options) {
41988
42030
  }
41989
42031
  if (!response) {
41990
42032
  yield selectStandardWallet();
41991
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest());
42033
+ response = yield getDeviceSession(device, sessionGetRequest());
41992
42034
  }
41993
42035
  }
41994
42036
  else if (cachedSessionId && expectedPassphraseState) {
41995
42037
  try {
41996
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest({
42038
+ response = yield getDeviceSession(device, sessionGetRequest({
41997
42039
  sessionId: cachedSessionId,
41998
42040
  expectedPassphraseState,
41999
42041
  }));
@@ -42009,7 +42051,7 @@ function getProtocolV2WalletSession(device, options) {
42009
42051
  }
42010
42052
  else if (expectedPassphraseState) {
42011
42053
  try {
42012
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest({
42054
+ response = yield getDeviceSession(device, sessionGetRequest({
42013
42055
  expectedPassphraseState,
42014
42056
  }));
42015
42057
  }
@@ -42024,6 +42066,7 @@ function getProtocolV2WalletSession(device, options) {
42024
42066
  device.clearInternalState();
42025
42067
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.WalletSessionInvalid);
42026
42068
  }
42069
+ yield lockAttachPinBeforePassphraseSelection();
42027
42070
  response = yield selectDeviceSession(device, expectedPassphraseState, options === null || options === void 0 ? void 0 : options.deriveCardano, markWalletStatusRefreshed);
42028
42071
  }
42029
42072
  let { message } = response;
@@ -42049,10 +42092,11 @@ function getProtocolV2WalletSession(device, options) {
42049
42092
  if (options === null || options === void 0 ? void 0 : options.onlyMainPin) {
42050
42093
  (_h = device.clearStandardInternalState) === null || _h === void 0 ? void 0 : _h.call(device);
42051
42094
  yield selectStandardWallet();
42052
- response = yield getDeviceSession(device, buildDeviceSessionGetRequest());
42095
+ response = yield getDeviceSession(device, sessionGetRequest());
42053
42096
  }
42054
42097
  else {
42055
42098
  device.clearInternalState();
42099
+ yield lockAttachPinBeforePassphraseSelection();
42056
42100
  response = yield selectDeviceSession(device, expectedPassphraseState, options === null || options === void 0 ? void 0 : options.deriveCardano, markWalletStatusRefreshed);
42057
42101
  }
42058
42102
  message = response.message;
@@ -42074,6 +42118,50 @@ function getProtocolV2WalletSession(device, options) {
42074
42118
  throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceCheckPassphraseStateError);
42075
42119
  }
42076
42120
  }
42121
+ if ((options === null || options === void 0 ? void 0 : options.deriveCardano) === true && !deviceSessionHasCardano(message)) {
42122
+ if (options === null || options === void 0 ? void 0 : options.resumeOnly) {
42123
+ device.clearInternalState();
42124
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.WalletSessionInvalid);
42125
+ }
42126
+ resumed = false;
42127
+ const previousAddress = message.btc_test_address;
42128
+ if (sessionIsAttachPinWallet(response)) {
42129
+ response = yield askEmptyPassphraseAndGet({ deriveCardano: true, keepAttachPin: true });
42130
+ }
42131
+ else if (((_k = device.features) === null || _k === void 0 ? void 0 : _k.passphraseProtection) === false) {
42132
+ response = yield getDeviceSession(device, buildDeviceSessionGetRequest());
42133
+ }
42134
+ else if (options === null || options === void 0 ? void 0 : options.onlyMainPin) {
42135
+ yield selectMainPin();
42136
+ response = yield askEmptyPassphraseAndGet({ deriveCardano: true });
42137
+ }
42138
+ else {
42139
+ yield lockAttachPinBeforePassphraseSelection();
42140
+ response = yield selectDeviceSession(device, expectedPassphraseState, true, markWalletStatusRefreshed);
42141
+ }
42142
+ message = response.message;
42143
+ try {
42144
+ assertCompleteDeviceSession(message);
42145
+ }
42146
+ catch (error) {
42147
+ if (options === null || options === void 0 ? void 0 : options.onlyMainPin) {
42148
+ (_l = device.clearStandardInternalState) === null || _l === void 0 ? void 0 : _l.call(device);
42149
+ }
42150
+ else {
42151
+ device.clearInternalState();
42152
+ }
42153
+ throw error;
42154
+ }
42155
+ if (previousAddress && previousAddress !== message.btc_test_address) {
42156
+ yield rejectMismatchedAttachPinWallet();
42157
+ clearCurrentWalletSession();
42158
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceCheckPassphraseStateError);
42159
+ }
42160
+ if (!deviceSessionHasCardano(message)) {
42161
+ clearCurrentWalletSession();
42162
+ throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.WalletSessionInvalid);
42163
+ }
42164
+ }
42077
42165
  const internalStateArgs = [
42078
42166
  true,
42079
42167
  message.btc_test_address,
@@ -42088,7 +42176,7 @@ function getProtocolV2WalletSession(device, options) {
42088
42176
  device.updateInternalState(...internalStateArgs);
42089
42177
  }
42090
42178
  let unlockedAttachPin;
42091
- if (readCurrentAttachPinSession) {
42179
+ if (readCurrentAttachPinSession || sessionIsAttachPinWallet(response)) {
42092
42180
  unlockedAttachPin = true;
42093
42181
  }
42094
42182
  else if (mainPinAuthenticated) {
@@ -1 +1 @@
1
- {"version":3,"file":"walletSession.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/walletSession.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuBlD,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,MAAM,0DAGjE;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,MAAM,qCAGjE;AAED,wBAAsB,qCAAqC,CAAC,MAAM,EAAE,MAAM,oBAgBzE;AAgJD,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,2BAA2B,CAAC,EAAE,OAAO,CAAC;IAEtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;;;;GAoRF;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,MAAM,EACd,uBAAuB,EAAE,MAAM,EAC/B,aAAa,CAAC,EAAE,OAAO;;;;;;GAOxB"}
1
+ {"version":3,"file":"walletSession.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/walletSession.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuBlD,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,MAAM,0DAGjE;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,MAAM,qCAGjE;AAED,wBAAsB,qCAAqC,CAAC,MAAM,EAAE,MAAM,oBAgBzE;AA8JD,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,2BAA2B,CAAC,EAAE,OAAO,CAAC;IAEtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;;;;GAuXF;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,MAAM,EACd,uBAAuB,EAAE,MAAM,EAC/B,aAAa,CAAC,EAAE,OAAO;;;;;;GAOxB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.2-alpha.4",
3
+ "version": "1.2.2-alpha.6",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.2-alpha.4",
29
- "@onekeyfe/hd-transport": "1.2.2-alpha.4",
28
+ "@onekeyfe/hd-shared": "1.2.2-alpha.6",
29
+ "@onekeyfe/hd-transport": "1.2.2-alpha.6",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "53bee9cdd24c8a4d124ca5067fa78756f287d391"
49
+ "gitHead": "538d5371d9369747beabf68c5dc7a617531bace2"
50
50
  }
@@ -394,7 +394,7 @@ export default abstract class AllNetworkGetAddressBase extends BaseMethod<
394
394
  // requested standard or hidden wallet before sending its device command.
395
395
  const useEmptyPassphrase = this.payload.useEmptyPassphrase === true;
396
396
  // Nested Cardano methods opt in to [Standard, Cardano] if Ask rebuilds.
397
- // Other chains omit the flag so V2 keeps firmware's default domains.
397
+ // Other chains stay Standard-only.
398
398
  const deriveCardano = method.name.startsWith('cardano') ? true : undefined;
399
399
  const shouldResumeWalletSession = useEmptyPassphrase || !!this.payload.passphraseState;
400
400
  if (this.device.isProtocolV2() && shouldResumeWalletSession) {
package/src/core/index.ts CHANGED
@@ -105,8 +105,8 @@ function resolveDeriveCardano(method: BaseMethod): boolean | undefined {
105
105
  if (method.name.startsWith('cardano')) {
106
106
  return true;
107
107
  }
108
- // V1 Initialize only acts on a true opt-in. V2 AskPassphrase must not treat
109
- // false as Standard-only, so never forward an explicit false.
108
+ // V1 Initialize only sends derive_cardano on an explicit true.
109
+ // V2 AskPassphrase maps true to [Standard, Cardano] and anything else to [Standard].
110
110
  if (method.payload?.deriveCardano === true) {
111
111
  return true;
112
112
  }
@@ -12334,6 +12334,11 @@
12334
12334
  "btc_test_address": {
12335
12335
  "type": "string",
12336
12336
  "id": 2
12337
+ },
12338
+ "seed_domains": {
12339
+ "rule": "repeated",
12340
+ "type": "DeviceSessionSeedDomain",
12341
+ "id": 3
12337
12342
  }
12338
12343
  }
12339
12344
  },
@@ -63,21 +63,20 @@ const negotiateEventlessWalletSession = async (device: Device) => {
63
63
  const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
64
64
  device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
65
65
 
66
- // V1 `deriveCardano` is a per-Initialize opt-in: false/omit skips Cardano this
67
- // round trip and a later Initialize can still add it. V2 seed domains are set
68
- // only on AskPassphrase and cannot be upgraded by Get, so false must not freeze
69
- // the session to Standard-only. true requests [Standard, Cardano]; anything
70
- // else leaves seed_domains empty and firmware keeps its default (all domains).
66
+ const STANDARD_SEED_DOMAINS = [DeviceSessionSeedDomain.SeedDomain_Standard];
67
+ const CARDANO_SEED_DOMAINS = [
68
+ DeviceSessionSeedDomain.SeedDomain_Standard,
69
+ DeviceSessionSeedDomain.SeedDomain_Cardano,
70
+ ];
71
+
71
72
  const buildDeviceSessionSeedDomains = (deriveCardano?: boolean): DeviceSessionSeedDomain[] =>
72
- deriveCardano === true
73
- ? [
74
- DeviceSessionSeedDomain.SeedDomain_Standard,
75
- DeviceSessionSeedDomain.SeedDomain_Cardano,
76
- ]
77
- : [];
78
-
79
- // Seed domains are applied when creating a passphrase session, not when reading/resuming one.
80
- // Current firmware rejects unknown DeviceSessionGet fields, including leftover seed_domains.
73
+ deriveCardano === true ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS;
74
+
75
+ const deviceSessionHasCardano = (message: { seed_domains?: DeviceSessionSeedDomain[] }) =>
76
+ Array.isArray(message.seed_domains) &&
77
+ message.seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano);
78
+
79
+ // origin/dev Get is read/resume only. Seed generation lives on AskPassphrase.
81
80
  const buildDeviceSessionGetRequest = ({
82
81
  sessionId,
83
82
  expectedPassphraseState,
@@ -170,7 +169,22 @@ const selectDeviceSession = async (
170
169
  interaction: attachPinInteraction,
171
170
  });
172
171
  onStatusRefreshed?.();
173
- return getDeviceSession(device, buildDeviceSessionGetRequest());
172
+ if (deriveCardano === true) {
173
+ await askDevicePassphrase(
174
+ device,
175
+ { passphrase: '', on_device: false },
176
+ true,
177
+ onStatusRefreshed
178
+ );
179
+ // Firmware AskPassphrase Success clears unlocked_by_attach_to_pin.
180
+ // Identity is still the Attach PIN wallet; keep the SDK flag so a later
181
+ // passphrase picker still locks instead of prompting.
182
+ if (device.features) {
183
+ device.features.unlockedAttachPin = true;
184
+ }
185
+ }
186
+ const attachPinSession = await getDeviceSession(device, buildDeviceSessionGetRequest());
187
+ return Object.assign(attachPinSession, { viaAttachPin: true as const });
174
188
  }
175
189
 
176
190
  if (hasHostPassphrase) {
@@ -220,6 +234,10 @@ export async function getProtocolV2WalletSession(
220
234
  const forceWalletSelection =
221
235
  options?.forceWalletSelection === true || options?.initSession === true;
222
236
  const readCurrentAttachPinSession = options?.readCurrentAttachPinSession === true;
237
+ const sessionIsAttachPinWallet = (session?: { viaAttachPin?: boolean }) =>
238
+ readCurrentAttachPinSession ||
239
+ session?.viaAttachPin === true ||
240
+ device.features?.unlockedAttachPin === true;
223
241
 
224
242
  if (forceWalletSelection) {
225
243
  if (options.onlyMainPin) {
@@ -270,6 +288,38 @@ export async function getProtocolV2WalletSession(
270
288
  }
271
289
  };
272
290
 
291
+ const sessionGetRequest = ({
292
+ sessionId,
293
+ expectedPassphraseState: passphraseState,
294
+ }: {
295
+ sessionId?: string;
296
+ expectedPassphraseState?: string;
297
+ } = {}) =>
298
+ buildDeviceSessionGetRequest({
299
+ sessionId,
300
+ expectedPassphraseState: passphraseState,
301
+ });
302
+
303
+ const askEmptyPassphraseAndGet = async ({
304
+ deriveCardano,
305
+ keepAttachPin,
306
+ }: {
307
+ deriveCardano?: boolean;
308
+ keepAttachPin?: boolean;
309
+ } = {}) => {
310
+ await askDevicePassphrase(
311
+ device,
312
+ { passphrase: '', on_device: false },
313
+ deriveCardano,
314
+ markWalletStatusRefreshed
315
+ );
316
+ if (keepAttachPin && device.features) {
317
+ device.features.unlockedAttachPin = true;
318
+ }
319
+ const session = await getDeviceSession(device, buildDeviceSessionGetRequest());
320
+ return keepAttachPin ? Object.assign(session, { viaAttachPin: true as const }) : session;
321
+ };
322
+
273
323
  const rejectMismatchedAttachPinWallet = async () => {
274
324
  const features = await refreshProtocolV2DeviceStatus(device);
275
325
  markWalletStatusRefreshed();
@@ -291,6 +341,19 @@ export async function getProtocolV2WalletSession(
291
341
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
292
342
  };
293
343
 
344
+ // The passphrase picker would prompt. Empty host AskPassphrase does not;
345
+ // that path is Attach PIN / standard Cardano. Switching to a different
346
+ // passphrase wallet still locks first.
347
+ const lockAttachPinBeforePassphraseSelection = async () => {
348
+ if (readCurrentAttachPinSession || options?.onlyMainPin) {
349
+ return;
350
+ }
351
+ if (device.features?.unlockedAttachPin !== true) {
352
+ return;
353
+ }
354
+ await rejectMismatchedAttachPinWallet();
355
+ };
356
+
294
357
  if (options?.onlyMainPin && options.rejectAttachPinForMainWallet) {
295
358
  await rejectMismatchedAttachPinWallet();
296
359
  }
@@ -341,7 +404,7 @@ export async function getProtocolV2WalletSession(
341
404
  device.clearInternalState();
342
405
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
343
406
  }
344
- response = await getDeviceSession(device, buildDeviceSessionGetRequest());
407
+ response = await getDeviceSession(device, sessionGetRequest());
345
408
  } else if (options?.onlyMainPin) {
346
409
  expectedPassphraseState = cachedStandardSession?.passphraseState;
347
410
  if (cachedStandardSession) {
@@ -351,7 +414,7 @@ export async function getProtocolV2WalletSession(
351
414
  }
352
415
  response = await getDeviceSession(
353
416
  device,
354
- buildDeviceSessionGetRequest({
417
+ sessionGetRequest({
355
418
  sessionId: cachedStandardSession.sessionId,
356
419
  expectedPassphraseState,
357
420
  })
@@ -368,13 +431,13 @@ export async function getProtocolV2WalletSession(
368
431
 
369
432
  if (!response) {
370
433
  await selectStandardWallet();
371
- response = await getDeviceSession(device, buildDeviceSessionGetRequest());
434
+ response = await getDeviceSession(device, sessionGetRequest());
372
435
  }
373
436
  } else if (cachedSessionId && expectedPassphraseState) {
374
437
  try {
375
438
  response = await getDeviceSession(
376
439
  device,
377
- buildDeviceSessionGetRequest({
440
+ sessionGetRequest({
378
441
  sessionId: cachedSessionId,
379
442
  expectedPassphraseState,
380
443
  })
@@ -391,7 +454,7 @@ export async function getProtocolV2WalletSession(
391
454
  try {
392
455
  response = await getDeviceSession(
393
456
  device,
394
- buildDeviceSessionGetRequest({
457
+ sessionGetRequest({
395
458
  expectedPassphraseState,
396
459
  })
397
460
  );
@@ -407,6 +470,7 @@ export async function getProtocolV2WalletSession(
407
470
  device.clearInternalState();
408
471
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
409
472
  }
473
+ await lockAttachPinBeforePassphraseSelection();
410
474
  response = await selectDeviceSession(
411
475
  device,
412
476
  expectedPassphraseState,
@@ -436,9 +500,10 @@ export async function getProtocolV2WalletSession(
436
500
  if (options?.onlyMainPin) {
437
501
  device.clearStandardInternalState?.();
438
502
  await selectStandardWallet();
439
- response = await getDeviceSession(device, buildDeviceSessionGetRequest());
503
+ response = await getDeviceSession(device, sessionGetRequest());
440
504
  } else {
441
505
  device.clearInternalState();
506
+ await lockAttachPinBeforePassphraseSelection();
442
507
  response = await selectDeviceSession(
443
508
  device,
444
509
  expectedPassphraseState,
@@ -464,6 +529,54 @@ export async function getProtocolV2WalletSession(
464
529
  }
465
530
  }
466
531
 
532
+ // origin/dev generates Cardano on AskPassphrase, not Get. Empty host
533
+ // passphrase is the Attach PIN / standard-wallet secret. Hidden wallets
534
+ // still need a real passphrase Ask. Passphrase-off Get auto-requests Cardano.
535
+ if (options?.deriveCardano === true && !deviceSessionHasCardano(message)) {
536
+ if (options?.resumeOnly) {
537
+ device.clearInternalState();
538
+ throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
539
+ }
540
+ resumed = false;
541
+ const previousAddress = message.btc_test_address;
542
+ if (sessionIsAttachPinWallet(response)) {
543
+ response = await askEmptyPassphraseAndGet({ deriveCardano: true, keepAttachPin: true });
544
+ } else if (device.features?.passphraseProtection === false) {
545
+ response = await getDeviceSession(device, buildDeviceSessionGetRequest());
546
+ } else if (options?.onlyMainPin) {
547
+ await selectMainPin();
548
+ response = await askEmptyPassphraseAndGet({ deriveCardano: true });
549
+ } else {
550
+ await lockAttachPinBeforePassphraseSelection();
551
+ response = await selectDeviceSession(
552
+ device,
553
+ expectedPassphraseState,
554
+ true,
555
+ markWalletStatusRefreshed
556
+ );
557
+ }
558
+ message = response.message;
559
+ try {
560
+ assertCompleteDeviceSession(message);
561
+ } catch (error) {
562
+ if (options?.onlyMainPin) {
563
+ device.clearStandardInternalState?.();
564
+ } else {
565
+ device.clearInternalState();
566
+ }
567
+ throw error;
568
+ }
569
+ if (previousAddress && previousAddress !== message.btc_test_address) {
570
+ await rejectMismatchedAttachPinWallet();
571
+ clearCurrentWalletSession();
572
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
573
+ }
574
+ if (!deviceSessionHasCardano(message)) {
575
+ clearCurrentWalletSession();
576
+ throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
577
+ }
578
+ }
579
+
467
580
  const internalStateArgs = [
468
581
  true,
469
582
  message.btc_test_address,
@@ -478,7 +591,7 @@ export async function getProtocolV2WalletSession(
478
591
  }
479
592
 
480
593
  let unlockedAttachPin: boolean | undefined;
481
- if (readCurrentAttachPinSession) {
594
+ if (readCurrentAttachPinSession || sessionIsAttachPinWallet(response)) {
482
595
  unlockedAttachPin = true;
483
596
  } else if (mainPinAuthenticated) {
484
597
  unlockedAttachPin = false;