@onekeyfe/hd-core 1.2.0-alpha.21 → 1.2.0-alpha.23
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 +85 -0
- package/__tests__/connectSettings.test.ts +47 -5
- package/__tests__/device-identity.test.ts +24 -2
- package/__tests__/device-settings.test.ts +2 -2
- package/__tests__/device-wallet-session-store.test.ts +152 -7
- package/__tests__/firmware-update/check-all-firmware-release.test.ts +86 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +119 -0
- package/__tests__/firmware-update/firmware-host-binding.test.ts +70 -0
- package/__tests__/firmware-update/firmware-update-capabilities.test.ts +13 -0
- package/__tests__/firmware-update/firmware-update-plan.test.ts +326 -0
- package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +213 -0
- package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +39 -30
- package/__tests__/firmware-update/firmware-upload-source.test.ts +70 -0
- package/__tests__/logBlockEvent.test.ts +17 -0
- package/__tests__/open-wallet-session.test.ts +311 -12
- package/__tests__/pro2Wallpaper.test.ts +7 -18
- package/__tests__/protocol-v2-unlock-policy.test.ts +45 -0
- package/__tests__/protocol-v2.test.ts +685 -177
- package/__tests__/public-device-state-api.test.ts +69 -62
- package/__tests__/public-pro2-api-boundary.test.ts +8 -34
- package/dist/api/BaseMethod.d.ts +2 -1
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
- package/dist/api/ClearSessionCache.d.ts.map +1 -1
- package/dist/api/FirmwareUpdate.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV2.d.ts +11 -0
- package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV3.d.ts +7 -3
- package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +29 -10
- 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/conflux/ConfluxGetAddress.d.ts +1 -0
- package/dist/api/conflux/ConfluxGetAddress.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessage.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessage.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignMessageCIP23.d.ts.map +1 -1
- package/dist/api/conflux/ConfluxSignTransaction.d.ts +1 -0
- package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
- package/dist/api/device/DeviceUnlock.d.ts +1 -1
- package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts +5 -1
- package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
- package/dist/api/device/DeviceWipe.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareArtifactSource.d.ts +25 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts +6 -0
- package/dist/api/firmware/FirmwareHostBinding.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +7 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +3 -0
- package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts +23 -0
- package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +30 -0
- package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/api/firmware/getBinary.d.ts +7 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/updateBootloader.d.ts +2 -0
- package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
- package/dist/api/firmware/uploadFirmware.d.ts +9 -1
- package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +0 -17
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/nexa/NexaSignTransaction.d.ts +1 -0
- package/dist/api/nexa/NexaSignTransaction.d.ts.map +1 -1
- package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -0
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -2
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.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/events/device.d.ts +1 -0
- package/dist/events/device.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +1 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +273 -230
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3060 -1676
- package/dist/inject.d.ts.map +1 -1
- package/dist/lowLevelInject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts +4 -0
- package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -0
- package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/topLevelInject.d.ts.map +1 -1
- package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -0
- package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
- package/dist/types/api/deviceUnlock.d.ts +2 -2
- package/dist/types/api/deviceUnlock.d.ts.map +1 -1
- package/dist/types/api/deviceUpdateBootloader.d.ts +6 -0
- package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +4 -1
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +69 -0
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdateCapabilities.d.ts +9 -0
- package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts +27 -0
- package/dist/types/api/firmwareUpdatePlan.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +42 -0
- package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +1 -0
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +10 -19
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/openWalletSession.d.ts +1 -0
- package/dist/types/api/openWalletSession.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +3 -76
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +4 -1
- package/dist/types/api/sessionCache.d.ts.map +1 -1
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +8 -3
- package/src/api/CheckAllFirmwareRelease.ts +23 -3
- package/src/api/ClearSessionCache.ts +4 -0
- package/src/api/FirmwareUpdate.ts +8 -1
- package/src/api/FirmwareUpdateV2.ts +292 -120
- package/src/api/FirmwareUpdateV3.ts +256 -55
- package/src/api/FirmwareUpdateV4.ts +821 -331
- package/src/api/GetPassphraseState.ts +11 -5
- package/src/api/OpenWalletSession.ts +74 -25
- package/src/api/UploadPortfolio.ts +1 -1
- package/src/api/conflux/ConfluxGetAddress.ts +2 -0
- package/src/api/conflux/ConfluxSignMessage.ts +2 -0
- package/src/api/conflux/ConfluxSignMessageCIP23.ts +2 -0
- package/src/api/conflux/ConfluxSignTransaction.ts +2 -0
- package/src/api/device/DeviceChangePin.ts +1 -1
- package/src/api/device/DeviceUnlock.ts +1 -2
- package/src/api/device/DeviceUpdateBootloader.ts +122 -6
- package/src/api/device/DeviceWipe.ts +3 -1
- package/src/api/firmware/FirmwareArtifactSource.ts +278 -0
- package/src/api/firmware/FirmwareHostBinding.ts +100 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +50 -0
- package/src/api/firmware/FirmwareUpdateCapabilities.ts +9 -0
- package/src/api/firmware/FirmwareUpdatePlan.ts +621 -0
- package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +392 -0
- package/src/api/firmware/updateBootloader.ts +19 -4
- package/src/api/firmware/uploadFirmware.ts +140 -22
- package/src/api/helpers/protocolV2FileWrite.ts +6 -0
- package/src/api/index.ts +0 -18
- package/src/api/nexa/NexaSignTransaction.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
- package/src/core/index.ts +1 -0
- package/src/data/messages/messages-protocol-v2.json +35 -1
- package/src/data-manager/DataManager.ts +33 -33
- package/src/data-manager/connectSettings.ts +11 -0
- package/src/device/Device.ts +80 -67
- package/src/device/DeviceCommands.ts +76 -159
- package/src/device/DeviceStateMapper.ts +18 -1
- package/src/device/DeviceStateProjector.ts +7 -1
- package/src/events/device.ts +1 -0
- package/src/events/logBlockEvent.ts +1 -14
- package/src/events/ui-request.ts +1 -0
- package/src/index.ts +5 -0
- package/src/inject.ts +22 -25
- package/src/lowLevelInject.ts +5 -1
- package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +2 -3
- package/src/protocols/protocol-v2/unlockPolicy.ts +105 -0
- package/src/protocols/protocol-v2/unlockRetry.ts +1 -3
- package/src/protocols/protocol-v2/walletSession.ts +86 -9
- package/src/topLevelInject.ts +5 -1
- package/src/types/api/checkAllFirmwareRelease.ts +3 -0
- package/src/types/api/deviceUnlock.ts +2 -5
- package/src/types/api/deviceUpdateBootloader.ts +6 -0
- package/src/types/api/export.ts +18 -0
- package/src/types/api/firmwareUpdate.ts +76 -0
- package/src/types/api/firmwareUpdateCapabilities.ts +9 -0
- package/src/types/api/firmwareUpdatePlan.ts +36 -0
- package/src/types/api/firmwareUpdatePreparedPlan.ts +53 -0
- package/src/types/api/getPassphraseState.ts +4 -3
- package/src/types/api/index.ts +14 -34
- package/src/types/api/openWalletSession.ts +2 -0
- package/src/types/api/protocolV2.ts +3 -156
- package/src/types/api/sessionCache.ts +9 -4
- package/src/types/settings.ts +13 -0
- package/src/utils/deviceFeaturesUtils.ts +5 -1
- package/src/utils/deviceInfoUtils.ts +3 -16
|
@@ -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';
|
|
@@ -28,6 +29,7 @@ const createDevice = ({
|
|
|
28
29
|
commands: { typedCall, promptPassphrase },
|
|
29
30
|
getDeviceState: jest.fn().mockResolvedValue({
|
|
30
31
|
identity: { deviceId: 'device-1' },
|
|
32
|
+
status: { passphraseProtection },
|
|
31
33
|
}),
|
|
32
34
|
getCurrentDeviceId: () => 'device-1',
|
|
33
35
|
getCurrentPassphraseProtection: () => passphraseProtection,
|
|
@@ -58,15 +60,42 @@ describe('openWalletSession', () => {
|
|
|
58
60
|
deviceWalletSessionStore.clear();
|
|
59
61
|
});
|
|
60
62
|
|
|
61
|
-
test('keeps getPassphraseState
|
|
63
|
+
test('keeps the legacy getPassphraseState App flow working on Protocol V2', async () => {
|
|
64
|
+
const typedCall = jest
|
|
65
|
+
.fn()
|
|
66
|
+
.mockResolvedValueOnce({ message: { eventless_wallet_session: true } })
|
|
67
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
68
|
+
.mockResolvedValueOnce({
|
|
69
|
+
message: {
|
|
70
|
+
btc_test_address: 'pro2-wallet-state',
|
|
71
|
+
session_id: 'pro2-wallet-session',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'legacy app secret' });
|
|
62
75
|
const method = new GetPassphraseState({
|
|
63
|
-
payload: {
|
|
76
|
+
payload: {
|
|
77
|
+
method: 'getPassphraseState',
|
|
78
|
+
connectId: 'connect-id',
|
|
79
|
+
initSession: true,
|
|
80
|
+
useEmptyPassphrase: false,
|
|
81
|
+
},
|
|
64
82
|
});
|
|
65
|
-
method.device = createDevice() as any;
|
|
83
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
66
84
|
|
|
67
|
-
await expect(method.run()).
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
await expect(method.run()).resolves.toBe('pro2-wallet-state');
|
|
86
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining({ deviceOnly: true }),
|
|
88
|
+
expect.anything()
|
|
89
|
+
);
|
|
90
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
91
|
+
2,
|
|
92
|
+
'DeviceSessionAskPassphrase',
|
|
93
|
+
'Success',
|
|
94
|
+
{},
|
|
95
|
+
{ timeoutMs: 120_000 }
|
|
96
|
+
);
|
|
97
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
98
|
+
expect(JSON.stringify(typedCall.mock.calls)).not.toContain('legacy app secret');
|
|
70
99
|
});
|
|
71
100
|
|
|
72
101
|
test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
|
|
@@ -145,6 +174,7 @@ describe('openWalletSession', () => {
|
|
|
145
174
|
walletType: 'standard',
|
|
146
175
|
deviceId: 'device-1',
|
|
147
176
|
passphraseState: null,
|
|
177
|
+
sessionId: 'main-wallet-session',
|
|
148
178
|
resumed: false,
|
|
149
179
|
});
|
|
150
180
|
expect(method.payload.useEmptyPassphrase).toBe(true);
|
|
@@ -173,6 +203,57 @@ describe('openWalletSession', () => {
|
|
|
173
203
|
walletType: 'hidden',
|
|
174
204
|
passphraseState: 'hidden-state',
|
|
175
205
|
});
|
|
206
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({ deviceOnly: true }),
|
|
208
|
+
expect.anything()
|
|
209
|
+
);
|
|
210
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
211
|
+
2,
|
|
212
|
+
'DeviceSessionAskPassphrase',
|
|
213
|
+
'Success',
|
|
214
|
+
{},
|
|
215
|
+
{ timeoutMs: 120_000 }
|
|
216
|
+
);
|
|
217
|
+
expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
|
|
218
|
+
expect(JSON.stringify(typedCall.mock.calls)).not.toContain('hidden secret');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('classifies the selected Pro2 wallet from the post-unlock device state', async () => {
|
|
222
|
+
const typedCall = jest
|
|
223
|
+
.fn()
|
|
224
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
225
|
+
.mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
|
|
226
|
+
.mockResolvedValueOnce({
|
|
227
|
+
message: {
|
|
228
|
+
btc_test_address: 'hidden-state-after-unlock',
|
|
229
|
+
session_id: 'hidden-session-after-unlock',
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
|
|
233
|
+
const method = new OpenWalletSession({
|
|
234
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
235
|
+
});
|
|
236
|
+
method.init();
|
|
237
|
+
const device = createDevice({ passphraseProtection: false, typedCall, promptPassphrase });
|
|
238
|
+
device.features.unlocked = false;
|
|
239
|
+
device.getCurrentPassphraseProtection = () => device.features.passphraseProtection;
|
|
240
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
241
|
+
device.features.unlocked = true;
|
|
242
|
+
device.features.passphraseProtection = true;
|
|
243
|
+
return Promise.resolve(device.features);
|
|
244
|
+
});
|
|
245
|
+
method.device = device as any;
|
|
246
|
+
|
|
247
|
+
await expect(method.run()).resolves.toEqual({
|
|
248
|
+
protocol: 'V2',
|
|
249
|
+
walletType: 'hidden',
|
|
250
|
+
deviceId: 'device-1',
|
|
251
|
+
passphraseState: 'hidden-state-after-unlock',
|
|
252
|
+
sessionId: 'hidden-session-after-unlock',
|
|
253
|
+
resumed: false,
|
|
254
|
+
});
|
|
255
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
|
|
256
|
+
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
176
257
|
});
|
|
177
258
|
|
|
178
259
|
test('uses legacy initSession to replace a hidden-wallet session on Protocol V2', async () => {
|
|
@@ -201,10 +282,22 @@ describe('openWalletSession', () => {
|
|
|
201
282
|
walletType: 'hidden',
|
|
202
283
|
deviceId: 'device-1',
|
|
203
284
|
passphraseState: 'new-hidden-state',
|
|
285
|
+
sessionId: 'new-hidden-session',
|
|
204
286
|
resumed: false,
|
|
205
287
|
});
|
|
288
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
289
|
+
eventless_wallet_session: true,
|
|
290
|
+
});
|
|
291
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
292
|
+
'DeviceSessionAskPassphrase',
|
|
293
|
+
'Success',
|
|
294
|
+
{},
|
|
295
|
+
{
|
|
296
|
+
timeoutMs: 120_000,
|
|
297
|
+
}
|
|
298
|
+
);
|
|
206
299
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
207
|
-
expect(promptPassphrase).
|
|
300
|
+
expect(promptPassphrase).toHaveBeenCalled();
|
|
208
301
|
expect(deviceWalletSessionStore.get('device-1', 'stale-hidden-state')).toBeUndefined();
|
|
209
302
|
expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
|
|
210
303
|
});
|
|
@@ -266,6 +359,7 @@ describe('openWalletSession', () => {
|
|
|
266
359
|
walletType: 'hidden',
|
|
267
360
|
deviceId: 'device-1',
|
|
268
361
|
passphraseState: 'hidden-state',
|
|
362
|
+
sessionId: 'renewed-session',
|
|
269
363
|
resumed: true,
|
|
270
364
|
});
|
|
271
365
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
@@ -378,7 +472,7 @@ describe('openWalletSession', () => {
|
|
|
378
472
|
});
|
|
379
473
|
|
|
380
474
|
test('opens the standard wallet without selecting a hidden wallet', async () => {
|
|
381
|
-
const typedCall = jest.fn();
|
|
475
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { version: 2 } });
|
|
382
476
|
const promptPassphrase = jest.fn();
|
|
383
477
|
const method = new OpenWalletSession({
|
|
384
478
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
@@ -395,17 +489,61 @@ describe('openWalletSession', () => {
|
|
|
395
489
|
passphraseState: null,
|
|
396
490
|
resumed: false,
|
|
397
491
|
});
|
|
398
|
-
expect(typedCall).
|
|
492
|
+
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
493
|
+
expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
|
|
494
|
+
eventless_wallet_session: true,
|
|
495
|
+
});
|
|
399
496
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
400
497
|
expect(device.passphraseState).toBeUndefined();
|
|
401
498
|
});
|
|
402
499
|
|
|
500
|
+
test('opens a locked Protocol V2 standard wallet after deviceId becomes available', async () => {
|
|
501
|
+
const typedCall = jest.fn().mockResolvedValue({ message: { version: 2 } });
|
|
502
|
+
const method = new OpenWalletSession({
|
|
503
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
504
|
+
});
|
|
505
|
+
method.init();
|
|
506
|
+
const device = createDevice({ typedCall });
|
|
507
|
+
device.features.unlocked = false;
|
|
508
|
+
device.getDeviceState = jest
|
|
509
|
+
.fn()
|
|
510
|
+
.mockResolvedValueOnce({
|
|
511
|
+
identity: { deviceId: undefined },
|
|
512
|
+
status: { passphraseProtection: true },
|
|
513
|
+
})
|
|
514
|
+
.mockResolvedValueOnce({
|
|
515
|
+
identity: { deviceId: 'device-1' },
|
|
516
|
+
status: { passphraseProtection: true },
|
|
517
|
+
});
|
|
518
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
519
|
+
device.features.unlocked = true;
|
|
520
|
+
return Promise.resolve(device.features);
|
|
521
|
+
});
|
|
522
|
+
method.device = device as any;
|
|
523
|
+
|
|
524
|
+
await expect(method.run()).resolves.toEqual({
|
|
525
|
+
protocol: 'V2',
|
|
526
|
+
walletType: 'standard',
|
|
527
|
+
deviceId: 'device-1',
|
|
528
|
+
passphraseState: null,
|
|
529
|
+
resumed: false,
|
|
530
|
+
});
|
|
531
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
532
|
+
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
533
|
+
});
|
|
534
|
+
|
|
403
535
|
test('rejects a standard-wallet request when Protocol V2 is unlocked by Attach PIN', async () => {
|
|
536
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
537
|
+
message: {
|
|
538
|
+
btc_test_address: 'attach-wallet-state',
|
|
539
|
+
session_id: 'attach-wallet-session',
|
|
540
|
+
},
|
|
541
|
+
});
|
|
404
542
|
const method = new OpenWalletSession({
|
|
405
543
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
406
544
|
});
|
|
407
545
|
method.init();
|
|
408
|
-
const device = createDevice();
|
|
546
|
+
const device = createDevice({ typedCall });
|
|
409
547
|
device.features.unlockedAttachPin = true;
|
|
410
548
|
method.device = device as any;
|
|
411
549
|
|
|
@@ -416,7 +554,7 @@ describe('openWalletSession', () => {
|
|
|
416
554
|
expect(device.clearInternalState).toHaveBeenCalled();
|
|
417
555
|
});
|
|
418
556
|
|
|
419
|
-
test('selects a hidden wallet and
|
|
557
|
+
test('selects a hidden wallet and returns the CLI compatibility session', async () => {
|
|
420
558
|
const typedCall = jest.fn().mockResolvedValue({
|
|
421
559
|
message: {
|
|
422
560
|
btc_test_address: 'hidden-state',
|
|
@@ -437,14 +575,74 @@ describe('openWalletSession', () => {
|
|
|
437
575
|
walletType: 'hidden',
|
|
438
576
|
deviceId: 'device-1',
|
|
439
577
|
passphraseState: 'hidden-state',
|
|
578
|
+
sessionId: 'hidden-session',
|
|
440
579
|
resumed: false,
|
|
441
580
|
});
|
|
581
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
582
|
+
'DeviceSessionAskPassphrase',
|
|
583
|
+
'Success',
|
|
584
|
+
{},
|
|
585
|
+
{
|
|
586
|
+
timeoutMs: 120_000,
|
|
587
|
+
}
|
|
588
|
+
);
|
|
442
589
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
443
|
-
expect(promptPassphrase).
|
|
590
|
+
expect(promptPassphrase).toHaveBeenCalled();
|
|
444
591
|
expect(device.passphraseState).toBeUndefined();
|
|
445
592
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
|
|
446
593
|
});
|
|
447
594
|
|
|
595
|
+
test('selects on-device passphrase entry before getting the prepared session', async () => {
|
|
596
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
597
|
+
message: { btc_test_address: 'device-state', session_id: 'device-session' },
|
|
598
|
+
});
|
|
599
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
|
|
600
|
+
const method = new OpenWalletSession({
|
|
601
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
602
|
+
});
|
|
603
|
+
method.init();
|
|
604
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
605
|
+
|
|
606
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
607
|
+
walletType: 'hidden',
|
|
608
|
+
passphraseState: 'device-state',
|
|
609
|
+
});
|
|
610
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
611
|
+
'DeviceSessionAskPassphrase',
|
|
612
|
+
'Success',
|
|
613
|
+
{},
|
|
614
|
+
{
|
|
615
|
+
timeoutMs: 120_000,
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test('selects Attach PIN only when the device reports an existing binding', async () => {
|
|
622
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
623
|
+
message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
|
|
624
|
+
});
|
|
625
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
626
|
+
const method = new OpenWalletSession({
|
|
627
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
628
|
+
});
|
|
629
|
+
method.init();
|
|
630
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
631
|
+
device.features.attachToPinEnabled = true;
|
|
632
|
+
method.device = device as any;
|
|
633
|
+
|
|
634
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
635
|
+
walletType: 'hidden',
|
|
636
|
+
passphraseState: 'attach-state',
|
|
637
|
+
});
|
|
638
|
+
expect(promptPassphrase).toHaveBeenCalledWith(
|
|
639
|
+
expect.objectContaining({ existsAttachPinUser: true, deviceOnly: true }),
|
|
640
|
+
{ cancelDeviceOnReject: false }
|
|
641
|
+
);
|
|
642
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
|
|
643
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
644
|
+
});
|
|
645
|
+
|
|
448
646
|
test.each([
|
|
449
647
|
{
|
|
450
648
|
missingField: 'session_id',
|
|
@@ -501,6 +699,7 @@ describe('openWalletSession', () => {
|
|
|
501
699
|
walletType: 'hidden',
|
|
502
700
|
deviceId: 'device-1',
|
|
503
701
|
passphraseState: 'hidden-state',
|
|
702
|
+
sessionId: 'renewed-session',
|
|
504
703
|
resumed: true,
|
|
505
704
|
});
|
|
506
705
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
@@ -509,6 +708,106 @@ describe('openWalletSession', () => {
|
|
|
509
708
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
510
709
|
});
|
|
511
710
|
|
|
711
|
+
test('validates a locked Protocol V2 resume binding after deviceId refresh', async () => {
|
|
712
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
713
|
+
message: {
|
|
714
|
+
btc_test_address: 'hidden-state',
|
|
715
|
+
session_id: 'renewed-session',
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
const method = new OpenWalletSession({
|
|
719
|
+
payload: {
|
|
720
|
+
method: 'openWalletSession',
|
|
721
|
+
connectId: 'connect-id',
|
|
722
|
+
mode: 'resume-hidden',
|
|
723
|
+
deviceId: 'device-1',
|
|
724
|
+
passphraseState: 'hidden-state',
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
method.init();
|
|
728
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'known-session');
|
|
729
|
+
const device = createDevice({ typedCall });
|
|
730
|
+
device.features.unlocked = false;
|
|
731
|
+
device.getDeviceState = jest
|
|
732
|
+
.fn()
|
|
733
|
+
.mockResolvedValueOnce({
|
|
734
|
+
identity: { deviceId: undefined },
|
|
735
|
+
status: { passphraseProtection: true },
|
|
736
|
+
})
|
|
737
|
+
.mockResolvedValueOnce({
|
|
738
|
+
identity: { deviceId: 'device-1' },
|
|
739
|
+
status: { passphraseProtection: true },
|
|
740
|
+
});
|
|
741
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
742
|
+
device.features.unlocked = true;
|
|
743
|
+
return Promise.resolve(device.features);
|
|
744
|
+
});
|
|
745
|
+
method.device = device as any;
|
|
746
|
+
|
|
747
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
748
|
+
protocol: 'V2',
|
|
749
|
+
walletType: 'hidden',
|
|
750
|
+
deviceId: 'device-1',
|
|
751
|
+
passphraseState: 'hidden-state',
|
|
752
|
+
resumed: true,
|
|
753
|
+
});
|
|
754
|
+
expect(device.unlockDevice).toHaveBeenCalledTimes(1);
|
|
755
|
+
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
756
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
757
|
+
session_id: 'known-session',
|
|
758
|
+
});
|
|
759
|
+
const sessionGetCall = typedCall.mock.calls.findIndex(
|
|
760
|
+
([requestName]) => requestName === 'DeviceSessionGet'
|
|
761
|
+
);
|
|
762
|
+
expect(device.getDeviceState.mock.invocationCallOrder[1]).toBeLessThan(
|
|
763
|
+
typedCall.mock.invocationCallOrder[sessionGetCall]
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
test('rejects a Protocol V2 resume when the refreshed deviceId does not match', async () => {
|
|
768
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
769
|
+
message: {
|
|
770
|
+
btc_test_address: 'hidden-state',
|
|
771
|
+
session_id: 'renewed-session',
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
const method = new OpenWalletSession({
|
|
775
|
+
payload: {
|
|
776
|
+
method: 'openWalletSession',
|
|
777
|
+
connectId: 'connect-id',
|
|
778
|
+
mode: 'resume-hidden',
|
|
779
|
+
deviceId: 'device-1',
|
|
780
|
+
passphraseState: 'hidden-state',
|
|
781
|
+
},
|
|
782
|
+
});
|
|
783
|
+
method.init();
|
|
784
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'known-session');
|
|
785
|
+
const device = createDevice({ typedCall });
|
|
786
|
+
device.features.unlocked = false;
|
|
787
|
+
device.getDeviceState = jest
|
|
788
|
+
.fn()
|
|
789
|
+
.mockResolvedValueOnce({
|
|
790
|
+
identity: { deviceId: undefined },
|
|
791
|
+
status: { passphraseProtection: true },
|
|
792
|
+
})
|
|
793
|
+
.mockResolvedValueOnce({
|
|
794
|
+
identity: { deviceId: 'other-device' },
|
|
795
|
+
status: { passphraseProtection: true },
|
|
796
|
+
});
|
|
797
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
798
|
+
device.features.unlocked = true;
|
|
799
|
+
return Promise.resolve(device.features);
|
|
800
|
+
});
|
|
801
|
+
method.device = device as any;
|
|
802
|
+
|
|
803
|
+
await expect(method.run()).rejects.toMatchObject({
|
|
804
|
+
errorCode: HardwareErrorCode.DeviceCheckDeviceIdError,
|
|
805
|
+
});
|
|
806
|
+
expect(typedCall).not.toHaveBeenCalled();
|
|
807
|
+
expect(device.clearInternalState).not.toHaveBeenCalled();
|
|
808
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBeUndefined();
|
|
809
|
+
});
|
|
810
|
+
|
|
512
811
|
test('does not fall back to wallet selection when a cached resume is invalid', async () => {
|
|
513
812
|
const typedCall = jest.fn().mockRejectedValue(
|
|
514
813
|
Object.assign(new Error('Invalid session'), {
|
|
@@ -10,18 +10,7 @@ describe('encodePro2Wallpaper', () => {
|
|
|
10
10
|
|
|
11
11
|
expect(result.colorFormat).toBe('RGB565');
|
|
12
12
|
expect(Array.from(result.data.slice(0, 12))).toEqual([
|
|
13
|
-
0x19,
|
|
14
|
-
0x12,
|
|
15
|
-
0,
|
|
16
|
-
0,
|
|
17
|
-
2,
|
|
18
|
-
0,
|
|
19
|
-
1,
|
|
20
|
-
0,
|
|
21
|
-
4,
|
|
22
|
-
0,
|
|
23
|
-
0,
|
|
24
|
-
0,
|
|
13
|
+
0x19, 0x12, 0, 0, 2, 0, 1, 0, 4, 0, 0, 0,
|
|
25
14
|
]);
|
|
26
15
|
expect(Array.from(result.data.slice(12))).toEqual([0x00, 0xf0, 0xc0, 0x07]);
|
|
27
16
|
});
|
|
@@ -40,11 +29,11 @@ describe('encodePro2Wallpaper', () => {
|
|
|
40
29
|
});
|
|
41
30
|
|
|
42
31
|
test('rejects invalid dimensions and RGBA byte length', () => {
|
|
43
|
-
expect(() =>
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
expect(() =>
|
|
47
|
-
|
|
48
|
-
)
|
|
32
|
+
expect(() => encodePro2Wallpaper({ width: 0, height: 1, rgba: new Uint8Array() })).toThrow(
|
|
33
|
+
'width'
|
|
34
|
+
);
|
|
35
|
+
expect(() => encodePro2Wallpaper({ width: 2, height: 1, rgba: new Uint8Array(4) })).toThrow(
|
|
36
|
+
'8'
|
|
37
|
+
);
|
|
49
38
|
});
|
|
50
39
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as publicMethods from '../src/api';
|
|
2
|
+
import {
|
|
3
|
+
PROTOCOL_V2_RETRY_ON_LOCKED_METHODS,
|
|
4
|
+
getProtocolV2UnlockPolicy,
|
|
5
|
+
} from '../src/protocols/protocol-v2/unlockPolicy';
|
|
6
|
+
|
|
7
|
+
jest.mock('../src/data/config', () => ({
|
|
8
|
+
DEFAULT_DOMAIN: 'https://example.com/',
|
|
9
|
+
getSDKVersion: () => '0.0.0-test',
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('Protocol V2 public method unlock policy', () => {
|
|
13
|
+
test('classifies the complete public method table with an explicit replay allowlist', () => {
|
|
14
|
+
const publicMethodNames = Object.keys(publicMethods);
|
|
15
|
+
const retryMethodNames = new Set<string>(PROTOCOL_V2_RETRY_ON_LOCKED_METHODS);
|
|
16
|
+
|
|
17
|
+
expect(publicMethodNames.length).toBeGreaterThan(100);
|
|
18
|
+
expect(new Set(PROTOCOL_V2_RETRY_ON_LOCKED_METHODS).size).toBe(
|
|
19
|
+
PROTOCOL_V2_RETRY_ON_LOCKED_METHODS.length
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
for (const methodName of PROTOCOL_V2_RETRY_ON_LOCKED_METHODS) {
|
|
23
|
+
expect(publicMethods).toHaveProperty(methodName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const methodName of publicMethodNames) {
|
|
27
|
+
expect(getProtocolV2UnlockPolicy(methodName)).toBe(
|
|
28
|
+
retryMethodNames.has(methodName) ? 'retry-on-locked' : 'none'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test.each([
|
|
34
|
+
'deviceUnlock',
|
|
35
|
+
'deviceLock',
|
|
36
|
+
'deviceWipe',
|
|
37
|
+
'deviceChangePin',
|
|
38
|
+
'deviceSettings',
|
|
39
|
+
'firmwareUpdateV4',
|
|
40
|
+
'deviceUploadWallpaper',
|
|
41
|
+
'uploadPortfolio',
|
|
42
|
+
])('does not make the state-changing method %s replayable by default', methodName => {
|
|
43
|
+
expect(getProtocolV2UnlockPolicy(methodName)).toBe('none');
|
|
44
|
+
});
|
|
45
|
+
});
|