@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
|
@@ -19,6 +19,90 @@ const createCommands = () => {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
describe('DeviceCommands failure mapping', () => {
|
|
22
|
+
it('logs passphrase call request and response without exposing the passphrase', async () => {
|
|
23
|
+
const commands = createCommands();
|
|
24
|
+
const requestLog = getLogger(LoggerNames.DeviceCommands);
|
|
25
|
+
const responseLog = getLogger(LoggerNames.Core);
|
|
26
|
+
requestLog.messages.length = 0;
|
|
27
|
+
responseLog.messages.length = 0;
|
|
28
|
+
commands.mainId = 'main-id';
|
|
29
|
+
commands.transport = {
|
|
30
|
+
call: jest.fn().mockResolvedValue({
|
|
31
|
+
type: 'Success',
|
|
32
|
+
message: {
|
|
33
|
+
message: 'Passphrase accepted',
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
} as any;
|
|
37
|
+
|
|
38
|
+
await expect(
|
|
39
|
+
commands.call('DeviceSessionAskPassphrase', {
|
|
40
|
+
passphrase: 'hidden-wallet-secret',
|
|
41
|
+
on_device: false,
|
|
42
|
+
})
|
|
43
|
+
).resolves.toMatchObject({ type: 'Success' });
|
|
44
|
+
|
|
45
|
+
expect(requestLog.messages.at(-1)?.message).toEqual([
|
|
46
|
+
'[DeviceCommands] [call] Sending',
|
|
47
|
+
'DeviceSessionAskPassphrase',
|
|
48
|
+
{
|
|
49
|
+
passphrase: '[REDACTED]',
|
|
50
|
+
on_device: false,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
expect(responseLog.messages.at(-1)?.message).toEqual([
|
|
54
|
+
'[DeviceCommands] [call] Received',
|
|
55
|
+
'Success',
|
|
56
|
+
{
|
|
57
|
+
message: 'Passphrase accepted',
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
expect(JSON.stringify([...requestLog.messages, ...responseLog.messages])).not.toContain(
|
|
61
|
+
'hidden-wallet-secret'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('logs DeviceStatus response fields without exposing the device ID', async () => {
|
|
66
|
+
const commands = createCommands();
|
|
67
|
+
const log = getLogger(LoggerNames.Core);
|
|
68
|
+
log.messages.length = 0;
|
|
69
|
+
commands.mainId = 'main-id';
|
|
70
|
+
commands.transport = {
|
|
71
|
+
call: jest.fn().mockResolvedValue({
|
|
72
|
+
type: 'DeviceStatus',
|
|
73
|
+
message: {
|
|
74
|
+
device_id: 'sensitive-device-id',
|
|
75
|
+
unlocked: true,
|
|
76
|
+
init_states: true,
|
|
77
|
+
backup_required: false,
|
|
78
|
+
passphrase_enabled: true,
|
|
79
|
+
attach_to_pin_enabled: false,
|
|
80
|
+
unlocked_by_attach_to_pin: false,
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
} as any;
|
|
84
|
+
|
|
85
|
+
await expect(commands.call('DeviceStatusGet', {})).resolves.toMatchObject({
|
|
86
|
+
type: 'DeviceStatus',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const receivedLog = log.messages.at(-1)?.message;
|
|
90
|
+
expect(receivedLog).toEqual([
|
|
91
|
+
'[DeviceCommands] [call] Received',
|
|
92
|
+
'DeviceStatus',
|
|
93
|
+
{
|
|
94
|
+
device_id: '[REDACTED]',
|
|
95
|
+
unlocked: true,
|
|
96
|
+
init_states: true,
|
|
97
|
+
backup_required: false,
|
|
98
|
+
passphrase_enabled: true,
|
|
99
|
+
attach_to_pin_enabled: false,
|
|
100
|
+
unlocked_by_attach_to_pin: false,
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
expect(JSON.stringify(receivedLog)).not.toContain('sensitive-device-id');
|
|
104
|
+
});
|
|
105
|
+
|
|
22
106
|
it('does not log DeviceSessionGet response secrets', async () => {
|
|
23
107
|
const commands = createCommands();
|
|
24
108
|
const log = getLogger(LoggerNames.DeviceCommands);
|
|
@@ -160,6 +160,37 @@ describe('checkAllFirmwareRelease Protocol V2 support', () => {
|
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test('keeps same-version hash-aware components unknown without metadata payloadHash', () => {
|
|
164
|
+
const packageSet: IFirmwareReleaseInfo = {
|
|
165
|
+
...release,
|
|
166
|
+
required: false,
|
|
167
|
+
resourceBundles: undefined,
|
|
168
|
+
installOrder: ['applicationP1'],
|
|
169
|
+
components: {
|
|
170
|
+
applicationP1: {
|
|
171
|
+
target: 'APPLICATION_P1',
|
|
172
|
+
version: [1, 0, 0],
|
|
173
|
+
url: 'https://untrusted.example/application-p1.okpkg',
|
|
174
|
+
fingerprint: 'file-sha256-p1',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
expect(
|
|
180
|
+
buildProtocolV2FirmwareRelease({
|
|
181
|
+
currentVersions: { ...currentVersions, applicationP1: '1.0.0' },
|
|
182
|
+
currentVerification: { applicationP1Hash: 'aa'.repeat(32) },
|
|
183
|
+
firmwareType: EFirmwareType.Universal,
|
|
184
|
+
release: packageSet,
|
|
185
|
+
})
|
|
186
|
+
).toMatchObject({
|
|
187
|
+
status: 'unknown',
|
|
188
|
+
hasUpgrade: false,
|
|
189
|
+
targetsToUpdate: [],
|
|
190
|
+
components: [{ configKey: 'applicationP1', status: 'unknown' }],
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
163
194
|
test('updates both application packages when normal mode only reports P1', () => {
|
|
164
195
|
const packageSet: IFirmwareReleaseInfo = {
|
|
165
196
|
...release,
|
|
@@ -17,18 +17,18 @@ describe('Core.dispose', () => {
|
|
|
17
17
|
const connectorStop = jest.spyOn(connector, 'stop');
|
|
18
18
|
const transportStop = jest.fn().mockResolvedValue(undefined);
|
|
19
19
|
jest.spyOn(TransportManager, 'getTransport').mockReturnValue({ stop: transportStop } as never);
|
|
20
|
-
const
|
|
20
|
+
const disposeDevicePool = jest.spyOn(DevicePool, 'dispose');
|
|
21
21
|
const core = new Core();
|
|
22
22
|
|
|
23
23
|
const firstDispose = core.dispose();
|
|
24
24
|
expect(connectorStop).toHaveBeenCalledTimes(1);
|
|
25
25
|
expect(transportStop).toHaveBeenCalledTimes(1);
|
|
26
|
-
expect(
|
|
26
|
+
expect(disposeDevicePool).toHaveBeenCalledTimes(1);
|
|
27
27
|
|
|
28
28
|
await firstDispose;
|
|
29
29
|
await core.dispose();
|
|
30
30
|
expect(connectorStop).toHaveBeenCalledTimes(1);
|
|
31
31
|
expect(transportStop).toHaveBeenCalledTimes(1);
|
|
32
|
-
expect(
|
|
32
|
+
expect(disposeDevicePool).toHaveBeenCalledTimes(1);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
@@ -79,6 +79,57 @@ describe('public device lifecycle events', () => {
|
|
|
79
79
|
expect(DevicePool.emitter.listenerCount(DEVICE.DISCONNECT)).toBe(1);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
test('keeps shared device lifecycle listeners across a device cache reset', () => {
|
|
83
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
84
|
+
core = initCore();
|
|
85
|
+
initConnector();
|
|
86
|
+
const messages: CoreMessage[] = [];
|
|
87
|
+
core.on(CORE_EVENT, message => messages.push(message));
|
|
88
|
+
const device = createInitializedDevice('V2');
|
|
89
|
+
|
|
90
|
+
DevicePool.resetState();
|
|
91
|
+
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
92
|
+
DevicePool.emitter.emit(DEVICE.DISCONNECT, device);
|
|
93
|
+
|
|
94
|
+
expect(messages.filter(message => message.type === DEVICE.CONNECT)).toHaveLength(1);
|
|
95
|
+
expect(messages.filter(message => message.type === DEVICE.DISCONNECT)).toHaveLength(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('uses a cached protocol as a fallback-capable acquire hint', async () => {
|
|
99
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
100
|
+
const device = Device.fromDescriptor({
|
|
101
|
+
id: 'ble-id',
|
|
102
|
+
path: 'ble-id',
|
|
103
|
+
commType: 'ble',
|
|
104
|
+
protocolType: 'V1',
|
|
105
|
+
} as never);
|
|
106
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
|
|
107
|
+
device.deviceConnector = { acquire } as never;
|
|
108
|
+
|
|
109
|
+
await device.acquire();
|
|
110
|
+
|
|
111
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, undefined, 'V1');
|
|
112
|
+
expect(device.getProtocol()).toBe('V2');
|
|
113
|
+
expect(device.originalDescriptor.protocolType).toBe('V2');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('keeps an explicit connectProtocol as a strict expected protocol', async () => {
|
|
117
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
118
|
+
const device = Device.fromDescriptor({
|
|
119
|
+
id: 'ble-id',
|
|
120
|
+
path: 'ble-id',
|
|
121
|
+
commType: 'ble',
|
|
122
|
+
protocolType: 'V1',
|
|
123
|
+
} as never);
|
|
124
|
+
const acquire = jest.fn().mockResolvedValue({ uuid: 'ble-id', protocolType: 'V2' });
|
|
125
|
+
device.deviceConnector = { acquire } as never;
|
|
126
|
+
|
|
127
|
+
await device.acquire('V2');
|
|
128
|
+
|
|
129
|
+
expect(acquire).toHaveBeenCalledWith('ble-id', undefined, true, 'V2', undefined);
|
|
130
|
+
expect(device.getProtocol()).toBe('V2');
|
|
131
|
+
});
|
|
132
|
+
|
|
82
133
|
test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
|
|
83
134
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
84
135
|
core = initCore();
|
|
@@ -125,6 +176,7 @@ describe('public device lifecycle events', () => {
|
|
|
125
176
|
const device = createInitializedDevice('V2');
|
|
126
177
|
|
|
127
178
|
expect(device.toMessageObject()?.status).toBe('available');
|
|
179
|
+
expect(device.toMessageObject()?.connectProtocol).toBe('V2');
|
|
128
180
|
|
|
129
181
|
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
130
182
|
expect(device.toMessageObject()?.status).toBe('used');
|
|
@@ -9,11 +9,11 @@ jest.mock('../src/data/config', () => ({
|
|
|
9
9
|
|
|
10
10
|
describe('DevicePool state lifecycle', () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
|
-
DevicePool.
|
|
12
|
+
DevicePool.dispose();
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
afterEach(() => {
|
|
16
|
-
DevicePool.
|
|
16
|
+
DevicePool.dispose();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test('refreshes runtime state for a cached Protocol V2 discovery result', async () => {
|
|
@@ -74,6 +74,18 @@ describe('DevicePool state lifecycle', () => {
|
|
|
74
74
|
expect(DevicePool.disconnectPool).toEqual([]);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
test('preserves shared listeners when resetting cached device state', () => {
|
|
78
|
+
const listener = jest.fn();
|
|
79
|
+
DevicePool.emitter.on(DEVICE.CONNECT, listener);
|
|
80
|
+
DevicePool.devicesCache = { stale: {} as Device };
|
|
81
|
+
|
|
82
|
+
DevicePool.resetState();
|
|
83
|
+
DevicePool.emitter.emit(DEVICE.CONNECT, { path: 'next-device' });
|
|
84
|
+
|
|
85
|
+
expect(DevicePool.devicesCache).toEqual({});
|
|
86
|
+
expect(listener).toHaveBeenCalledWith({ path: 'next-device' });
|
|
87
|
+
});
|
|
88
|
+
|
|
77
89
|
test('releases a newly created device when initialization fails', async () => {
|
|
78
90
|
const descriptor = { path: 'pro2-path', protocolType: 'V2' } as any;
|
|
79
91
|
const release = jest.fn().mockResolvedValue(undefined);
|
|
@@ -40,6 +40,9 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
40
40
|
['language', { language: 'ja-JP' }],
|
|
41
41
|
['brightness', { brightness: 80 }],
|
|
42
42
|
['haptic feedback', { hapticFeedback: true }],
|
|
43
|
+
['animation', { animationEnabled: true }],
|
|
44
|
+
['tap to wake', { tapToWake: true }],
|
|
45
|
+
['device name display', { deviceNameDisplayEnabled: true }],
|
|
43
46
|
])('does not unlock Protocol V2 before changing %s', (_name, settings) => {
|
|
44
47
|
const method = new DeviceSettings({
|
|
45
48
|
id: 1,
|
|
@@ -226,6 +229,28 @@ describe('DeviceSettings protocol routing', () => {
|
|
|
226
229
|
});
|
|
227
230
|
});
|
|
228
231
|
|
|
232
|
+
it('accepts a locked Pro2 as confirmation after disabling passphrase', async () => {
|
|
233
|
+
const { device, getDeviceState } = createDevice({ protocol: 'V2' });
|
|
234
|
+
getDeviceState
|
|
235
|
+
.mockResolvedValueOnce({
|
|
236
|
+
status: { unlocked: true, passphraseProtection: true },
|
|
237
|
+
})
|
|
238
|
+
.mockResolvedValueOnce({
|
|
239
|
+
status: { unlocked: false, passphraseProtection: true },
|
|
240
|
+
});
|
|
241
|
+
const method = new DeviceSettings({
|
|
242
|
+
id: 6,
|
|
243
|
+
payload: {
|
|
244
|
+
method: 'deviceSettings',
|
|
245
|
+
usePassphrase: false,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
method.init();
|
|
249
|
+
(method as any).device = device;
|
|
250
|
+
|
|
251
|
+
await expect(method.run()).resolves.toEqual({ message: 'Success' });
|
|
252
|
+
});
|
|
253
|
+
|
|
229
254
|
it('keeps Protocol V1 passphrase settings on ApplySettings', async () => {
|
|
230
255
|
const { device, typedCall, getDeviceState } = createDevice({ protocol: 'V1' });
|
|
231
256
|
const method = new DeviceSettings({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
|
-
import { Enum_SafetyCheckLevel } from '@onekeyfe/hd-transport';
|
|
3
|
+
import { DeviceType, Enum_SafetyCheckLevel } from '@onekeyfe/hd-transport';
|
|
4
4
|
import {
|
|
5
5
|
mapApplySettingsToState,
|
|
6
6
|
mapDeviceSettingsToState,
|
|
@@ -109,7 +109,7 @@ describe('DeviceStateMapper', () => {
|
|
|
109
109
|
const patch = mapProtocolV2DeviceInfoToState(
|
|
110
110
|
{
|
|
111
111
|
protocol_version: 2,
|
|
112
|
-
hw: { serial_no: 'SERIAL-1' },
|
|
112
|
+
hw: { Device_type: DeviceType.PRO2, serial_no: 'SERIAL-1' },
|
|
113
113
|
fw: {
|
|
114
114
|
application: { version: '5.0.0', build_id: 'p1-build', hash: [0x01, 0x02] },
|
|
115
115
|
application_data: { version: '5.0.1', build_id: 'p2-build', hash: [0x03, 0x04] },
|
|
@@ -145,6 +145,17 @@ describe('DeviceStateMapper', () => {
|
|
|
145
145
|
expect(patch.status?.unlocked).toBeUndefined();
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
test('maps the hardware model independently from Protocol V2', () => {
|
|
149
|
+
const pro = mapProtocolV2DeviceInfoToState({
|
|
150
|
+
protocol_version: 2,
|
|
151
|
+
hw: { Device_type: 'PRO' as unknown as DeviceType, serial_no: 'SERIAL-PRO' },
|
|
152
|
+
});
|
|
153
|
+
const unknown = mapProtocolV2DeviceInfoToState({ protocol_version: 2, hw: {} });
|
|
154
|
+
|
|
155
|
+
expect(pro.identity).toMatchObject({ deviceType: EDeviceType.Pro, model: 'pro' });
|
|
156
|
+
expect(unknown.identity).toMatchObject({ deviceType: EDeviceType.Unknown, model: null });
|
|
157
|
+
});
|
|
158
|
+
|
|
148
159
|
test('keeps normal mode when Protocol V2 application and SE versions are returned together', () => {
|
|
149
160
|
const patch = mapProtocolV2DeviceInfoToState(
|
|
150
161
|
{
|
|
@@ -73,7 +73,7 @@ describe('DeviceStateProjector', () => {
|
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test('returns an isolated compatibility snapshot', () => {
|
|
76
|
+
test('returns an isolated compatibility snapshot without internal raw state', () => {
|
|
77
77
|
const state = createEmptyDeviceState({ deviceType: EDeviceType.Pro });
|
|
78
78
|
state.protocol = 'V1';
|
|
79
79
|
state.capabilities = [1];
|
|
@@ -85,10 +85,10 @@ describe('DeviceStateProjector', () => {
|
|
|
85
85
|
const features = projectFeatures(state);
|
|
86
86
|
features.capabilities.push(2);
|
|
87
87
|
if (features.verify) features.verify.firmwareBuildId = 'mutated';
|
|
88
|
-
if (features.raw?.protocolV1Features) features.raw.protocolV1Features.label = 'Mutated';
|
|
89
88
|
|
|
90
89
|
expect(state.capabilities).toEqual([1]);
|
|
91
90
|
expect(state.verification?.firmwareBuildId).toBe('build-1');
|
|
92
91
|
expect(state.raw.protocolV1Features?.label).toBe('Original');
|
|
92
|
+
expect(features).not.toHaveProperty('raw');
|
|
93
93
|
});
|
|
94
94
|
});
|
|
@@ -61,6 +61,7 @@ describe('device feature selectors', () => {
|
|
|
61
61
|
} as PROTO.Features;
|
|
62
62
|
|
|
63
63
|
expect(getDeviceType(features)).toBe(EDeviceType.Classic);
|
|
64
|
+
expect(getDeviceFirmwareVersion(features)).toEqual([0, 0, 0]);
|
|
64
65
|
expect(getDeviceBootloaderVersion(features)).toEqual([1, 8, 3]);
|
|
65
66
|
});
|
|
66
67
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { EDeviceType, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
2
|
|
|
3
3
|
import ClearSessionCache from '../src/api/ClearSessionCache';
|
|
4
|
+
import GetPassphraseState from '../src/api/GetPassphraseState';
|
|
4
5
|
import DeviceWipe from '../src/api/device/DeviceWipe';
|
|
5
6
|
import {
|
|
6
7
|
DeviceWalletSessionStore,
|
|
@@ -35,6 +36,38 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
35
36
|
expect(store.get('device-2', 'hidden-a')).toBe('session-c');
|
|
36
37
|
});
|
|
37
38
|
|
|
39
|
+
test('keeps SDK session handles without mirroring hardware eviction policy', () => {
|
|
40
|
+
const store = new DeviceWalletSessionStore();
|
|
41
|
+
store.setStandard('device-1', 'wallet-a', 'session-a');
|
|
42
|
+
store.set('device-1', 'wallet-b', 'session-b');
|
|
43
|
+
store.set('device-1', 'wallet-c', 'session-c');
|
|
44
|
+
store.set('device-1', 'wallet-d', 'session-d');
|
|
45
|
+
|
|
46
|
+
expect(store.get('device-1', 'wallet-a')).toBe('session-a');
|
|
47
|
+
expect(store.getStandard('device-1')).toEqual({
|
|
48
|
+
passphraseState: 'wallet-a',
|
|
49
|
+
sessionId: 'session-a',
|
|
50
|
+
});
|
|
51
|
+
expect(store.get('device-1', 'wallet-b')).toBe('session-b');
|
|
52
|
+
expect(store.get('device-1', 'wallet-c')).toBe('session-c');
|
|
53
|
+
expect(store.get('device-1', 'wallet-d')).toBe('session-d');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('indexes the standard wallet without replacing hidden-wallet sessions', () => {
|
|
57
|
+
const store = new DeviceWalletSessionStore();
|
|
58
|
+
store.set('device-1', 'hidden-a', 'hidden-session-a');
|
|
59
|
+
|
|
60
|
+
store.setStandard('device-1', 'standard-state', 'standard-session-1');
|
|
61
|
+
store.setStandard('device-1', 'standard-state', 'standard-session-2');
|
|
62
|
+
|
|
63
|
+
expect(store.getStandard('device-1')).toEqual({
|
|
64
|
+
passphraseState: 'standard-state',
|
|
65
|
+
sessionId: 'standard-session-2',
|
|
66
|
+
});
|
|
67
|
+
expect(store.get('device-1', 'standard-state')).toBe('standard-session-2');
|
|
68
|
+
expect(store.get('device-1', 'hidden-a')).toBe('hidden-session-a');
|
|
69
|
+
});
|
|
70
|
+
|
|
38
71
|
test('keeps pending sessions unreadable until wallet binding', () => {
|
|
39
72
|
const store = new DeviceWalletSessionStore();
|
|
40
73
|
store.setPending('device-1', 'pending-session');
|
|
@@ -46,6 +79,7 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
46
79
|
test('migrates descriptor keys to stable device ids', () => {
|
|
47
80
|
const store = new DeviceWalletSessionStore();
|
|
48
81
|
store.set('ble-path', 'hidden-a', 'session-a');
|
|
82
|
+
store.setStandard('ble-path', 'standard-state', 'standard-session');
|
|
49
83
|
store.setPending('ble-path', 'pending-session');
|
|
50
84
|
|
|
51
85
|
store.reconcileDeviceIdentity({
|
|
@@ -55,10 +89,37 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
55
89
|
|
|
56
90
|
expect(store.get('ble-path', 'hidden-a')).toBeUndefined();
|
|
57
91
|
expect(store.get('stable-device-id', 'hidden-a')).toBe('session-a');
|
|
92
|
+
expect(store.getStandard('ble-path')).toBeUndefined();
|
|
93
|
+
expect(store.getStandard('stable-device-id')).toEqual({
|
|
94
|
+
passphraseState: 'standard-state',
|
|
95
|
+
sessionId: 'standard-session',
|
|
96
|
+
});
|
|
58
97
|
expect(store.getPending('ble-path')).toBeUndefined();
|
|
59
98
|
expect(store.getPending('stable-device-id')).toBe('pending-session');
|
|
60
99
|
});
|
|
61
100
|
|
|
101
|
+
test('keeps every known handle when descriptor sessions merge into a stable device id', () => {
|
|
102
|
+
const store = new DeviceWalletSessionStore();
|
|
103
|
+
store.setStandard('stable-device-id', 'wallet-a', 'session-a');
|
|
104
|
+
store.set('stable-device-id', 'wallet-b', 'session-b');
|
|
105
|
+
store.set('ble-path', 'wallet-c', 'session-c');
|
|
106
|
+
store.set('ble-path', 'wallet-d', 'session-d');
|
|
107
|
+
|
|
108
|
+
store.reconcileDeviceIdentity({
|
|
109
|
+
temporaryKey: 'ble-path',
|
|
110
|
+
nextDeviceId: 'stable-device-id',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(store.get('stable-device-id', 'wallet-a')).toBe('session-a');
|
|
114
|
+
expect(store.getStandard('stable-device-id')).toEqual({
|
|
115
|
+
passphraseState: 'wallet-a',
|
|
116
|
+
sessionId: 'session-a',
|
|
117
|
+
});
|
|
118
|
+
expect(store.get('stable-device-id', 'wallet-b')).toBe('session-b');
|
|
119
|
+
expect(store.get('stable-device-id', 'wallet-c')).toBe('session-c');
|
|
120
|
+
expect(store.get('stable-device-id', 'wallet-d')).toBe('session-d');
|
|
121
|
+
});
|
|
122
|
+
|
|
62
123
|
test('drops sessions from an old stable identity without overwriting the new device', () => {
|
|
63
124
|
const store = new DeviceWalletSessionStore();
|
|
64
125
|
store.set('device-a', 'hidden-a', 'session-a');
|
|
@@ -82,11 +143,16 @@ describe('DeviceWalletSessionStore', () => {
|
|
|
82
143
|
const store = new DeviceWalletSessionStore();
|
|
83
144
|
store.set('device-1', 'hidden-a', 'session-a');
|
|
84
145
|
store.set('device-1', 'hidden-b', 'session-b');
|
|
146
|
+
store.setStandard('device-1', 'standard-state', 'standard-session');
|
|
85
147
|
store.set('device-2', 'hidden-a', 'session-c');
|
|
86
148
|
|
|
87
149
|
store.delete('device-1', 'hidden-a');
|
|
88
150
|
expect(store.get('device-1', 'hidden-a')).toBeUndefined();
|
|
89
151
|
expect(store.get('device-1', 'hidden-b')).toBe('session-b');
|
|
152
|
+
expect(store.getStandard('device-1')?.sessionId).toBe('standard-session');
|
|
153
|
+
|
|
154
|
+
store.delete('device-1', 'standard-state');
|
|
155
|
+
expect(store.getStandard('device-1')).toBeUndefined();
|
|
90
156
|
|
|
91
157
|
store.deleteDevice('device-1');
|
|
92
158
|
expect(store.get('device-1', 'hidden-b')).toBeUndefined();
|
|
@@ -195,8 +261,7 @@ describe('ClearSessionCache', () => {
|
|
|
195
261
|
session_id: null,
|
|
196
262
|
});
|
|
197
263
|
expect(device.features).not.toHaveProperty('passphraseState');
|
|
198
|
-
expect(device.features
|
|
199
|
-
expect(device.features?.raw?.protocolV1Features).not.toHaveProperty('session_id');
|
|
264
|
+
expect(device.features).not.toHaveProperty('raw');
|
|
200
265
|
|
|
201
266
|
const method = new ClearSessionCache({
|
|
202
267
|
payload: {
|
|
@@ -287,7 +352,7 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
287
352
|
jest.restoreAllMocks();
|
|
288
353
|
});
|
|
289
354
|
|
|
290
|
-
test('verifies the live device id
|
|
355
|
+
test('verifies the live device id without resetting the cached wallet session', async () => {
|
|
291
356
|
const device = Device.fromDescriptor({ id: 'connect-b', path: 'connect-b' } as never);
|
|
292
357
|
device.features = {
|
|
293
358
|
protocol: 'V1',
|
|
@@ -307,15 +372,10 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
307
372
|
device.initialize({ deviceId: 'cached-device-a', passphraseState: 'hidden-a' })
|
|
308
373
|
).rejects.toMatchObject({ errorCode: HardwareErrorCode.DeviceCheckDeviceIdError });
|
|
309
374
|
expect(typedCall).toHaveBeenCalledTimes(1);
|
|
310
|
-
expect(typedCall).toHaveBeenCalledWith(
|
|
311
|
-
'Initialize',
|
|
312
|
-
'Features',
|
|
313
|
-
expect.not.objectContaining({ session_id: 'session-a' }),
|
|
314
|
-
expect.any(Object)
|
|
315
|
-
);
|
|
375
|
+
expect(typedCall).toHaveBeenCalledWith('GetFeatures', 'Features', {});
|
|
316
376
|
});
|
|
317
377
|
|
|
318
|
-
test('resumes a cached V1 wallet
|
|
378
|
+
test('resumes a cached V1 wallet after a non-destructive live identity read', async () => {
|
|
319
379
|
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
320
380
|
device.features = {
|
|
321
381
|
protocol: 'V1',
|
|
@@ -337,22 +397,82 @@ describe('Protocol V1 wallet identity initialization', () => {
|
|
|
337
397
|
await device.initialize({ deviceId: 'device-a', passphraseState: 'hidden-a' });
|
|
338
398
|
|
|
339
399
|
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
400
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
|
|
340
401
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
341
|
-
|
|
402
|
+
2,
|
|
342
403
|
'Initialize',
|
|
343
404
|
'Features',
|
|
344
|
-
expect.
|
|
405
|
+
expect.objectContaining({
|
|
406
|
+
session_id: 'session-a',
|
|
407
|
+
passphrase_state: 'hidden-a',
|
|
408
|
+
}),
|
|
345
409
|
expect.any(Object)
|
|
346
410
|
);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('selects the standard V1 wallet after a non-destructive live identity read', async () => {
|
|
414
|
+
const device = Device.fromDescriptor({ id: 'connect-a', path: 'connect-a' } as never);
|
|
415
|
+
device.features = {
|
|
416
|
+
protocol: 'V1',
|
|
417
|
+
deviceId: 'device-a',
|
|
418
|
+
unlocked: true,
|
|
419
|
+
passphraseProtection: false,
|
|
420
|
+
} as never;
|
|
421
|
+
const typedCall = jest
|
|
422
|
+
.fn()
|
|
423
|
+
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } })
|
|
424
|
+
.mockResolvedValueOnce({ type: 'Features', message: { device_id: 'device-a' } });
|
|
425
|
+
device.commands = { typedCall } as never;
|
|
426
|
+
jest.spyOn(TransportManager, 'reconfigure').mockResolvedValue(undefined);
|
|
427
|
+
|
|
428
|
+
await device.initialize({ deviceId: 'device-a' });
|
|
429
|
+
|
|
430
|
+
expect(typedCall).toHaveBeenCalledTimes(2);
|
|
431
|
+
expect(typedCall).toHaveBeenNthCalledWith(1, 'GetFeatures', 'Features', {});
|
|
347
432
|
expect(typedCall).toHaveBeenNthCalledWith(
|
|
348
433
|
2,
|
|
349
434
|
'Initialize',
|
|
350
435
|
'Features',
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
436
|
+
{
|
|
437
|
+
passphrase_state: undefined,
|
|
438
|
+
is_contains_attach: true,
|
|
439
|
+
},
|
|
355
440
|
expect.any(Object)
|
|
356
441
|
);
|
|
357
442
|
});
|
|
358
443
|
});
|
|
444
|
+
|
|
445
|
+
describe('Protocol V1 passphrase state classification', () => {
|
|
446
|
+
test('does not expose a standard-wallet address as passphrase state', async () => {
|
|
447
|
+
const typedCall = jest.fn().mockResolvedValue({
|
|
448
|
+
type: 'Address',
|
|
449
|
+
message: { address: 'standard-wallet-address' },
|
|
450
|
+
});
|
|
451
|
+
const method = new GetPassphraseState({
|
|
452
|
+
payload: {
|
|
453
|
+
method: 'getPassphraseState',
|
|
454
|
+
connectId: 'connect-a',
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
method.device = {
|
|
458
|
+
features: {},
|
|
459
|
+
commands: { typedCall },
|
|
460
|
+
isProtocolV2: () => false,
|
|
461
|
+
getCurrentDeviceType: () => EDeviceType.Classic,
|
|
462
|
+
getCurrentFirmwareVersionString: () => '1.0.0',
|
|
463
|
+
getCurrentPassphraseProtection: () => false,
|
|
464
|
+
getCurrentDeviceId: () => 'device-a',
|
|
465
|
+
getFeatures: jest.fn(),
|
|
466
|
+
getInternalState: () => undefined,
|
|
467
|
+
updateInternalState: jest.fn(),
|
|
468
|
+
} as never;
|
|
469
|
+
|
|
470
|
+
await expect(method.run()).resolves.toBeUndefined();
|
|
471
|
+
expect(typedCall).toHaveBeenCalledWith('GetAddress', 'Address', {
|
|
472
|
+
address_n: expect.any(Array),
|
|
473
|
+
coin_name: 'Testnet',
|
|
474
|
+
script_type: 'SPENDADDRESS',
|
|
475
|
+
show_display: false,
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
});
|