@onekeyfe/hd-core 1.2.0-alpha.22 → 1.2.0-alpha.24

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 (151) hide show
  1. package/__tests__/DeviceCommands.test.ts +43 -8
  2. package/__tests__/connectSettings.test.ts +5 -47
  3. package/__tests__/device-lifecycle-events.test.ts +59 -0
  4. package/__tests__/device-settings.test.ts +1 -1
  5. package/__tests__/device-wallet-session-store.test.ts +41 -0
  6. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +30 -39
  7. package/__tests__/logBlockEvent.test.ts +17 -0
  8. package/__tests__/open-wallet-session.test.ts +233 -111
  9. package/__tests__/protocol-v2-ping.test.ts +28 -0
  10. package/__tests__/protocol-v2.test.ts +513 -313
  11. package/__tests__/public-pro2-api-boundary.test.ts +22 -0
  12. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  13. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  14. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  15. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  16. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  17. package/dist/api/FirmwareUpdateV4.d.ts +5 -21
  18. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  19. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  20. package/dist/api/OpenWalletSession.d.ts +1 -8
  21. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  22. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  23. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  24. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  25. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -7
  26. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  27. package/dist/api/firmware/getBinary.d.ts +0 -7
  28. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  29. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  30. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  31. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  32. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  33. package/dist/api/index.d.ts +1 -0
  34. package/dist/api/index.d.ts.map +1 -1
  35. package/dist/api/protocol-v2/Ping.d.ts +2 -0
  36. package/dist/api/protocol-v2/Ping.d.ts.map +1 -1
  37. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  38. package/dist/core/index.d.ts.map +1 -1
  39. package/dist/data-manager/DataManager.d.ts +0 -1
  40. package/dist/data-manager/DataManager.d.ts.map +1 -1
  41. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  42. package/dist/device/Device.d.ts +3 -2
  43. package/dist/device/Device.d.ts.map +1 -1
  44. package/dist/device/DeviceCommands.d.ts.map +1 -1
  45. package/dist/events/device.d.ts +1 -0
  46. package/dist/events/device.d.ts.map +1 -1
  47. package/dist/events/logBlockEvent.d.ts.map +1 -1
  48. package/dist/events/ui-request.d.ts +1 -0
  49. package/dist/events/ui-request.d.ts.map +1 -1
  50. package/dist/index.d.ts +98 -278
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +876 -2647
  53. package/dist/inject.d.ts.map +1 -1
  54. package/dist/lowLevelInject.d.ts.map +1 -1
  55. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  56. package/dist/protocols/protocol-v2/unlockRetry.d.ts +2 -3
  57. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  58. package/dist/protocols/protocol-v2/walletSession.d.ts +12 -0
  59. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  60. package/dist/topLevelInject.d.ts.map +1 -1
  61. package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -3
  62. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  63. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  64. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  65. package/dist/types/api/export.d.ts +1 -4
  66. package/dist/types/api/export.d.ts.map +1 -1
  67. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  68. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  69. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  70. package/dist/types/api/index.d.ts +4 -10
  71. package/dist/types/api/index.d.ts.map +1 -1
  72. package/dist/types/api/openWalletSession.d.ts +0 -12
  73. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  74. package/dist/types/api/protocolV2.d.ts +4 -0
  75. package/dist/types/api/protocolV2.d.ts.map +1 -1
  76. package/dist/types/settings.d.ts +0 -13
  77. package/dist/types/settings.d.ts.map +1 -1
  78. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  79. package/dist/utils/patch.d.ts +1 -1
  80. package/dist/utils/patch.d.ts.map +1 -1
  81. package/package.json +4 -4
  82. package/src/api/CheckAllFirmwareRelease.ts +3 -16
  83. package/src/api/FirmwareUpdateV2.ts +119 -284
  84. package/src/api/FirmwareUpdateV3.ts +55 -248
  85. package/src/api/FirmwareUpdateV4.ts +293 -751
  86. package/src/api/GetPassphraseState.ts +11 -5
  87. package/src/api/OpenWalletSession.ts +23 -86
  88. package/src/api/device/DeviceUpdateBootloader.ts +6 -114
  89. package/src/api/device/DeviceWipe.ts +2 -0
  90. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -50
  91. package/src/api/firmware/updateBootloader.ts +4 -19
  92. package/src/api/firmware/uploadFirmware.ts +22 -140
  93. package/src/api/index.ts +1 -0
  94. package/src/api/protocol-v2/Ping.ts +32 -1
  95. package/src/core/index.ts +40 -7
  96. package/src/data/messages/messages-protocol-v2.json +49 -56
  97. package/src/data-manager/DataManager.ts +33 -33
  98. package/src/data-manager/connectSettings.ts +0 -11
  99. package/src/device/Device.ts +29 -8
  100. package/src/device/DeviceCommands.ts +43 -151
  101. package/src/events/device.ts +1 -0
  102. package/src/events/logBlockEvent.ts +1 -15
  103. package/src/events/ui-request.ts +1 -0
  104. package/src/index.ts +0 -5
  105. package/src/inject.ts +4 -22
  106. package/src/lowLevelInject.ts +1 -5
  107. package/src/protocols/protocol-v2/deviceSession.ts +1 -1
  108. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +0 -1
  109. package/src/protocols/protocol-v2/unlockRetry.ts +20 -3
  110. package/src/protocols/protocol-v2/walletSession.ts +57 -18
  111. package/src/topLevelInject.ts +1 -5
  112. package/src/types/api/checkAllFirmwareRelease.ts +0 -3
  113. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  114. package/src/types/api/export.ts +0 -18
  115. package/src/types/api/firmwareUpdate.ts +0 -76
  116. package/src/types/api/getPassphraseState.ts +4 -3
  117. package/src/types/api/index.ts +3 -14
  118. package/src/types/api/openWalletSession.ts +0 -19
  119. package/src/types/api/protocolV2.ts +10 -0
  120. package/src/types/settings.ts +0 -13
  121. package/src/utils/deviceInfoUtils.ts +2 -3
  122. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  123. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  124. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  125. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -326
  126. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -200
  127. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  128. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -26
  129. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  130. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  131. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  132. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  133. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  134. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -24
  135. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  136. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -25
  137. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  138. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  139. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  140. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -27
  141. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  142. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  143. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  144. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  145. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  146. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  147. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -621
  148. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -378
  149. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  150. package/src/types/api/firmwareUpdatePlan.ts +0 -36
  151. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -1,4 +1,5 @@
1
1
  import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import GetPassphraseState from '../src/api/GetPassphraseState';
4
5
  import OpenWalletSession from '../src/api/OpenWalletSession';
@@ -59,15 +60,54 @@ describe('openWalletSession', () => {
59
60
  deviceWalletSessionStore.clear();
60
61
  });
61
62
 
62
- test('keeps getPassphraseState unsupported on Protocol V2', async () => {
63
+ test.each([{ useEmptyPassphrase: true }, { initSession: true }, {}])(
64
+ 'requires the explicit mode in the new public API: %p',
65
+ legacyParams => {
66
+ const method = new OpenWalletSession({
67
+ payload: {
68
+ method: 'openWalletSession',
69
+ connectId: 'connect-id',
70
+ ...legacyParams,
71
+ },
72
+ });
73
+
74
+ expect(() => method.init()).toThrow('Parameter [mode] is required');
75
+ }
76
+ );
77
+
78
+ test('keeps the legacy getPassphraseState App flow working on Protocol V2', async () => {
79
+ const typedCall = jest
80
+ .fn()
81
+ .mockResolvedValueOnce({ message: { version: 2 } })
82
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
83
+ .mockResolvedValueOnce({
84
+ message: {
85
+ btc_test_address: 'pro2-wallet-state',
86
+ session_id: 'pro2-wallet-session',
87
+ },
88
+ });
89
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
63
90
  const method = new GetPassphraseState({
64
- payload: { method: 'getPassphraseState', connectId: 'connect-id' },
91
+ payload: {
92
+ method: 'getPassphraseState',
93
+ connectId: 'connect-id',
94
+ initSession: true,
95
+ useEmptyPassphrase: false,
96
+ },
65
97
  });
66
- method.device = createDevice() as any;
98
+ method.device = createDevice({ typedCall, promptPassphrase }) as any;
67
99
 
68
- await expect(method.run()).rejects.toMatchObject({
69
- errorCode: HardwareErrorCode.DeviceNotSupportMethod,
70
- });
100
+ await expect(method.run()).resolves.toBe('pro2-wallet-state');
101
+ expect(promptPassphrase).toHaveBeenCalledWith(
102
+ {
103
+ existsAttachPinUser: false,
104
+ source: 'wallet-session-coordinator',
105
+ reason: 'open-wallet',
106
+ },
107
+ { cancelDeviceOnReject: false }
108
+ );
109
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
110
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
71
111
  });
72
112
 
73
113
  test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
@@ -122,9 +162,7 @@ describe('openWalletSession', () => {
122
162
  payload: {
123
163
  method: 'openWalletSession',
124
164
  connectId: 'connect-id',
125
- useEmptyPassphrase: true,
126
- initSession: true,
127
- passphraseState: 'stale-hidden-state',
165
+ mode: 'standard',
128
166
  },
129
167
  });
130
168
  method.init();
@@ -153,19 +191,27 @@ describe('openWalletSession', () => {
153
191
  expect(typedCall).toHaveBeenCalledWith('GetPassphraseState', 'PassphraseState', {
154
192
  passphrase_state: undefined,
155
193
  });
156
- expect(device.clearInternalState).toHaveBeenCalled();
194
+ expect(device.clearInternalState).not.toHaveBeenCalled();
157
195
  });
158
196
 
159
- test('uses the compatibility form to select a hidden wallet on Protocol V2', async () => {
160
- const typedCall = jest.fn().mockResolvedValue({
161
- message: {
162
- btc_test_address: 'hidden-state',
163
- session_id: 'hidden-session',
164
- },
165
- });
166
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
197
+ test('uses the explicit mode to select a hidden wallet on Protocol V2', async () => {
198
+ const typedCall = jest
199
+ .fn()
200
+ .mockResolvedValueOnce({ message: { version: 2 } })
201
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
202
+ .mockResolvedValueOnce({
203
+ message: {
204
+ btc_test_address: 'hidden-state',
205
+ session_id: 'hidden-session',
206
+ },
207
+ });
208
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
167
209
  const method = new OpenWalletSession({
168
- payload: { method: 'openWalletSession', connectId: 'connect-id' },
210
+ payload: {
211
+ method: 'openWalletSession',
212
+ connectId: 'connect-id',
213
+ mode: 'select-hidden',
214
+ },
169
215
  });
170
216
  method.init();
171
217
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
@@ -175,24 +221,32 @@ describe('openWalletSession', () => {
175
221
  walletType: 'hidden',
176
222
  passphraseState: 'hidden-state',
177
223
  });
224
+ expect(promptPassphrase).toHaveBeenCalled();
225
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
226
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
178
227
  });
179
228
 
180
229
  test('classifies the selected Pro2 wallet from the post-unlock device state', async () => {
181
230
  const typedCall = jest
182
231
  .fn()
183
232
  .mockResolvedValueOnce({ message: { version: 2 } })
233
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
184
234
  .mockResolvedValueOnce({
185
235
  message: {
186
236
  btc_test_address: 'hidden-state-after-unlock',
187
237
  session_id: 'hidden-session-after-unlock',
188
238
  },
189
239
  });
190
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
240
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
191
241
  const method = new OpenWalletSession({
192
242
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
193
243
  });
194
244
  method.init();
195
- const device = createDevice({ passphraseProtection: false, typedCall, promptPassphrase });
245
+ const device = createDevice({
246
+ passphraseProtection: false,
247
+ typedCall,
248
+ promptPassphrase,
249
+ });
196
250
  device.features.unlocked = false;
197
251
  device.getCurrentPassphraseProtection = () => device.features.passphraseProtection;
198
252
  device.unlockDevice = jest.fn().mockImplementation(() => {
@@ -210,29 +264,30 @@ describe('openWalletSession', () => {
210
264
  sessionId: 'hidden-session-after-unlock',
211
265
  resumed: false,
212
266
  });
213
- expect(device.unlockDevice).toHaveBeenCalledTimes(1);
267
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
214
268
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
215
269
  });
216
270
 
217
- test('uses legacy initSession to replace a hidden-wallet session on Protocol V2', async () => {
218
- const typedCall = jest.fn().mockResolvedValue({
219
- message: {
220
- btc_test_address: 'new-hidden-state',
221
- session_id: 'new-hidden-session',
222
- },
223
- });
224
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'new hidden secret' });
271
+ test('select-hidden starts a fresh hidden-wallet session on Protocol V2', async () => {
272
+ const typedCall = jest
273
+ .fn()
274
+ .mockResolvedValueOnce({ message: { version: 2 } })
275
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
276
+ .mockResolvedValueOnce({
277
+ message: {
278
+ btc_test_address: 'new-hidden-state',
279
+ session_id: 'new-hidden-session',
280
+ },
281
+ });
282
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
225
283
  const method = new OpenWalletSession({
226
284
  payload: {
227
285
  method: 'openWalletSession',
228
286
  connectId: 'connect-id',
229
- initSession: true,
230
- deviceId: 'device-1',
231
- passphraseState: 'stale-hidden-state',
287
+ mode: 'select-hidden',
232
288
  },
233
289
  });
234
290
  method.init();
235
- deviceWalletSessionStore.set('device-1', 'stale-hidden-state', 'stale-hidden-session');
236
291
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
237
292
 
238
293
  await expect(method.run()).resolves.toEqual({
@@ -246,29 +301,29 @@ describe('openWalletSession', () => {
246
301
  expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
247
302
  eventless_wallet_session: true,
248
303
  });
249
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
250
- select: { host_passphrase: { passphrase: 'new hidden secret' } },
251
- });
304
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
305
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
252
306
  expect(promptPassphrase).toHaveBeenCalled();
253
- expect(deviceWalletSessionStore.get('device-1', 'stale-hidden-state')).toBeUndefined();
254
307
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
255
308
  });
256
309
 
257
- test('does not clear another device wallet through legacy initSession', async () => {
258
- const typedCall = jest.fn().mockResolvedValue({
259
- message: {
260
- btc_test_address: 'current-device-state',
261
- session_id: 'current-device-session',
262
- },
263
- });
264
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
310
+ test('select-hidden does not clear another device wallet', async () => {
311
+ const typedCall = jest
312
+ .fn()
313
+ .mockResolvedValueOnce({ message: { version: 2 } })
314
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
315
+ .mockResolvedValueOnce({
316
+ message: {
317
+ btc_test_address: 'current-device-state',
318
+ session_id: 'current-device-session',
319
+ },
320
+ });
321
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
265
322
  const method = new OpenWalletSession({
266
323
  payload: {
267
324
  method: 'openWalletSession',
268
325
  connectId: 'connect-id',
269
- initSession: true,
270
- deviceId: 'other-device',
271
- passphraseState: 'other-device-state',
326
+ mode: 'select-hidden',
272
327
  },
273
328
  });
274
329
  method.init();
@@ -285,19 +340,22 @@ describe('openWalletSession', () => {
285
340
  );
286
341
  });
287
342
 
288
- test('uses a complete legacy wallet binding to resume on Protocol V2', async () => {
289
- const typedCall = jest.fn().mockResolvedValue({
290
- message: {
291
- btc_test_address: 'hidden-state',
292
- session_id: 'renewed-session',
293
- },
294
- });
343
+ test('uses an explicit wallet binding to resume on Protocol V2', async () => {
344
+ const typedCall = jest
345
+ .fn()
346
+ .mockResolvedValueOnce({ message: { version: 2 } })
347
+ .mockResolvedValueOnce({
348
+ message: {
349
+ btc_test_address: 'hidden-state',
350
+ session_id: 'renewed-session',
351
+ },
352
+ });
295
353
  const promptPassphrase = jest.fn();
296
354
  const method = new OpenWalletSession({
297
355
  payload: {
298
356
  method: 'openWalletSession',
299
357
  connectId: 'connect-id',
300
- initSession: false,
358
+ mode: 'resume-hidden',
301
359
  deviceId: 'device-1',
302
360
  passphraseState: 'hidden-state',
303
361
  },
@@ -314,8 +372,8 @@ describe('openWalletSession', () => {
314
372
  sessionId: 'renewed-session',
315
373
  resumed: true,
316
374
  });
317
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
318
- resume: { session_id: 'known-session' },
375
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
376
+ session_id: 'known-session',
319
377
  });
320
378
  expect(promptPassphrase).not.toHaveBeenCalled();
321
379
  });
@@ -357,7 +415,7 @@ describe('openWalletSession', () => {
357
415
  });
358
416
 
359
417
  test.each([{ initSession: 'true' }, { useEmptyPassphrase: 'false' }])(
360
- 'rejects a non-boolean legacy flag: %o',
418
+ 'rejects legacy flags without an explicit mode: %o',
361
419
  params => {
362
420
  const method = new OpenWalletSession({
363
421
  payload: {
@@ -485,12 +543,15 @@ describe('openWalletSession', () => {
485
543
  });
486
544
 
487
545
  test('rejects a standard-wallet request when Protocol V2 is unlocked by Attach PIN', async () => {
488
- const typedCall = jest.fn().mockResolvedValue({
489
- message: {
490
- btc_test_address: 'attach-wallet-state',
491
- session_id: 'attach-wallet-session',
492
- },
493
- });
546
+ const typedCall = jest
547
+ .fn()
548
+ .mockResolvedValueOnce({ message: { version: 2 } })
549
+ .mockResolvedValueOnce({
550
+ message: {
551
+ btc_test_address: 'attach-wallet-state',
552
+ session_id: 'attach-wallet-session',
553
+ },
554
+ });
494
555
  const method = new OpenWalletSession({
495
556
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
496
557
  });
@@ -507,13 +568,17 @@ describe('openWalletSession', () => {
507
568
  });
508
569
 
509
570
  test('selects a hidden wallet and returns the CLI compatibility session', async () => {
510
- const typedCall = jest.fn().mockResolvedValue({
511
- message: {
512
- btc_test_address: 'hidden-state',
513
- session_id: 'hidden-session',
514
- },
515
- });
516
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
571
+ const typedCall = jest
572
+ .fn()
573
+ .mockResolvedValueOnce({ message: { version: 2 } })
574
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
575
+ .mockResolvedValueOnce({
576
+ message: {
577
+ btc_test_address: 'hidden-state',
578
+ session_id: 'hidden-session',
579
+ },
580
+ });
581
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
517
582
  const method = new OpenWalletSession({
518
583
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
519
584
  });
@@ -530,18 +595,21 @@ describe('openWalletSession', () => {
530
595
  sessionId: 'hidden-session',
531
596
  resumed: false,
532
597
  });
533
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
534
- select: { host_passphrase: { passphrase: 'hidden secret' } },
535
- });
598
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
599
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
536
600
  expect(promptPassphrase).toHaveBeenCalled();
537
601
  expect(device.passphraseState).toBeUndefined();
538
602
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
539
603
  });
540
604
 
541
- test('selects on-device passphrase entry through DeviceSessionOpen', async () => {
542
- const typedCall = jest.fn().mockResolvedValue({
543
- message: { btc_test_address: 'device-state', session_id: 'device-session' },
544
- });
605
+ test('selects on-device passphrase entry before getting the prepared session', async () => {
606
+ const typedCall = jest
607
+ .fn()
608
+ .mockResolvedValueOnce({ message: { version: 2 } })
609
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
610
+ .mockResolvedValueOnce({
611
+ message: { btc_test_address: 'device-state', session_id: 'device-session' },
612
+ });
545
613
  const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
546
614
  const method = new OpenWalletSession({
547
615
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
@@ -553,15 +621,56 @@ describe('openWalletSession', () => {
553
621
  walletType: 'hidden',
554
622
  passphraseState: 'device-state',
555
623
  });
556
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
557
- select: { passphrase_on_device: {} },
624
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
625
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
626
+ });
627
+
628
+ test('forwards a host passphrase to Pro2 firmware', async () => {
629
+ const typedCall = jest
630
+ .fn()
631
+ .mockResolvedValueOnce({ message: { version: 2 } })
632
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
633
+ .mockResolvedValueOnce({
634
+ message: { btc_test_address: 'host-state', session_id: 'host-session' },
635
+ });
636
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
637
+ const method = new OpenWalletSession({
638
+ payload: {
639
+ method: 'openWalletSession',
640
+ connectId: 'connect-id',
641
+ mode: 'select-hidden',
642
+ },
643
+ });
644
+ method.init();
645
+ method.device = createDevice({
646
+ typedCall,
647
+ promptPassphrase,
648
+ }) as any;
649
+
650
+ await expect(method.run()).resolves.toMatchObject({
651
+ walletType: 'hidden',
652
+ passphraseState: 'host-state',
653
+ });
654
+ expect(promptPassphrase).toHaveBeenCalledWith(
655
+ {
656
+ existsAttachPinUser: false,
657
+ source: 'wallet-session-coordinator',
658
+ reason: 'open-wallet',
659
+ },
660
+ { cancelDeviceOnReject: false }
661
+ );
662
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
663
+ passphrase: 'host hidden wallet',
558
664
  });
559
665
  });
560
666
 
561
667
  test('selects Attach PIN only when the device reports an existing binding', async () => {
562
- const typedCall = jest.fn().mockResolvedValue({
563
- message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
564
- });
668
+ const typedCall = jest
669
+ .fn()
670
+ .mockResolvedValueOnce({ message: { version: 2 } })
671
+ .mockResolvedValueOnce({
672
+ message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
673
+ });
565
674
  const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
566
675
  const method = new OpenWalletSession({
567
676
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
@@ -576,12 +685,15 @@ describe('openWalletSession', () => {
576
685
  passphraseState: 'attach-state',
577
686
  });
578
687
  expect(promptPassphrase).toHaveBeenCalledWith(
579
- expect.objectContaining({ existsAttachPinUser: true }),
688
+ {
689
+ existsAttachPinUser: true,
690
+ source: 'wallet-session-coordinator',
691
+ reason: 'open-wallet',
692
+ },
580
693
  { cancelDeviceOnReject: false }
581
694
  );
582
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
583
- select: { attach_pin_on_device: {} },
584
- });
695
+ expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
696
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
585
697
  });
586
698
 
587
699
  test.each([
@@ -596,8 +708,12 @@ describe('openWalletSession', () => {
596
708
  ])(
597
709
  'rejects an incomplete Protocol V2 hidden-wallet response missing $missingField',
598
710
  async ({ message }) => {
599
- const typedCall = jest.fn().mockResolvedValue({ message });
600
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
711
+ const typedCall = jest
712
+ .fn()
713
+ .mockResolvedValueOnce({ message: { version: 2 } })
714
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
715
+ .mockResolvedValueOnce({ message });
716
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
601
717
  const method = new OpenWalletSession({
602
718
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
603
719
  });
@@ -607,7 +723,7 @@ describe('openWalletSession', () => {
607
723
 
608
724
  await expect(method.run()).rejects.toMatchObject({
609
725
  errorCode: HardwareErrorCode.RuntimeError,
610
- message: 'DeviceSessionOpen returned an incomplete DeviceSession response.',
726
+ message: 'DeviceSessionGet returned an incomplete DeviceSession response.',
611
727
  });
612
728
  expect(device.clearInternalState).toHaveBeenCalled();
613
729
  expect(device.updateInternalState).not.toHaveBeenCalled();
@@ -615,12 +731,15 @@ describe('openWalletSession', () => {
615
731
  );
616
732
 
617
733
  test('resumes a known hidden wallet without prompting or device selection', async () => {
618
- const typedCall = jest.fn().mockResolvedValue({
619
- message: {
620
- btc_test_address: 'hidden-state',
621
- session_id: 'renewed-session',
622
- },
623
- });
734
+ const typedCall = jest
735
+ .fn()
736
+ .mockResolvedValueOnce({ message: { version: 2 } })
737
+ .mockResolvedValueOnce({
738
+ message: {
739
+ btc_test_address: 'hidden-state',
740
+ session_id: 'renewed-session',
741
+ },
742
+ });
624
743
  const promptPassphrase = jest.fn();
625
744
  const method = new OpenWalletSession({
626
745
  payload: {
@@ -643,19 +762,22 @@ describe('openWalletSession', () => {
643
762
  sessionId: 'renewed-session',
644
763
  resumed: true,
645
764
  });
646
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
647
- resume: { session_id: 'known-session' },
765
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
766
+ session_id: 'known-session',
648
767
  });
649
768
  expect(promptPassphrase).not.toHaveBeenCalled();
650
769
  });
651
770
 
652
771
  test('validates a locked Protocol V2 resume binding after deviceId refresh', async () => {
653
- const typedCall = jest.fn().mockResolvedValue({
654
- message: {
655
- btc_test_address: 'hidden-state',
656
- session_id: 'renewed-session',
657
- },
658
- });
772
+ const typedCall = jest
773
+ .fn()
774
+ .mockResolvedValueOnce({ message: { version: 2 } })
775
+ .mockResolvedValueOnce({
776
+ message: {
777
+ btc_test_address: 'hidden-state',
778
+ session_id: 'renewed-session',
779
+ },
780
+ });
659
781
  const method = new OpenWalletSession({
660
782
  payload: {
661
783
  method: 'openWalletSession',
@@ -694,14 +816,14 @@ describe('openWalletSession', () => {
694
816
  });
695
817
  expect(device.unlockDevice).toHaveBeenCalledTimes(1);
696
818
  expect(device.getDeviceState).toHaveBeenCalledTimes(2);
697
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionOpen', 'DeviceSession', {
698
- resume: { session_id: 'known-session' },
819
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
820
+ session_id: 'known-session',
699
821
  });
700
- const sessionOpenCall = typedCall.mock.calls.findIndex(
701
- ([requestName]) => requestName === 'DeviceSessionOpen'
822
+ const sessionGetCall = typedCall.mock.calls.findIndex(
823
+ ([requestName]) => requestName === 'DeviceSessionGet'
702
824
  );
703
825
  expect(device.getDeviceState.mock.invocationCallOrder[1]).toBeLessThan(
704
- typedCall.mock.invocationCallOrder[sessionOpenCall]
826
+ typedCall.mock.invocationCallOrder[sessionGetCall]
705
827
  );
706
828
  });
707
829
 
@@ -0,0 +1,28 @@
1
+ import { validateProtocolV2PingMessage } from '../src/api/protocol-v2/Ping';
2
+
3
+ jest.mock('../src/data/config', () => ({
4
+ DEFAULT_DOMAIN: 'https://example.com/',
5
+ getSDKVersion: () => '0.0.0-test',
6
+ }));
7
+
8
+ describe('Protocol V2 diagnostic Ping validation', () => {
9
+ test('accepts messages up to the firmware UTF-8 byte limit', () => {
10
+ expect(validateProtocolV2PingMessage('x'.repeat(63))).toBe('x'.repeat(63));
11
+ expect(validateProtocolV2PingMessage('测'.repeat(21))).toBe('测'.repeat(21));
12
+ });
13
+
14
+ test('rejects messages larger than the firmware UTF-8 byte limit', () => {
15
+ expect(() => validateProtocolV2PingMessage('x'.repeat(64))).toThrow(
16
+ 'Protocol V2 Ping message must not exceed 63 UTF-8 bytes.'
17
+ );
18
+ expect(() => validateProtocolV2PingMessage('测'.repeat(22))).toThrow(
19
+ 'Protocol V2 Ping message must not exceed 63 UTF-8 bytes.'
20
+ );
21
+ });
22
+
23
+ test('rejects non-string messages at the runtime API boundary', () => {
24
+ expect(() => validateProtocolV2PingMessage(64)).toThrow(
25
+ 'Protocol V2 Ping message must be a string.'
26
+ );
27
+ });
28
+ });