@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.11
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__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
- package/__tests__/DeviceCommands.test.ts +13 -0
- package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
- package/__tests__/device-lifecycle-events.test.ts +86 -0
- package/__tests__/deviceUploadNft.test.ts +68 -0
- package/__tests__/evmSignTypedData.test.ts +42 -0
- package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
- package/__tests__/logBlockEvent.test.ts +45 -122
- package/__tests__/open-wallet-session.test.ts +781 -69
- package/__tests__/pro2HostAssetPackage.test.ts +165 -0
- package/__tests__/protocol-v2-resources.test.ts +7 -4
- package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
- package/__tests__/protocol-v2.test.ts +925 -97
- package/__tests__/protocolV2FileWrite.test.ts +40 -0
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/OpenWalletSession.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +2 -2
- package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/core/RequestQueue.d.ts +2 -0
- package/dist/core/RequestQueue.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceConnector.d.ts +1 -1
- package/dist/device/DeviceConnector.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +3 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.js +2511 -1538
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
- package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
- package/dist/types/settings.d.ts +2 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
- 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/pro2HostAssetPackage.d.ts +8 -0
- package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +474 -83
- package/src/api/OpenWalletSession.ts +39 -12
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
- package/src/api/evm/EVMSignTypedData.ts +1 -0
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
- package/src/api/helpers/protocolV2FileWrite.ts +11 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
- package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
- package/src/api/protocol-v2/helpers.ts +8 -0
- package/src/core/RequestQueue.ts +16 -1
- package/src/core/index.ts +164 -26
- package/src/data/messages/messages-protocol-v2.json +1589 -1375
- package/src/device/Device.ts +3 -1
- package/src/device/DeviceConnector.ts +7 -3
- package/src/events/logBlockEvent.ts +6 -75
- package/src/events/ui-request.ts +3 -0
- package/src/protocols/protocol-v2/resources.ts +9 -1
- package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
- package/src/protocols/protocol-v2/walletSession.ts +223 -56
- package/src/types/settings.ts +4 -2
- package/src/utils/deviceFeaturesUtils.ts +4 -0
- package/src/utils/pro2HostAssetPackage.ts +400 -0
|
@@ -11,6 +11,13 @@ import {
|
|
|
11
11
|
getProtocolV2WalletSession,
|
|
12
12
|
} from '../src/protocols/protocol-v2/walletSession';
|
|
13
13
|
|
|
14
|
+
const STANDARD_SEED_DOMAINS = [DeviceSessionSeedDomain.SeedDomain_Standard];
|
|
15
|
+
const CARDANO_SEED_DOMAINS = [
|
|
16
|
+
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
17
|
+
DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
18
|
+
];
|
|
19
|
+
const standardSessionGet = {};
|
|
20
|
+
|
|
14
21
|
jest.mock('../src/data/config', () => ({
|
|
15
22
|
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
16
23
|
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
@@ -19,12 +26,16 @@ jest.mock('../src/data/config', () => ({
|
|
|
19
26
|
const createDevice = ({
|
|
20
27
|
passphraseProtection = true,
|
|
21
28
|
unlockedAttachPin = false,
|
|
29
|
+
refreshedUnlocked = true,
|
|
30
|
+
refreshedPassphraseProtection = passphraseProtection,
|
|
22
31
|
refreshedUnlockedAttachPin = unlockedAttachPin,
|
|
23
32
|
typedCall = jest.fn(),
|
|
24
33
|
promptPassphrase = jest.fn(),
|
|
25
34
|
}: {
|
|
26
35
|
passphraseProtection?: boolean;
|
|
27
36
|
unlockedAttachPin?: boolean;
|
|
37
|
+
refreshedUnlocked?: boolean;
|
|
38
|
+
refreshedPassphraseProtection?: boolean;
|
|
28
39
|
refreshedUnlockedAttachPin?: boolean;
|
|
29
40
|
typedCall?: jest.Mock;
|
|
30
41
|
promptPassphrase?: jest.Mock;
|
|
@@ -43,11 +54,11 @@ const createDevice = ({
|
|
|
43
54
|
return {
|
|
44
55
|
message: {
|
|
45
56
|
device_id: 'device-1',
|
|
46
|
-
unlocked:
|
|
57
|
+
unlocked: refreshedUnlocked,
|
|
47
58
|
attach_to_pin_enabled: unlockedAttachPin,
|
|
48
59
|
unlocked_attach_pin: refreshedUnlockedAttachPin,
|
|
49
60
|
unlocked_by_attach_to_pin: refreshedUnlockedAttachPin,
|
|
50
|
-
passphrase_enabled:
|
|
61
|
+
passphrase_enabled: refreshedPassphraseProtection,
|
|
51
62
|
},
|
|
52
63
|
};
|
|
53
64
|
}
|
|
@@ -88,6 +99,8 @@ const createDevice = ({
|
|
|
88
99
|
unlockDevice: jest.fn(),
|
|
89
100
|
updateProtocolV2Status: jest.fn((status: Record<string, unknown>) => {
|
|
90
101
|
device.features.unlocked = status.unlocked ?? device.features.unlocked;
|
|
102
|
+
device.features.passphraseProtection =
|
|
103
|
+
status.passphrase_enabled ?? device.features.passphraseProtection;
|
|
91
104
|
device.features.attachToPinEnabled =
|
|
92
105
|
status.attach_to_pin_enabled ?? device.features.attachToPinEnabled;
|
|
93
106
|
device.features.unlockedAttachPin =
|
|
@@ -142,13 +155,12 @@ describe('openWalletSession', () => {
|
|
|
142
155
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
143
156
|
passphrase: '',
|
|
144
157
|
on_device: false,
|
|
158
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
145
159
|
});
|
|
146
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
147
|
-
seed_domains: [],
|
|
148
|
-
});
|
|
160
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
149
161
|
});
|
|
150
162
|
|
|
151
|
-
test('opens the standard wallet
|
|
163
|
+
test('opens the already-unlocked standard wallet without repeating Main PIN when passphrase is disabled', async () => {
|
|
152
164
|
const typedCall = jest.fn((request: string) => {
|
|
153
165
|
if (request === 'ProtocolInfoRequest') {
|
|
154
166
|
return { message: { version: 2 } };
|
|
@@ -167,14 +179,139 @@ describe('openWalletSession', () => {
|
|
|
167
179
|
|
|
168
180
|
await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
|
|
169
181
|
|
|
182
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
170
183
|
expect(typedCall).not.toHaveBeenCalledWith(
|
|
171
184
|
'DeviceSessionAskPassphrase',
|
|
172
185
|
'Success',
|
|
173
186
|
expect.anything()
|
|
174
187
|
);
|
|
175
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
176
|
-
|
|
188
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('refreshes stale wallet status before accepting an only-Main-PIN session', async () => {
|
|
192
|
+
let attachPinSelected = true;
|
|
193
|
+
const typedCall = jest.fn((request: string) => {
|
|
194
|
+
if (request === 'ProtocolInfoRequest') {
|
|
195
|
+
return { message: { version: 2 } };
|
|
196
|
+
}
|
|
197
|
+
if (request === 'DeviceSessionGet') {
|
|
198
|
+
return {
|
|
199
|
+
message: {
|
|
200
|
+
btc_test_address: 'standard-state',
|
|
201
|
+
session_id: 'standard-session',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
206
|
+
return { message: {} };
|
|
207
|
+
}
|
|
208
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
209
|
+
});
|
|
210
|
+
const device = createDevice({
|
|
211
|
+
passphraseProtection: false,
|
|
212
|
+
unlockedAttachPin: false,
|
|
213
|
+
typedCall,
|
|
214
|
+
});
|
|
215
|
+
device.commands.typedCall.mockImplementation((request: string, ...args: unknown[]) => {
|
|
216
|
+
if (request === 'DeviceStatusGet') {
|
|
217
|
+
return {
|
|
218
|
+
message: {
|
|
219
|
+
device_id: 'device-1',
|
|
220
|
+
unlocked: true,
|
|
221
|
+
attach_to_pin_enabled: true,
|
|
222
|
+
unlocked_attach_pin: attachPinSelected,
|
|
223
|
+
unlocked_by_attach_to_pin: attachPinSelected,
|
|
224
|
+
passphrase_enabled: true,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return typedCall(request, ...args);
|
|
229
|
+
});
|
|
230
|
+
device.unlockDevice.mockImplementation(() => {
|
|
231
|
+
attachPinSelected = false;
|
|
232
|
+
device.features.unlockedAttachPin = false;
|
|
233
|
+
return Promise.resolve(device.features);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await expect(
|
|
237
|
+
getProtocolV2WalletSession(device as any, { onlyMainPin: true })
|
|
238
|
+
).resolves.toMatchObject({
|
|
239
|
+
unlockedAttachPin: false,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith('DeviceStatusGet', 'DeviceStatus', {});
|
|
243
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
244
|
+
source: 'wallet-session-coordinator',
|
|
245
|
+
reason: 'open-wallet',
|
|
246
|
+
deviceOnly: true,
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('still requires Main PIN when a cached standard session resolves to another wallet', async () => {
|
|
251
|
+
let sessionGetCount = 0;
|
|
252
|
+
const typedCall = jest.fn((request: string) => {
|
|
253
|
+
if (request === 'ProtocolInfoRequest') {
|
|
254
|
+
return { message: { version: 2 } };
|
|
255
|
+
}
|
|
256
|
+
if (request === 'DeviceSessionGet') {
|
|
257
|
+
sessionGetCount += 1;
|
|
258
|
+
return {
|
|
259
|
+
message: {
|
|
260
|
+
btc_test_address:
|
|
261
|
+
sessionGetCount === 1 ? 'unexpected-wallet-state' : 'cached-standard-state',
|
|
262
|
+
session_id:
|
|
263
|
+
sessionGetCount === 1 ? 'unexpected-wallet-session' : 'cached-standard-session',
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
268
|
+
});
|
|
269
|
+
const device = createDevice({ passphraseProtection: false, typedCall });
|
|
270
|
+
device.getStandardInternalState = jest.fn(() => ({
|
|
271
|
+
passphraseState: 'cached-standard-state',
|
|
272
|
+
sessionId: 'cached-standard-session',
|
|
273
|
+
}));
|
|
274
|
+
device.clearStandardInternalState = jest.fn();
|
|
275
|
+
|
|
276
|
+
await expect(
|
|
277
|
+
getProtocolV2WalletSession(device as any, { onlyMainPin: true })
|
|
278
|
+
).resolves.toMatchObject({
|
|
279
|
+
passphraseState: 'cached-standard-state',
|
|
280
|
+
newSession: 'cached-standard-session',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main, {
|
|
284
|
+
source: 'wallet-session-coordinator',
|
|
285
|
+
reason: 'session-recovery',
|
|
286
|
+
deviceOnly: true,
|
|
287
|
+
});
|
|
288
|
+
expect(sessionGetCount).toBe(2);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('reuses a Main PIN selected by the current preflight when passphrase is disabled', async () => {
|
|
292
|
+
const typedCall = jest.fn((request: string) => {
|
|
293
|
+
if (request === 'ProtocolInfoRequest') {
|
|
294
|
+
return { message: { version: 2 } };
|
|
295
|
+
}
|
|
296
|
+
if (request === 'DeviceSessionGet') {
|
|
297
|
+
return {
|
|
298
|
+
message: {
|
|
299
|
+
btc_test_address: 'standard-state',
|
|
300
|
+
session_id: 'standard-session',
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
305
|
+
});
|
|
306
|
+
const device = createDevice({ passphraseProtection: false, typedCall });
|
|
307
|
+
|
|
308
|
+
await getProtocolV2WalletSession(device as any, {
|
|
309
|
+
onlyMainPin: true,
|
|
310
|
+
mainPinSelected: true,
|
|
177
311
|
});
|
|
312
|
+
|
|
313
|
+
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
314
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
178
315
|
});
|
|
179
316
|
|
|
180
317
|
test('does not treat an Attach PIN DeviceStatus as the Main wallet', async () => {
|
|
@@ -195,8 +332,7 @@ describe('openWalletSession', () => {
|
|
|
195
332
|
}
|
|
196
333
|
throw new Error(`Unexpected request: ${request}`);
|
|
197
334
|
});
|
|
198
|
-
const device = createDevice({ typedCall });
|
|
199
|
-
device.features.unlockedAttachPin = true;
|
|
335
|
+
const device = createDevice({ refreshedUnlockedAttachPin: true, typedCall });
|
|
200
336
|
|
|
201
337
|
await getProtocolV2WalletSession(device as any, { onlyMainPin: true });
|
|
202
338
|
|
|
@@ -301,10 +437,14 @@ describe('openWalletSession', () => {
|
|
|
301
437
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
302
438
|
passphrase: 'host hidden wallet',
|
|
303
439
|
on_device: false,
|
|
440
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
304
441
|
});
|
|
305
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
306
|
-
|
|
307
|
-
|
|
442
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
443
|
+
3,
|
|
444
|
+
'DeviceSessionGet',
|
|
445
|
+
'DeviceSession',
|
|
446
|
+
standardSessionGet
|
|
447
|
+
);
|
|
308
448
|
});
|
|
309
449
|
|
|
310
450
|
test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
|
|
@@ -421,10 +561,14 @@ describe('openWalletSession', () => {
|
|
|
421
561
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
422
562
|
passphrase: 'host hidden wallet',
|
|
423
563
|
on_device: false,
|
|
564
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
424
565
|
});
|
|
425
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
426
|
-
|
|
427
|
-
|
|
566
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
567
|
+
3,
|
|
568
|
+
'DeviceSessionGet',
|
|
569
|
+
'DeviceSession',
|
|
570
|
+
standardSessionGet
|
|
571
|
+
);
|
|
428
572
|
});
|
|
429
573
|
|
|
430
574
|
test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
|
|
@@ -698,9 +842,11 @@ describe('openWalletSession', () => {
|
|
|
698
842
|
});
|
|
699
843
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
700
844
|
expect(device.unlockDevice).not.toHaveBeenCalled();
|
|
701
|
-
expect(device.commands.typedCall).toHaveBeenCalledWith(
|
|
702
|
-
|
|
703
|
-
|
|
845
|
+
expect(device.commands.typedCall).toHaveBeenCalledWith(
|
|
846
|
+
'DeviceSessionGet',
|
|
847
|
+
'DeviceSession',
|
|
848
|
+
standardSessionGet
|
|
849
|
+
);
|
|
704
850
|
expect(device.commands.typedCall).not.toHaveBeenCalledWith(
|
|
705
851
|
'DeviceSessionAskPassphrase',
|
|
706
852
|
'Success',
|
|
@@ -796,10 +942,9 @@ describe('openWalletSession', () => {
|
|
|
796
942
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
797
943
|
passphrase: 'host hidden wallet',
|
|
798
944
|
on_device: false,
|
|
945
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
799
946
|
});
|
|
800
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
801
|
-
seed_domains: [],
|
|
802
|
-
});
|
|
947
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
803
948
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
804
949
|
expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
|
|
805
950
|
});
|
|
@@ -871,7 +1016,7 @@ describe('openWalletSession', () => {
|
|
|
871
1016
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
872
1017
|
session_id: 'known-session',
|
|
873
1018
|
btc_test_address: 'hidden-state',
|
|
874
|
-
|
|
1019
|
+
...standardSessionGet,
|
|
875
1020
|
});
|
|
876
1021
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
877
1022
|
});
|
|
@@ -1054,10 +1199,9 @@ describe('openWalletSession', () => {
|
|
|
1054
1199
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1055
1200
|
passphrase: '',
|
|
1056
1201
|
on_device: false,
|
|
1202
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1057
1203
|
});
|
|
1058
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
1059
|
-
seed_domains: [],
|
|
1060
|
-
});
|
|
1204
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
1061
1205
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1062
1206
|
expect(device.passphraseState).toBeUndefined();
|
|
1063
1207
|
});
|
|
@@ -1077,7 +1221,7 @@ describe('openWalletSession', () => {
|
|
|
1077
1221
|
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
|
|
1078
1222
|
});
|
|
1079
1223
|
method.init();
|
|
1080
|
-
const device = createDevice({ typedCall });
|
|
1224
|
+
const device = createDevice({ refreshedUnlocked: false, typedCall });
|
|
1081
1225
|
device.features.unlocked = false;
|
|
1082
1226
|
device.getDeviceState = jest
|
|
1083
1227
|
.fn()
|
|
@@ -1114,7 +1258,70 @@ describe('openWalletSession', () => {
|
|
|
1114
1258
|
expect(device.getDeviceState).toHaveBeenCalledTimes(2);
|
|
1115
1259
|
});
|
|
1116
1260
|
|
|
1261
|
+
test('does not restrict a locked hidden-wallet selection to the Main PIN', async () => {
|
|
1262
|
+
const typedCall = jest
|
|
1263
|
+
.fn()
|
|
1264
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1265
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1266
|
+
.mockResolvedValueOnce({
|
|
1267
|
+
message: {
|
|
1268
|
+
btc_test_address: 'hidden-state',
|
|
1269
|
+
session_id: 'hidden-session',
|
|
1270
|
+
},
|
|
1271
|
+
});
|
|
1272
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1273
|
+
const method = new OpenWalletSession({
|
|
1274
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1275
|
+
});
|
|
1276
|
+
method.init();
|
|
1277
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1278
|
+
device.features.unlocked = false;
|
|
1279
|
+
device.getDeviceState = jest
|
|
1280
|
+
.fn()
|
|
1281
|
+
.mockResolvedValueOnce({
|
|
1282
|
+
identity: { deviceId: undefined },
|
|
1283
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
1284
|
+
})
|
|
1285
|
+
.mockResolvedValueOnce({
|
|
1286
|
+
identity: { deviceId: 'device-1' },
|
|
1287
|
+
status: {
|
|
1288
|
+
unlocked: true,
|
|
1289
|
+
passphraseProtection: true,
|
|
1290
|
+
unlockedAttachPin: false,
|
|
1291
|
+
},
|
|
1292
|
+
})
|
|
1293
|
+
.mockResolvedValueOnce({
|
|
1294
|
+
identity: { deviceId: 'device-1' },
|
|
1295
|
+
status: {
|
|
1296
|
+
unlocked: true,
|
|
1297
|
+
passphraseProtection: true,
|
|
1298
|
+
unlockedAttachPin: false,
|
|
1299
|
+
},
|
|
1300
|
+
});
|
|
1301
|
+
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
1302
|
+
device.features.unlocked = true;
|
|
1303
|
+
return Promise.resolve(device.features);
|
|
1304
|
+
});
|
|
1305
|
+
method.device = device as any;
|
|
1306
|
+
|
|
1307
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
1308
|
+
walletType: 'hidden',
|
|
1309
|
+
passphraseState: 'hidden-state',
|
|
1310
|
+
});
|
|
1311
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Any, {
|
|
1312
|
+
source: 'unlock-coordinator',
|
|
1313
|
+
reason: 'device-locked',
|
|
1314
|
+
deviceOnly: true,
|
|
1315
|
+
method: 'openWalletSession',
|
|
1316
|
+
});
|
|
1317
|
+
expect(device.unlockDevice).not.toHaveBeenCalledWith(
|
|
1318
|
+
DeviceSessionPinType.Main,
|
|
1319
|
+
expect.anything()
|
|
1320
|
+
);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1117
1323
|
test('switches from Attach PIN to Main PIN before opening the standard wallet', async () => {
|
|
1324
|
+
let attachPinSelected = true;
|
|
1118
1325
|
const typedCall = jest
|
|
1119
1326
|
.fn()
|
|
1120
1327
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
@@ -1131,6 +1338,21 @@ describe('openWalletSession', () => {
|
|
|
1131
1338
|
method.init();
|
|
1132
1339
|
const device = createDevice({ typedCall });
|
|
1133
1340
|
device.features.unlockedAttachPin = true;
|
|
1341
|
+
device.commands.typedCall.mockImplementation((request: string, ...args: unknown[]) => {
|
|
1342
|
+
if (request === 'DeviceStatusGet') {
|
|
1343
|
+
return {
|
|
1344
|
+
message: {
|
|
1345
|
+
device_id: 'device-1',
|
|
1346
|
+
unlocked: true,
|
|
1347
|
+
attach_to_pin_enabled: true,
|
|
1348
|
+
unlocked_attach_pin: attachPinSelected,
|
|
1349
|
+
unlocked_by_attach_to_pin: attachPinSelected,
|
|
1350
|
+
passphrase_enabled: true,
|
|
1351
|
+
},
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
return typedCall(request, ...args);
|
|
1355
|
+
});
|
|
1134
1356
|
device.getDeviceState = jest
|
|
1135
1357
|
.fn()
|
|
1136
1358
|
.mockResolvedValueOnce({
|
|
@@ -1150,6 +1372,7 @@ describe('openWalletSession', () => {
|
|
|
1150
1372
|
},
|
|
1151
1373
|
});
|
|
1152
1374
|
device.unlockDevice = jest.fn().mockImplementation(() => {
|
|
1375
|
+
attachPinSelected = false;
|
|
1153
1376
|
device.features.unlockedAttachPin = false;
|
|
1154
1377
|
return Promise.resolve(device.features);
|
|
1155
1378
|
});
|
|
@@ -1164,9 +1387,7 @@ describe('openWalletSession', () => {
|
|
|
1164
1387
|
reason: 'open-wallet',
|
|
1165
1388
|
deviceOnly: true,
|
|
1166
1389
|
});
|
|
1167
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
1168
|
-
seed_domains: [],
|
|
1169
|
-
});
|
|
1390
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
1170
1391
|
});
|
|
1171
1392
|
|
|
1172
1393
|
test('selects a hidden wallet without exposing the internal device session', async () => {
|
|
@@ -1199,15 +1420,45 @@ describe('openWalletSession', () => {
|
|
|
1199
1420
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1200
1421
|
passphrase: 'host hidden wallet',
|
|
1201
1422
|
on_device: false,
|
|
1423
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1202
1424
|
});
|
|
1203
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
1204
|
-
seed_domains: [],
|
|
1205
|
-
});
|
|
1425
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
1206
1426
|
expect(promptPassphrase).toHaveBeenCalled();
|
|
1207
1427
|
expect(device.passphraseState).toBeUndefined();
|
|
1208
1428
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
|
|
1209
1429
|
});
|
|
1210
1430
|
|
|
1431
|
+
test('reuses the fresh unlock and wallet-selection statuses', async () => {
|
|
1432
|
+
const typedCall = jest
|
|
1433
|
+
.fn()
|
|
1434
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1435
|
+
.mockResolvedValueOnce({ message: {} })
|
|
1436
|
+
.mockResolvedValueOnce({
|
|
1437
|
+
message: {
|
|
1438
|
+
btc_test_address: 'hidden-state',
|
|
1439
|
+
session_id: 'hidden-session',
|
|
1440
|
+
},
|
|
1441
|
+
});
|
|
1442
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1443
|
+
const method = new OpenWalletSession({
|
|
1444
|
+
payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
|
|
1445
|
+
});
|
|
1446
|
+
method.init();
|
|
1447
|
+
method.protocolV2UnlockContext = {
|
|
1448
|
+
preflightCompleted: true,
|
|
1449
|
+
preflightStatusRefreshed: true,
|
|
1450
|
+
};
|
|
1451
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
1452
|
+
method.device = device as any;
|
|
1453
|
+
|
|
1454
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
1455
|
+
walletType: 'hidden',
|
|
1456
|
+
passphraseState: 'hidden-state',
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
expect(device.getDeviceState.mock.calls).toEqual([[], []]);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1211
1462
|
test('gets the prepared session after host passphrase entry', async () => {
|
|
1212
1463
|
const typedCall = jest
|
|
1213
1464
|
.fn()
|
|
@@ -1230,10 +1481,9 @@ describe('openWalletSession', () => {
|
|
|
1230
1481
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
1231
1482
|
passphrase: 'host hidden wallet',
|
|
1232
1483
|
on_device: false,
|
|
1484
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1233
1485
|
});
|
|
1234
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
1235
|
-
seed_domains: [],
|
|
1236
|
-
});
|
|
1486
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
1237
1487
|
});
|
|
1238
1488
|
|
|
1239
1489
|
test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
|
|
@@ -1278,10 +1528,14 @@ describe('openWalletSession', () => {
|
|
|
1278
1528
|
});
|
|
1279
1529
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
1280
1530
|
on_device: true,
|
|
1531
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1281
1532
|
});
|
|
1282
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1283
|
-
|
|
1284
|
-
|
|
1533
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1534
|
+
3,
|
|
1535
|
+
'DeviceSessionGet',
|
|
1536
|
+
'DeviceSession',
|
|
1537
|
+
standardSessionGet
|
|
1538
|
+
);
|
|
1285
1539
|
expect(device.createProtocolV2UiPhaseMetadata).toHaveBeenNthCalledWith(
|
|
1286
1540
|
2,
|
|
1287
1541
|
'passphrase-on-device',
|
|
@@ -1332,10 +1586,14 @@ describe('openWalletSession', () => {
|
|
|
1332
1586
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
1333
1587
|
passphrase: 'host hidden wallet',
|
|
1334
1588
|
on_device: false,
|
|
1589
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1335
1590
|
});
|
|
1336
|
-
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1337
|
-
|
|
1338
|
-
|
|
1591
|
+
expect(typedCall).toHaveBeenNthCalledWith(
|
|
1592
|
+
3,
|
|
1593
|
+
'DeviceSessionGet',
|
|
1594
|
+
'DeviceSession',
|
|
1595
|
+
standardSessionGet
|
|
1596
|
+
);
|
|
1339
1597
|
});
|
|
1340
1598
|
|
|
1341
1599
|
test('normalizes a Unicode Host passphrase before sending it to Pro2 firmware', async () => {
|
|
@@ -1357,6 +1615,7 @@ describe('openWalletSession', () => {
|
|
|
1357
1615
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
1358
1616
|
passphrase: 'cafe\u0301',
|
|
1359
1617
|
on_device: false,
|
|
1618
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1360
1619
|
});
|
|
1361
1620
|
});
|
|
1362
1621
|
|
|
@@ -1380,6 +1639,7 @@ describe('openWalletSession', () => {
|
|
|
1380
1639
|
expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
|
|
1381
1640
|
passphrase,
|
|
1382
1641
|
on_device: false,
|
|
1642
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
1383
1643
|
});
|
|
1384
1644
|
});
|
|
1385
1645
|
|
|
@@ -1415,9 +1675,7 @@ describe('openWalletSession', () => {
|
|
|
1415
1675
|
expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin, {
|
|
1416
1676
|
emitUiEvent: false,
|
|
1417
1677
|
});
|
|
1418
|
-
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession',
|
|
1419
|
-
seed_domains: [],
|
|
1420
|
-
});
|
|
1678
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
1421
1679
|
});
|
|
1422
1680
|
|
|
1423
1681
|
test('uses the complete Attach PIN wire flow without a main PIN unlock', async () => {
|
|
@@ -1446,7 +1704,7 @@ describe('openWalletSession', () => {
|
|
|
1446
1704
|
['ProtocolInfoRequest', 'ProtocolInfo', { eventless_wallet_session: true }],
|
|
1447
1705
|
['DeviceSessionAskPin', 'Success', { type: DeviceSessionPinType.AttachToPin }],
|
|
1448
1706
|
['DeviceStatusGet', 'DeviceStatus', {}],
|
|
1449
|
-
['DeviceSessionGet', 'DeviceSession',
|
|
1707
|
+
['DeviceSessionGet', 'DeviceSession', standardSessionGet],
|
|
1450
1708
|
]);
|
|
1451
1709
|
expect(device.commands.typedCall).not.toHaveBeenCalledWith('DeviceSessionAskPin', 'Success', {
|
|
1452
1710
|
type: DeviceSessionPinType.Main,
|
|
@@ -1658,7 +1916,7 @@ describe('openWalletSession', () => {
|
|
|
1658
1916
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1659
1917
|
session_id: 'known-session',
|
|
1660
1918
|
btc_test_address: 'hidden-state',
|
|
1661
|
-
|
|
1919
|
+
...standardSessionGet,
|
|
1662
1920
|
});
|
|
1663
1921
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1664
1922
|
});
|
|
@@ -1718,7 +1976,7 @@ describe('openWalletSession', () => {
|
|
|
1718
1976
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1719
1977
|
session_id: 'known-session',
|
|
1720
1978
|
btc_test_address: 'hidden-state',
|
|
1721
|
-
|
|
1979
|
+
...standardSessionGet,
|
|
1722
1980
|
});
|
|
1723
1981
|
const sessionGetCall = typedCall.mock.calls.findIndex(
|
|
1724
1982
|
([requestName]) => requestName === 'DeviceSessionGet'
|
|
@@ -1816,23 +2074,28 @@ describe('openWalletSession', () => {
|
|
|
1816
2074
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
1817
2075
|
session_id: 'expired-session',
|
|
1818
2076
|
btc_test_address: 'hidden-state',
|
|
1819
|
-
|
|
2077
|
+
...standardSessionGet,
|
|
1820
2078
|
});
|
|
1821
2079
|
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
1822
2080
|
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
|
|
1823
2081
|
});
|
|
1824
2082
|
|
|
1825
|
-
test('
|
|
2083
|
+
test('recovers the current Attach PIN wallet when the cached session is invalid', async () => {
|
|
1826
2084
|
const typedCall = jest
|
|
1827
2085
|
.fn()
|
|
1828
2086
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
2087
|
+
.mockRejectedValueOnce(
|
|
2088
|
+
Object.assign(new Error('Invalid session'), {
|
|
2089
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
2090
|
+
})
|
|
2091
|
+
)
|
|
1829
2092
|
.mockResolvedValueOnce({
|
|
1830
2093
|
message: {
|
|
1831
2094
|
btc_test_address: 'hidden-state',
|
|
1832
|
-
session_id: '
|
|
2095
|
+
session_id: 'renewed-session',
|
|
1833
2096
|
},
|
|
1834
2097
|
});
|
|
1835
|
-
const promptPassphrase = jest.fn()
|
|
2098
|
+
const promptPassphrase = jest.fn();
|
|
1836
2099
|
const method = new OpenWalletSession({
|
|
1837
2100
|
payload: {
|
|
1838
2101
|
method: 'openWalletSession',
|
|
@@ -1843,7 +2106,9 @@ describe('openWalletSession', () => {
|
|
|
1843
2106
|
},
|
|
1844
2107
|
});
|
|
1845
2108
|
method.init();
|
|
1846
|
-
|
|
2109
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'expired-session');
|
|
2110
|
+
const device = createDevice({ unlockedAttachPin: true, typedCall, promptPassphrase });
|
|
2111
|
+
method.device = device as any;
|
|
1847
2112
|
|
|
1848
2113
|
await expect(method.run()).resolves.toEqual({
|
|
1849
2114
|
protocol: 'V2',
|
|
@@ -1852,48 +2117,495 @@ describe('openWalletSession', () => {
|
|
|
1852
2117
|
passphraseState: 'hidden-state',
|
|
1853
2118
|
resumed: false,
|
|
1854
2119
|
});
|
|
1855
|
-
expect(typedCall
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2120
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toEqual([
|
|
2121
|
+
[
|
|
2122
|
+
'DeviceSessionGet',
|
|
2123
|
+
'DeviceSession',
|
|
2124
|
+
{
|
|
2125
|
+
session_id: 'expired-session',
|
|
2126
|
+
btc_test_address: 'hidden-state',
|
|
2127
|
+
},
|
|
2128
|
+
],
|
|
2129
|
+
['DeviceSessionGet', 'DeviceSession', {}],
|
|
2130
|
+
]);
|
|
1859
2131
|
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
1860
|
-
expect(
|
|
2132
|
+
expect(device.lockDevice).not.toHaveBeenCalled();
|
|
2133
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
|
|
1861
2134
|
});
|
|
1862
2135
|
|
|
1863
|
-
test
|
|
2136
|
+
test('selects the expected hidden wallet when Protocol V2 has no cached session', async () => {
|
|
1864
2137
|
const typedCall = jest
|
|
1865
2138
|
.fn()
|
|
1866
2139
|
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
1867
|
-
.mockResolvedValueOnce({ message: {} })
|
|
1868
2140
|
.mockResolvedValueOnce({
|
|
1869
2141
|
message: {
|
|
1870
2142
|
btc_test_address: 'hidden-state',
|
|
1871
2143
|
session_id: 'new-session',
|
|
1872
2144
|
},
|
|
1873
2145
|
});
|
|
2146
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
|
|
1874
2147
|
const method = new OpenWalletSession({
|
|
1875
2148
|
payload: {
|
|
1876
2149
|
method: 'openWalletSession',
|
|
1877
2150
|
connectId: 'connect-id',
|
|
1878
|
-
mode: '
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2151
|
+
mode: 'resume-hidden',
|
|
2152
|
+
deviceId: 'device-1',
|
|
2153
|
+
passphraseState: 'hidden-state',
|
|
2154
|
+
},
|
|
2155
|
+
});
|
|
1882
2156
|
method.init();
|
|
1883
|
-
method.device = createDevice({
|
|
2157
|
+
method.device = createDevice({ typedCall, promptPassphrase }) as any;
|
|
2158
|
+
|
|
2159
|
+
await expect(method.run()).resolves.toEqual({
|
|
2160
|
+
protocol: 'V2',
|
|
2161
|
+
walletType: 'hidden',
|
|
2162
|
+
deviceId: 'device-1',
|
|
2163
|
+
passphraseState: 'hidden-state',
|
|
2164
|
+
resumed: false,
|
|
2165
|
+
});
|
|
2166
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2167
|
+
btc_test_address: 'hidden-state',
|
|
2168
|
+
...standardSessionGet,
|
|
2169
|
+
});
|
|
2170
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2171
|
+
expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
test.each([false, true])(
|
|
2175
|
+
'openWalletSession does not thread CommonParams.deriveCardano: %s',
|
|
2176
|
+
async deriveCardano => {
|
|
2177
|
+
const typedCall = jest
|
|
2178
|
+
.fn()
|
|
2179
|
+
.mockResolvedValueOnce({ message: { version: 2 } })
|
|
2180
|
+
.mockResolvedValueOnce({ message: {} })
|
|
2181
|
+
.mockResolvedValueOnce({
|
|
2182
|
+
message: {
|
|
2183
|
+
btc_test_address: 'hidden-state',
|
|
2184
|
+
session_id: 'new-session',
|
|
2185
|
+
},
|
|
2186
|
+
});
|
|
2187
|
+
const method = new OpenWalletSession({
|
|
2188
|
+
payload: {
|
|
2189
|
+
method: 'openWalletSession',
|
|
2190
|
+
connectId: 'connect-id',
|
|
2191
|
+
mode: 'select-hidden',
|
|
2192
|
+
deriveCardano,
|
|
2193
|
+
},
|
|
2194
|
+
});
|
|
2195
|
+
method.init();
|
|
2196
|
+
method.device = createDevice({
|
|
2197
|
+
typedCall,
|
|
2198
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
2199
|
+
}) as any;
|
|
2200
|
+
|
|
2201
|
+
await expect(method.run()).resolves.toMatchObject({
|
|
2202
|
+
walletType: 'hidden',
|
|
2203
|
+
passphraseState: 'hidden-state',
|
|
2204
|
+
});
|
|
2205
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2206
|
+
passphrase: 'host hidden wallet',
|
|
2207
|
+
on_device: false,
|
|
2208
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2209
|
+
});
|
|
2210
|
+
expect(typedCall).toHaveBeenCalledWith(
|
|
2211
|
+
'DeviceSessionGet',
|
|
2212
|
+
'DeviceSession',
|
|
2213
|
+
standardSessionGet
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
|
|
2218
|
+
test('requests Cardano seed domains when a V2 session rebuild has Cardano intent', async () => {
|
|
2219
|
+
const typedCall = jest.fn((request: string) => {
|
|
2220
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2221
|
+
return { message: { version: 2 } };
|
|
2222
|
+
}
|
|
2223
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2224
|
+
return { message: {} };
|
|
2225
|
+
}
|
|
2226
|
+
if (request === 'DeviceSessionGet') {
|
|
2227
|
+
return {
|
|
2228
|
+
message: {
|
|
2229
|
+
btc_test_address: 'hidden-state',
|
|
2230
|
+
session_id: 'new-session',
|
|
2231
|
+
seed_domains: [
|
|
2232
|
+
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
2233
|
+
DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
2234
|
+
],
|
|
2235
|
+
},
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2239
|
+
});
|
|
2240
|
+
const device = createDevice({
|
|
1884
2241
|
typedCall,
|
|
1885
2242
|
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
1886
|
-
})
|
|
2243
|
+
});
|
|
1887
2244
|
|
|
1888
|
-
await expect(
|
|
1889
|
-
|
|
2245
|
+
await expect(
|
|
2246
|
+
getProtocolV2WalletSession(device as any, {
|
|
2247
|
+
forceWalletSelection: true,
|
|
2248
|
+
deriveCardano: true,
|
|
2249
|
+
})
|
|
2250
|
+
).resolves.toMatchObject({
|
|
1890
2251
|
passphraseState: 'hidden-state',
|
|
2252
|
+
newSession: 'new-session',
|
|
2253
|
+
});
|
|
2254
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2255
|
+
passphrase: 'host hidden wallet',
|
|
2256
|
+
on_device: false,
|
|
2257
|
+
seed_domains: CARDANO_SEED_DOMAINS,
|
|
1891
2258
|
});
|
|
2259
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', standardSessionGet);
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
test('asks Cardano seed domains after Get reports a Standard-only session', async () => {
|
|
2263
|
+
const typedCall = jest.fn((request: string) => {
|
|
2264
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2265
|
+
return { message: { version: 2 } };
|
|
2266
|
+
}
|
|
2267
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2268
|
+
return { message: {} };
|
|
2269
|
+
}
|
|
2270
|
+
if (request === 'DeviceSessionGet') {
|
|
2271
|
+
const hasCardanoAsk = typedCall.mock.calls.some(
|
|
2272
|
+
call =>
|
|
2273
|
+
call[0] === 'DeviceSessionAskPassphrase' &&
|
|
2274
|
+
Array.isArray(call[2]?.seed_domains) &&
|
|
2275
|
+
call[2].seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano)
|
|
2276
|
+
);
|
|
2277
|
+
return {
|
|
2278
|
+
message: {
|
|
2279
|
+
btc_test_address: 'hidden-state',
|
|
2280
|
+
session_id: 'new-session',
|
|
2281
|
+
seed_domains: hasCardanoAsk
|
|
2282
|
+
? [
|
|
2283
|
+
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
2284
|
+
DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
2285
|
+
]
|
|
2286
|
+
: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2287
|
+
},
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2291
|
+
});
|
|
2292
|
+
const device = createDevice({
|
|
2293
|
+
typedCall,
|
|
2294
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
2295
|
+
});
|
|
2296
|
+
device.passphraseState = 'hidden-state';
|
|
2297
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'cached-session');
|
|
2298
|
+
|
|
2299
|
+
await getProtocolV2WalletSession(device as any, {
|
|
2300
|
+
expectedPassphraseState: 'hidden-state',
|
|
2301
|
+
deriveCardano: true,
|
|
2302
|
+
});
|
|
2303
|
+
|
|
1892
2304
|
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
|
|
2305
|
+
session_id: 'cached-session',
|
|
2306
|
+
btc_test_address: 'hidden-state',
|
|
2307
|
+
});
|
|
2308
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2309
|
+
passphrase: 'host hidden wallet',
|
|
2310
|
+
on_device: false,
|
|
1893
2311
|
seed_domains: [
|
|
1894
2312
|
DeviceSessionSeedDomain.SeedDomain_Standard,
|
|
1895
|
-
|
|
2313
|
+
DeviceSessionSeedDomain.SeedDomain_Cardano,
|
|
1896
2314
|
],
|
|
1897
2315
|
});
|
|
1898
2316
|
});
|
|
2317
|
+
|
|
2318
|
+
test('uses empty AskPassphrase to add Cardano on an Attach PIN session', async () => {
|
|
2319
|
+
const typedCall = jest.fn((request: string) => {
|
|
2320
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2321
|
+
return { message: { version: 2 } };
|
|
2322
|
+
}
|
|
2323
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2324
|
+
return { message: {} };
|
|
2325
|
+
}
|
|
2326
|
+
if (request === 'DeviceSessionGet') {
|
|
2327
|
+
const askedCardano = typedCall.mock.calls.some(
|
|
2328
|
+
call =>
|
|
2329
|
+
call[0] === 'DeviceSessionAskPassphrase' &&
|
|
2330
|
+
Array.isArray(call[2]?.seed_domains) &&
|
|
2331
|
+
call[2].seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano)
|
|
2332
|
+
);
|
|
2333
|
+
return {
|
|
2334
|
+
message: {
|
|
2335
|
+
btc_test_address: 'attach-state',
|
|
2336
|
+
session_id: 'attach-session',
|
|
2337
|
+
seed_domains: askedCardano ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS,
|
|
2338
|
+
},
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2342
|
+
});
|
|
2343
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
|
|
2344
|
+
const device = createDevice({
|
|
2345
|
+
typedCall,
|
|
2346
|
+
promptPassphrase,
|
|
2347
|
+
unlockedAttachPin: true,
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
await expect(
|
|
2351
|
+
getProtocolV2WalletSession(device as any, {
|
|
2352
|
+
readCurrentAttachPinSession: true,
|
|
2353
|
+
deriveCardano: true,
|
|
2354
|
+
})
|
|
2355
|
+
).resolves.toMatchObject({
|
|
2356
|
+
passphraseState: 'attach-state',
|
|
2357
|
+
newSession: 'attach-session',
|
|
2358
|
+
unlockedAttachPin: true,
|
|
2359
|
+
});
|
|
2360
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2361
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2362
|
+
passphrase: '',
|
|
2363
|
+
on_device: false,
|
|
2364
|
+
seed_domains: CARDANO_SEED_DOMAINS,
|
|
2365
|
+
});
|
|
2366
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2367
|
+
expect(
|
|
2368
|
+
typedCall.mock.calls
|
|
2369
|
+
.filter(call => call[0] === 'DeviceSessionGet')
|
|
2370
|
+
.every(call => !('seed_domains' in (call[2] ?? {})))
|
|
2371
|
+
).toBe(true);
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
test('uses empty AskPassphrase after selecting Attach PIN for Cardano intent', async () => {
|
|
2375
|
+
const typedCall = jest.fn((request: string) => {
|
|
2376
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2377
|
+
return { message: { version: 2 } };
|
|
2378
|
+
}
|
|
2379
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2380
|
+
return { message: {} };
|
|
2381
|
+
}
|
|
2382
|
+
if (request === 'DeviceSessionGet') {
|
|
2383
|
+
return {
|
|
2384
|
+
message: {
|
|
2385
|
+
btc_test_address: 'attach-state',
|
|
2386
|
+
session_id: 'attach-session',
|
|
2387
|
+
seed_domains: CARDANO_SEED_DOMAINS,
|
|
2388
|
+
},
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2392
|
+
});
|
|
2393
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
|
|
2394
|
+
const device = createDevice({ typedCall, promptPassphrase });
|
|
2395
|
+
device.features.attachToPinEnabled = true;
|
|
2396
|
+
|
|
2397
|
+
await expect(
|
|
2398
|
+
getProtocolV2WalletSession(device as any, {
|
|
2399
|
+
forceWalletSelection: true,
|
|
2400
|
+
deriveCardano: true,
|
|
2401
|
+
})
|
|
2402
|
+
).resolves.toMatchObject({
|
|
2403
|
+
passphraseState: 'attach-state',
|
|
2404
|
+
newSession: 'attach-session',
|
|
2405
|
+
unlockedAttachPin: true,
|
|
2406
|
+
});
|
|
2407
|
+
expect(promptPassphrase).toHaveBeenCalledTimes(1);
|
|
2408
|
+
expect(device.unlockDevice).toHaveBeenCalledWith(
|
|
2409
|
+
DeviceSessionPinType.AttachToPin,
|
|
2410
|
+
expect.objectContaining({ emitUiEvent: false })
|
|
2411
|
+
);
|
|
2412
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2413
|
+
passphrase: '',
|
|
2414
|
+
on_device: false,
|
|
2415
|
+
seed_domains: CARDANO_SEED_DOMAINS,
|
|
2416
|
+
});
|
|
2417
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2418
|
+
expect(
|
|
2419
|
+
typedCall.mock.calls
|
|
2420
|
+
.filter(call => call[0] === 'DeviceSessionGet')
|
|
2421
|
+
.every(call => !('seed_domains' in (call[2] ?? {})))
|
|
2422
|
+
).toBe(true);
|
|
2423
|
+
});
|
|
2424
|
+
|
|
2425
|
+
test('still locks before a passphrase picker after Attach PIN Cardano empty Ask', async () => {
|
|
2426
|
+
const typedCall = jest.fn((request: string) => {
|
|
2427
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2428
|
+
return { message: { version: 2 } };
|
|
2429
|
+
}
|
|
2430
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2431
|
+
return { message: {} };
|
|
2432
|
+
}
|
|
2433
|
+
if (request === 'DeviceSessionGet') {
|
|
2434
|
+
return {
|
|
2435
|
+
message: {
|
|
2436
|
+
btc_test_address: 'attach-state',
|
|
2437
|
+
session_id: 'attach-session',
|
|
2438
|
+
seed_domains: CARDANO_SEED_DOMAINS,
|
|
2439
|
+
},
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2443
|
+
});
|
|
2444
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
|
|
2445
|
+
const device = createDevice({
|
|
2446
|
+
typedCall,
|
|
2447
|
+
promptPassphrase,
|
|
2448
|
+
unlockedAttachPin: true,
|
|
2449
|
+
});
|
|
2450
|
+
|
|
2451
|
+
await getProtocolV2WalletSession(device as any, {
|
|
2452
|
+
readCurrentAttachPinSession: true,
|
|
2453
|
+
deriveCardano: true,
|
|
2454
|
+
});
|
|
2455
|
+
await expect(
|
|
2456
|
+
getProtocolV2WalletSession(device as any, { forceWalletSelection: true })
|
|
2457
|
+
).rejects.toMatchObject({
|
|
2458
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2459
|
+
});
|
|
2460
|
+
expect(device.lockDevice).toHaveBeenCalled();
|
|
2461
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2462
|
+
});
|
|
2463
|
+
|
|
2464
|
+
test('uses a second Get for Cardano when passphrase protection is off', async () => {
|
|
2465
|
+
const typedCall = jest.fn((request: string) => {
|
|
2466
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2467
|
+
return { message: { version: 2 } };
|
|
2468
|
+
}
|
|
2469
|
+
if (request === 'DeviceSessionGet') {
|
|
2470
|
+
const getCount = typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet').length;
|
|
2471
|
+
return {
|
|
2472
|
+
message: {
|
|
2473
|
+
btc_test_address: 'standard-state',
|
|
2474
|
+
session_id: 'standard-session',
|
|
2475
|
+
seed_domains: getCount > 1 ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS,
|
|
2476
|
+
},
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2480
|
+
});
|
|
2481
|
+
const device = createDevice({
|
|
2482
|
+
typedCall,
|
|
2483
|
+
passphraseProtection: false,
|
|
2484
|
+
});
|
|
2485
|
+
|
|
2486
|
+
await expect(
|
|
2487
|
+
getProtocolV2WalletSession(device as any, {
|
|
2488
|
+
onlyMainPin: true,
|
|
2489
|
+
deriveCardano: true,
|
|
2490
|
+
})
|
|
2491
|
+
).resolves.toMatchObject({
|
|
2492
|
+
passphraseState: 'standard-state',
|
|
2493
|
+
newSession: 'standard-session',
|
|
2494
|
+
});
|
|
2495
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2496
|
+
'DeviceSessionAskPassphrase',
|
|
2497
|
+
'Success',
|
|
2498
|
+
expect.anything()
|
|
2499
|
+
);
|
|
2500
|
+
expect(typedCall.mock.calls.filter(call => call[0] === 'DeviceSessionGet')).toHaveLength(2);
|
|
2501
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
test('fails closed when Cardano fallback still lacks Cardano', async () => {
|
|
2505
|
+
const typedCall = jest.fn((request: string) => {
|
|
2506
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2507
|
+
return { message: { version: 2 } };
|
|
2508
|
+
}
|
|
2509
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2510
|
+
return { message: {} };
|
|
2511
|
+
}
|
|
2512
|
+
if (request === 'DeviceSessionGet') {
|
|
2513
|
+
return {
|
|
2514
|
+
message: {
|
|
2515
|
+
btc_test_address: 'hidden-state',
|
|
2516
|
+
session_id: 'hidden-session',
|
|
2517
|
+
seed_domains: STANDARD_SEED_DOMAINS,
|
|
2518
|
+
},
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2522
|
+
});
|
|
2523
|
+
const device = createDevice({
|
|
2524
|
+
typedCall,
|
|
2525
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
2526
|
+
});
|
|
2527
|
+
deviceWalletSessionStore.set('device-1', 'hidden-state', 'cached-session');
|
|
2528
|
+
|
|
2529
|
+
await expect(
|
|
2530
|
+
getProtocolV2WalletSession(device as any, {
|
|
2531
|
+
expectedPassphraseState: 'hidden-state',
|
|
2532
|
+
deriveCardano: true,
|
|
2533
|
+
})
|
|
2534
|
+
).rejects.toMatchObject({
|
|
2535
|
+
errorCode: HardwareErrorCode.WalletSessionInvalid,
|
|
2536
|
+
});
|
|
2537
|
+
expect(device.clearInternalState).toHaveBeenCalled();
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
test('locks before selecting a passphrase wallet from an Attach PIN session', async () => {
|
|
2541
|
+
const typedCall = jest.fn((request: string) => {
|
|
2542
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2543
|
+
return { message: { version: 2 } };
|
|
2544
|
+
}
|
|
2545
|
+
if (request === 'DeviceStatusGet') {
|
|
2546
|
+
return {
|
|
2547
|
+
message: {
|
|
2548
|
+
device_id: 'device-1',
|
|
2549
|
+
unlocked: true,
|
|
2550
|
+
passphrase_enabled: true,
|
|
2551
|
+
unlocked_by_attach_to_pin: true,
|
|
2552
|
+
},
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2556
|
+
});
|
|
2557
|
+
const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'should-not-ask' });
|
|
2558
|
+
const device = createDevice({
|
|
2559
|
+
typedCall,
|
|
2560
|
+
promptPassphrase,
|
|
2561
|
+
unlockedAttachPin: true,
|
|
2562
|
+
});
|
|
2563
|
+
|
|
2564
|
+
await expect(
|
|
2565
|
+
getProtocolV2WalletSession(device as any, { forceWalletSelection: true })
|
|
2566
|
+
).rejects.toMatchObject({
|
|
2567
|
+
errorCode: HardwareErrorCode.DeviceCheckUnlockTypeError,
|
|
2568
|
+
});
|
|
2569
|
+
expect(device.lockDevice).toHaveBeenCalled();
|
|
2570
|
+
expect(promptPassphrase).not.toHaveBeenCalled();
|
|
2571
|
+
expect(typedCall).not.toHaveBeenCalledWith(
|
|
2572
|
+
'DeviceSessionAskPassphrase',
|
|
2573
|
+
'Success',
|
|
2574
|
+
expect.anything()
|
|
2575
|
+
);
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2578
|
+
test('asks Standard-only seed domains when deriveCardano is false', async () => {
|
|
2579
|
+
const typedCall = jest.fn((request: string) => {
|
|
2580
|
+
if (request === 'ProtocolInfoRequest') {
|
|
2581
|
+
return { message: { version: 2 } };
|
|
2582
|
+
}
|
|
2583
|
+
if (request === 'DeviceSessionAskPassphrase') {
|
|
2584
|
+
return { message: {} };
|
|
2585
|
+
}
|
|
2586
|
+
if (request === 'DeviceSessionGet') {
|
|
2587
|
+
return {
|
|
2588
|
+
message: {
|
|
2589
|
+
btc_test_address: 'hidden-state',
|
|
2590
|
+
session_id: 'new-session',
|
|
2591
|
+
},
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
throw new Error(`Unexpected request: ${request}`);
|
|
2595
|
+
});
|
|
2596
|
+
const device = createDevice({
|
|
2597
|
+
typedCall,
|
|
2598
|
+
promptPassphrase: jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' }),
|
|
2599
|
+
});
|
|
2600
|
+
|
|
2601
|
+
await getProtocolV2WalletSession(device as any, {
|
|
2602
|
+
forceWalletSelection: true,
|
|
2603
|
+
deriveCardano: false,
|
|
2604
|
+
});
|
|
2605
|
+
expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
|
|
2606
|
+
passphrase: 'host hidden wallet',
|
|
2607
|
+
on_device: false,
|
|
2608
|
+
seed_domains: [DeviceSessionSeedDomain.SeedDomain_Standard],
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
1899
2611
|
});
|