@onekeyfe/hd-core 1.2.8-alpha.9 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +13 -4
- package/__tests__/DeviceCommands.test.ts +25 -0
- package/__tests__/allNetworkGetAddress-privacy-networks.test.ts +77 -0
- package/__tests__/allNetworkGetAddress-skippable.test.ts +49 -0
- package/__tests__/btc-derive-context-hash.test.ts +200 -0
- package/__tests__/core-dispose.test.ts +114 -1
- package/__tests__/core-error-output.test.ts +21 -0
- package/__tests__/device-connector-protocol.test.ts +5 -2
- package/__tests__/device-lifecycle-events.test.ts +127 -10
- package/__tests__/firmware-update/firmware-artifact-source.test.ts +11 -0
- package/__tests__/logBlockEvent.test.ts +331 -9
- package/__tests__/logger.test.ts +18 -0
- package/__tests__/monero-get-address.test.ts +147 -0
- package/__tests__/monero-key-image-sync.test.ts +272 -0
- package/__tests__/monero-sign-transaction.test.ts +436 -0
- package/__tests__/protocol-v2-key-export-confirmation.test.ts +75 -0
- package/__tests__/protocol-v2-resources.test.ts +14 -0
- package/__tests__/protocol-v2.test.ts +845 -3
- package/__tests__/protocolV2FileWrite.test.ts +454 -7
- package/__tests__/search-devices.test.ts +36 -0
- package/__tests__/sol-sign-offchain-message.test.ts +8 -0
- package/__tests__/usb-identity-probe.test.ts +358 -0
- package/__tests__/zcash-protobuf.test.ts +152 -0
- package/__tests__/zcash-sign-pczt.test.ts +525 -0
- package/dist/api/BaseMethod.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +5 -0
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/SearchDevices.d.ts +21 -1
- package/dist/api/SearchDevices.d.ts.map +1 -1
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts +3 -0
- package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
- package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
- package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
- package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +1 -1
- package/dist/api/helpers/pathUtils.d.ts +1 -0
- package/dist/api/helpers/pathUtils.d.ts.map +1 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts +5 -1
- package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/monero/MoneroGetAddress.d.ts +11 -0
- package/dist/api/monero/MoneroGetAddress.d.ts.map +1 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts +11 -0
- package/dist/api/monero/MoneroGetWatchKey.d.ts.map +1 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts +20 -0
- package/dist/api/monero/MoneroKeyImageSync.d.ts.map +1 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts +17 -0
- package/dist/api/monero/MoneroSignTransaction.d.ts.map +1 -0
- package/dist/api/monero/helpers.d.ts +5 -0
- package/dist/api/monero/helpers.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/api/zcash/ZcashGetAddress.d.ts +12 -0
- package/dist/api/zcash/ZcashGetAddress.d.ts.map +1 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts +12 -0
- package/dist/api/zcash/ZcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts +16 -0
- package/dist/api/zcash/ZcashSignPczt.d.ts.map +1 -0
- package/dist/api/zcash/helpers.d.ts +2 -0
- package/dist/api/zcash/helpers.d.ts.map +1 -0
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +1 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts +4 -4
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/events/logBlockEvent.d.ts +10 -0
- package/dist/events/logBlockEvent.d.ts.map +1 -1
- package/dist/events/ui-request.d.ts +2 -0
- package/dist/events/ui-request.d.ts.map +1 -1
- package/dist/index.d.ts +202 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5849 -4205
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/resources.d.ts +1 -0
- package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/uiInteraction.d.ts +1 -0
- package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
- package/dist/types/api/allNetworkGetAddress.d.ts +4 -1
- package/dist/types/api/allNetworkGetAddress.d.ts.map +1 -1
- package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
- package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
- package/dist/types/api/export.d.ts +8 -0
- package/dist/types/api/export.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +16 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/moneroGetAddress.d.ts +15 -0
- package/dist/types/api/moneroGetAddress.d.ts.map +1 -0
- package/dist/types/api/moneroGetWatchKey.d.ts +15 -0
- package/dist/types/api/moneroGetWatchKey.d.ts.map +1 -0
- package/dist/types/api/moneroKeyImageSync.d.ts +24 -0
- package/dist/types/api/moneroKeyImageSync.d.ts.map +1 -0
- package/dist/types/api/moneroSignTransaction.d.ts +32 -0
- package/dist/types/api/moneroSignTransaction.d.ts.map +1 -0
- package/dist/types/api/zcashGetAddress.d.ts +20 -0
- package/dist/types/api/zcashGetAddress.d.ts.map +1 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts +13 -0
- package/dist/types/api/zcashGetFullViewingKey.d.ts.map +1 -0
- package/dist/types/api/zcashSignPczt.d.ts +9 -0
- package/dist/types/api/zcashSignPczt.d.ts.map +1 -0
- package/dist/types/device.d.ts +10 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/BaseMethod.ts +2 -0
- package/src/api/FileWrite.ts +7 -1
- package/src/api/FirmwareUpdateV4.ts +358 -74
- package/src/api/SearchDevices.ts +172 -7
- package/src/api/allnetwork/AllNetworkGetAddressBase.ts +55 -15
- package/src/api/btc/BTCDeriveContextHash.ts +90 -0
- package/src/api/firmware/FirmwareArtifactSource.ts +12 -3
- package/src/api/helpers/pathUtils.ts +23 -0
- package/src/api/helpers/protocolV2FileWrite.ts +382 -111
- package/src/api/index.ts +9 -0
- package/src/api/monero/MoneroGetAddress.ts +71 -0
- package/src/api/monero/MoneroGetWatchKey.ts +75 -0
- package/src/api/monero/MoneroKeyImageSync.ts +170 -0
- package/src/api/monero/MoneroSignTransaction.ts +319 -0
- package/src/api/monero/helpers.ts +28 -0
- package/src/api/protocol-v2/DeviceUploadNft.ts +6 -1
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +15 -1
- package/src/api/zcash/ZcashGetAddress.ts +108 -0
- package/src/api/zcash/ZcashGetFullViewingKey.ts +78 -0
- package/src/api/zcash/ZcashSignPczt.ts +152 -0
- package/src/api/zcash/helpers.ts +16 -0
- package/src/core/index.ts +49 -17
- package/src/data/messages/messages-protocol-v2.json +152 -0
- package/src/data/messages/messages.json +51 -0
- package/src/device/Device.ts +21 -3
- package/src/device/DeviceCommands.ts +17 -0
- package/src/device/DeviceConnector.ts +4 -4
- package/src/events/logBlockEvent.ts +58 -3
- package/src/events/ui-request.ts +2 -0
- package/src/index.ts +1 -0
- package/src/inject.ts +17 -0
- package/src/protocols/protocol-v2/resources.ts +6 -1
- package/src/protocols/protocol-v2/uiInteraction.ts +1 -1
- package/src/types/api/allNetworkGetAddress.ts +8 -1
- package/src/types/api/btcDeriveContextHash.ts +15 -0
- package/src/types/api/export.ts +17 -0
- package/src/types/api/index.ts +24 -0
- package/src/types/api/moneroGetAddress.ts +27 -0
- package/src/types/api/moneroGetWatchKey.ts +28 -0
- package/src/types/api/moneroKeyImageSync.ts +45 -0
- package/src/types/api/moneroSignTransaction.ts +54 -0
- package/src/types/api/zcashGetAddress.ts +32 -0
- package/src/types/api/zcashGetFullViewingKey.ts +24 -0
- package/src/types/api/zcashSignPczt.ts +18 -0
- package/src/types/device.ts +18 -0
- package/src/utils/logger.ts +4 -0
|
@@ -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 {
|
|
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: '1.0.4',
|
|
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: '1.0.4',
|
|
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: '1.0.4',
|
|
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,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
formatLogMethodLabel,
|
|
61
62
|
getLogBlockLabel,
|
|
62
63
|
getSafeLogPayload,
|
|
64
|
+
getSafeLogTask,
|
|
63
65
|
} from '../events';
|
|
64
66
|
import TransportManager from '../data-manager/TransportManager';
|
|
65
67
|
import DeviceConnector from '../device/DeviceConnector';
|
|
@@ -125,6 +127,8 @@ const parseInitOptions = (method?: BaseMethod): InitOptions => ({
|
|
|
125
127
|
});
|
|
126
128
|
|
|
127
129
|
let _core: Core | undefined;
|
|
130
|
+
let transportSwitchGeneration = 0;
|
|
131
|
+
let pendingTransportSwitch: Promise<void> = Promise.resolve();
|
|
128
132
|
let _deviceList: DeviceList | undefined;
|
|
129
133
|
let _connector: DeviceConnector | undefined;
|
|
130
134
|
let _uiPromises: UiPromise<UiPromiseResponse['type']>[] = []; // Waiting for ui response
|
|
@@ -990,15 +994,11 @@ function canSkipInitialize(method: BaseMethod, device: Device): boolean {
|
|
|
990
994
|
}
|
|
991
995
|
|
|
992
996
|
export function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown) {
|
|
993
|
-
const typedError = error as { errorCode?: unknown;
|
|
994
|
-
const message =
|
|
995
|
-
typeof typedError?.message === 'string' ? typedError.message : String(error ?? '');
|
|
997
|
+
const typedError = error as { errorCode?: unknown; params?: { expectedProtocol?: unknown } };
|
|
996
998
|
return (
|
|
997
999
|
method.payload.connectProtocol === 'V2' &&
|
|
998
|
-
typedError?.errorCode === HardwareErrorCode.
|
|
999
|
-
|
|
1000
|
-
message.includes('expected V2') &&
|
|
1001
|
-
message.includes('did not respond to expected protocol')
|
|
1000
|
+
typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
|
|
1001
|
+
typedError.params?.expectedProtocol === 'V2'
|
|
1002
1002
|
);
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
@@ -1008,7 +1008,7 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
1008
1008
|
}
|
|
1009
1009
|
const typedError = error as {
|
|
1010
1010
|
errorCode?: unknown;
|
|
1011
|
-
params?: { acquireDeadlineExceeded?: unknown };
|
|
1011
|
+
params?: { acquireDeadlineExceeded?: unknown; expectedProtocol?: unknown };
|
|
1012
1012
|
};
|
|
1013
1013
|
if (
|
|
1014
1014
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
|
|
@@ -1019,6 +1019,8 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
|
|
|
1019
1019
|
return (
|
|
1020
1020
|
typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
1021
1021
|
typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
1022
|
+
(typedError?.errorCode === HardwareErrorCode.TransportProtocolProbeFailed &&
|
|
1023
|
+
!typedError.params?.expectedProtocol) ||
|
|
1022
1024
|
isRetryableBleProtocolV2ProbeError(method, error) ||
|
|
1023
1025
|
isMissingDetectedProtocolV2Error(method, error)
|
|
1024
1026
|
);
|
|
@@ -1273,6 +1275,8 @@ const ensureConnected = async (
|
|
|
1273
1275
|
HardwareErrorCode.BridgeTimeoutError,
|
|
1274
1276
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
1275
1277
|
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
1278
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
1279
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
1276
1280
|
].includes(error.errorCode)
|
|
1277
1281
|
) {
|
|
1278
1282
|
_deviceList = undefined;
|
|
@@ -1360,6 +1364,8 @@ const ensureConnected = async (
|
|
|
1360
1364
|
HardwareErrorCode.BleCharacteristicNotifyChangeFailure,
|
|
1361
1365
|
HardwareErrorCode.BridgeNeedsPermission,
|
|
1362
1366
|
HardwareErrorCode.WebUsbDeviceAccessError,
|
|
1367
|
+
HardwareErrorCode.TransportProtocolProbeFailed,
|
|
1368
|
+
HardwareErrorCode.TransportProtocolMismatch,
|
|
1363
1369
|
HardwareErrorCode.DeviceInterruptedFromUser,
|
|
1364
1370
|
HardwareErrorCode.CallQueueActionCancelled,
|
|
1365
1371
|
].includes(error.errorCode) ||
|
|
@@ -1446,7 +1452,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1446
1452
|
};
|
|
1447
1453
|
for (const requestId of requestIds) {
|
|
1448
1454
|
const task = requestQueue.getTask(requestId);
|
|
1449
|
-
Log.debug('Cancel Api connect task: ', task);
|
|
1455
|
+
Log.debug('Cancel Api connect task: ', getSafeLogTask(task));
|
|
1450
1456
|
if (task) {
|
|
1451
1457
|
// During ensureConnected the method has a connectId but device is
|
|
1452
1458
|
// assigned only after the poll succeeds. Interrupt the cached BLE
|
|
@@ -1481,7 +1487,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1481
1487
|
};
|
|
1482
1488
|
for (const requestId of requestQueue.getRequestTasksId()) {
|
|
1483
1489
|
const task = requestQueue.getTask(requestId);
|
|
1484
|
-
Log.debug('Cancel Api connect task: ', task);
|
|
1490
|
+
Log.debug('Cancel Api connect task: ', getSafeLogTask(task));
|
|
1485
1491
|
if (task) {
|
|
1486
1492
|
interruptDevice(task.method?.device);
|
|
1487
1493
|
if (task.method.connectId) {
|
|
@@ -1826,6 +1832,10 @@ export default class Core extends EventEmitter {
|
|
|
1826
1832
|
|
|
1827
1833
|
private requestQueue = new RequestQueue();
|
|
1828
1834
|
|
|
1835
|
+
getOwnedUsbDeviceIdentity(descriptor: Parameters<typeof getOwnedUsbDeviceIdentity>[0]) {
|
|
1836
|
+
return getOwnedUsbDeviceIdentity(descriptor, this.requestQueue);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1829
1839
|
private disposePromise?: Promise<void>;
|
|
1830
1840
|
|
|
1831
1841
|
// background task
|
|
@@ -1940,7 +1950,7 @@ export default class Core extends EventEmitter {
|
|
|
1940
1950
|
break;
|
|
1941
1951
|
}
|
|
1942
1952
|
case IFRAME.CALLBACK: {
|
|
1943
|
-
Log.log('callback message: ', message);
|
|
1953
|
+
Log.log('callback message: ', getSafeLogPayload(message));
|
|
1944
1954
|
postMessage(message);
|
|
1945
1955
|
break;
|
|
1946
1956
|
}
|
|
@@ -1959,6 +1969,8 @@ export default class Core extends EventEmitter {
|
|
|
1959
1969
|
|
|
1960
1970
|
private async disposeResources(): Promise<void> {
|
|
1961
1971
|
Log.debug(`[Core] Disposing SDK instance: ${this.sdkInstanceId}`);
|
|
1972
|
+
transportSwitchGeneration += 1;
|
|
1973
|
+
pendingTransportSwitch = Promise.resolve();
|
|
1962
1974
|
pollingManager.stopAll();
|
|
1963
1975
|
this.requestQueue.abortAllRequests();
|
|
1964
1976
|
cleanup();
|
|
@@ -1993,6 +2005,8 @@ export default class Core extends EventEmitter {
|
|
|
1993
2005
|
}
|
|
1994
2006
|
|
|
1995
2007
|
export const initCore = () => {
|
|
2008
|
+
transportSwitchGeneration += 1;
|
|
2009
|
+
pendingTransportSwitch = Promise.resolve();
|
|
1996
2010
|
const core = new Core();
|
|
1997
2011
|
_core = core;
|
|
1998
2012
|
return core;
|
|
@@ -2022,6 +2036,8 @@ export const init = async (
|
|
|
2022
2036
|
plugin?: LowlevelTransportSharedPlugin
|
|
2023
2037
|
) => {
|
|
2024
2038
|
try {
|
|
2039
|
+
transportSwitchGeneration += 1;
|
|
2040
|
+
pendingTransportSwitch = Promise.resolve();
|
|
2025
2041
|
await DataManager.load(settings);
|
|
2026
2042
|
initTransport(Transport, plugin);
|
|
2027
2043
|
enableLog(DataManager.getSettings('debug'));
|
|
@@ -2047,10 +2063,26 @@ export const switchTransport = ({
|
|
|
2047
2063
|
Transport: any;
|
|
2048
2064
|
plugin?: LowlevelTransportSharedPlugin;
|
|
2049
2065
|
}) => {
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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;
|
|
2056
2088
|
};
|