@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.
Files changed (166) hide show
  1. package/README.md +27 -0
  2. package/__tests__/DeviceCommands.test.ts +66 -13
  3. package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +77 -0
  4. package/__tests__/allNetworkGetAddress-skippable.test.ts +41 -0
  5. package/__tests__/btc-derive-context-hash.test.ts +200 -0
  6. package/__tests__/core-dispose.test.ts +114 -1
  7. package/__tests__/core-error-output.test.ts +55 -3
  8. package/__tests__/device-connector-protocol.test.ts +5 -2
  9. package/__tests__/device-lifecycle-events.test.ts +191 -27
  10. package/__tests__/device-pool-state.test.ts +50 -0
  11. package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
  12. package/__tests__/logBlockEvent.test.ts +331 -9
  13. package/__tests__/logger.test.ts +18 -0
  14. package/__tests__/monero-get-address.test.ts +147 -0
  15. package/__tests__/monero-key-image-sync.test.ts +272 -0
  16. package/__tests__/monero-sign-transaction.test.ts +436 -0
  17. package/__tests__/protocol-v2-bootloader-mode.test.ts +43 -2
  18. package/__tests__/protocol-v2-key-export-confirmation.test.ts +75 -0
  19. package/__tests__/protocol-v2-resources.test.ts +14 -0
  20. package/__tests__/protocol-v2.test.ts +841 -46
  21. package/__tests__/protocolV2FileWrite.test.ts +454 -7
  22. package/__tests__/refresh-device-state.test.ts +19 -17
  23. package/__tests__/search-devices.test.ts +36 -0
  24. package/__tests__/sol-sign-offchain-message.test.ts +8 -0
  25. package/__tests__/usb-identity-probe.test.ts +358 -0
  26. package/__tests__/zcash-protobuf.test.ts +152 -0
  27. package/__tests__/zcash-sign-pczt.test.ts +525 -0
  28. package/dist/api/BaseMethod.d.ts.map +1 -1
  29. package/dist/api/FileWrite.d.ts.map +1 -1
  30. package/dist/api/FirmwareUpdateV4.d.ts +5 -0
  31. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  32. package/dist/api/GetDeviceState.d.ts.map +1 -1
  33. package/dist/api/SearchDevices.d.ts +21 -1
  34. package/dist/api/SearchDevices.d.ts.map +1 -1
  35. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +3 -0
  36. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  37. package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
  38. package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
  39. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
  40. package/dist/api/helpers/pathUtils.d.ts +1 -0
  41. package/dist/api/helpers/pathUtils.d.ts.map +1 -1
  42. package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
  43. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  44. package/dist/api/index.d.ts +8 -0
  45. package/dist/api/index.d.ts.map +1 -1
  46. package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
  47. package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
  48. package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
  49. package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
  50. package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
  51. package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
  52. package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
  53. package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
  54. package/dist/api/monero/helpers.d.ts +5 -0
  55. package/dist/api/monero/helpers.d.ts.map +1 -0
  56. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  57. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  58. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  59. package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
  60. package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
  61. package/dist/api/zcash/ZcashGetFullViewingKey.d.ts +12 -0
  62. package/dist/api/zcash/ZcashGetFullViewingKey.d.ts.map +1 -0
  63. package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
  64. package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
  65. package/dist/api/zcash/helpers.d.ts +2 -0
  66. package/dist/api/zcash/helpers.d.ts.map +1 -0
  67. package/dist/core/index.d.ts +3 -2
  68. package/dist/core/index.d.ts.map +1 -1
  69. package/dist/device/Device.d.ts +3 -0
  70. package/dist/device/Device.d.ts.map +1 -1
  71. package/dist/device/DeviceCommands.d.ts +4 -4
  72. package/dist/device/DeviceCommands.d.ts.map +1 -1
  73. package/dist/device/DevicePool.d.ts.map +1 -1
  74. package/dist/events/logBlockEvent.d.ts +10 -0
  75. package/dist/events/logBlockEvent.d.ts.map +1 -1
  76. package/dist/events/ui-request.d.ts +2 -0
  77. package/dist/events/ui-request.d.ts.map +1 -1
  78. package/dist/index.d.ts +206 -7
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +4443 -2797
  81. package/dist/inject.d.ts.map +1 -1
  82. package/dist/protocols/protocol-v2/resources.d.ts +1 -0
  83. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  84. package/dist/protocols/protocol-v2/uiInteraction.d.ts +1 -0
  85. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  86. package/dist/types/api/allNetworkGetAddress.d.ts +4 -1
  87. package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
  88. package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
  89. package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
  90. package/dist/types/api/export.d.ts +8 -0
  91. package/dist/types/api/export.d.ts.map +1 -1
  92. package/dist/types/api/index.d.ts +16 -0
  93. package/dist/types/api/index.d.ts.map +1 -1
  94. package/dist/types/api/moneroGetAddress.d.ts +15 -0
  95. package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
  96. package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
  97. package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
  98. package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
  99. package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
  100. package/dist/types/api/moneroSignTransaction.d.ts +32 -0
  101. package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
  102. package/dist/types/api/zcashGetAddress.d.ts +20 -0
  103. package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
  104. package/dist/types/api/zcashGetFullViewingKey.d.ts +13 -0
  105. package/dist/types/api/zcashGetFullViewingKey.d.ts.map +1 -0
  106. package/dist/types/api/zcashSignPczt.d.ts +9 -0
  107. package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
  108. package/dist/types/device.d.ts +10 -0
  109. package/dist/types/device.d.ts.map +1 -1
  110. package/dist/types/params.d.ts +1 -0
  111. package/dist/types/params.d.ts.map +1 -1
  112. package/dist/utils/logger.d.ts.map +1 -1
  113. package/dist/utils/patch.d.ts +1 -1
  114. package/dist/utils/patch.d.ts.map +1 -1
  115. package/package.json +4 -4
  116. package/src/api/BaseMethod.ts +2 -0
  117. package/src/api/FileWrite.ts +7 -1
  118. package/src/api/FirmwareUpdateV4.ts +366 -99
  119. package/src/api/GetDeviceState.ts +2 -5
  120. package/src/api/SearchDevices.ts +172 -7
  121. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +46 -6
  122. package/src/api/btc/BTCDeriveContextHash.ts +90 -0
  123. package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
  124. package/src/api/helpers/pathUtils.ts +23 -0
  125. package/src/api/helpers/protocolV2FileWrite.ts +382 -111
  126. package/src/api/index.ts +9 -0
  127. package/src/api/monero/MoneroGetAddress.ts +71 -0
  128. package/src/api/monero/MoneroGetWatchKey.ts +75 -0
  129. package/src/api/monero/MoneroKeyImageSync.ts +170 -0
  130. package/src/api/monero/MoneroSignTransaction.ts +319 -0
  131. package/src/api/monero/helpers.ts +28 -0
  132. package/src/api/protocol-v2/DeviceReboot.ts +2 -0
  133. package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
  134. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
  135. package/src/api/zcash/ZcashGetAddress.ts +108 -0
  136. package/src/api/zcash/ZcashGetFullViewingKey.ts +78 -0
  137. package/src/api/zcash/ZcashSignPczt.ts +152 -0
  138. package/src/api/zcash/helpers.ts +16 -0
  139. package/src/core/index.ts +52 -24
  140. package/src/data/messages/messages-protocol-v2.json +152 -0
  141. package/src/data/messages/messages.json +51 -0
  142. package/src/device/Device.ts +32 -4
  143. package/src/device/DeviceCommands.ts +15 -1
  144. package/src/device/DeviceConnector.ts +4 -4
  145. package/src/device/DevicePool.ts +12 -2
  146. package/src/events/core.ts +2 -2
  147. package/src/events/logBlockEvent.ts +58 -3
  148. package/src/events/ui-request.ts +2 -0
  149. package/src/index.ts +1 -0
  150. package/src/inject.ts +17 -0
  151. package/src/protocols/protocol-v2/resources.ts +6 -1
  152. package/src/protocols/protocol-v2/uiInteraction.ts +1 -1
  153. package/src/types/api/allNetworkGetAddress.ts +8 -1
  154. package/src/types/api/btcDeriveContextHash.ts +15 -0
  155. package/src/types/api/export.ts +17 -0
  156. package/src/types/api/index.ts +24 -0
  157. package/src/types/api/moneroGetAddress.ts +27 -0
  158. package/src/types/api/moneroGetWatchKey.ts +28 -0
  159. package/src/types/api/moneroKeyImageSync.ts +45 -0
  160. package/src/types/api/moneroSignTransaction.ts +54 -0
  161. package/src/types/api/zcashGetAddress.ts +32 -0
  162. package/src/types/api/zcashGetFullViewingKey.ts +24 -0
  163. package/src/types/api/zcashSignPczt.ts +18 -0
  164. package/src/types/device.ts +18 -0
  165. package/src/types/params.ts +1 -1
  166. package/src/utils/logger.ts +4 -0
@@ -27,7 +27,10 @@ import {
27
27
  import { BaseMethod } from '../BaseMethod';
28
28
  import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
29
29
  import { invalidParameter } from '../helpers/filesystemValidation';
30
- import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
30
+ import {
31
+ createProtocolV2AssetFileWriteRecovery,
32
+ writeProtocolV2File,
33
+ } from '../helpers/protocolV2FileWrite';
31
34
 
32
35
  export type DeviceUploadNftParams = {
33
36
  imageJpegBase64: string;
@@ -238,6 +241,8 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
238
241
  chunkSize: this.params.chunkSize,
239
242
  timeoutMs: this.params.timeoutMs,
240
243
  paceMs: this.params.paceMs,
244
+ maxChunkRetries: 3,
245
+ recoverConnection: createProtocolV2AssetFileWriteRecovery(this),
241
246
  overwrite: true,
242
247
  append: false,
243
248
  throwIfAborted: () => this.throwIfAborted(),
@@ -6,7 +6,11 @@ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
6
6
  import { BaseMethod } from '../BaseMethod';
7
7
  import { decodeJpegBase64ToRgba } from '../helpers/base64Data';
8
8
  import { invalidParameter } from '../helpers/filesystemValidation';
9
- import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
9
+ import {
10
+ createProtocolV2AssetFileWriteRecovery,
11
+ writeProtocolV2File,
12
+ } from '../helpers/protocolV2FileWrite';
13
+ import { ProtocolV2UiInteractionCoordinator } from '../../protocols/protocol-v2/uiInteraction';
10
14
  import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
11
15
  import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
12
16
  import { LoggerNames, getLogger } from '../../utils';
@@ -142,6 +146,7 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
142
146
  chunkSize: this.params.chunkSize,
143
147
  bleChunkSizeLimit,
144
148
  maxChunkRetries: 3,
149
+ recoverConnection: createProtocolV2AssetFileWriteRecovery(this),
145
150
  overwrite: true,
146
151
  append: false,
147
152
  throwIfAborted: () => this.throwIfAborted(),
@@ -171,6 +176,15 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
171
176
  data,
172
177
  useHostAssetPackage ? WALLPAPER_PACKAGE_BLE_CHUNK_SIZE : undefined
173
178
  );
179
+ this.throwIfAborted();
180
+ new ProtocolV2UiInteractionCoordinator(this.device, this.postMessage).enterMethodInteraction({
181
+ request: 'button',
182
+ source: 'method-lifecycle',
183
+ reason: 'device-management',
184
+ completion: 'operation-completed',
185
+ deviceOnly: true,
186
+ operation: this.name,
187
+ });
174
188
  const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
175
189
  settings: { wallpaper_path: this.path },
176
190
  });
@@ -0,0 +1,108 @@
1
+ import { ZcashAddressScope, ZcashAddressType } from '@onekeyfe/hd-transport';
2
+
3
+ import { BaseMethod } from '../BaseMethod';
4
+ import { serializedPath } from '../helpers/pathUtils';
5
+ import { invalidParameter, validateParams, validateResult } from '../helpers/paramsValidator';
6
+ import { UI_REQUEST } from '../../constants/ui-request';
7
+ import { validateZcashAccountPath } from './helpers';
8
+
9
+ import type { ZcashGetAddress as HardwareZcashGetAddress } from '@onekeyfe/hd-transport';
10
+ import type { ZcashAddress, ZcashGetAddressParams } from '../../types/api/zcashGetAddress';
11
+ import type { DeviceFirmwareRange } from '../../types';
12
+
13
+ export default class ZcashGetAddress extends BaseMethod<HardwareZcashGetAddress[]> {
14
+ hasBundle = false;
15
+
16
+ getSupportedProtocols() {
17
+ return ['V2'] as const;
18
+ }
19
+
20
+ init() {
21
+ this.checkDeviceId = true;
22
+ this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
23
+ this.strictCheckDeviceSupport = true;
24
+
25
+ this.hasBundle = !!this.payload?.bundle;
26
+ const payload = this.hasBundle ? this.payload : { bundle: [this.payload] };
27
+
28
+ validateParams(payload, [{ name: 'bundle', type: 'array' }]);
29
+
30
+ this.params = [];
31
+ payload.bundle.forEach((batch: ZcashGetAddressParams) => {
32
+ const addressN = validateZcashAccountPath(batch.path);
33
+ const addressType = batch.addressType ?? ZcashAddressType.UNIFIED_ORCHARD_P2PKH;
34
+ const scope = batch.scope ?? ZcashAddressScope.EXTERNAL;
35
+ const diversifierIndex = batch.diversifierIndex ?? 0;
36
+ if (
37
+ ![
38
+ ZcashAddressType.UNIFIED_ORCHARD_P2PKH,
39
+ ZcashAddressType.TRANSPARENT_P2PKH,
40
+ ZcashAddressType.UNIFIED_ORCHARD,
41
+ ].includes(addressType) ||
42
+ ![ZcashAddressScope.EXTERNAL, ZcashAddressScope.INTERNAL].includes(scope)
43
+ ) {
44
+ throw invalidParameter('Invalid Zcash address type or scope');
45
+ }
46
+ const maxIndex = addressType === ZcashAddressType.UNIFIED_ORCHARD ? 0xffffffff : 0x7fffffff;
47
+ if (
48
+ !Number.isSafeInteger(diversifierIndex) ||
49
+ diversifierIndex < 0 ||
50
+ diversifierIndex > maxIndex
51
+ ) {
52
+ throw invalidParameter('Invalid Zcash diversifier index');
53
+ }
54
+
55
+ validateParams(batch, [
56
+ { name: 'path', required: true },
57
+ { name: 'showOnOneKey', type: 'boolean' },
58
+ { name: 'includeSeedFingerprint', type: 'boolean' },
59
+ ]);
60
+
61
+ this.params.push({
62
+ address_n: addressN,
63
+ address_type: addressType,
64
+ scope,
65
+ diversifier_index: diversifierIndex,
66
+ show_display: batch.showOnOneKey ?? true,
67
+ include_seed_fingerprint: batch.includeSeedFingerprint ?? false,
68
+ });
69
+ });
70
+ }
71
+
72
+ getVersionRange(): DeviceFirmwareRange {
73
+ return {
74
+ model_pro2: {
75
+ min: '0.0.0',
76
+ },
77
+ };
78
+ }
79
+
80
+ async run() {
81
+ const responses: ZcashAddress[] = [];
82
+
83
+ for (let i = 0; i < this.params.length; i++) {
84
+ const param = this.params[i];
85
+
86
+ const res = await this.device.commands.typedCall('ZcashGetAddress', 'ZcashAddress', {
87
+ ...param,
88
+ });
89
+
90
+ const path = serializedPath(param.address_n);
91
+ // The V2 decoder returns null for absent optional fields; omit them instead.
92
+ const { address, seed_fingerprint: seedFingerprint } = res.message;
93
+ responses.push({
94
+ path,
95
+ address,
96
+ ...(seedFingerprint ? { seedFingerprint } : {}),
97
+ });
98
+
99
+ this.postPreviousAddressMessage({ address, path });
100
+ }
101
+
102
+ validateResult(responses, ['address'], {
103
+ expectedLength: this.params.length,
104
+ });
105
+
106
+ return Promise.resolve(this.hasBundle ? responses : responses[0]);
107
+ }
108
+ }
@@ -0,0 +1,78 @@
1
+ import { BaseMethod } from '../BaseMethod';
2
+ import { serializedPath } from '../helpers/pathUtils';
3
+ import { validateParams, validateResult } from '../helpers/paramsValidator';
4
+ import { UI_REQUEST } from '../../constants/ui-request';
5
+ import { createMethodInteraction } from '../../protocols/protocol-v2/uiInteraction';
6
+ import { validateZcashAccountPath } from './helpers';
7
+
8
+ import type { ZcashGetFullViewingKey as HardwareZcashGetFullViewingKey } from '@onekeyfe/hd-transport';
9
+ import type {
10
+ ZcashFullViewingKey,
11
+ ZcashGetFullViewingKeyParams,
12
+ } from '../../types/api/zcashGetFullViewingKey';
13
+ import type { DeviceFirmwareRange } from '../../types';
14
+
15
+ // The device exports the UFVK only after the user confirms it on screen.
16
+ export default class ZcashGetFullViewingKey extends BaseMethod<HardwareZcashGetFullViewingKey[]> {
17
+ hasBundle = false;
18
+
19
+ getSupportedProtocols() {
20
+ return ['V2'] as const;
21
+ }
22
+
23
+ init() {
24
+ this.checkDeviceId = true;
25
+ this.allowDeviceMode = [...this.allowDeviceMode, UI_REQUEST.NOT_INITIALIZE];
26
+ this.strictCheckDeviceSupport = true;
27
+ // The name matches no Protocol V2 confirmation rule, so the export prompt is declared here.
28
+ this.protocolV2UiInteraction = createMethodInteraction(
29
+ 'public-key-confirmation',
30
+ 'zcashGetFullViewingKey'
31
+ );
32
+
33
+ this.hasBundle = !!this.payload?.bundle;
34
+ const payload = this.hasBundle ? this.payload : { bundle: [this.payload] };
35
+
36
+ validateParams(payload, [{ name: 'bundle', type: 'array' }]);
37
+
38
+ this.params = [];
39
+ payload.bundle.forEach((batch: ZcashGetFullViewingKeyParams) => {
40
+ validateParams(batch, [{ name: 'path', required: true }]);
41
+ this.params.push({ address_n: validateZcashAccountPath(batch.path) });
42
+ });
43
+ }
44
+
45
+ getVersionRange(): DeviceFirmwareRange {
46
+ return {
47
+ model_pro2: {
48
+ min: '0.0.0',
49
+ },
50
+ };
51
+ }
52
+
53
+ async run() {
54
+ // Missing fields stay undefined so validateResult rejects them.
55
+ const responses: Partial<ZcashFullViewingKey>[] = [];
56
+
57
+ for (let i = 0; i < this.params.length; i++) {
58
+ const param = this.params[i];
59
+
60
+ const res = await this.device.commands.typedCall(
61
+ 'ZcashGetFullViewingKey',
62
+ 'ZcashFullViewingKey',
63
+ { ...param }
64
+ );
65
+
66
+ responses.push({
67
+ path: serializedPath(param.address_n),
68
+ ufvk: res.message.ufvk,
69
+ });
70
+ }
71
+
72
+ validateResult(responses, ['ufvk'], {
73
+ expectedLength: this.params.length,
74
+ });
75
+
76
+ return Promise.resolve(this.hasBundle ? responses : responses[0]);
77
+ }
78
+ }
@@ -0,0 +1,152 @@
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
+ first: TypedResponseMessage<'ZcashSignedPczt'> | TypedResponseMessage<'ZcashPcztChunkRequest'>,
61
+ initialOffset: number
62
+ ): Promise<TypedResponseMessage<'ZcashSignedPczt'>> => {
63
+ let res = first;
64
+ let offset = initialOffset;
65
+ while (res.type === 'ZcashPcztChunkRequest') {
66
+ const { chunk_length: chunkLength } = res.message;
67
+ if (
68
+ !Number.isSafeInteger(chunkLength) ||
69
+ chunkLength <= 0 ||
70
+ offset + chunkLength > this.pczt.length
71
+ ) {
72
+ throw ERRORS.TypedError(
73
+ HardwareErrorCode.ResponseUnexpectTypeError,
74
+ `device requested ${chunkLength} bytes at offset ${offset} of ${this.pczt.length}`
75
+ );
76
+ }
77
+
78
+ const chunk = this.pczt.subarray(offset, offset + chunkLength);
79
+ res = await typedCall('ZcashPcztChunkAck', ['ZcashSignedPczt', 'ZcashPcztChunkRequest'], {
80
+ data_chunk: bytesToHex(chunk),
81
+ });
82
+ offset += chunkLength;
83
+ }
84
+
85
+ if (offset !== this.pczt.length) {
86
+ throw ERRORS.TypedError(
87
+ HardwareErrorCode.ResponseUnexpectTypeError,
88
+ 'device completed signing before receiving the whole pczt'
89
+ );
90
+ }
91
+ return res;
92
+ };
93
+
94
+ // Download: the host decides chunk lengths via ZcashSignedPcztChunkRequest
95
+ // until the announced pczt_length has been received.
96
+ downloadSignedPczt = async (
97
+ typedCall: TypedCall,
98
+ res: TypedResponseMessage<'ZcashSignedPczt'>
99
+ ): Promise<Buffer> => {
100
+ const { pczt_length: total, pczt_initial_chunk: initial } = res.message;
101
+ if (!Number.isSafeInteger(total) || total <= 0 || total > SIGNED_PCZT_SANITY_MAX_BYTES) {
102
+ throw ERRORS.TypedError(
103
+ HardwareErrorCode.ResponseUnexpectTypeError,
104
+ `device announced invalid signed pczt length ${total}`
105
+ );
106
+ }
107
+
108
+ const parts: Buffer[] = [Buffer.from(initial ?? '', 'hex')];
109
+ let received = parts[0].length;
110
+ if (received > total) {
111
+ throw ERRORS.TypedError(
112
+ HardwareErrorCode.ResponseUnexpectTypeError,
113
+ 'device returned more initial pczt bytes than announced'
114
+ );
115
+ }
116
+
117
+ while (received < total) {
118
+ const chunkLength = Math.min(CHUNK_BYTE_SIZE, total - received);
119
+ const ack = await typedCall('ZcashSignedPcztChunkRequest', 'ZcashPcztChunkAck', {
120
+ chunk_length: chunkLength,
121
+ });
122
+ const chunk = Buffer.from(ack.message.data_chunk ?? '', 'hex');
123
+ if (chunk.length !== chunkLength) {
124
+ throw ERRORS.TypedError(
125
+ HardwareErrorCode.ResponseUnexpectTypeError,
126
+ `device returned ${chunk.length} of ${chunkLength} requested bytes at offset ${received} of ${total}`
127
+ );
128
+ }
129
+ parts.push(chunk);
130
+ received += chunk.length;
131
+ }
132
+
133
+ return Buffer.concat(parts);
134
+ };
135
+
136
+ async run(): Promise<ZcashSignedPczt> {
137
+ const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
138
+
139
+ const first = await typedCall('ZcashSignPczt', ['ZcashSignedPczt', 'ZcashPcztChunkRequest'], {
140
+ ...this.params,
141
+ });
142
+
143
+ const signed = await this.uploadPczt(
144
+ typedCall,
145
+ first,
146
+ Math.min(CHUNK_BYTE_SIZE, this.pczt.length)
147
+ );
148
+ const pczt = await this.downloadSignedPczt(typedCall, signed);
149
+
150
+ return { pczt: bytesToHex(pczt) };
151
+ }
152
+ }
@@ -0,0 +1,16 @@
1
+ import { toHardened, validateStrictPath } from '../helpers/pathUtils';
2
+ import { invalidParameter } from '../helpers/paramsValidator';
3
+
4
+ // ZIP-32 account path: m/32'/133'/account'
5
+ export function validateZcashAccountPath(path: string | number[]): number[] {
6
+ const addressN = validateStrictPath(path, 3);
7
+ if (
8
+ addressN.length !== 3 ||
9
+ addressN[0] !== toHardened(32) ||
10
+ addressN[1] !== toHardened(133) ||
11
+ addressN[2] < toHardened(0)
12
+ ) {
13
+ throw invalidParameter("Zcash requires a ZIP-32 account path m/32'/133'/account'");
14
+ }
15
+ return addressN;
16
+ }
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';
@@ -61,6 +61,7 @@ import {
61
61
  formatLogMethodLabel,
62
62
  getLogBlockLabel,
63
63
  getSafeLogPayload,
64
+ getSafeLogTask,
64
65
  } from '../events';
65
66
  import TransportManager from '../data-manager/TransportManager';
66
67
  import DeviceConnector from '../device/DeviceConnector';
@@ -126,6 +127,8 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
126
127
  });
127
128
 
128
129
  let _core: Core | undefined;
130
+ let transportSwitchGeneration = 0;
131
+ let pendingTransportSwitch: Promise<void> = Promise.resolve();
129
132
  let _deviceList: DeviceList | undefined;
130
133
  let _connector: DeviceConnector | undefined;
131
134
  let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
@@ -991,15 +994,11 @@ function canSkipInitialize(method: BaseMethod, device: Device): boolean {
991
994
  }
992
995
 
993
996
  export function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
994
- const typedError = error as { errorCode?: unknown; message?: unknown };
995
- const message =
996
- typeof typedError?.message === 'string' ? typedError.message : String(error ?? '');
997
+ const typedError = error as { errorCode?: unknown; params?: { expectedProtocol?: unknown } };
997
998
  return (
998
999
  method.payload.connectProtocol === 'V2' &&
999
- typedError?.errorCode === HardwareErrorCode.RuntimeError &&
1000
- message.includes('Device protocol mismatch') &&
1001
- message.includes('expected V2') &&
1002
- message.includes('did not respond to expected protocol')
1000
+ typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
1001
+ typedError.params?.expectedProtocol === 'V2'
1003
1002
  );
1004
1003
  }
1005
1004
 
@@ -1009,7 +1008,7 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
1009
1008
  }
1010
1009
  const typedError = error as {
1011
1010
  errorCode?: unknown;
1012
- params?: { acquireDeadlineExceeded?: unknown };
1011
+ params?: { acquireDeadlineExceeded?: unknown; expectedProtocol?: unknown };
1013
1012
  };
1014
1013
  if (
1015
1014
  typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
@@ -1020,6 +1019,8 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
1020
1019
  return (
1021
1020
  typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
1022
1021
  typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
1022
+ (typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
1023
+ !typedError.params?.expectedProtocol) ||
1023
1024
  isRetryableBleProtocolV2ProbeError(method, error) ||
1024
1025
  isMissingDetectedProtocolV2Error(method, error)
1025
1026
  );
@@ -1035,10 +1036,6 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
1035
1036
  );
1036
1037
  }
1037
1038
 
1038
- export function isTerminalBleStaleBondError(error: unknown) {
1039
- return isBleStaleBondHardwareError(error);
1040
- }
1041
-
1042
1039
  export function isDeviceIdentityMismatchError(error: unknown) {
1043
1040
  return (
1044
1041
  (error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.DeviceCheckDeviceIdError
@@ -1277,6 +1274,9 @@ const ensureConnected = async (
1277
1274
  HardwareErrorCode.BridgeNotInstalled,
1278
1275
  HardwareErrorCode.BridgeTimeoutError,
1279
1276
  HardwareErrorCode.BridgeNeedsPermission,
1277
+ HardwareErrorCode.WebUsbDeviceAccessError,
1278
+ HardwareErrorCode.TransportProtocolProbeFailed,
1279
+ HardwareErrorCode.TransportProtocolMismatch,
1280
1280
  ].includes(error.errorCode)
1281
1281
  ) {
1282
1282
  _deviceList = undefined;
@@ -1363,6 +1363,9 @@ const ensureConnected = async (
1363
1363
  HardwareErrorCode.DeviceDetectInBootloaderMode,
1364
1364
  HardwareErrorCode.BleCharacteristicNotifyChangeFailure,
1365
1365
  HardwareErrorCode.BridgeNeedsPermission,
1366
+ HardwareErrorCode.WebUsbDeviceAccessError,
1367
+ HardwareErrorCode.TransportProtocolProbeFailed,
1368
+ HardwareErrorCode.TransportProtocolMismatch,
1366
1369
  HardwareErrorCode.DeviceInterruptedFromUser,
1367
1370
  HardwareErrorCode.CallQueueActionCancelled,
1368
1371
  ].includes(error.errorCode) ||
@@ -1370,7 +1373,6 @@ const ensureConnected = async (
1370
1373
  [HardwareErrorCode.BleDeviceDisconnected, HardwareErrorCode.PollingTimeout].includes(
1371
1374
  error.errorCode
1372
1375
  )) ||
1373
- isTerminalBleStaleBondError(error) ||
1374
1376
  isDeviceIdentityMismatchError(error)
1375
1377
  ) {
1376
1378
  reject(error);
@@ -1450,7 +1452,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1450
1452
  };
1451
1453
  for (const requestId of requestIds) {
1452
1454
  const task = requestQueue.getTask(requestId);
1453
- Log.debug('Cancel Api connect task: ', task);
1455
+ Log.debug('Cancel Api connect task: ', getSafeLogTask(task));
1454
1456
  if (task) {
1455
1457
  // During ensureConnected the method has a connectId but device is
1456
1458
  // assigned only after the poll succeeds. Interrupt the cached BLE
@@ -1485,7 +1487,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1485
1487
  };
1486
1488
  for (const requestId of requestQueue.getRequestTasksId()) {
1487
1489
  const task = requestQueue.getTask(requestId);
1488
- Log.debug('Cancel Api connect task: ', task);
1490
+ Log.debug('Cancel Api connect task: ', getSafeLogTask(task));
1489
1491
  if (task) {
1490
1492
  interruptDevice(task.method?.device);
1491
1493
  if (task.method.connectId) {
@@ -1593,7 +1595,7 @@ const onDeviceConnectHandler = (device: Device) => {
1593
1595
 
1594
1596
  const onDeviceDisconnectHandler = (device: Device) => {
1595
1597
  device.clearPreInitialized();
1596
- const deviceObject = device.toMessageObject();
1598
+ const deviceObject = device.toDisconnectMessageObject();
1597
1599
  if (!deviceObject) return;
1598
1600
  postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject }));
1599
1601
  };
@@ -1830,6 +1832,10 @@ export default class Core extends EventEmitter {
1830
1832
 
1831
1833
  private requestQueue = new RequestQueue();
1832
1834
 
1835
+ getOwnedUsbDeviceIdentity(descriptor: Parameters<typeof getOwnedUsbDeviceIdentity>[0]) {
1836
+ return getOwnedUsbDeviceIdentity(descriptor, this.requestQueue);
1837
+ }
1838
+
1833
1839
  private disposePromise?: Promise<void>;
1834
1840
 
1835
1841
  // background task
@@ -1944,7 +1950,7 @@ export default class Core extends EventEmitter {
1944
1950
  break;
1945
1951
  }
1946
1952
  case IFRAME.CALLBACK: {
1947
- Log.log('callback message: ', message);
1953
+ Log.log('callback message: ', getSafeLogPayload(message));
1948
1954
  postMessage(message);
1949
1955
  break;
1950
1956
  }
@@ -1963,6 +1969,8 @@ export default class Core extends EventEmitter {
1963
1969
 
1964
1970
  private async disposeResources(): Promise<void> {
1965
1971
  Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
1972
+ transportSwitchGeneration += 1;
1973
+ pendingTransportSwitch = Promise.resolve();
1966
1974
  pollingManager.stopAll();
1967
1975
  this.requestQueue.abortAllRequests();
1968
1976
  cleanup();
@@ -1997,6 +2005,8 @@ export default class Core extends EventEmitter {
1997
2005
  }
1998
2006
 
1999
2007
  export const initCore = () => {
2008
+ transportSwitchGeneration += 1;
2009
+ pendingTransportSwitch = Promise.resolve();
2000
2010
  const core = new Core();
2001
2011
  _core = core;
2002
2012
  return core;
@@ -2026,6 +2036,8 @@ export const init = async (
2026
2036
  plugin?: LowlevelTransportSharedPlugin
2027
2037
  ) => {
2028
2038
  try {
2039
+ transportSwitchGeneration += 1;
2040
+ pendingTransportSwitch = Promise.resolve();
2029
2041
  await DataManager.load(settings);
2030
2042
  initTransport(Transport, plugin);
2031
2043
  enableLog(DataManager.getSettings('debug'));
@@ -2051,10 +2063,26 @@ export const switchTransport = ({
2051
2063
  Transport: any;
2052
2064
  plugin?: LowlevelTransportSharedPlugin;
2053
2065
  }) => {
2054
- DataManager.updateEnv(env);
2055
- TransportManager.setTransport(Transport, plugin);
2056
- _deviceList = undefined;
2057
- DevicePool.resetState();
2058
- _connector = undefined;
2059
- initConnector();
2066
+ const generation = transportSwitchGeneration;
2067
+ // Preserve request order while native transport cleanup is pending.
2068
+ const switching = pendingTransportSwitch
2069
+ .catch(() => undefined)
2070
+ .then(async () => {
2071
+ if (generation !== transportSwitchGeneration) {
2072
+ throw ERRORS.TypedError(HardwareErrorCode.NotInitialized);
2073
+ }
2074
+ _connector?.stop();
2075
+ await TransportManager.getTransport()?.stop?.();
2076
+ if (generation !== transportSwitchGeneration) {
2077
+ throw ERRORS.TypedError(HardwareErrorCode.NotInitialized);
2078
+ }
2079
+ DataManager.updateEnv(env);
2080
+ TransportManager.setTransport(Transport, plugin);
2081
+ _deviceList = undefined;
2082
+ DevicePool.resetState();
2083
+ _connector = undefined;
2084
+ initConnector();
2085
+ });
2086
+ pendingTransportSwitch = switching;
2087
+ return switching;
2060
2088
  };