@onekeyfe/hd-core 1.2.8-alpha.9 → 1.2.10-alpha.1

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.
Files changed (135) hide show
  1. package/README.md +27 -0
  2. package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +78 -0
  3. package/__tests__/btc-derive-context-hash.test.ts +200 -0
  4. package/__tests__/core-dispose.test.ts +114 -1
  5. package/__tests__/core-error-output.test.ts +21 -0
  6. package/__tests__/device-connector-protocol.test.ts +5 -2
  7. package/__tests__/device-lifecycle-events.test.ts +127 -10
  8. package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
  9. package/__tests__/logBlockEvent.test.ts +331 -9
  10. package/__tests__/logger.test.ts +18 -0
  11. package/__tests__/monero-get-address.test.ts +147 -0
  12. package/__tests__/monero-key-image-sync.test.ts +272 -0
  13. package/__tests__/monero-sign-transaction.test.ts +436 -0
  14. package/__tests__/protocol-v2-resources.test.ts +14 -0
  15. package/__tests__/protocol-v2.test.ts +845 -3
  16. package/__tests__/protocolV2FileWrite.test.ts +454 -7
  17. package/__tests__/search-devices.test.ts +36 -0
  18. package/__tests__/sol-sign-offchain-message.test.ts +8 -0
  19. package/__tests__/usb-identity-probe.test.ts +358 -0
  20. package/__tests__/zcash-protobuf.test.ts +141 -0
  21. package/__tests__/zcash-sign-pczt.test.ts +446 -0
  22. package/dist/api/BaseMethod.d.ts.map +1 -1
  23. package/dist/api/FileWrite.d.ts.map +1 -1
  24. package/dist/api/FirmwareUpdateV4.d.ts +5 -0
  25. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  26. package/dist/api/SearchDevices.d.ts +21 -1
  27. package/dist/api/SearchDevices.d.ts.map +1 -1
  28. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  29. package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
  30. package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
  31. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  32. package/dist/api/helpers/pathUtils.d.ts +1 -0
  33. package/dist/api/helpers/pathUtils.d.ts.map +1 -1
  34. package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
  35. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  36. package/dist/api/index.d.ts +7 -0
  37. package/dist/api/index.d.ts.map +1 -1
  38. package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
  39. package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
  40. package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
  41. package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
  42. package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
  43. package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
  44. package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
  45. package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
  46. package/dist/api/monero/helpers.d.ts +5 -0
  47. package/dist/api/monero/helpers.d.ts.map +1 -0
  48. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  49. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  50. package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
  51. package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
  52. package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
  53. package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
  54. package/dist/core/index.d.ts +3 -1
  55. package/dist/core/index.d.ts.map +1 -1
  56. package/dist/device/Device.d.ts +1 -0
  57. package/dist/device/Device.d.ts.map +1 -1
  58. package/dist/device/DeviceCommands.d.ts +4 -4
  59. package/dist/events/logBlockEvent.d.ts +10 -0
  60. package/dist/events/logBlockEvent.d.ts.map +1 -1
  61. package/dist/events/ui-request.d.ts +2 -0
  62. package/dist/events/ui-request.d.ts.map +1 -1
  63. package/dist/index.d.ts +192 -7
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +5751 -4191
  66. package/dist/inject.d.ts.map +1 -1
  67. package/dist/protocols/protocol-v2/resources.d.ts +1 -0
  68. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  69. package/dist/types/api/allNetworkGetAddress.d.ts +5 -1
  70. package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
  71. package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
  72. package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
  73. package/dist/types/api/export.d.ts +7 -0
  74. package/dist/types/api/export.d.ts.map +1 -1
  75. package/dist/types/api/index.d.ts +14 -0
  76. package/dist/types/api/index.d.ts.map +1 -1
  77. package/dist/types/api/moneroGetAddress.d.ts +15 -0
  78. package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
  79. package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
  80. package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
  81. package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
  82. package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
  83. package/dist/types/api/moneroSignTransaction.d.ts +32 -0
  84. package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
  85. package/dist/types/api/zcashGetAddress.d.ts +22 -0
  86. package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
  87. package/dist/types/api/zcashSignPczt.d.ts +9 -0
  88. package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
  89. package/dist/types/device.d.ts +10 -0
  90. package/dist/types/device.d.ts.map +1 -1
  91. package/dist/utils/logger.d.ts.map +1 -1
  92. package/dist/utils/patch.d.ts +1 -1
  93. package/dist/utils/patch.d.ts.map +1 -1
  94. package/package.json +4 -4
  95. package/src/api/BaseMethod.ts +2 -0
  96. package/src/api/FileWrite.ts +7 -1
  97. package/src/api/FirmwareUpdateV4.ts +358 -74
  98. package/src/api/SearchDevices.ts +172 -7
  99. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +38 -1
  100. package/src/api/btc/BTCDeriveContextHash.ts +90 -0
  101. package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
  102. package/src/api/helpers/pathUtils.ts +23 -0
  103. package/src/api/helpers/protocolV2FileWrite.ts +382 -111
  104. package/src/api/index.ts +8 -0
  105. package/src/api/monero/MoneroGetAddress.ts +71 -0
  106. package/src/api/monero/MoneroGetWatchKey.ts +69 -0
  107. package/src/api/monero/MoneroKeyImageSync.ts +164 -0
  108. package/src/api/monero/MoneroSignTransaction.ts +319 -0
  109. package/src/api/monero/helpers.ts +28 -0
  110. package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
  111. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
  112. package/src/api/zcash/ZcashGetAddress.ts +119 -0
  113. package/src/api/zcash/ZcashSignPczt.ts +152 -0
  114. package/src/core/index.ts +49 -17
  115. package/src/data/messages/messages-protocol-v2.json +138 -1
  116. package/src/data/messages/messages.json +51 -0
  117. package/src/device/Device.ts +21 -3
  118. package/src/device/DeviceConnector.ts +4 -4
  119. package/src/events/logBlockEvent.ts +57 -3
  120. package/src/events/ui-request.ts +2 -0
  121. package/src/index.ts +1 -0
  122. package/src/inject.ts +15 -0
  123. package/src/protocols/protocol-v2/resources.ts +6 -1
  124. package/src/types/api/allNetworkGetAddress.ts +9 -1
  125. package/src/types/api/btcDeriveContextHash.ts +15 -0
  126. package/src/types/api/export.ts +16 -0
  127. package/src/types/api/index.ts +22 -0
  128. package/src/types/api/moneroGetAddress.ts +27 -0
  129. package/src/types/api/moneroGetWatchKey.ts +28 -0
  130. package/src/types/api/moneroKeyImageSync.ts +45 -0
  131. package/src/types/api/moneroSignTransaction.ts +54 -0
  132. package/src/types/api/zcashGetAddress.ts +35 -0
  133. package/src/types/api/zcashSignPczt.ts +18 -0
  134. package/src/types/device.ts +18 -0
  135. package/src/utils/logger.ts +4 -0
@@ -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 { OneKeyDeviceInfo as DeviceDescriptor } from '@onekeyfe/hd-transport';
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
- const getDescriptorKeys = (descriptor: DeviceDescriptor) =>
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?: DeviceDescriptor) =>
49
+ const getUsbHandle = (descriptor?: UsbIdentityProbeDescriptor) =>
37
50
  (descriptor as { device?: unknown } | undefined)?.device;
38
51
 
39
52
  const getOwningRequestDevice = (
40
- descriptor: DeviceDescriptor,
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
  }
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  import { serializedPath, toHardened } from '../helpers/pathUtils';
11
11
  import { BaseMethod } from '../BaseMethod';
12
- import { validateParams } from '../helpers/paramsValidator';
12
+ import { invalidParameter, validateParams } from '../helpers/paramsValidator';
13
13
  import { PROTO } from '../../constants';
14
14
  import { findMethod } from '../utils';
15
15
  import { DEVICE, IFRAME, createUiMessage } from '../../events';
@@ -35,6 +35,12 @@ import type {
35
35
 
36
36
  const Mainnet = 'mainnet';
37
37
 
38
+ const MoneroNetworkTypes = new Map<string, number>([
39
+ [Mainnet, PROTO.MoneroNetworkType.MAINNET],
40
+ ['testnet', PROTO.MoneroNetworkType.TESTNET],
41
+ ['stagenet', PROTO.MoneroNetworkType.STAGENET],
42
+ ]);
43
+
38
44
  export type NetworkConfig = {
39
45
  methodName: keyof CoreApi;
40
46
  getParams?: (baseParams: AllNetworkAddressParams, chainName?: string, methodName?: string) => any;
@@ -246,6 +252,37 @@ const networkConfigMap: NetworkConfigMap = {
246
252
  stellar: {
247
253
  methodName: 'stellarGetAddress',
248
254
  },
255
+ xmr: {
256
+ methodName: 'moneroGetAddress',
257
+ getParams: (baseParams: AllNetworkAddressParams, chainName?: string) => {
258
+ const { path, showOnOneKey } = baseParams;
259
+ const networkType = MoneroNetworkTypes.get(chainName ?? Mainnet);
260
+ if (networkType === undefined) {
261
+ throw invalidParameter(`Unsupported Monero chainName: ${chainName}`);
262
+ }
263
+ return {
264
+ path,
265
+ networkType,
266
+ showOnOneKey,
267
+ };
268
+ },
269
+ },
270
+ zcash: {
271
+ methodName: 'zcashGetAddress',
272
+ // The account's unified address with the keys a wallet needs to watch it.
273
+ getParams: (baseParams: AllNetworkAddressParams) => {
274
+ const { path, showOnOneKey } = baseParams;
275
+ return {
276
+ path,
277
+ showOnOneKey,
278
+ addressType: PROTO.ZcashAddressType.UNIFIED_ORCHARD,
279
+ scope: PROTO.ZcashAddressScope.EXTERNAL,
280
+ diversifierIndex: 0,
281
+ includeUfvk: true,
282
+ includeSeedFingerprint: true,
283
+ };
284
+ },
285
+ },
249
286
  };
250
287
 
251
288
  type MethodParams = {
@@ -0,0 +1,90 @@
1
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { Buffer } from 'buffer';
3
+
4
+ import { BaseMethod } from '../BaseMethod';
5
+ import { invalidParameter, validateParams } from '../helpers/paramsValidator';
6
+ import { getScriptType, validatePath } from '../helpers/pathUtils';
7
+
8
+ import type { BabylonDeriveContextHash, CanonicalBitcoinNetwork } from '@onekeyfe/hd-transport';
9
+ import type { BTCDeriveContextHashParams } from '../../types';
10
+
11
+ const NETWORKS: Record<BTCDeriveContextHashParams['network'], CanonicalBitcoinNetwork> = {
12
+ 'bitcoin-mainnet': 'BITCOIN_MAINNET',
13
+ 'bitcoin-testnet': 'BITCOIN_TESTNET',
14
+ 'bitcoin-signet': 'BITCOIN_SIGNET',
15
+ 'bitcoin-regtest': 'BITCOIN_REGTEST',
16
+ };
17
+
18
+ export default class BTCDeriveContextHash extends BaseMethod<BabylonDeriveContextHash> {
19
+ strictCheckDeviceSupport = true;
20
+
21
+ init() {
22
+ this.checkDeviceId = true;
23
+ this.allowUsePreInitialize = true;
24
+
25
+ validateParams(this.payload, [
26
+ { name: 'path', required: true },
27
+ { name: 'appName', type: 'string', required: true },
28
+ { name: 'context', type: 'string', required: true },
29
+ { name: 'network', type: 'string', required: true },
30
+ ]);
31
+
32
+ const { path, appName, context, network } = this.payload;
33
+ if (typeof appName !== 'string' || !/^[a-z0-9-]{1,64}$/.test(appName)) {
34
+ throw invalidParameter(
35
+ 'appName must contain 1..64 ASCII lowercase letters, digits or hyphens'
36
+ );
37
+ }
38
+ if (typeof context !== 'string' || !/^(?:[0-9a-f]{2}){1,1024}$/.test(context)) {
39
+ throw invalidParameter('context must be 1..1024 bytes of lowercase hex without a prefix');
40
+ }
41
+ if (typeof network !== 'string' || !Object.prototype.hasOwnProperty.call(NETWORKS, network)) {
42
+ throw invalidParameter('Unsupported Bitcoin network');
43
+ }
44
+
45
+ // Reject malformed or overflowing indices before validatePath can truncate them.
46
+ if (
47
+ (typeof path === 'string' &&
48
+ (!/^m(?:\/\d+'?)+$/i.test(path) ||
49
+ path
50
+ .split('/')
51
+ .slice(1)
52
+ .some(index => Number(index.replace(/'/g, '')) >= 0x80000000))) ||
53
+ (Array.isArray(path) &&
54
+ path.findIndex(index => !Number.isInteger(index) || index < 0 || index > 0xffffffff) !== -1)
55
+ ) {
56
+ throw invalidParameter('Not a valid BIP-32 path');
57
+ }
58
+ const addressN = validatePath(path, 3);
59
+
60
+ this.params = {
61
+ address_n: addressN,
62
+ script_type: getScriptType(addressN),
63
+ app_name: Buffer.from(appName, 'ascii').toString('hex'),
64
+ context,
65
+ network: NETWORKS[network as BTCDeriveContextHashParams['network']],
66
+ };
67
+ }
68
+
69
+ getVersionRange() {
70
+ return {
71
+ classic1s: { min: '3.21.0' },
72
+ classicpure: { min: '3.21.0' },
73
+ };
74
+ }
75
+
76
+ async run() {
77
+ const { message } = await this.device.commands.typedCall(
78
+ 'BabylonDeriveContextHash',
79
+ 'BabylonDerivedContextHash',
80
+ this.params
81
+ );
82
+ if (typeof message.secret !== 'string' || !/^[0-9a-f]{64}$/.test(message.secret)) {
83
+ throw ERRORS.TypedError(
84
+ HardwareErrorCode.CallMethodError,
85
+ 'Invalid Babylon context hash response'
86
+ );
87
+ }
88
+ return message;
89
+ }
90
+ }
@@ -212,17 +212,26 @@ export const readVerifiedFirmwareArtifact = async ({
212
212
  }): Promise<ArrayBuffer> => {
213
213
  assertFirmwareArtifactReference(artifact);
214
214
  const source = await ReaderFirmwareByteSource.open(artifact, reader);
215
+ const hasher = sha256.create();
215
216
  try {
216
- const binary = await readFirmwareByteSourceFully(source);
217
- const actualSha256 = bytesToHex(sha256(new Uint8Array(binary)));
217
+ const bytes = new Uint8Array(source.size);
218
+ let offset = 0;
219
+ while (offset < source.size) {
220
+ const length = Math.min(MAX_FIRMWARE_ARTIFACT_READ_BYTES, source.size - offset);
221
+ bytes.set(new Uint8Array(await source.readAt(offset, length)), offset);
222
+ hasher.update(bytes.subarray(offset, offset + length));
223
+ offset += length;
224
+ }
225
+ const actualSha256 = bytesToHex(hasher.digest());
218
226
  if (actualSha256 !== artifact.sha256.toLowerCase()) {
219
227
  return artifactError(
220
228
  'FirmwareArtifactReceiptMismatch',
221
229
  'Firmware artifact bytes do not match the approved SHA-256 receipt'
222
230
  );
223
231
  }
224
- return binary;
232
+ return bytes.buffer;
225
233
  } finally {
234
+ hasher.destroy();
226
235
  await source.close().catch(() => undefined);
227
236
  }
228
237
  };
@@ -143,3 +143,26 @@ export const validatePath = (
143
143
  if (length > 0 && valid.length < length) throw PATH_NOT_VALID;
144
144
  return base ? valid.splice(0, 3) : valid;
145
145
  };
146
+
147
+ const isTextIndex = (part: string) => {
148
+ const digits = /^(\d+)'?$/.exec(part)?.[1];
149
+ return digits !== undefined && Number(digits) < HD_HARDENED;
150
+ };
151
+
152
+ const isUint32 = (n: number) => Number.isInteger(n) && n >= 0 && n <= 0xffffffff;
153
+
154
+ // validatePath lets parseInt and 32-bit hardening alias fractional or oversized
155
+ // components to another index: 0.5' and 4294967296' both become 0'.
156
+ export const validateStrictPath = (path: string | Array<number>, length = 0): Array<number> => {
157
+ const addressN = validatePath(path, length);
158
+ const isStrict =
159
+ typeof path === 'string'
160
+ ? path
161
+ .toLowerCase()
162
+ .split('/')
163
+ .filter(p => p !== 'm' && p !== '')
164
+ .every(isTextIndex)
165
+ : path.every(isUint32);
166
+ if (!isStrict) throw PATH_NOT_VALID;
167
+ return addressN;
168
+ };