@onekeyfe/hd-core 1.2.8 → 1.2.10-alpha.2
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/README.md +27 -0
- package/__tests__/DeviceCommands.test.ts +66 -13
- package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +77 -0
- package/__tests__/allNetworkGetAddress-skippable.test.ts +41 -0
- package/__tests__/btc-derive-context-hash.test.ts +200 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +55 -3
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +191 -27
- package/__tests__/device-pool-state.test.ts +50 -0
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +331 -9
- package/__tests__/logger.test.ts +18 -0
- package/__tests__/monero-get-address.test.ts +147 -0
- package/__tests__/monero-key-image-sync.test.ts +272 -0
- package/__tests__/monero-sign-transaction.test.ts +436 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
- package/__tests__/protocol-v2-key-export-confirmation.test.ts +75 -0
- package/__tests__/protocol-v2-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +841 -46
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/sol-sign-offchain-message.test.ts +8 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +152 -0
- package/__tests__/zcash-sign-pczt.test.ts +525 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetDeviceState.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +3 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
- package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/helpers/pathUtils.d.ts +1 -0
- package/dist/api/helpers/pathUtils.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
- package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
- package/dist/api/monero/helpers.d.ts +5 -0
- package/dist/api/monero/helpers.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceReboot.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/zcash/ZcashGetAddress.d.ts +12 -0
- package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts +12 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/api/zcash/helpers.d.ts +2 -0
- package/dist/api/zcash/helpers.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +3 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/device/DevicePool.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +10 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +206 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4443 -2797
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/types/api/allNetworkGetAddress.d.ts +4 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
- package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
- package/dist/types/api/export.d.ts +8 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +16 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/moneroGetAddress.d.ts +15 -0
- package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
- package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
- package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
- package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
- package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
- package/dist/types/api/moneroSignTransaction.d.ts +32 -0
- package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
- package/dist/types/api/zcashGetAddress.d.ts +20 -0
- package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts +13 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/types/api/zcashSignPczt.d.ts +9 -0
- package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
- package/dist/types/device.d.ts +10 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/utils/logger.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 +2 -0
- package/src/api/FileWrite.ts +7 -1
- package/src/api/FirmwareUpdateV4.ts +366 -99
- package/src/api/GetDeviceState.ts +2 -5
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +46 -6
- package/src/api/btc/BTCDeriveContextHash.ts +90 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
- package/src/api/helpers/pathUtils.ts +23 -0
- package/src/api/helpers/protocolV2FileWrite.ts +382 -111
- package/src/api/index.ts +9 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +75 -0
- package/src/api/monero/MoneroKeyImageSync.ts +170 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -0
- package/src/api/protocol-v2/DeviceReboot.ts +2 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
- package/src/api/zcash/ZcashGetAddress.ts +108 -0
- package/src/api/zcash/ZcashGetFullViewingKey.ts +78 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/api/zcash/helpers.ts +16 -0
- package/src/core/index.ts +52 -24
- package/src/data/messages/messages-protocol-v2.json +152 -0
- package/src/data/messages/messages.json +51 -0
- package/src/device/Device.ts +32 -4
- package/src/device/DeviceCommands.ts +15 -1
- package/src/device/DeviceConnector.ts +4 -4
- package/src/device/DevicePool.ts +12 -2
- package/src/events/core.ts +2 -2
- package/src/events/logBlockEvent.ts +58 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +17 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/protocols/protocol-v2/uiInteraction.ts +1 -1
- package/src/types/api/allNetworkGetAddress.ts +8 -1
- package/src/types/api/btcDeriveContextHash.ts +15 -0
- package/src/types/api/export.ts +17 -0
- package/src/types/api/index.ts +24 -0
- package/src/types/api/moneroGetAddress.ts +27 -0
- package/src/types/api/moneroGetWatchKey.ts +28 -0
- package/src/types/api/moneroKeyImageSync.ts +45 -0
- package/src/types/api/moneroSignTransaction.ts +54 -0
- package/src/types/api/zcashGetAddress.ts +32 -0
- package/src/types/api/zcashGetFullViewingKey.ts +24 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/types/params.ts +1 -1
- package/src/utils/logger.ts +4 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import protocolTransport, { DeviceType } from '@onekeyfe/hd-transport';
|
|
2
|
+
import { EDeviceType } from '@onekeyfe/hd-shared';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getOwnedUsbDeviceIdentity,
|
|
6
|
+
probeUsbDeviceIdentitiesWithTransport,
|
|
7
|
+
} from '../src/api/SearchDevices';
|
|
8
|
+
import DataManager from '../src/data-manager/DataManager';
|
|
9
|
+
import TransportManager from '../src/data-manager/TransportManager';
|
|
10
|
+
|
|
11
|
+
import type { UsbIdentityProbeTransport } from '../src/api/SearchDevices';
|
|
12
|
+
import type { OneKeyDeviceInfo, Transport } from '@onekeyfe/hd-transport';
|
|
13
|
+
import type { ConnectSettings } from '../src/types';
|
|
14
|
+
import type { Device } from '../src/device/Device';
|
|
15
|
+
|
|
16
|
+
jest.mock('../src/data/config', () => ({
|
|
17
|
+
getSDKVersion: () => '1.2.8-alpha.12',
|
|
18
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.2.8-alpha.12/',
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const descriptor = { path: 'USB_DESCRIPTOR_PATH', commType: 'webusb' } as OneKeyDeviceInfo;
|
|
22
|
+
const createTransport = () => ({
|
|
23
|
+
init: jest.fn(),
|
|
24
|
+
configure: jest.fn(),
|
|
25
|
+
configureProtocolV2: jest.fn(),
|
|
26
|
+
enumerate: jest.fn().mockResolvedValue([descriptor]),
|
|
27
|
+
acquire: jest.fn().mockResolvedValue('usb-transport-handle'),
|
|
28
|
+
release: jest.fn().mockResolvedValue(undefined),
|
|
29
|
+
getProtocolType: jest.fn().mockReturnValue('V1'),
|
|
30
|
+
call: jest.fn().mockResolvedValue({
|
|
31
|
+
type: 'Features',
|
|
32
|
+
message: {
|
|
33
|
+
onekey_serial_no: 'PHYSICAL_SERIAL',
|
|
34
|
+
onekey_device_type: 'CLASSIC1S',
|
|
35
|
+
device_id: 'WALLET_ID',
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('isolated USB identity probe', () => {
|
|
41
|
+
test('identifies V1 by live identity while preserving the active BLE settings and transport', async () => {
|
|
42
|
+
const previousSettings = DataManager.settings;
|
|
43
|
+
const previousTransport = TransportManager.transport;
|
|
44
|
+
const activeBLE = { name: 'active-ble', pendingOperation: true } as unknown as Transport;
|
|
45
|
+
const settings = { env: 'desktop-web-ble' } as ConnectSettings;
|
|
46
|
+
DataManager.settings = settings;
|
|
47
|
+
TransportManager.transport = activeBLE;
|
|
48
|
+
const transport = createTransport();
|
|
49
|
+
try {
|
|
50
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toEqual([
|
|
51
|
+
{
|
|
52
|
+
path: 'USB_DESCRIPTOR_PATH',
|
|
53
|
+
serialNo: 'PHYSICAL_SERIAL',
|
|
54
|
+
deviceId: 'WALLET_ID',
|
|
55
|
+
deviceType: EDeviceType.Classic1s,
|
|
56
|
+
connectProtocol: 'V1',
|
|
57
|
+
commType: 'webusb',
|
|
58
|
+
mode: 'normal',
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
expect(DataManager.settings).toBe(settings);
|
|
62
|
+
expect(TransportManager.transport).toBe(activeBLE);
|
|
63
|
+
expect(transport.call).toHaveBeenCalledWith(
|
|
64
|
+
'usb-transport-handle',
|
|
65
|
+
'GetFeatures',
|
|
66
|
+
{},
|
|
67
|
+
{ timeoutMs: 3000 }
|
|
68
|
+
);
|
|
69
|
+
expect(transport.release).toHaveBeenCalledWith('usb-transport-handle', false);
|
|
70
|
+
} finally {
|
|
71
|
+
DataManager.settings = previousSettings;
|
|
72
|
+
TransportManager.transport = previousTransport;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test.each([
|
|
77
|
+
EDeviceType.Classic,
|
|
78
|
+
EDeviceType.Classic1s,
|
|
79
|
+
EDeviceType.ClassicPure,
|
|
80
|
+
EDeviceType.Mini,
|
|
81
|
+
EDeviceType.Touch,
|
|
82
|
+
EDeviceType.Pro,
|
|
83
|
+
])('reads the V1 identity for %s without initializing a wallet', async deviceType => {
|
|
84
|
+
const transport = createTransport();
|
|
85
|
+
transport.call.mockResolvedValue({
|
|
86
|
+
type: 'Features',
|
|
87
|
+
message: {
|
|
88
|
+
onekey_serial_no: 'V1_PHYSICAL_SERIAL',
|
|
89
|
+
onekey_device_type:
|
|
90
|
+
deviceType === EDeviceType.ClassicPure ? 'PURE' : deviceType.toUpperCase(),
|
|
91
|
+
device_id: 'V1_WALLET_ID',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const result = await probeUsbDeviceIdentitiesWithTransport(transport, 'webusb');
|
|
95
|
+
expect(result).toMatchObject([
|
|
96
|
+
{
|
|
97
|
+
serialNo: 'V1_PHYSICAL_SERIAL',
|
|
98
|
+
deviceId: 'V1_WALLET_ID',
|
|
99
|
+
deviceType,
|
|
100
|
+
connectProtocol: 'V1',
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
expect(transport.call.mock.calls.map(call => call[1])).toEqual(['GetFeatures']);
|
|
104
|
+
expect(transport.release).toHaveBeenCalledTimes(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('continues identifying other devices after one device disconnects', async () => {
|
|
108
|
+
const transport = createTransport();
|
|
109
|
+
transport.enumerate.mockResolvedValue([descriptor, { ...descriptor, path: 'SECOND_USB_PATH' }]);
|
|
110
|
+
transport.call.mockRejectedValueOnce(new Error('Disconnected'));
|
|
111
|
+
const result = await probeUsbDeviceIdentitiesWithTransport(transport, 'webusb');
|
|
112
|
+
expect(result).toMatchObject([{ path: 'SECOND_USB_PATH', serialNo: 'PHYSICAL_SERIAL' }]);
|
|
113
|
+
expect(transport.release).toHaveBeenCalledTimes(2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test.each([DeviceType.PRO2, DeviceType.NEO])(
|
|
117
|
+
'reads V2 hardware identity without opening a wallet (%s)',
|
|
118
|
+
async deviceType => {
|
|
119
|
+
const transport = createTransport();
|
|
120
|
+
transport.getProtocolType.mockReturnValue('V2');
|
|
121
|
+
transport.call.mockResolvedValue({
|
|
122
|
+
type: 'DeviceInfo',
|
|
123
|
+
message: { hw: { Device_type: deviceType, serial_no: 'V2_PHYSICAL_SERIAL' } },
|
|
124
|
+
});
|
|
125
|
+
const result = await probeUsbDeviceIdentitiesWithTransport(transport, 'webusb');
|
|
126
|
+
expect(result[0]).toMatchObject({
|
|
127
|
+
serialNo: 'V2_PHYSICAL_SERIAL',
|
|
128
|
+
connectProtocol: 'V2',
|
|
129
|
+
deviceId: null,
|
|
130
|
+
});
|
|
131
|
+
expect(transport.call.mock.calls.map(call => call[1])).toEqual(['DeviceInfoGet']);
|
|
132
|
+
expect(transport.release).toHaveBeenCalledTimes(1);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
test('reads vendor identity when legacy Features do not report a physical serial', async () => {
|
|
137
|
+
const transport = createTransport();
|
|
138
|
+
transport.call
|
|
139
|
+
.mockResolvedValueOnce({
|
|
140
|
+
type: 'Features',
|
|
141
|
+
message: { device_id: 'WALLET_ID', onekey_device_type: 'CLASSIC1S' },
|
|
142
|
+
})
|
|
143
|
+
.mockResolvedValueOnce({
|
|
144
|
+
type: 'OnekeyFeatures',
|
|
145
|
+
message: { onekey_serial_no: 'VENDOR_SERIAL' },
|
|
146
|
+
});
|
|
147
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toMatchObject(
|
|
148
|
+
[{ serialNo: 'VENDOR_SERIAL', deviceId: 'WALLET_ID' }]
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test.each([
|
|
153
|
+
{ onekey_device_type: 'CLASSIC', major_version: 2, minor_version: 8, patch_version: 0 },
|
|
154
|
+
{ onekey_device_type: 'TOUCH', major_version: 4, minor_version: 4, patch_version: 0 },
|
|
155
|
+
])(
|
|
156
|
+
'preserves GetFeatures identity when the legacy V1 schema has no vendor query (%s)',
|
|
157
|
+
async firmware => {
|
|
158
|
+
const transport = createTransport();
|
|
159
|
+
let messages = protocolTransport.parseConfigure(
|
|
160
|
+
JSON.stringify(DataManager.getProtobufMessages())
|
|
161
|
+
);
|
|
162
|
+
transport.configure.mockImplementation((schema: string) => {
|
|
163
|
+
messages = protocolTransport.parseConfigure(schema);
|
|
164
|
+
});
|
|
165
|
+
transport.call.mockImplementation((_session, name, data) => {
|
|
166
|
+
protocolTransport.ProtocolV1.encodeMessageChunks(messages, name, data);
|
|
167
|
+
return Promise.resolve({
|
|
168
|
+
type: 'Features',
|
|
169
|
+
message: { ...firmware, device_id: 'LEGACY_WALLET_ID' },
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
await expect(
|
|
173
|
+
probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')
|
|
174
|
+
).resolves.toMatchObject([
|
|
175
|
+
{
|
|
176
|
+
path: descriptor.path,
|
|
177
|
+
serialNo: null,
|
|
178
|
+
deviceId: 'LEGACY_WALLET_ID',
|
|
179
|
+
connectProtocol: 'V1',
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
expect(transport.call.mock.calls.map(call => call[1])).toEqual(['GetFeatures']);
|
|
183
|
+
expect(transport.release).toHaveBeenCalledWith('usb-transport-handle', false);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
test('does not substitute a USB path for a missing business identity', async () => {
|
|
188
|
+
const transport = createTransport();
|
|
189
|
+
transport.call
|
|
190
|
+
.mockResolvedValueOnce({ type: 'Features', message: { onekey_device_type: 'MINI' } })
|
|
191
|
+
.mockResolvedValueOnce({ type: 'Failure', message: {} });
|
|
192
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toMatchObject(
|
|
193
|
+
[{ path: 'USB_DESCRIPTOR_PATH', serialNo: null, deviceId: null }]
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test.each([
|
|
198
|
+
['CLASSIC1S', EDeviceType.Classic1s],
|
|
199
|
+
['PURE', EDeviceType.ClassicPure],
|
|
200
|
+
])(
|
|
201
|
+
'retains %s bootloader state without querying unsupported vendor features',
|
|
202
|
+
async (model, deviceType) => {
|
|
203
|
+
const transport = createTransport();
|
|
204
|
+
const path = '000000000000000000000000';
|
|
205
|
+
transport.enumerate.mockResolvedValue([{ ...descriptor, path }]);
|
|
206
|
+
transport.call.mockImplementation((_session, method) => {
|
|
207
|
+
if (method !== 'GetFeatures')
|
|
208
|
+
return Promise.reject(new Error('Unsupported loader query timed out'));
|
|
209
|
+
return Promise.resolve({
|
|
210
|
+
type: 'Features',
|
|
211
|
+
message: {
|
|
212
|
+
onekey_device_type: model,
|
|
213
|
+
model: '1',
|
|
214
|
+
bootloader_mode: true,
|
|
215
|
+
firmware_present: true,
|
|
216
|
+
major_version: 3,
|
|
217
|
+
minor_version: 0,
|
|
218
|
+
patch_version: 0,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toEqual([
|
|
223
|
+
{
|
|
224
|
+
path,
|
|
225
|
+
serialNo: null,
|
|
226
|
+
deviceId: null,
|
|
227
|
+
deviceType,
|
|
228
|
+
mode: 'bootloader',
|
|
229
|
+
connectProtocol: 'V1',
|
|
230
|
+
commType: 'webusb',
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
expect(transport.call.mock.calls.map(call => call[1])).toEqual(['GetFeatures']);
|
|
234
|
+
expect(transport.release).toHaveBeenCalledWith('usb-transport-handle', false);
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
test.each(['TOUCH', 'PRO'])(
|
|
239
|
+
'preserves the existing %s loader vendor-identity query',
|
|
240
|
+
async model => {
|
|
241
|
+
const transport = createTransport();
|
|
242
|
+
transport.call
|
|
243
|
+
.mockResolvedValueOnce({
|
|
244
|
+
type: 'Features',
|
|
245
|
+
message: { onekey_device_type: model, bootloader_mode: true },
|
|
246
|
+
})
|
|
247
|
+
.mockResolvedValueOnce({
|
|
248
|
+
type: 'OnekeyFeatures',
|
|
249
|
+
message: { onekey_serial_no: 'VENDOR_SERIAL' },
|
|
250
|
+
});
|
|
251
|
+
await expect(
|
|
252
|
+
probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')
|
|
253
|
+
).resolves.toMatchObject([{ serialNo: 'VENDOR_SERIAL', mode: 'bootloader' }]);
|
|
254
|
+
expect(transport.call.mock.calls.map(call => call[1])).toEqual([
|
|
255
|
+
'GetFeatures',
|
|
256
|
+
'OnekeyGetFeatures',
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
test.each([
|
|
262
|
+
{ ...descriptor, device: { opened: true } },
|
|
263
|
+
{ ...descriptor, commType: 'bridge', session: 'owned-bridge-transport' },
|
|
264
|
+
])('leaves an owned USB connection untouched', async occupied => {
|
|
265
|
+
const transport = createTransport();
|
|
266
|
+
transport.enumerate.mockResolvedValue([occupied]);
|
|
267
|
+
await expect(
|
|
268
|
+
probeUsbDeviceIdentitiesWithTransport(transport, occupied.commType as 'webusb' | 'bridge')
|
|
269
|
+
).resolves.toEqual([]);
|
|
270
|
+
expect(transport.acquire).not.toHaveBeenCalled();
|
|
271
|
+
expect(transport.release).not.toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test.each([undefined, 'occupied-transport-handle'])(
|
|
275
|
+
'does not initialize or acquire a Bridge identity probe (session: %s)',
|
|
276
|
+
async session => {
|
|
277
|
+
const transport = createTransport();
|
|
278
|
+
transport.enumerate.mockResolvedValue([{ ...descriptor, commType: 'bridge', session }]);
|
|
279
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'bridge')).resolves.toEqual([]);
|
|
280
|
+
expect(transport.init).not.toHaveBeenCalled();
|
|
281
|
+
expect(transport.enumerate).not.toHaveBeenCalled();
|
|
282
|
+
expect(transport.acquire).not.toHaveBeenCalled();
|
|
283
|
+
expect(transport.release).not.toHaveBeenCalled();
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
test('reuses identity only for the exact active WebUSB handle', () => {
|
|
288
|
+
const handle = {};
|
|
289
|
+
const endpoint = { ...descriptor, device: handle };
|
|
290
|
+
const device = {
|
|
291
|
+
features: {},
|
|
292
|
+
mainId: descriptor.path,
|
|
293
|
+
originalDescriptor: endpoint,
|
|
294
|
+
getCurrentSerialNo: () => 'OWNER_SERIAL',
|
|
295
|
+
getCurrentDeviceId: () => 'OWNER_WALLET',
|
|
296
|
+
getCurrentDeviceType: () => EDeviceType.Pro,
|
|
297
|
+
getProtocol: () => 'V1',
|
|
298
|
+
} as unknown as Device;
|
|
299
|
+
const queue = {
|
|
300
|
+
getRequestTasksIdByConnectId: () => [1],
|
|
301
|
+
getTask: () => ({ method: { device } }),
|
|
302
|
+
};
|
|
303
|
+
expect(getOwnedUsbDeviceIdentity(endpoint, queue)).toMatchObject({ serialNo: 'OWNER_SERIAL' });
|
|
304
|
+
expect(
|
|
305
|
+
getOwnedUsbDeviceIdentity({ ...endpoint, device: {} } as typeof endpoint, queue)
|
|
306
|
+
).toBeUndefined();
|
|
307
|
+
expect(getOwnedUsbDeviceIdentity({ ...endpoint, commType: 'ble' }, queue)).toBeUndefined();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('returns the active request owner identity without acquiring its USB handle', async () => {
|
|
311
|
+
const transport = createTransport();
|
|
312
|
+
transport.enumerate.mockResolvedValue([{ ...descriptor, device: { opened: true } }]);
|
|
313
|
+
const owner = {
|
|
314
|
+
path: descriptor.path,
|
|
315
|
+
serialNo: 'OWNER_SERIAL',
|
|
316
|
+
deviceId: 'OWNER_WALLET',
|
|
317
|
+
deviceType: EDeviceType.Pro,
|
|
318
|
+
connectProtocol: 'V1' as const,
|
|
319
|
+
commType: 'webusb' as const,
|
|
320
|
+
};
|
|
321
|
+
await expect(
|
|
322
|
+
probeUsbDeviceIdentitiesWithTransport(transport, 'webusb', () => owner)
|
|
323
|
+
).resolves.toEqual([owner]);
|
|
324
|
+
expect(transport.acquire).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('rejects ambiguous paths rather than probing a different USB handle', async () => {
|
|
328
|
+
const transport = createTransport();
|
|
329
|
+
transport.enumerate.mockResolvedValue([descriptor, { ...descriptor }]);
|
|
330
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toEqual([]);
|
|
331
|
+
expect(transport.acquire).not.toHaveBeenCalled();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test.each(['read failure', 'unexpected response', 'release failure'] as const)(
|
|
335
|
+
'cleans up without approving the target after %s',
|
|
336
|
+
async failure => {
|
|
337
|
+
const transport = createTransport();
|
|
338
|
+
if (failure === 'read failure')
|
|
339
|
+
transport.call.mockRejectedValue(new Error('USB disconnected'));
|
|
340
|
+
if (failure === 'unexpected response')
|
|
341
|
+
transport.call.mockResolvedValue({ type: 'Failure', message: {} });
|
|
342
|
+
if (failure === 'release failure')
|
|
343
|
+
transport.release.mockRejectedValue(new Error('release failed'));
|
|
344
|
+
await expect(probeUsbDeviceIdentitiesWithTransport(transport, 'webusb')).resolves.toEqual([]);
|
|
345
|
+
expect(transport.release).toHaveBeenCalledTimes(1);
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
test('does not approve an endpoint without a wire-confirmed protocol', async () => {
|
|
350
|
+
const transport = createTransport();
|
|
351
|
+
transport.getProtocolType.mockReturnValue(undefined);
|
|
352
|
+
await expect(
|
|
353
|
+
probeUsbDeviceIdentitiesWithTransport(transport as UsbIdentityProbeTransport, 'webusb')
|
|
354
|
+
).resolves.toEqual([]);
|
|
355
|
+
expect(transport.call).not.toHaveBeenCalled();
|
|
356
|
+
expect(transport.release).toHaveBeenCalledTimes(1);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import * as protobuf from 'protobufjs/light';
|
|
2
|
+
import transport from '@onekeyfe/hd-transport';
|
|
3
|
+
|
|
4
|
+
import messagesJson from '../src/data/messages/messages-protocol-v2.json';
|
|
5
|
+
|
|
6
|
+
// Round-trips the Zcash messages through the SDK's real protobuf encoder and
|
|
7
|
+
// decoder against the shipped protocol V2 schema, pinning wire ids and defaults.
|
|
8
|
+
const {
|
|
9
|
+
createMessageFromName,
|
|
10
|
+
decodeProtobuf: decode,
|
|
11
|
+
encodeProtobuf: encode,
|
|
12
|
+
parseConfigure,
|
|
13
|
+
} = transport;
|
|
14
|
+
const messages = parseConfigure(messagesJson as protobuf.INamespace);
|
|
15
|
+
|
|
16
|
+
const wireFieldIds = (buf: Uint8Array) => {
|
|
17
|
+
const reader = protobuf.Reader.create(buf);
|
|
18
|
+
const ids: number[] = [];
|
|
19
|
+
while (reader.pos < reader.len) {
|
|
20
|
+
const tag = reader.uint32();
|
|
21
|
+
// eslint-disable-next-line no-bitwise
|
|
22
|
+
ids.push(tag >>> 3);
|
|
23
|
+
// eslint-disable-next-line no-bitwise
|
|
24
|
+
reader.skipType(tag & 7);
|
|
25
|
+
}
|
|
26
|
+
return ids;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const roundTrip = (name: string, data: Record<string, unknown>) => {
|
|
30
|
+
const { Message, messageTypeId } = createMessageFromName(messages, name);
|
|
31
|
+
const encoded = encode(Message, data);
|
|
32
|
+
const bytes = new Uint8Array(encoded.toBuffer());
|
|
33
|
+
return { messageTypeId, ids: wireFieldIds(bytes), decoded: decode(Message, encoded) };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('Zcash protobuf messages (protocol V2)', () => {
|
|
37
|
+
test.each([
|
|
38
|
+
['ZcashGetAddress', 12400],
|
|
39
|
+
['ZcashAddress', 12401],
|
|
40
|
+
['ZcashSignPczt', 12402],
|
|
41
|
+
['ZcashSignedPczt', 12403],
|
|
42
|
+
['ZcashPcztChunkRequest', 12404],
|
|
43
|
+
['ZcashPcztChunkAck', 12405],
|
|
44
|
+
['ZcashSignedPcztChunkRequest', 12406],
|
|
45
|
+
['ZcashGetFullViewingKey', 12407],
|
|
46
|
+
['ZcashFullViewingKey', 12408],
|
|
47
|
+
])('%s has wire type %d', (name, typeId) => {
|
|
48
|
+
expect(createMessageFromName(messages, name).messageTypeId).toBe(typeId);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test.each(['ZcashSignPczt', 'ZcashSignedPczt'])('%s round trips length and bytes', name => {
|
|
52
|
+
const chunk = 'ab'.repeat(1024);
|
|
53
|
+
const { ids, decoded } = roundTrip(name, { pczt_length: 4096, pczt_initial_chunk: chunk });
|
|
54
|
+
expect(ids).toEqual([1, 2]);
|
|
55
|
+
expect(decoded).toEqual({ pczt_length: 4096, pczt_initial_chunk: chunk });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('ZcashPcztChunkAck round trips the data chunk', () => {
|
|
59
|
+
const { ids, decoded } = roundTrip('ZcashPcztChunkAck', { data_chunk: '00ff10' });
|
|
60
|
+
expect(ids).toEqual([1]);
|
|
61
|
+
expect(decoded).toEqual({ data_chunk: '00ff10' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test.each(['ZcashPcztChunkRequest', 'ZcashSignedPcztChunkRequest'])(
|
|
65
|
+
'%s round trips the chunk length',
|
|
66
|
+
name => {
|
|
67
|
+
const { ids, decoded } = roundTrip(name, { chunk_length: 1024 });
|
|
68
|
+
expect(ids).toEqual([1]);
|
|
69
|
+
expect(decoded).toEqual({ chunk_length: 1024 });
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
test('ZcashGetAddress encodes every field with its schema id', () => {
|
|
74
|
+
const { ids, decoded } = roundTrip('ZcashGetAddress', {
|
|
75
|
+
address_n: [2147483680, 2147483781, 2147483648],
|
|
76
|
+
show_display: true,
|
|
77
|
+
include_seed_fingerprint: true,
|
|
78
|
+
diversifier_index: 0xffffffff,
|
|
79
|
+
address_type: 2,
|
|
80
|
+
scope: 1,
|
|
81
|
+
});
|
|
82
|
+
// address_n is unpacked: one tag per element. Field 4 (include_ufvk) was removed.
|
|
83
|
+
expect(ids).toEqual([1, 1, 1, 2, 3, 5, 6, 7]);
|
|
84
|
+
expect(decoded).toEqual({
|
|
85
|
+
address_n: [2147483680, 2147483781, 2147483648],
|
|
86
|
+
show_display: true,
|
|
87
|
+
include_seed_fingerprint: true,
|
|
88
|
+
diversifier_index: 0xffffffff,
|
|
89
|
+
address_type: 'UNIFIED_ORCHARD',
|
|
90
|
+
scope: 'INTERNAL',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('ZcashGetAddress enum values match the schema', () => {
|
|
95
|
+
const addressType = messages.lookupEnum('ZcashAddressType').values;
|
|
96
|
+
const scope = messages.lookupEnum('ZcashAddressScope').values;
|
|
97
|
+
expect(addressType).toEqual({
|
|
98
|
+
UNIFIED_ORCHARD_P2PKH: 0,
|
|
99
|
+
TRANSPARENT_P2PKH: 1,
|
|
100
|
+
UNIFIED_ORCHARD: 2,
|
|
101
|
+
});
|
|
102
|
+
expect(scope).toEqual({ EXTERNAL: 0, INTERNAL: 1 });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('ZcashGetAddress omits unset optional fields and keeps schema defaults', () => {
|
|
106
|
+
const { Message } = createMessageFromName(messages, 'ZcashGetAddress');
|
|
107
|
+
const { ids } = roundTrip('ZcashGetAddress', { address_n: [2147483680] });
|
|
108
|
+
expect(ids).toEqual([1]);
|
|
109
|
+
expect(Message.fields.diversifier_index.defaultValue).toBe(0);
|
|
110
|
+
expect(Message.fields.address_type.defaultValue).toBe(0);
|
|
111
|
+
expect(Message.fields.scope.defaultValue).toBe(0);
|
|
112
|
+
const empty = Message.decode(new Uint8Array(0)) as unknown as Record<string, unknown>;
|
|
113
|
+
expect(empty.diversifier_index).toBe(0);
|
|
114
|
+
expect(empty.address_type).toBe(0);
|
|
115
|
+
expect(empty.scope).toBe(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('ZcashAddress decodes the optional seed fingerprint and no viewing key', () => {
|
|
119
|
+
const fingerprint = '0f'.repeat(32);
|
|
120
|
+
const full = roundTrip('ZcashAddress', { address: 'u1abc', seed_fingerprint: fingerprint });
|
|
121
|
+
expect(full.ids).toEqual([1, 3]);
|
|
122
|
+
expect(full.decoded).toEqual({ address: 'u1abc', seed_fingerprint: fingerprint });
|
|
123
|
+
|
|
124
|
+
const minimal = roundTrip('ZcashAddress', { address: 't1abc' });
|
|
125
|
+
expect(minimal.ids).toEqual([1]);
|
|
126
|
+
expect(minimal.decoded).toEqual({ address: 't1abc', seed_fingerprint: null });
|
|
127
|
+
expect(createMessageFromName(messages, 'ZcashAddress').Message.fields).not.toHaveProperty(
|
|
128
|
+
'ufvk'
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('ZcashGetFullViewingKey carries only the account path', () => {
|
|
133
|
+
const { ids, decoded } = roundTrip('ZcashGetFullViewingKey', {
|
|
134
|
+
address_n: [2147483680, 2147483781, 2147483648],
|
|
135
|
+
});
|
|
136
|
+
expect(ids).toEqual([1, 1, 1]);
|
|
137
|
+
expect(decoded).toEqual({ address_n: [2147483680, 2147483781, 2147483648] });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('ZcashFullViewingKey round trips the required UFVK', () => {
|
|
141
|
+
const { ids, decoded } = roundTrip('ZcashFullViewingKey', { ufvk: 'uview1abc' });
|
|
142
|
+
expect(ids).toEqual([1]);
|
|
143
|
+
expect(decoded).toEqual({ ufvk: 'uview1abc' });
|
|
144
|
+
const { Message } = createMessageFromName(messages, 'ZcashFullViewingKey');
|
|
145
|
+
expect(() => Message.decode(new Uint8Array(0))).toThrow(/missing required/);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('required fields are enforced on decode', () => {
|
|
149
|
+
const { Message } = createMessageFromName(messages, 'ZcashSignedPczt');
|
|
150
|
+
expect(() => Message.decode(new Uint8Array(0))).toThrow(/missing required/);
|
|
151
|
+
});
|
|
152
|
+
});
|