@onekeyfe/hd-core 1.2.0-alpha.33 → 1.2.0-alpha.35
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.
- package/__tests__/DeviceCommands.test.ts +84 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +31 -0
- package/__tests__/core-dispose.test.ts +3 -3
- package/__tests__/device-lifecycle-events.test.ts +52 -0
- package/__tests__/device-pool-state.test.ts +14 -2
- package/__tests__/device-settings.test.ts +25 -0
- package/__tests__/device-state-mapper.test.ts +13 -2
- package/__tests__/device-state-projector.test.ts +2 -2
- package/__tests__/device-utils.test.ts +1 -0
- package/__tests__/device-wallet-session-store.test.ts +137 -17
- package/__tests__/evmSignTransaction.test.ts +69 -0
- package/__tests__/get-device-state.test.ts +78 -30
- package/__tests__/kaspaSignTransaction.test.ts +9 -0
- package/__tests__/known-device-public-types.type-test.ts +3 -0
- package/__tests__/logBlockEvent.test.ts +89 -5
- package/__tests__/open-wallet-session.test.ts +499 -60
- package/__tests__/pro2Wallpaper.test.ts +1 -1
- package/__tests__/protocol-v2.test.ts +1180 -169
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +2 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/evm/protocol.d.ts +3 -0
- package/dist/api/evm/protocol.d.ts.map +1 -0
- package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +10 -4
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts +1 -0
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/device/DeviceStateMapper.d.ts.map +1 -1
- package/dist/device/DeviceStateProjector.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +7 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
- package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
- package/dist/events/logBlockEvent.d.ts +2 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +25 -11
- package/dist/index.js +1037 -838
- package/dist/protocols/protocol-v2/features.d.ts +17 -6
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +2 -2
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +1 -10
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +2 -2
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/device.d.ts +6 -3
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +0 -5
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/CheckAllFirmwareRelease.ts +11 -81
- package/src/api/FirmwareUpdateV4.ts +59 -25
- package/src/api/GetPassphraseState.ts +11 -2
- package/src/api/OpenWalletSession.ts +16 -8
- package/src/api/device/DeviceSettings.ts +8 -1
- package/src/api/evm/EVMGetAddress.ts +2 -2
- package/src/api/evm/EVMGetPublicKey.ts +2 -2
- package/src/api/evm/EVMSignMessage.ts +2 -2
- package/src/api/evm/EVMSignTransaction.ts +2 -2
- package/src/api/evm/EVMSignTypedData.ts +35 -45
- package/src/api/evm/EVMVerifyMessage.ts +2 -2
- package/src/api/evm/protocol.ts +6 -0
- package/src/api/kaspa/KaspaSignTransaction.ts +7 -0
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/core/index.ts +7 -5
- package/src/data/messages/messages-protocol-v2.json +393 -485
- package/src/device/Device.ts +98 -44
- package/src/device/DeviceCommands.ts +12 -2
- package/src/device/DeviceConnector.ts +10 -7
- package/src/device/DevicePool.ts +5 -3
- package/src/device/DeviceStateMapper.ts +5 -3
- package/src/device/DeviceStateProjector.ts +0 -7
- package/src/device/DeviceWalletSessionStore.ts +43 -4
- package/src/deviceProfile/buildDeviceFeatures.ts +11 -4
- package/src/deviceProfile/protocolV2DeviceIdentity.ts +32 -0
- package/src/events/logBlockEvent.ts +84 -1
- package/src/protocols/protocol-v2/deviceSession.ts +1 -1
- package/src/protocols/protocol-v2/features.ts +58 -16
- package/src/protocols/protocol-v2/index.ts +5 -0
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
- package/src/protocols/protocol-v2/unlockRetry.ts +18 -7
- package/src/protocols/protocol-v2/walletSession.ts +144 -62
- package/src/types/api/protocolV2.ts +2 -2
- package/src/types/device.ts +8 -2
- package/src/types/params.ts +2 -1
- package/src/utils/deviceFeaturesCompat.ts +8 -1
- package/src/utils/pro2Wallpaper.ts +3 -9
- package/tsconfig.type-tests.json +5 -1
|
@@ -3,6 +3,7 @@ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
|
3
3
|
|
|
4
4
|
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
5
5
|
import OpenWalletSession from '../src/api/OpenWalletSession';
|
|
6
|
+
import { Device } from '../src/device/Device';
|
|
6
7
|
import { deviceWalletSessionStore } from '../src/device/DeviceWalletSessionStore';
|
|
7
8
|
|
|
8
9
|
jest.mock('../src/data/config', () => ({
|
|
@@ -26,7 +27,22 @@ const createDevice = ({
|
|
|
26
27
|
passphraseProtection,
|
|
27
28
|
attachToPinEnabled: false,
|
|
28
29
|
},
|
|
29
|
-
commands: {
|
|
30
|
+
commands: {
|
|
31
|
+
typedCall: jest.fn((request: string, ...args: unknown[]) => {
|
|
32
|
+
if (request === 'DeviceStatusGet') {
|
|
33
|
+
return {
|
|
34
|
+
message: {
|
|
35
|
+
device_id: 'device-1',
|
|
36
|
+
unlocked: true,
|
|
37
|
+
unlocked_attach_pin: false,
|
|
38
|
+
passphrase_enabled: passphraseProtection,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return typedCall(request, ...args);
|
|
43
|
+
}),
|
|
44
|
+
promptPassphrase,
|
|
45
|
+
},
|
|
30
46
|
getDeviceState: jest.fn().mockResolvedValue({
|
|
31
47
|
identity: { deviceId: 'device-1' },
|
|
32
48
|
status: { passphraseProtection },
|
|
@@ -47,6 +63,12 @@ const createDevice = ({
|
|
|
47
63
|
}),
|
|
48
64
|
lockDevice: jest.fn(),
|
|
49
65
|
unlockDevice: jest.fn(),
|
|
66
|
+
updateProtocolV2Status: jest.fn((status: Record<string, unknown>) => {
|
|
67
|
+
device.features.unlocked = status.unlocked ?? device.features.unlocked;
|
|
68
|
+
device.features.unlockedAttachPin =
|
|
69
|
+
status.unlocked_attach_pin ?? device.features.unlockedAttachPin;
|
|
70
|
+
return device.features;
|
|
71
|
+
}),
|
|
50
72
|
initialize: jest.fn(),
|
|
51
73
|
emit: jest.fn(),
|
|
52
74
|
isProtocolV2: () => true,
|
|
@@ -79,14 +101,14 @@ describe('openWalletSession', () => {
|
|
|
79
101
|
const typedCall = jest
|
|
80
102
|
.fn()
|
|
81
103
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
82
|
-
.mockResolvedValueOnce({ message: {
|
|
104
|
+
.mockResolvedValueOnce({ message: {} })
|
|
83
105
|
.mockResolvedValueOnce({
|
|
84
106
|
message: {
|
|
85
107
|
btc_test_address: 'pro2-wallet-state',
|
|
86
108
|
session_id: 'pro2-wallet-session',
|
|
87
109
|
},
|
|
88
110
|
});
|
|
89
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
111
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
90
112
|
const method = new GetPassphraseState({
|
|
91
113
|
payload: {
|
|
92
114
|
method: 'getPassphraseState',
|
|
@@ -108,7 +130,8 @@ describe('openWalletSession', () => {
|
|
|
108
130
|
{ cancelDeviceOnReject: false }
|
|
109
131
|
);
|
|
110
132
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
111
|
-
|
|
133
|
+
passphrase: 'host hidden wallet',
|
|
134
|
+
on_device: false,
|
|
112
135
|
});
|
|
113
136
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
114
137
|
});
|
|
@@ -200,14 +223,14 @@ describe('openWalletSession', () => {
|
|
|
200
223
|
const typedCall = jest
|
|
201
224
|
.fn()
|
|
202
225
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
203
|
-
.mockResolvedValueOnce({ message: {
|
|
226
|
+
.mockResolvedValueOnce({ message: {} })
|
|
204
227
|
.mockResolvedValueOnce({
|
|
205
228
|
message: {
|
|
206
229
|
btc_test_address: 'hidden-state',
|
|
207
230
|
session_id: 'hidden-session',
|
|
208
231
|
},
|
|
209
232
|
});
|
|
210
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
233
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
211
234
|
const method = new OpenWalletSession({
|
|
212
235
|
payload: {
|
|
213
236
|
method: 'openWalletSession',
|
|
@@ -225,23 +248,59 @@ describe('openWalletSession', () => {
|
|
|
225
248
|
});
|
|
226
249
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
227
250
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
228
|
-
|
|
251
|
+
passphrase: 'host hidden wallet',
|
|
252
|
+
on_device: false,
|
|
229
253
|
});
|
|
230
254
|
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
231
255
|
});
|
|
232
256
|
|
|
257
|
+
test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
|
|
258
|
+
const typedCall = jest
|
|
259
|
+
.fn()
|
|
260
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
261
|
+
.mockResolvedValueOnce({ message: {} })
|
|
262
|
+
.mockResolvedValueOnce({
|
|
263
|
+
message: {
|
|
264
|
+
btc_test_address: 'hidden-state-after-preflight',
|
|
265
|
+
session_id: 'hidden-session-after-preflight',
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const method = new OpenWalletSession({
|
|
269
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
270
|
+
});
|
|
271
|
+
method.init();
|
|
272
|
+
const device = createDevice({
|
|
273
|
+
typedCall,
|
|
274
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
|
|
275
|
+
});
|
|
276
|
+
device.getDeviceState = jest.fn().mockResolvedValue({
|
|
277
|
+
identity: { deviceId: 'device-1' },
|
|
278
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
279
|
+
});
|
|
280
|
+
method.device = device as any;
|
|
281
|
+
|
|
282
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
283
|
+
walletType: 'hidden',
|
|
284
|
+
passphraseState: 'hidden-state-after-preflight',
|
|
285
|
+
});
|
|
286
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
287
|
+
expect(device.unlockDevice.mock.invocationCallOrder[0]).toBeLessThan(
|
|
288
|
+
typedCall.mock.invocationCallOrder[0]
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
233
292
|
test('classifies the selected Pro2 wallet from the post-unlock device state', async () => {
|
|
234
293
|
const typedCall = jest
|
|
235
294
|
.fn()
|
|
236
295
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
237
|
-
.mockResolvedValueOnce({ message: {
|
|
296
|
+
.mockResolvedValueOnce({ message: {} })
|
|
238
297
|
.mockResolvedValueOnce({
|
|
239
298
|
message: {
|
|
240
299
|
btc_test_address: 'hidden-state-after-unlock',
|
|
241
300
|
session_id: 'hidden-session-after-unlock',
|
|
242
301
|
},
|
|
243
302
|
});
|
|
244
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
303
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
245
304
|
const method = new OpenWalletSession({
|
|
246
305
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
247
306
|
});
|
|
@@ -253,10 +312,23 @@ describe('openWalletSession', () => {
|
|
|
253
312
|
});
|
|
254
313
|
device.features.unlocked = false;
|
|
255
314
|
device.getCurrentPassphraseProtection = () => device.features.passphraseProtection;
|
|
256
|
-
device.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
315
|
+
device.commands.typedCall = jest.fn((request: string, ...args: unknown[]) => {
|
|
316
|
+
if (request === 'DeviceStatusGet') {
|
|
317
|
+
return {
|
|
318
|
+
message: {
|
|
319
|
+
device_id: 'device-1',
|
|
320
|
+
unlocked: true,
|
|
321
|
+
passphrase_enabled: true,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return typedCall(request, ...args);
|
|
326
|
+
});
|
|
327
|
+
device.updateProtocolV2Status = jest.fn((status: Record<string, unknown>) => {
|
|
328
|
+
device.features.unlocked = status.unlocked ?? device.features.unlocked;
|
|
329
|
+
device.features.passphraseProtection =
|
|
330
|
+
status.passphrase_enabled ?? device.features.passphraseProtection;
|
|
331
|
+
return device.features;
|
|
260
332
|
});
|
|
261
333
|
method.device = device as any;
|
|
262
334
|
|
|
@@ -267,22 +339,51 @@ describe('openWalletSession', () => {
|
|
|
267
339
|
passphraseState: 'hidden-state-after-unlock',
|
|
268
340
|
resumed: false,
|
|
269
341
|
});
|
|
270
|
-
expect(device.unlockDevice).
|
|
342
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
271
343
|
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
272
344
|
});
|
|
273
345
|
|
|
346
|
+
test('rejects a Pro2 standard-wallet fallback after selecting a hidden wallet', async () => {
|
|
347
|
+
const typedCall = jest
|
|
348
|
+
.fn()
|
|
349
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
350
|
+
.mockResolvedValueOnce({ message: {} })
|
|
351
|
+
.mockResolvedValueOnce({
|
|
352
|
+
message: {
|
|
353
|
+
btc_test_address: 'standard-wallet-state',
|
|
354
|
+
session_id: 'standard-wallet-session',
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
|
|
358
|
+
const method = new OpenWalletSession({
|
|
359
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
360
|
+
});
|
|
361
|
+
method.init();
|
|
362
|
+
const device = createDevice({
|
|
363
|
+
passphraseProtection: false,
|
|
364
|
+
typedCall,
|
|
365
|
+
promptPassphrase,
|
|
366
|
+
});
|
|
367
|
+
method.device = device as any;
|
|
368
|
+
|
|
369
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
370
|
+
errorCode: HardwareErrorCode.DeviceNotOpenedPassphrase,
|
|
371
|
+
});
|
|
372
|
+
expect(device.clearInternalState).toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
|
|
274
375
|
test('select-hidden starts a fresh hidden-wallet session on Protocol V2', async () => {
|
|
275
376
|
const typedCall = jest
|
|
276
377
|
.fn()
|
|
277
378
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
278
|
-
.mockResolvedValueOnce({ message: {
|
|
379
|
+
.mockResolvedValueOnce({ message: {} })
|
|
279
380
|
.mockResolvedValueOnce({
|
|
280
381
|
message: {
|
|
281
382
|
btc_test_address: 'new-hidden-state',
|
|
282
383
|
session_id: 'new-hidden-session',
|
|
283
384
|
},
|
|
284
385
|
});
|
|
285
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
386
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
286
387
|
const method = new OpenWalletSession({
|
|
287
388
|
payload: {
|
|
288
389
|
method: 'openWalletSession',
|
|
@@ -304,7 +405,8 @@ describe('openWalletSession', () => {
|
|
|
304
405
|
eventless_wallet_session: true,
|
|
305
406
|
});
|
|
306
407
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
307
|
-
|
|
408
|
+
passphrase: 'host hidden wallet',
|
|
409
|
+
on_device: false,
|
|
308
410
|
});
|
|
309
411
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
310
412
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
@@ -315,14 +417,14 @@ describe('openWalletSession', () => {
|
|
|
315
417
|
const typedCall = jest
|
|
316
418
|
.fn()
|
|
317
419
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
318
|
-
.mockResolvedValueOnce({ message: {
|
|
420
|
+
.mockResolvedValueOnce({ message: {} })
|
|
319
421
|
.mockResolvedValueOnce({
|
|
320
422
|
message: {
|
|
321
423
|
btc_test_address: 'current-device-state',
|
|
322
424
|
session_id: 'current-device-session',
|
|
323
425
|
},
|
|
324
426
|
});
|
|
325
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
427
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
326
428
|
const method = new OpenWalletSession({
|
|
327
429
|
payload: {
|
|
328
430
|
method: 'openWalletSession',
|
|
@@ -484,8 +586,56 @@ describe('openWalletSession', () => {
|
|
|
484
586
|
});
|
|
485
587
|
});
|
|
486
588
|
|
|
589
|
+
test('rejects a Protocol V1 resume when the device returns another hidden wallet', async () => {
|
|
590
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
591
|
+
type: 'PassphraseState',
|
|
592
|
+
message: {
|
|
593
|
+
passphrase_state: 'other-hidden-state',
|
|
594
|
+
session_id: 'other-session',
|
|
595
|
+
unlocked_attach_pin: false,
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
const method = new OpenWalletSession({
|
|
599
|
+
payload: {
|
|
600
|
+
method: 'openWalletSession',
|
|
601
|
+
connectId: 'connect-id',
|
|
602
|
+
mode: 'resume-hidden',
|
|
603
|
+
deviceId: 'device-1',
|
|
604
|
+
passphraseState: 'hidden-state',
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
method.init();
|
|
608
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'known-session');
|
|
609
|
+
const device = createDevice({ typedCall });
|
|
610
|
+
device.isProtocolV2 = () => false;
|
|
611
|
+
device.features = {
|
|
612
|
+
...device.features,
|
|
613
|
+
deviceId: 'device-1',
|
|
614
|
+
unlocked: true,
|
|
615
|
+
sessionId: null,
|
|
616
|
+
};
|
|
617
|
+
device.getCurrentFirmwareVersionString = () => '4.15.0';
|
|
618
|
+
device.getCurrentDeviceType = () => EDeviceType.Pro;
|
|
619
|
+
device.getFeatures = jest.fn();
|
|
620
|
+
method.device = device as any;
|
|
621
|
+
|
|
622
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
623
|
+
errorCode: HardwareErrorCode.DeviceCheckPassphraseStateError,
|
|
624
|
+
});
|
|
625
|
+
expect(device.clearInternalState).toHaveBeenCalledTimes(1);
|
|
626
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBeUndefined();
|
|
627
|
+
});
|
|
628
|
+
|
|
487
629
|
test('opens the standard wallet without selecting a hidden wallet', async () => {
|
|
488
|
-
const typedCall = jest
|
|
630
|
+
const typedCall = jest
|
|
631
|
+
.fn()
|
|
632
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
633
|
+
.mockResolvedValueOnce({
|
|
634
|
+
message: {
|
|
635
|
+
btc_test_address: 'main-wallet-state',
|
|
636
|
+
session_id: 'main-wallet-session',
|
|
637
|
+
},
|
|
638
|
+
});
|
|
489
639
|
const promptPassphrase = jest.fn();
|
|
490
640
|
const method = new OpenWalletSession({
|
|
491
641
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
@@ -502,16 +652,26 @@ describe('openWalletSession', () => {
|
|
|
502
652
|
passphraseState: null,
|
|
503
653
|
resumed: false,
|
|
504
654
|
});
|
|
505
|
-
expect(typedCall).toHaveBeenCalledTimes(
|
|
655
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
506
656
|
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
507
657
|
eventless_wallet_session: true,
|
|
508
658
|
});
|
|
659
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
|
|
660
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
509
661
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
510
662
|
expect(device.passphraseState).toBeUndefined();
|
|
511
663
|
});
|
|
512
664
|
|
|
513
665
|
test('opens a locked Protocol V2 standard wallet after deviceId becomes available', async () => {
|
|
514
|
-
const typedCall = jest
|
|
666
|
+
const typedCall = jest
|
|
667
|
+
.fn()
|
|
668
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
669
|
+
.mockResolvedValueOnce({
|
|
670
|
+
message: {
|
|
671
|
+
btc_test_address: 'main-wallet-state',
|
|
672
|
+
session_id: 'main-wallet-session',
|
|
673
|
+
},
|
|
674
|
+
});
|
|
515
675
|
const method = new OpenWalletSession({
|
|
516
676
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
517
677
|
});
|
|
@@ -522,7 +682,7 @@ describe('openWalletSession', () => {
|
|
|
522
682
|
.fn()
|
|
523
683
|
.mockResolvedValueOnce({
|
|
524
684
|
identity: { deviceId: undefined },
|
|
525
|
-
status: { passphraseProtection: true },
|
|
685
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
526
686
|
})
|
|
527
687
|
.mockResolvedValueOnce({
|
|
528
688
|
identity: { deviceId: 'device-1' },
|
|
@@ -541,18 +701,18 @@ describe('openWalletSession', () => {
|
|
|
541
701
|
passphraseState: null,
|
|
542
702
|
resumed: false,
|
|
543
703
|
});
|
|
544
|
-
expect(device.unlockDevice).
|
|
704
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
|
|
545
705
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
546
706
|
});
|
|
547
707
|
|
|
548
|
-
test('
|
|
708
|
+
test('switches from Attach PIN to Main PIN before opening the standard wallet', async () => {
|
|
549
709
|
const typedCall = jest
|
|
550
710
|
.fn()
|
|
551
711
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
552
712
|
.mockResolvedValueOnce({
|
|
553
713
|
message: {
|
|
554
|
-
btc_test_address: '
|
|
555
|
-
session_id: '
|
|
714
|
+
btc_test_address: 'main-wallet-state',
|
|
715
|
+
session_id: 'main-wallet-session',
|
|
556
716
|
},
|
|
557
717
|
});
|
|
558
718
|
const method = new OpenWalletSession({
|
|
@@ -561,27 +721,32 @@ describe('openWalletSession', () => {
|
|
|
561
721
|
method.init();
|
|
562
722
|
const device = createDevice({ typedCall });
|
|
563
723
|
device.features.unlockedAttachPin = true;
|
|
724
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
725
|
+
device.features.unlockedAttachPin = false;
|
|
726
|
+
return Promise.resolve(device.features);
|
|
727
|
+
});
|
|
564
728
|
method.device = device as any;
|
|
565
729
|
|
|
566
|
-
await expect(method.run()).
|
|
567
|
-
|
|
730
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
731
|
+
walletType: 'standard',
|
|
732
|
+
passphraseState: null,
|
|
568
733
|
});
|
|
569
|
-
expect(device.
|
|
570
|
-
expect(
|
|
734
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
|
|
735
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
571
736
|
});
|
|
572
737
|
|
|
573
738
|
test('selects a hidden wallet without exposing the internal device session', async () => {
|
|
574
739
|
const typedCall = jest
|
|
575
740
|
.fn()
|
|
576
741
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
577
|
-
.mockResolvedValueOnce({ message: {
|
|
742
|
+
.mockResolvedValueOnce({ message: {} })
|
|
578
743
|
.mockResolvedValueOnce({
|
|
579
744
|
message: {
|
|
580
745
|
btc_test_address: 'hidden-state',
|
|
581
746
|
session_id: 'hidden-session',
|
|
582
747
|
},
|
|
583
748
|
});
|
|
584
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
749
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
585
750
|
const method = new OpenWalletSession({
|
|
586
751
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
587
752
|
});
|
|
@@ -598,7 +763,8 @@ describe('openWalletSession', () => {
|
|
|
598
763
|
resumed: false,
|
|
599
764
|
});
|
|
600
765
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
601
|
-
|
|
766
|
+
passphrase: 'host hidden wallet',
|
|
767
|
+
on_device: false,
|
|
602
768
|
});
|
|
603
769
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
604
770
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
@@ -606,15 +772,15 @@ describe('openWalletSession', () => {
|
|
|
606
772
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
|
|
607
773
|
});
|
|
608
774
|
|
|
609
|
-
test('
|
|
775
|
+
test('gets the prepared session after host passphrase entry', async () => {
|
|
610
776
|
const typedCall = jest
|
|
611
777
|
.fn()
|
|
612
778
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
613
|
-
.mockResolvedValueOnce({ message: {
|
|
779
|
+
.mockResolvedValueOnce({ message: {} })
|
|
614
780
|
.mockResolvedValueOnce({
|
|
615
781
|
message: { btc_test_address: 'device-state', session_id: 'device-session' },
|
|
616
782
|
});
|
|
617
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
783
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
618
784
|
const method = new OpenWalletSession({
|
|
619
785
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
620
786
|
});
|
|
@@ -626,16 +792,42 @@ describe('openWalletSession', () => {
|
|
|
626
792
|
passphraseState: 'device-state',
|
|
627
793
|
});
|
|
628
794
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
629
|
-
|
|
795
|
+
passphrase: 'host hidden wallet',
|
|
796
|
+
on_device: false,
|
|
630
797
|
});
|
|
631
798
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
632
799
|
});
|
|
633
800
|
|
|
801
|
+
test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
|
|
802
|
+
const typedCall = jest
|
|
803
|
+
.fn()
|
|
804
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
805
|
+
.mockResolvedValueOnce({ message: {} })
|
|
806
|
+
.mockResolvedValueOnce({
|
|
807
|
+
message: { btc_test_address: 'device-state', session_id: 'device-session' },
|
|
808
|
+
});
|
|
809
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
|
|
810
|
+
const method = new OpenWalletSession({
|
|
811
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
812
|
+
});
|
|
813
|
+
method.init();
|
|
814
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
815
|
+
|
|
816
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
817
|
+
walletType: 'hidden',
|
|
818
|
+
passphraseState: 'device-state',
|
|
819
|
+
});
|
|
820
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
821
|
+
on_device: true,
|
|
822
|
+
});
|
|
823
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
824
|
+
});
|
|
825
|
+
|
|
634
826
|
test('forwards a host passphrase to Pro2 firmware', async () => {
|
|
635
827
|
const typedCall = jest
|
|
636
828
|
.fn()
|
|
637
829
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
638
|
-
.mockResolvedValueOnce({ message: {
|
|
830
|
+
.mockResolvedValueOnce({ message: {} })
|
|
639
831
|
.mockResolvedValueOnce({
|
|
640
832
|
message: { btc_test_address: 'host-state', session_id: 'host-session' },
|
|
641
833
|
});
|
|
@@ -670,6 +862,52 @@ describe('openWalletSession', () => {
|
|
|
670
862
|
passphrase: 'host hidden wallet',
|
|
671
863
|
on_device: false,
|
|
672
864
|
});
|
|
865
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test('normalizes a Unicode Host passphrase before sending it to Pro2 firmware', async () => {
|
|
869
|
+
const typedCall = jest
|
|
870
|
+
.fn()
|
|
871
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
872
|
+
.mockResolvedValueOnce({ message: {} })
|
|
873
|
+
.mockResolvedValueOnce({
|
|
874
|
+
message: { btc_test_address: 'host-state', session_id: 'host-session' },
|
|
875
|
+
});
|
|
876
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'caf\u00e9' });
|
|
877
|
+
const method = new OpenWalletSession({
|
|
878
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
879
|
+
});
|
|
880
|
+
method.init();
|
|
881
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
882
|
+
|
|
883
|
+
await expect(method.run()).resolves.toMatchObject({ passphraseState: 'host-state' });
|
|
884
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
885
|
+
passphrase: 'cafe\u0301',
|
|
886
|
+
on_device: false,
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test('accepts a Host passphrase at the 50-byte firmware boundary', async () => {
|
|
891
|
+
const passphrase = 'a'.repeat(50);
|
|
892
|
+
const typedCall = jest
|
|
893
|
+
.fn()
|
|
894
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
895
|
+
.mockResolvedValueOnce({ message: {} })
|
|
896
|
+
.mockResolvedValueOnce({
|
|
897
|
+
message: { btc_test_address: 'host-state', session_id: 'host-session' },
|
|
898
|
+
});
|
|
899
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase });
|
|
900
|
+
const method = new OpenWalletSession({
|
|
901
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
902
|
+
});
|
|
903
|
+
method.init();
|
|
904
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
905
|
+
|
|
906
|
+
await expect(method.run()).resolves.toMatchObject({ passphraseState: 'host-state' });
|
|
907
|
+
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
908
|
+
passphrase,
|
|
909
|
+
on_device: false,
|
|
910
|
+
});
|
|
673
911
|
});
|
|
674
912
|
|
|
675
913
|
test('selects Attach PIN only when the device reports an existing binding', async () => {
|
|
@@ -705,6 +943,176 @@ describe('openWalletSession', () => {
|
|
|
705
943
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
706
944
|
});
|
|
707
945
|
|
|
946
|
+
test('uses the complete Attach PIN wire flow without a main PIN unlock', async () => {
|
|
947
|
+
const typedCall = jest
|
|
948
|
+
.fn()
|
|
949
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
950
|
+
.mockResolvedValueOnce({ message: { message: 'PIN verified' } })
|
|
951
|
+
.mockResolvedValueOnce({
|
|
952
|
+
message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
|
|
953
|
+
});
|
|
954
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
955
|
+
const method = new OpenWalletSession({
|
|
956
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
957
|
+
});
|
|
958
|
+
method.init();
|
|
959
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
960
|
+
device.features.attachToPinEnabled = true;
|
|
961
|
+
device.unlockDevice = Device.prototype.unlockDevice.bind(device);
|
|
962
|
+
method.device = device as any;
|
|
963
|
+
|
|
964
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
965
|
+
walletType: 'hidden',
|
|
966
|
+
passphraseState: 'attach-state',
|
|
967
|
+
});
|
|
968
|
+
expect(device.commands.typedCall.mock.calls).toEqual([
|
|
969
|
+
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
970
|
+
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.AttachToPin }],
|
|
971
|
+
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
972
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
973
|
+
]);
|
|
974
|
+
expect(device.commands.typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
975
|
+
type: DeviceSessionPinType.Main,
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test.each([
|
|
980
|
+
['no selection', {}],
|
|
981
|
+
['an empty Host passphrase', { passphrase: '' }],
|
|
982
|
+
['more than one selection', { passphrase: 'host hidden wallet', passphraseOnDevice: true }],
|
|
983
|
+
])('rejects %s before sending a wallet selection command', async (_name, response) => {
|
|
984
|
+
const typedCall = jest.fn().mockResolvedValueOnce({ message: { version: 2 } });
|
|
985
|
+
const promptPassphrase = jest.fn().mockResolvedValue(response);
|
|
986
|
+
const method = new OpenWalletSession({
|
|
987
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
988
|
+
});
|
|
989
|
+
method.init();
|
|
990
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
991
|
+
|
|
992
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
993
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
994
|
+
message: 'Wallet selection must contain exactly one passphrase access mode.',
|
|
995
|
+
});
|
|
996
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test('stops before sending a wallet command when the App cancels selection', async () => {
|
|
1000
|
+
const cancellation = new Error('Wallet selection cancelled');
|
|
1001
|
+
const typedCall = jest.fn().mockResolvedValueOnce({ message: { version: 2 } });
|
|
1002
|
+
const promptPassphrase = jest.fn().mockRejectedValue(cancellation);
|
|
1003
|
+
const method = new OpenWalletSession({
|
|
1004
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1005
|
+
});
|
|
1006
|
+
method.init();
|
|
1007
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
1008
|
+
|
|
1009
|
+
await expect(method.run()).rejects.toBe(cancellation);
|
|
1010
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
test.each(['User cancelled', 'Device disconnected'])(
|
|
1014
|
+
'does not issue a resume request after the wallet selection reports %s',
|
|
1015
|
+
async message => {
|
|
1016
|
+
const typedCall = jest
|
|
1017
|
+
.fn()
|
|
1018
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1019
|
+
.mockRejectedValueOnce(new Error(message));
|
|
1020
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1021
|
+
const method = new OpenWalletSession({
|
|
1022
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1023
|
+
});
|
|
1024
|
+
method.init();
|
|
1025
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
1026
|
+
|
|
1027
|
+
await expect(method.run()).rejects.toThrow(message);
|
|
1028
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1029
|
+
expect(typedCall).not.toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
1030
|
+
}
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
test.each([
|
|
1034
|
+
['a NUL byte', 'hidden\0wallet'],
|
|
1035
|
+
['more than 50 UTF-8 bytes', 'a'.repeat(51)],
|
|
1036
|
+
['more than 50 UTF-8 bytes after NFKD normalization', '\u00e9'.repeat(17)],
|
|
1037
|
+
['an unpaired UTF-16 surrogate', '\ud800'],
|
|
1038
|
+
])('rejects a Host passphrase containing %s', async (_name, passphrase) => {
|
|
1039
|
+
const typedCall = jest.fn().mockResolvedValueOnce({ message: { version: 2 } });
|
|
1040
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase });
|
|
1041
|
+
const method = new OpenWalletSession({
|
|
1042
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1043
|
+
});
|
|
1044
|
+
method.init();
|
|
1045
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
1046
|
+
|
|
1047
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
1048
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1049
|
+
message: 'Host passphrase must contain 1 to 50 valid UTF-8 bytes without NUL.',
|
|
1050
|
+
});
|
|
1051
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test('rejects an unavailable Attach PIN selection before asking the device for a PIN', async () => {
|
|
1055
|
+
const typedCall = jest.fn().mockResolvedValueOnce({ message: { version: 2 } });
|
|
1056
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
1057
|
+
const method = new OpenWalletSession({
|
|
1058
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1059
|
+
});
|
|
1060
|
+
method.init();
|
|
1061
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1062
|
+
device.features.attachToPinEnabled = false;
|
|
1063
|
+
method.device = device as any;
|
|
1064
|
+
|
|
1065
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
1066
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1067
|
+
message: 'Attach PIN wallet selection is unavailable on this device.',
|
|
1068
|
+
});
|
|
1069
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
1070
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test('does not unlock or retry when wallet preparation reports a late DeviceLocked', async () => {
|
|
1074
|
+
const lockedError = { errorCode: HardwareErrorCode.DeviceLocked };
|
|
1075
|
+
const typedCall = jest
|
|
1076
|
+
.fn()
|
|
1077
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1078
|
+
.mockRejectedValueOnce(lockedError)
|
|
1079
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1080
|
+
.mockResolvedValueOnce({
|
|
1081
|
+
message: { btc_test_address: 'hidden-state', session_id: 'hidden-session' },
|
|
1082
|
+
});
|
|
1083
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1084
|
+
const method = new OpenWalletSession({
|
|
1085
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1086
|
+
});
|
|
1087
|
+
method.init();
|
|
1088
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1089
|
+
method.device = device as any;
|
|
1090
|
+
|
|
1091
|
+
await expect(method.run()).rejects.toBe(lockedError);
|
|
1092
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
1093
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
test('does not fall back when wallet preparation is rejected', async () => {
|
|
1097
|
+
const invalidSessionError = { errorCode: HardwareErrorCode.WalletSessionInvalid };
|
|
1098
|
+
const typedCall = jest
|
|
1099
|
+
.fn()
|
|
1100
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1101
|
+
.mockRejectedValueOnce(invalidSessionError);
|
|
1102
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1103
|
+
const method = new OpenWalletSession({
|
|
1104
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1105
|
+
});
|
|
1106
|
+
method.init();
|
|
1107
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1108
|
+
method.device = device as any;
|
|
1109
|
+
|
|
1110
|
+
await expect(method.run()).rejects.toBe(invalidSessionError);
|
|
1111
|
+
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1112
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
1113
|
+
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
1114
|
+
});
|
|
1115
|
+
|
|
708
1116
|
test.each([
|
|
709
1117
|
{
|
|
710
1118
|
missingField: 'session_id',
|
|
@@ -720,9 +1128,9 @@ describe('openWalletSession', () => {
|
|
|
720
1128
|
const typedCall = jest
|
|
721
1129
|
.fn()
|
|
722
1130
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
723
|
-
.mockResolvedValueOnce({ message: {
|
|
1131
|
+
.mockResolvedValueOnce({ message: {} })
|
|
724
1132
|
.mockResolvedValueOnce({ message });
|
|
725
|
-
const promptPassphrase = jest.fn().mockResolvedValue({
|
|
1133
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
726
1134
|
const method = new OpenWalletSession({
|
|
727
1135
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
728
1136
|
});
|
|
@@ -732,7 +1140,7 @@ describe('openWalletSession', () => {
|
|
|
732
1140
|
|
|
733
1141
|
await expect(method.run()).rejects.toMatchObject({
|
|
734
1142
|
errorCode: HardwareErrorCode.RuntimeError,
|
|
735
|
-
message: '
|
|
1143
|
+
message: 'Device returned an incomplete DeviceSession response.',
|
|
736
1144
|
});
|
|
737
1145
|
expect(device.clearInternalState).toHaveBeenCalled();
|
|
738
1146
|
expect(device.updateInternalState).not.toHaveBeenCalled();
|
|
@@ -803,7 +1211,7 @@ describe('openWalletSession', () => {
|
|
|
803
1211
|
.fn()
|
|
804
1212
|
.mockResolvedValueOnce({
|
|
805
1213
|
identity: { deviceId: undefined },
|
|
806
|
-
status: { passphraseProtection: true },
|
|
1214
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
807
1215
|
})
|
|
808
1216
|
.mockResolvedValueOnce({
|
|
809
1217
|
identity: { deviceId: 'device-1' },
|
|
@@ -879,13 +1287,23 @@ describe('openWalletSession', () => {
|
|
|
879
1287
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBeUndefined();
|
|
880
1288
|
});
|
|
881
1289
|
|
|
882
|
-
test('
|
|
883
|
-
const typedCall = jest
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1290
|
+
test('reselects the expected hidden wallet when a cached Protocol V2 session is invalid', async () => {
|
|
1291
|
+
const typedCall = jest
|
|
1292
|
+
.fn()
|
|
1293
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1294
|
+
.mockRejectedValueOnce(
|
|
1295
|
+
Object.assign(new Error('Invalid session'), {
|
|
1296
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
1297
|
+
})
|
|
1298
|
+
)
|
|
1299
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1300
|
+
.mockResolvedValueOnce({
|
|
1301
|
+
message: {
|
|
1302
|
+
btc_test_address: 'hidden-state',
|
|
1303
|
+
session_id: 'renewed-session',
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
1306
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
889
1307
|
const method = new OpenWalletSession({
|
|
890
1308
|
payload: {
|
|
891
1309
|
method: 'openWalletSession',
|
|
@@ -899,15 +1317,32 @@ describe('openWalletSession', () => {
|
|
|
899
1317
|
deviceWalletSessionStore.set('device-1', 'hidden-state', 'expired-session');
|
|
900
1318
|
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
901
1319
|
|
|
902
|
-
await expect(method.run()).
|
|
903
|
-
|
|
1320
|
+
await expect(method.run()).resolves.toEqual({
|
|
1321
|
+
protocol: 'V2',
|
|
1322
|
+
walletType: 'hidden',
|
|
1323
|
+
deviceId: 'device-1',
|
|
1324
|
+
passphraseState: 'hidden-state',
|
|
1325
|
+
resumed: false,
|
|
904
1326
|
});
|
|
905
|
-
expect(
|
|
1327
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1328
|
+
session_id: 'expired-session',
|
|
1329
|
+
});
|
|
1330
|
+
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1331
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
|
|
906
1332
|
});
|
|
907
1333
|
|
|
908
|
-
test('
|
|
909
|
-
const typedCall = jest
|
|
910
|
-
|
|
1334
|
+
test('selects the expected hidden wallet when Protocol V2 has no cached session', async () => {
|
|
1335
|
+
const typedCall = jest
|
|
1336
|
+
.fn()
|
|
1337
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1338
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1339
|
+
.mockResolvedValueOnce({
|
|
1340
|
+
message: {
|
|
1341
|
+
btc_test_address: 'hidden-state',
|
|
1342
|
+
session_id: 'new-session',
|
|
1343
|
+
},
|
|
1344
|
+
});
|
|
1345
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
911
1346
|
const method = new OpenWalletSession({
|
|
912
1347
|
payload: {
|
|
913
1348
|
method: 'openWalletSession',
|
|
@@ -920,10 +1355,14 @@ describe('openWalletSession', () => {
|
|
|
920
1355
|
method.init();
|
|
921
1356
|
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
922
1357
|
|
|
923
|
-
await expect(method.run()).
|
|
924
|
-
|
|
1358
|
+
await expect(method.run()).resolves.toEqual({
|
|
1359
|
+
protocol: 'V2',
|
|
1360
|
+
walletType: 'hidden',
|
|
1361
|
+
deviceId: 'device-1',
|
|
1362
|
+
passphraseState: 'hidden-state',
|
|
1363
|
+
resumed: false,
|
|
925
1364
|
});
|
|
926
|
-
expect(
|
|
927
|
-
expect(
|
|
1365
|
+
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1366
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
|
|
928
1367
|
});
|
|
929
1368
|
});
|