@onekeyfe/hd-core 1.2.8-alpha.10 → 1.2.8-alpha.101
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 +66 -13
- 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 +44 -20
- package/__tests__/logBlockEvent.test.ts +43 -0
- package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
- package/__tests__/protocol-v2.test.ts +0 -47
- package/__tests__/refresh-device-state.test.ts +19 -17
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +141 -0
- package/__tests__/zcash-sign-pczt.test.ts +387 -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/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceReboot.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/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.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/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/index.d.ts +71 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3369 -2887
- package/dist/inject.d.ts.map +1 -1
- package/dist/types/api/export.d.ts +2 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +4 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/zcashGetAddress.d.ts +22 -0
- package/dist/types/api/zcashGetAddress.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 +9 -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/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/FirmwareUpdateV4.ts +12 -32
- package/src/api/GetDeviceState.ts +2 -5
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/index.ts +2 -0
- package/src/api/protocol-v2/DeviceReboot.ts +2 -0
- package/src/api/zcash/ZcashGetAddress.ts +121 -0
- package/src/api/zcash/ZcashSignPczt.ts +155 -0
- package/src/core/index.ts +47 -20
- package/src/data/messages/messages-protocol-v2.json +138 -1
- package/src/device/Device.ts +1 -1
- package/src/device/DeviceCommands.ts +15 -1
- package/src/device/DeviceConnector.ts +4 -4
- package/src/events/core.ts +2 -2
- package/src/events/logBlockEvent.ts +4 -2
- package/src/index.ts +1 -0
- package/src/inject.ts +4 -0
- package/src/types/api/export.ts +2 -0
- package/src/types/api/index.ts +8 -0
- package/src/types/api/zcashGetAddress.ts +36 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +12 -0
- package/src/types/params.ts +1 -1
package/src/api/SearchDevices.ts
CHANGED
|
@@ -5,10 +5,20 @@ import TransportManager from '../data-manager/TransportManager';
|
|
|
5
5
|
import { DataManager } from '../data-manager';
|
|
6
6
|
import { LoggerNames, getDeviceTypeByBleName, getLogger } from '../utils';
|
|
7
7
|
import { DevicePool } from '../device/DevicePool';
|
|
8
|
+
import { buildProtocolV1FeaturesPayload } from '../deviceProfile/buildDeviceFeatures';
|
|
9
|
+
import { mapProtocolV2DeviceInfoToState } from '../device/DeviceStateMapper';
|
|
10
|
+
import { getSupportProtocolV1MessageSchema } from '../utils/deviceFeaturesUtils';
|
|
11
|
+
import { PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST } from '../protocols/protocol-v2/features';
|
|
8
12
|
|
|
9
|
-
import type { SearchDevice } from '../types/device';
|
|
13
|
+
import type { SearchDevice, UsbDeviceIdentity } from '../types/device';
|
|
14
|
+
import type { PROTO } from '../constants';
|
|
10
15
|
import type DeviceConnector from '../device/DeviceConnector';
|
|
11
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
AcquireInput,
|
|
18
|
+
OneKeyDeviceInfo as DeviceDescriptor,
|
|
19
|
+
ProtocolV2DeviceInfo,
|
|
20
|
+
Transport,
|
|
21
|
+
} from '@onekeyfe/hd-transport';
|
|
12
22
|
import type { Device } from '../device/Device';
|
|
13
23
|
|
|
14
24
|
const Log = getLogger(LoggerNames.DevicePool);
|
|
@@ -18,7 +28,10 @@ type RequestQueueLookup = {
|
|
|
18
28
|
getTask: (requestId: number) => { method: { device?: Device } } | undefined;
|
|
19
29
|
};
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
export type UsbIdentityProbeDescriptor = Pick<DeviceDescriptor, 'path' | 'commType'> &
|
|
32
|
+
Partial<Pick<DeviceDescriptor, 'id' | 'session'>>;
|
|
33
|
+
|
|
34
|
+
const getDescriptorKeys = (descriptor: UsbIdentityProbeDescriptor) =>
|
|
22
35
|
[descriptor.path, descriptor.id].filter(
|
|
23
36
|
(key): key is string => typeof key === 'string' && key.length > 0
|
|
24
37
|
);
|
|
@@ -33,11 +46,11 @@ const isOwnedByActiveWebUsbRequest = (
|
|
|
33
46
|
);
|
|
34
47
|
};
|
|
35
48
|
|
|
36
|
-
const getUsbHandle = (descriptor?:
|
|
49
|
+
const getUsbHandle = (descriptor?: UsbIdentityProbeDescriptor) =>
|
|
37
50
|
(descriptor as { device?: unknown } | undefined)?.device;
|
|
38
51
|
|
|
39
52
|
const getOwningRequestDevice = (
|
|
40
|
-
descriptor:
|
|
53
|
+
descriptor: UsbIdentityProbeDescriptor,
|
|
41
54
|
requestQueue?: RequestQueueLookup
|
|
42
55
|
) => {
|
|
43
56
|
if (!requestQueue) return undefined;
|
|
@@ -60,6 +73,158 @@ const getOwningRequestDevice = (
|
|
|
60
73
|
return undefined;
|
|
61
74
|
};
|
|
62
75
|
|
|
76
|
+
export const getOwnedUsbDeviceIdentity = (
|
|
77
|
+
descriptor: UsbIdentityProbeDescriptor,
|
|
78
|
+
requestQueue: RequestQueueLookup
|
|
79
|
+
): UsbDeviceIdentity | undefined => {
|
|
80
|
+
const owner = getOwningRequestDevice(descriptor, requestQueue);
|
|
81
|
+
if (!owner || descriptor.commType !== 'webusb' || owner.originalDescriptor?.commType !== 'webusb')
|
|
82
|
+
return undefined;
|
|
83
|
+
return {
|
|
84
|
+
path: descriptor.path,
|
|
85
|
+
serialNo: owner.getCurrentSerialNo() || null,
|
|
86
|
+
deviceId: owner.getCurrentDeviceId() || null,
|
|
87
|
+
deviceType: owner.getCurrentDeviceType(),
|
|
88
|
+
connectProtocol: owner.getProtocol(),
|
|
89
|
+
commType: 'webusb',
|
|
90
|
+
mode: owner.features?.mode ?? undefined,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type UsbIdentityProbeTransport = Pick<
|
|
95
|
+
Transport,
|
|
96
|
+
'release' | 'call' | 'getProtocolType' | 'configureProtocolV2'
|
|
97
|
+
> & {
|
|
98
|
+
init: (logger: object) => unknown;
|
|
99
|
+
enumerate: () => Promise<UsbIdentityProbeDescriptor[]>;
|
|
100
|
+
acquire: (input: AcquireInput) => Promise<string | undefined>;
|
|
101
|
+
configure: (signedData: string) => void | Promise<void>;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Identity discovery must not publish device events or log raw Features/session data.
|
|
105
|
+
const identityProbeLogger = {
|
|
106
|
+
debug: () => undefined,
|
|
107
|
+
log: () => undefined,
|
|
108
|
+
info: () => undefined,
|
|
109
|
+
warn: () => undefined,
|
|
110
|
+
error: () => undefined,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** Runs on a separate transport; never initializes or switches the current Core. */
|
|
114
|
+
export const probeUsbDeviceIdentitiesWithTransport = async (
|
|
115
|
+
transport: UsbIdentityProbeTransport,
|
|
116
|
+
commType: UsbDeviceIdentity['commType'],
|
|
117
|
+
getOwnedIdentity?: (descriptor: UsbIdentityProbeDescriptor) => UsbDeviceIdentity | undefined
|
|
118
|
+
): Promise<UsbDeviceIdentity[]> => {
|
|
119
|
+
if (commType !== 'webusb') return [];
|
|
120
|
+
await transport.init(identityProbeLogger);
|
|
121
|
+
const defaultMessages = JSON.stringify(DataManager.getProtobufMessages());
|
|
122
|
+
await transport.configure(defaultMessages);
|
|
123
|
+
await transport.configureProtocolV2?.(
|
|
124
|
+
JSON.stringify(DataManager.getProtobufMessages('v2Schema'))
|
|
125
|
+
);
|
|
126
|
+
const descriptors = await transport.enumerate();
|
|
127
|
+
const identities: UsbDeviceIdentity[] = [];
|
|
128
|
+
const readIdentity = async (
|
|
129
|
+
descriptor: UsbIdentityProbeDescriptor
|
|
130
|
+
): Promise<UsbDeviceIdentity | undefined> => {
|
|
131
|
+
const { path } = descriptor;
|
|
132
|
+
if (!path || descriptor.commType !== commType) return undefined;
|
|
133
|
+
if (descriptors.filter(candidate => candidate.path === path).length !== 1) return undefined;
|
|
134
|
+
const owner = getOwnedIdentity?.(descriptor);
|
|
135
|
+
if (owner) {
|
|
136
|
+
return owner;
|
|
137
|
+
}
|
|
138
|
+
// Do not borrow a browser handle or replace a Bridge session owned by another call.
|
|
139
|
+
if (descriptor.session || (getUsbHandle(descriptor) as USBDevice | undefined)?.opened)
|
|
140
|
+
return undefined;
|
|
141
|
+
|
|
142
|
+
let session: string | undefined;
|
|
143
|
+
let identified: UsbDeviceIdentity | undefined;
|
|
144
|
+
try {
|
|
145
|
+
await transport.configure(defaultMessages);
|
|
146
|
+
session = await transport.acquire({ path, previous: null, forceProtocolDetection: true });
|
|
147
|
+
if (!session) return undefined;
|
|
148
|
+
const protocol = transport.getProtocolType(path);
|
|
149
|
+
if (protocol === 'V1') {
|
|
150
|
+
const response = await transport.call(session, 'GetFeatures', {}, { timeoutMs: 3000 });
|
|
151
|
+
if (response.type !== 'Features') return undefined;
|
|
152
|
+
let features = buildProtocolV1FeaturesPayload(response.message as PROTO.Features);
|
|
153
|
+
// Classic-family loaders expose their supported information in Features.
|
|
154
|
+
// Touch/Pro retain their existing vendor-feature query.
|
|
155
|
+
if (
|
|
156
|
+
!features.serialNo &&
|
|
157
|
+
(features.mode !== 'bootloader' ||
|
|
158
|
+
features.deviceType === EDeviceType.Touch ||
|
|
159
|
+
features.deviceType === EDeviceType.Pro)
|
|
160
|
+
) {
|
|
161
|
+
const { messages, protocolV1MessageSchema } = getSupportProtocolV1MessageSchema(features);
|
|
162
|
+
// Legacy V1 has no OnekeyGetFeatures; keep the identity confirmed by GetFeatures.
|
|
163
|
+
if (protocolV1MessageSchema === 'v1CurrentSchema') {
|
|
164
|
+
if (messages) await transport.configure(JSON.stringify(messages));
|
|
165
|
+
const vendor = await transport.call(
|
|
166
|
+
session,
|
|
167
|
+
'OnekeyGetFeatures',
|
|
168
|
+
{},
|
|
169
|
+
{ timeoutMs: 3000 }
|
|
170
|
+
);
|
|
171
|
+
if (vendor.type === 'OnekeyFeatures') {
|
|
172
|
+
features = buildProtocolV1FeaturesPayload({
|
|
173
|
+
...response.message,
|
|
174
|
+
...vendor.message,
|
|
175
|
+
} as PROTO.Features);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
identified = {
|
|
180
|
+
path,
|
|
181
|
+
serialNo: features.serialNo || null,
|
|
182
|
+
deviceId: features.deviceId || null,
|
|
183
|
+
deviceType: features.deviceType,
|
|
184
|
+
connectProtocol: 'V1',
|
|
185
|
+
commType,
|
|
186
|
+
mode: features.mode ?? undefined,
|
|
187
|
+
};
|
|
188
|
+
} else if (protocol === 'V2') {
|
|
189
|
+
const response = await transport.call(
|
|
190
|
+
session,
|
|
191
|
+
'DeviceInfoGet',
|
|
192
|
+
PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
|
|
193
|
+
{ timeoutMs: 3000 }
|
|
194
|
+
);
|
|
195
|
+
if (response.type !== 'DeviceInfo') return undefined;
|
|
196
|
+
const { identity } = mapProtocolV2DeviceInfoToState(
|
|
197
|
+
response.message as ProtocolV2DeviceInfo
|
|
198
|
+
);
|
|
199
|
+
identified = {
|
|
200
|
+
path,
|
|
201
|
+
serialNo: identity?.serialNo || null,
|
|
202
|
+
deviceId: identity?.deviceId || null,
|
|
203
|
+
deviceType: identity?.deviceType ?? EDeviceType.Unknown,
|
|
204
|
+
connectProtocol: 'V2',
|
|
205
|
+
commType,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
// Discovery is best effort; a busy/disconnected device cannot confirm identity.
|
|
210
|
+
} finally {
|
|
211
|
+
if (session) {
|
|
212
|
+
try {
|
|
213
|
+
await transport.release(session, false);
|
|
214
|
+
} catch {
|
|
215
|
+
identified = undefined;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return identified;
|
|
220
|
+
};
|
|
221
|
+
for (const descriptor of descriptors) {
|
|
222
|
+
const identity = await readIdentity(descriptor);
|
|
223
|
+
if (identity) identities.push(identity);
|
|
224
|
+
}
|
|
225
|
+
return identities;
|
|
226
|
+
};
|
|
227
|
+
|
|
63
228
|
// A USB path is a routing key (the USB serial, a synthesized usb-vid-pid-name, or the
|
|
64
229
|
// bootloader placeholder), not a hardware identity, so it never fills uuid/serialNo.
|
|
65
230
|
const toSearchDeviceFromDescriptor = (descriptor: DeviceDescriptor): SearchDevice => {
|
|
@@ -81,7 +246,7 @@ const toSearchDevice = (device: Device): SearchDevice | null => {
|
|
|
81
246
|
};
|
|
82
247
|
|
|
83
248
|
export default class SearchDevices extends BaseMethod {
|
|
84
|
-
connector?: DeviceConnector;
|
|
249
|
+
connector?: DeviceConnector = undefined;
|
|
85
250
|
|
|
86
251
|
init() {
|
|
87
252
|
this.useDevice = false;
|
|
@@ -130,7 +295,7 @@ export default class SearchDevices extends BaseMethod {
|
|
|
130
295
|
// BLE discovery cannot provide a physical-device identity before initialization.
|
|
131
296
|
uuid: '',
|
|
132
297
|
deviceId: null,
|
|
133
|
-
name: bleName || device.name,
|
|
298
|
+
name: bleName || device.name || '',
|
|
134
299
|
deviceType: getDeviceTypeByBleName(bleName),
|
|
135
300
|
});
|
|
136
301
|
}
|
package/src/api/index.ts
CHANGED
|
@@ -175,3 +175,5 @@ export { default as benfenSignTransaction } from './benfen/BenfenSignTransaction
|
|
|
175
175
|
|
|
176
176
|
export { default as neoGetAddress } from './neo/NeoGetAddress';
|
|
177
177
|
export { default as neoSignTransaction } from './neo/NeoSignTransaction';
|
|
178
|
+
export { default as zcashGetAddress } from './zcash/ZcashGetAddress';
|
|
179
|
+
export { default as zcashSignPczt } from './zcash/ZcashSignPczt';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
|
|
2
2
|
|
|
3
|
+
import { UI_REQUEST } from '../../constants/ui-request';
|
|
3
4
|
import { BaseMethod } from '../BaseMethod';
|
|
4
5
|
import { normalizeRebootType } from './helpers';
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ export default class DeviceReboot extends BaseMethod<DeviceRebootParams> {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
init() {
|
|
15
|
+
this.allowDeviceMode.push(UI_REQUEST.BOOTLOADER);
|
|
14
16
|
this.skipForceUpdateCheck = true;
|
|
15
17
|
this.useDevicePassphraseState = false;
|
|
16
18
|
this.unlockPolicy = 'unlock-before-run';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { ZcashAddressScope, ZcashAddressType } from '@onekeyfe/hd-transport';
|
|
2
|
+
|
|
3
|
+
import { BaseMethod } from '../BaseMethod';
|
|
4
|
+
import { serializedPath, toHardened, validatePath } from '../helpers/pathUtils';
|
|
5
|
+
import { invalidParameter, validateParams, validateResult } from '../helpers/paramsValidator';
|
|
6
|
+
import { UI_REQUEST } from '../../constants/ui-request';
|
|
7
|
+
|
|
8
|
+
import type { ZcashGetAddress as HardwareZcashGetAddress } from '@onekeyfe/hd-transport';
|
|
9
|
+
import type { ZcashGetAddressParams, ZcashAddress } from '../../types/api/zcashGetAddress';
|
|
10
|
+
import type { DeviceFirmwareRange } from '../../types';
|
|
11
|
+
|
|
12
|
+
export default class ZcashGetAddress extends BaseMethod<HardwareZcashGetAddress[]> {
|
|
13
|
+
hasBundle = false;
|
|
14
|
+
|
|
15
|
+
getSupportedProtocols() {
|
|
16
|
+
return ['V2'] as const;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
init() {
|
|
20
|
+
this.checkDeviceId = true;
|
|
21
|
+
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
22
|
+
this.strictCheckDeviceSupport = true;
|
|
23
|
+
|
|
24
|
+
this.hasBundle = !!this.payload?.bundle;
|
|
25
|
+
const payload = this.hasBundle ? this.payload : { bundle: [this.payload] };
|
|
26
|
+
|
|
27
|
+
validateParams(payload, [{ name: 'bundle', type: 'array' }]);
|
|
28
|
+
|
|
29
|
+
this.params = [];
|
|
30
|
+
payload.bundle.forEach((batch: ZcashGetAddressParams) => {
|
|
31
|
+
// ZIP-32 account path: m/32'/133'/account'
|
|
32
|
+
const addressN = validatePath(batch.path, 3);
|
|
33
|
+
if (
|
|
34
|
+
addressN.length !== 3 ||
|
|
35
|
+
addressN[0] !== toHardened(32) ||
|
|
36
|
+
addressN[1] !== toHardened(133) ||
|
|
37
|
+
addressN[2] < toHardened(0) ||
|
|
38
|
+
addressN[2] > 0xffffffff
|
|
39
|
+
) {
|
|
40
|
+
throw invalidParameter("Zcash requires a ZIP-32 account path m/32'/133'/account'");
|
|
41
|
+
}
|
|
42
|
+
const addressType = batch.addressType ?? ZcashAddressType.UNIFIED_ORCHARD_P2PKH;
|
|
43
|
+
const scope = batch.scope ?? ZcashAddressScope.EXTERNAL;
|
|
44
|
+
const diversifierIndex = batch.diversifierIndex ?? 0;
|
|
45
|
+
if (
|
|
46
|
+
![
|
|
47
|
+
ZcashAddressType.UNIFIED_ORCHARD_P2PKH,
|
|
48
|
+
ZcashAddressType.TRANSPARENT_P2PKH,
|
|
49
|
+
ZcashAddressType.UNIFIED_ORCHARD,
|
|
50
|
+
].includes(addressType) ||
|
|
51
|
+
![ZcashAddressScope.EXTERNAL, ZcashAddressScope.INTERNAL].includes(scope)
|
|
52
|
+
) {
|
|
53
|
+
throw invalidParameter('Invalid Zcash address type or scope');
|
|
54
|
+
}
|
|
55
|
+
const maxIndex = addressType === ZcashAddressType.UNIFIED_ORCHARD ? 0xffffffff : 0x7fffffff;
|
|
56
|
+
if (
|
|
57
|
+
!Number.isSafeInteger(diversifierIndex) ||
|
|
58
|
+
diversifierIndex < 0 ||
|
|
59
|
+
diversifierIndex > maxIndex
|
|
60
|
+
) {
|
|
61
|
+
throw invalidParameter('Invalid Zcash diversifier index');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
validateParams(batch, [
|
|
65
|
+
{ name: 'path', required: true },
|
|
66
|
+
{ name: 'showOnOneKey', type: 'boolean' },
|
|
67
|
+
{ name: 'includeUfvk', type: 'boolean' },
|
|
68
|
+
{ name: 'includeSeedFingerprint', type: 'boolean' },
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
this.params.push({
|
|
72
|
+
address_n: addressN,
|
|
73
|
+
address_type: addressType,
|
|
74
|
+
scope,
|
|
75
|
+
diversifier_index: diversifierIndex,
|
|
76
|
+
show_display: batch.showOnOneKey ?? true,
|
|
77
|
+
include_ufvk: batch.includeUfvk ?? false,
|
|
78
|
+
include_seed_fingerprint: batch.includeSeedFingerprint ?? false,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getVersionRange(): DeviceFirmwareRange {
|
|
84
|
+
return {
|
|
85
|
+
model_pro2: {
|
|
86
|
+
min: '0.0.0',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async run() {
|
|
92
|
+
const responses: ZcashAddress[] = [];
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < this.params.length; i++) {
|
|
95
|
+
const param = this.params[i];
|
|
96
|
+
|
|
97
|
+
const res = await this.device.commands.typedCall('ZcashGetAddress', 'ZcashAddress', {
|
|
98
|
+
...param,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const path = serializedPath(param.address_n);
|
|
102
|
+
responses.push({
|
|
103
|
+
path,
|
|
104
|
+
address: res.message.address,
|
|
105
|
+
ufvk: res.message.ufvk,
|
|
106
|
+
seedFingerprint: res.message.seed_fingerprint,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.postPreviousAddressMessage({
|
|
110
|
+
address: res.message.address,
|
|
111
|
+
path,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
validateResult(responses, ['address'], {
|
|
116
|
+
expectedLength: this.params.length,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return Promise.resolve(this.hasBundle ? responses : responses[0]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
2
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
3
|
+
|
|
4
|
+
import { BaseMethod } from '../BaseMethod';
|
|
5
|
+
import { invalidParameter, validateParams } from '../helpers/paramsValidator';
|
|
6
|
+
import { stripHexPrefix } from '../helpers/hexUtils';
|
|
7
|
+
import { UI_REQUEST } from '../../constants/ui-request';
|
|
8
|
+
|
|
9
|
+
import type { ZcashSignPczt as HardwareZcashSignPczt } from '@onekeyfe/hd-transport';
|
|
10
|
+
import type { TypedCall, TypedResponseMessage } from '../../device/DeviceCommands';
|
|
11
|
+
import type { ZcashSignedPczt } from '../../types/api/zcashSignPczt';
|
|
12
|
+
import type { DeviceFirmwareRange } from '../../types';
|
|
13
|
+
|
|
14
|
+
const CHUNK_BYTE_SIZE = 1024;
|
|
15
|
+
// Host memory sanity bound on the device-announced length, not a device limit.
|
|
16
|
+
const SIGNED_PCZT_SANITY_MAX_BYTES = 16 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
export default class ZcashSignPczt extends BaseMethod<HardwareZcashSignPczt> {
|
|
19
|
+
pczt: Buffer = Buffer.alloc(0);
|
|
20
|
+
|
|
21
|
+
getSupportedProtocols() {
|
|
22
|
+
return ['V2'] as const;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
init() {
|
|
26
|
+
this.checkDeviceId = true;
|
|
27
|
+
this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
|
|
28
|
+
this.strictCheckDeviceSupport = true;
|
|
29
|
+
|
|
30
|
+
validateParams(this.payload, [{ name: 'pczt', type: 'hexString', required: true }]);
|
|
31
|
+
|
|
32
|
+
// Reject odd-length hex instead of left-padding it, which would shift every nibble.
|
|
33
|
+
const pcztHex = stripHexPrefix(this.payload.pczt);
|
|
34
|
+
if (pcztHex.length % 2 !== 0) {
|
|
35
|
+
throw invalidParameter('pczt hex must have an even length');
|
|
36
|
+
}
|
|
37
|
+
this.pczt = Buffer.from(pcztHex, 'hex');
|
|
38
|
+
if (this.pczt.length === 0) {
|
|
39
|
+
throw invalidParameter('pczt is empty');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.params = {
|
|
43
|
+
pczt_length: this.pczt.length,
|
|
44
|
+
pczt_initial_chunk: bytesToHex(this.pczt.subarray(0, CHUNK_BYTE_SIZE)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getVersionRange(): DeviceFirmwareRange {
|
|
49
|
+
return {
|
|
50
|
+
model_pro2: {
|
|
51
|
+
min: '0.0.0',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Upload: the device decides every chunk length via ZcashPcztChunkRequest and
|
|
57
|
+
// the host must answer with exactly that many bytes.
|
|
58
|
+
uploadPczt = async (
|
|
59
|
+
typedCall: TypedCall,
|
|
60
|
+
res: TypedResponseMessage<'ZcashSignedPczt'> | TypedResponseMessage<'ZcashPcztChunkRequest'>,
|
|
61
|
+
offset: number
|
|
62
|
+
): Promise<TypedResponseMessage<'ZcashSignedPczt'>> => {
|
|
63
|
+
if (res.type === 'ZcashSignedPczt') {
|
|
64
|
+
if (offset !== this.pczt.length) {
|
|
65
|
+
throw ERRORS.TypedError(
|
|
66
|
+
HardwareErrorCode.ResponseUnexpectTypeError,
|
|
67
|
+
'device completed signing before receiving the whole pczt'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { chunk_length: chunkLength } = res.message;
|
|
74
|
+
if (
|
|
75
|
+
!Number.isSafeInteger(chunkLength) ||
|
|
76
|
+
chunkLength <= 0 ||
|
|
77
|
+
offset + chunkLength > this.pczt.length
|
|
78
|
+
) {
|
|
79
|
+
throw ERRORS.TypedError(
|
|
80
|
+
HardwareErrorCode.ResponseUnexpectTypeError,
|
|
81
|
+
`device requested ${chunkLength} bytes at offset ${offset} of ${this.pczt.length}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const chunk = this.pczt.subarray(offset, offset + chunkLength);
|
|
86
|
+
const next = await typedCall(
|
|
87
|
+
'ZcashPcztChunkAck',
|
|
88
|
+
['ZcashSignedPczt', 'ZcashPcztChunkRequest'],
|
|
89
|
+
{
|
|
90
|
+
data_chunk: bytesToHex(chunk),
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return this.uploadPczt(typedCall, next, offset + chunkLength);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Download: the host decides chunk lengths via ZcashSignedPcztChunkRequest
|
|
98
|
+
// until the announced pczt_length has been received.
|
|
99
|
+
downloadSignedPczt = async (
|
|
100
|
+
typedCall: TypedCall,
|
|
101
|
+
res: TypedResponseMessage<'ZcashSignedPczt'>
|
|
102
|
+
): Promise<Buffer> => {
|
|
103
|
+
const { pczt_length: total, pczt_initial_chunk: initial } = res.message;
|
|
104
|
+
if (!Number.isSafeInteger(total) || total <= 0 || total > SIGNED_PCZT_SANITY_MAX_BYTES) {
|
|
105
|
+
throw ERRORS.TypedError(
|
|
106
|
+
HardwareErrorCode.ResponseUnexpectTypeError,
|
|
107
|
+
`device announced invalid signed pczt length ${total}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parts: Buffer[] = [Buffer.from(initial ?? '', 'hex')];
|
|
112
|
+
let received = parts[0].length;
|
|
113
|
+
if (received > total) {
|
|
114
|
+
throw ERRORS.TypedError(
|
|
115
|
+
HardwareErrorCode.ResponseUnexpectTypeError,
|
|
116
|
+
'device returned more initial pczt bytes than announced'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
while (received < total) {
|
|
121
|
+
const chunkLength = Math.min(CHUNK_BYTE_SIZE, total - received);
|
|
122
|
+
const ack = await typedCall('ZcashSignedPcztChunkRequest', 'ZcashPcztChunkAck', {
|
|
123
|
+
chunk_length: chunkLength,
|
|
124
|
+
});
|
|
125
|
+
const chunk = Buffer.from(ack.message.data_chunk ?? '', 'hex');
|
|
126
|
+
if (chunk.length !== chunkLength) {
|
|
127
|
+
throw ERRORS.TypedError(
|
|
128
|
+
HardwareErrorCode.ResponseUnexpectTypeError,
|
|
129
|
+
`device returned ${chunk.length} of ${chunkLength} requested bytes at offset ${received} of ${total}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
parts.push(chunk);
|
|
133
|
+
received += chunk.length;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Buffer.concat(parts);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
async run(): Promise<ZcashSignedPczt> {
|
|
140
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
141
|
+
|
|
142
|
+
const first = await typedCall('ZcashSignPczt', ['ZcashSignedPczt', 'ZcashPcztChunkRequest'], {
|
|
143
|
+
...this.params,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const signed = await this.uploadPczt(
|
|
147
|
+
typedCall,
|
|
148
|
+
first,
|
|
149
|
+
Math.min(CHUNK_BYTE_SIZE, this.pczt.length)
|
|
150
|
+
);
|
|
151
|
+
const pczt = await this.downloadSignedPczt(typedCall, signed);
|
|
152
|
+
|
|
153
|
+
return { pczt: bytesToHex(pczt) };
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
|
+
import { getOwnedUsbDeviceIdentity } from '../api/SearchDevices';
|
|
2
3
|
import EventEmitter from 'events';
|
|
3
4
|
import {
|
|
4
5
|
DeviceSessionPinType,
|
|
@@ -23,7 +24,6 @@ import {
|
|
|
23
24
|
createNeedUpgradeFirmwareHardwareError,
|
|
24
25
|
createNewFirmwareForceUpdateHardwareError,
|
|
25
26
|
createNewFirmwareUnReleaseHardwareError,
|
|
26
|
-
isBleStaleBondHardwareError,
|
|
27
27
|
} from '@onekeyfe/hd-shared';
|
|
28
28
|
|
|
29
29
|
import { LoggerNames, enableLog, getLogger, setLoggerPostMessage, wait } from '../utils';
|
|
@@ -126,6 +126,8 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
|
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
let _core: Core | undefined;
|
|
129
|
+
let transportSwitchGeneration = 0;
|
|
130
|
+
let pendingTransportSwitch: Promise<void> = Promise.resolve();
|
|
129
131
|
let _deviceList: DeviceList | undefined;
|
|
130
132
|
let _connector: DeviceConnector | undefined;
|
|
131
133
|
let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
|
|
@@ -991,15 +993,11 @@ function canSkipInitialize(method: BaseMethod, device: Device): boolean {
|
|
|
991
993
|
}
|
|
992
994
|
|
|
993
995
|
export function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
|
|
994
|
-
const typedError = error as { errorCode?: unknown;
|
|
995
|
-
const message =
|
|
996
|
-
typeof typedError?.message === 'string' ? typedError.message : String(error ?? '');
|
|
996
|
+
const typedError = error as { errorCode?: unknown; params?: { expectedProtocol?: unknown } };
|
|
997
997
|
return (
|
|
998
998
|
method.payload.connectProtocol === 'V2' &&
|
|
999
|
-
typedError?.errorCode === HardwareErrorCode.
|
|
1000
|
-
|
|
1001
|
-
message.includes('expected V2') &&
|
|
1002
|
-
message.includes('did not respond to expected protocol')
|
|
999
|
+
typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
|
|
1000
|
+
typedError.params?.expectedProtocol === 'V2'
|
|
1003
1001
|
);
|
|
1004
1002
|
}
|
|
1005
1003
|
|
|
@@ -1009,7 +1007,7 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
1009
1007
|
}
|
|
1010
1008
|
const typedError = error as {
|
|
1011
1009
|
errorCode?: unknown;
|
|
1012
|
-
params?: { acquireDeadlineExceeded?: unknown };
|
|
1010
|
+
params?: { acquireDeadlineExceeded?: unknown; expectedProtocol?: unknown };
|
|
1013
1011
|
};
|
|
1014
1012
|
if (
|
|
1015
1013
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
@@ -1020,6 +1018,8 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
1020
1018
|
return (
|
|
1021
1019
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
1022
1020
|
typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
1021
|
+
(typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
|
|
1022
|
+
!typedError.params?.expectedProtocol) ||
|
|
1023
1023
|
isRetryableBleProtocolV2ProbeError(method, error) ||
|
|
1024
1024
|
isMissingDetectedProtocolV2Error(method, error)
|
|
1025
1025
|
);
|
|
@@ -1035,10 +1035,6 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
|
|
|
1035
1035
|
);
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
|
-
export function isTerminalBleStaleBondError(error: unknown) {
|
|
1039
|
-
return isBleStaleBondHardwareError(error);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
1038
|
export function isDeviceIdentityMismatchError(error: unknown) {
|
|
1043
1039
|
return (
|
|
1044
1040
|
(error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.DeviceCheckDeviceIdError
|
|
@@ -1277,6 +1273,9 @@ const ensureConnected = async (
|
|
|
1277
1273
|
HardwareErrorCode.BridgeNotInstalled,
|
|
1278
1274
|
HardwareErrorCode.BridgeTimeoutError,
|
|
1279
1275
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
1276
|
+
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
1277
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
1278
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
1280
1279
|
].includes(error.errorCode)
|
|
1281
1280
|
) {
|
|
1282
1281
|
_deviceList = undefined;
|
|
@@ -1363,6 +1362,9 @@ const ensureConnected = async (
|
|
|
1363
1362
|
HardwareErrorCode.DeviceDetectInBootloaderMode,
|
|
1364
1363
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure,
|
|
1365
1364
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
1365
|
+
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
1366
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
1367
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
1366
1368
|
HardwareErrorCode.DeviceInterruptedFromUser,
|
|
1367
1369
|
HardwareErrorCode.CallQueueActionCancelled,
|
|
1368
1370
|
].includes(error.errorCode) ||
|
|
@@ -1370,7 +1372,6 @@ const ensureConnected = async (
|
|
|
1370
1372
|
[HardwareErrorCode.BleDeviceDisconnected, HardwareErrorCode.PollingTimeout].includes(
|
|
1371
1373
|
error.errorCode
|
|
1372
1374
|
)) ||
|
|
1373
|
-
isTerminalBleStaleBondError(error) ||
|
|
1374
1375
|
isDeviceIdentityMismatchError(error)
|
|
1375
1376
|
) {
|
|
1376
1377
|
reject(error);
|
|
@@ -1830,6 +1831,10 @@ export default class Core extends EventEmitter {
|
|
|
1830
1831
|
|
|
1831
1832
|
private requestQueue = new RequestQueue();
|
|
1832
1833
|
|
|
1834
|
+
getOwnedUsbDeviceIdentity(descriptor: Parameters<typeof getOwnedUsbDeviceIdentity>[0]) {
|
|
1835
|
+
return getOwnedUsbDeviceIdentity(descriptor, this.requestQueue);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1833
1838
|
private disposePromise?: Promise<void>;
|
|
1834
1839
|
|
|
1835
1840
|
// background task
|
|
@@ -1963,6 +1968,8 @@ export default class Core extends EventEmitter {
|
|
|
1963
1968
|
|
|
1964
1969
|
private async disposeResources(): Promise<void> {
|
|
1965
1970
|
Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
|
|
1971
|
+
transportSwitchGeneration += 1;
|
|
1972
|
+
pendingTransportSwitch = Promise.resolve();
|
|
1966
1973
|
pollingManager.stopAll();
|
|
1967
1974
|
this.requestQueue.abortAllRequests();
|
|
1968
1975
|
cleanup();
|
|
@@ -1997,6 +2004,8 @@ export default class Core extends EventEmitter {
|
|
|
1997
2004
|
}
|
|
1998
2005
|
|
|
1999
2006
|
export const initCore = () => {
|
|
2007
|
+
transportSwitchGeneration += 1;
|
|
2008
|
+
pendingTransportSwitch = Promise.resolve();
|
|
2000
2009
|
const core = new Core();
|
|
2001
2010
|
_core = core;
|
|
2002
2011
|
return core;
|
|
@@ -2026,6 +2035,8 @@ export const init = async (
|
|
|
2026
2035
|
plugin?: LowlevelTransportSharedPlugin
|
|
2027
2036
|
) => {
|
|
2028
2037
|
try {
|
|
2038
|
+
transportSwitchGeneration += 1;
|
|
2039
|
+
pendingTransportSwitch = Promise.resolve();
|
|
2029
2040
|
await DataManager.load(settings);
|
|
2030
2041
|
initTransport(Transport, plugin);
|
|
2031
2042
|
enableLog(DataManager.getSettings('debug'));
|
|
@@ -2051,10 +2062,26 @@ export const switchTransport = ({
|
|
|
2051
2062
|
Transport: any;
|
|
2052
2063
|
plugin?: LowlevelTransportSharedPlugin;
|
|
2053
2064
|
}) => {
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2065
|
+
const generation = transportSwitchGeneration;
|
|
2066
|
+
// Preserve request order while native transport cleanup is pending.
|
|
2067
|
+
const switching = pendingTransportSwitch
|
|
2068
|
+
.catch(() => undefined)
|
|
2069
|
+
.then(async () => {
|
|
2070
|
+
if (generation !== transportSwitchGeneration) {
|
|
2071
|
+
throw ERRORS.TypedError(HardwareErrorCode.NotInitialized);
|
|
2072
|
+
}
|
|
2073
|
+
_connector?.stop();
|
|
2074
|
+
await TransportManager.getTransport()?.stop?.();
|
|
2075
|
+
if (generation !== transportSwitchGeneration) {
|
|
2076
|
+
throw ERRORS.TypedError(HardwareErrorCode.NotInitialized);
|
|
2077
|
+
}
|
|
2078
|
+
DataManager.updateEnv(env);
|
|
2079
|
+
TransportManager.setTransport(Transport, plugin);
|
|
2080
|
+
_deviceList = undefined;
|
|
2081
|
+
DevicePool.resetState();
|
|
2082
|
+
_connector = undefined;
|
|
2083
|
+
initConnector();
|
|
2084
|
+
});
|
|
2085
|
+
pendingTransportSwitch = switching;
|
|
2086
|
+
return switching;
|
|
2060
2087
|
};
|