@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
|
@@ -80,6 +80,102 @@ describe('public device lifecycle events', () => {
|
|
|
80
80
|
jest.restoreAllMocks();
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
+
test.each([
|
|
84
|
+
['react-native', 'V2', EDeviceType.Pro2, true],
|
|
85
|
+
['react-native', 'V2', EDeviceType.Neo, true],
|
|
86
|
+
['desktop-web-ble', 'V2', EDeviceType.Pro2, true],
|
|
87
|
+
['desktop-web-ble', 'V2', EDeviceType.Neo, true],
|
|
88
|
+
['react-native', 'V1', EDeviceType.Pro, false],
|
|
89
|
+
['desktop-web-ble', 'V1', EDeviceType.Touch, false],
|
|
90
|
+
['react-native', 'V1', EDeviceType.Pro2, false],
|
|
91
|
+
['react-native', 'V2', EDeviceType.Unknown, false],
|
|
92
|
+
['desktop-webusb', 'V2', EDeviceType.Pro2, false],
|
|
93
|
+
['lowlevel', 'V2', EDeviceType.Neo, false],
|
|
94
|
+
] as const)(
|
|
95
|
+
'releases the task and keeps only the intended BLE links: %s / %s / %s',
|
|
96
|
+
async (env, protocol, deviceType, keepConnection) => {
|
|
97
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue(env as never);
|
|
98
|
+
const device = createInitializedDevice(protocol);
|
|
99
|
+
device.features = { ...device.features, deviceType } as never;
|
|
100
|
+
device.originalDescriptor.session = device.mainId;
|
|
101
|
+
(device as unknown as { deviceAcquired: boolean }).deviceAcquired = true;
|
|
102
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
103
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
104
|
+
device.deviceConnector = { release, disconnect } as never;
|
|
105
|
+
const commands = {
|
|
106
|
+
disposed: false,
|
|
107
|
+
dispose: jest.fn(() => {
|
|
108
|
+
commands.disposed = true;
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
}),
|
|
111
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
112
|
+
};
|
|
113
|
+
device.commands = commands as never;
|
|
114
|
+
|
|
115
|
+
await device.release();
|
|
116
|
+
|
|
117
|
+
expect(release).toHaveBeenCalledWith(device.mainId, false, keepConnection);
|
|
118
|
+
expect(device.hasDeviceAcquire()).toBe(false);
|
|
119
|
+
expect(commands.disposed).toBe(true);
|
|
120
|
+
expect(device.keepSession).toBe(false);
|
|
121
|
+
|
|
122
|
+
await device.interruptionFromUser();
|
|
123
|
+
expect(disconnect).toHaveBeenCalledTimes(keepConnection ? 0 : 1);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
test('still disconnects when setup is cancelled after a kept-alive release', async () => {
|
|
128
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
129
|
+
const device = createInitializedDevice('V2');
|
|
130
|
+
const pendingAcquire = createDeferred<{ uuid: string; protocolType: 'V2' }>();
|
|
131
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
132
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
133
|
+
device.deviceConnector = {
|
|
134
|
+
release,
|
|
135
|
+
disconnect,
|
|
136
|
+
acquire: jest.fn(() => pendingAcquire.promise),
|
|
137
|
+
} as never;
|
|
138
|
+
device.commands = {
|
|
139
|
+
disposed: true,
|
|
140
|
+
dispose: jest.fn().mockResolvedValue(undefined),
|
|
141
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
142
|
+
} as never;
|
|
143
|
+
await device.release();
|
|
144
|
+
device.beginConnectionAttempt();
|
|
145
|
+
const acquiring = device.acquire('V2');
|
|
146
|
+
|
|
147
|
+
await device.interruptionFromUser();
|
|
148
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
149
|
+
pendingAcquire.resolve({ uuid: 'late-kept-alive-session', protocolType: 'V2' });
|
|
150
|
+
await expect(acquiring).rejects.toMatchObject({
|
|
151
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromUser,
|
|
152
|
+
});
|
|
153
|
+
expect(disconnect).toHaveBeenLastCalledWith('late-kept-alive-session');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test.each(['release failure', 'disconnect event'])(
|
|
157
|
+
'does not treat %s as a kept-alive connection',
|
|
158
|
+
async reason => {
|
|
159
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
160
|
+
const device = createInitializedDevice('V2');
|
|
161
|
+
const release = jest.fn().mockResolvedValue(true);
|
|
162
|
+
if (reason === 'release failure') release.mockRejectedValueOnce(new Error('release failed'));
|
|
163
|
+
const disconnect = jest.fn().mockResolvedValue(undefined);
|
|
164
|
+
device.deviceConnector = { release, disconnect } as never;
|
|
165
|
+
device.commands = {
|
|
166
|
+
disposed: true,
|
|
167
|
+
dispose: jest.fn().mockResolvedValue(undefined),
|
|
168
|
+
cancel: jest.fn().mockResolvedValue(undefined),
|
|
169
|
+
} as never;
|
|
170
|
+
await device.release();
|
|
171
|
+
if (reason === 'disconnect event') device.markTransportDisconnected();
|
|
172
|
+
|
|
173
|
+
await device.interruptionFromUser();
|
|
174
|
+
|
|
175
|
+
expect(disconnect).toHaveBeenCalledWith(device.mainId);
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
83
179
|
test.each(['V1', 'V2'] as const)(
|
|
84
180
|
'preserves the %s acknowledged result and waits for release before the next acquire',
|
|
85
181
|
async protocol => {
|
|
@@ -363,11 +459,11 @@ describe('public device lifecycle events', () => {
|
|
|
363
459
|
|
|
364
460
|
test('clears the cleanup barrier when its deadline expires', async () => {
|
|
365
461
|
const realSetTimeout = setTimeout;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
.
|
|
369
|
-
|
|
370
|
-
|
|
462
|
+
const cleanupDeadline = createDeferred<() => void>();
|
|
463
|
+
jest.spyOn(global, 'setTimeout').mockImplementation((callback, delay, ...args) => {
|
|
464
|
+
if (delay === 5_000) cleanupDeadline.resolve(() => callback(...args));
|
|
465
|
+
return realSetTimeout(callback, delay, ...args);
|
|
466
|
+
});
|
|
371
467
|
{
|
|
372
468
|
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
373
469
|
const acquire = jest.spyOn(Device.prototype, 'acquire').mockResolvedValue(undefined);
|
|
@@ -381,13 +477,15 @@ describe('public device lifecycle events', () => {
|
|
|
381
477
|
type: IFRAME.CALL,
|
|
382
478
|
payload: { method: 'getDeviceState', connectId: 'draining-device', connectProtocol: 'V2' },
|
|
383
479
|
} as CoreMessage);
|
|
384
|
-
|
|
480
|
+
const expireCleanupDeadline = await cleanupDeadline.promise;
|
|
481
|
+
cancel(context, 'draining-device');
|
|
482
|
+
expireCleanupDeadline();
|
|
385
483
|
await expect(result).resolves.toBeDefined();
|
|
386
|
-
gate.resolve();
|
|
387
484
|
await new Promise(resolve => {
|
|
388
485
|
setImmediate(resolve);
|
|
389
486
|
});
|
|
390
487
|
expect(context.getPrePendingCallPromise('draining-device')).toBeUndefined();
|
|
488
|
+
gate.resolve();
|
|
391
489
|
}
|
|
392
490
|
});
|
|
393
491
|
|
|
@@ -608,7 +706,9 @@ describe('public device lifecycle events', () => {
|
|
|
608
706
|
);
|
|
609
707
|
|
|
610
708
|
test.each([
|
|
611
|
-
[HardwareErrorCode.
|
|
709
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, true],
|
|
710
|
+
[HardwareErrorCode.TransportProtocolMismatch, false],
|
|
711
|
+
[HardwareErrorCode.RuntimeError, false],
|
|
612
712
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
613
713
|
] as const)(
|
|
614
714
|
'retries a Protocol V2 probe mismatch with error code %s: %s',
|
|
@@ -616,8 +716,8 @@ describe('public device lifecycle events', () => {
|
|
|
616
716
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
617
717
|
const error = {
|
|
618
718
|
errorCode,
|
|
619
|
-
message:
|
|
620
|
-
|
|
719
|
+
message: 'diagnostic wording is not used for classification',
|
|
720
|
+
params: { expectedProtocol: 'V2' },
|
|
621
721
|
};
|
|
622
722
|
|
|
623
723
|
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(expected);
|
|
@@ -627,6 +727,8 @@ describe('public device lifecycle events', () => {
|
|
|
627
727
|
test.each([
|
|
628
728
|
[HardwareErrorCode.BleConnectedError, true],
|
|
629
729
|
[HardwareErrorCode.BleTimeoutError, true],
|
|
730
|
+
[HardwareErrorCode.TransportProtocolProbeFailed, true],
|
|
731
|
+
[HardwareErrorCode.TransportProtocolMismatch, false],
|
|
630
732
|
[HardwareErrorCode.PollingTimeout, false],
|
|
631
733
|
[HardwareErrorCode.BleDeviceBondError, false],
|
|
632
734
|
] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
|
|
@@ -639,6 +741,21 @@ describe('public device lifecycle events', () => {
|
|
|
639
741
|
expect(isRetryableBleConnectionError(method, error)).toBe(expected);
|
|
640
742
|
});
|
|
641
743
|
|
|
744
|
+
test('does not retry a failed strict V1 probe as a V2 probe or after user cancellation', () => {
|
|
745
|
+
const error = ERRORS.TypedError(HardwareErrorCode.TransportProtocolProbeFailed, undefined, {
|
|
746
|
+
expectedProtocol: 'V1',
|
|
747
|
+
});
|
|
748
|
+
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
749
|
+
expect(isRetryableBleProtocolV2ProbeError(method, error)).toBe(false);
|
|
750
|
+
expect(isRetryableBleConnectionError(method, error)).toBe(false);
|
|
751
|
+
expect(
|
|
752
|
+
isRetryableBleConnectionError(
|
|
753
|
+
{ payload: {}, device: { wasInterruptedByUser: () => true } } as never,
|
|
754
|
+
ERRORS.TypedError(HardwareErrorCode.TransportProtocolProbeFailed)
|
|
755
|
+
)
|
|
756
|
+
).toBe(false);
|
|
757
|
+
});
|
|
758
|
+
|
|
642
759
|
test('does not retry the desktop acquire deadline fallback', () => {
|
|
643
760
|
const method = { payload: { connectProtocol: 'V2' } } as never;
|
|
644
761
|
const error = {
|
|
@@ -31,6 +31,10 @@ const createReader = (bytes = artifactBytes) => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
describe('FirmwareArtifactSource', () => {
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.restoreAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
34
38
|
it('materializes verified bytes before exposing the source', async () => {
|
|
35
39
|
const backingBytes = Uint8Array.from(artifactBytes);
|
|
36
40
|
const { reader, read, close } = createReader(backingBytes);
|
|
@@ -86,6 +90,10 @@ describe('FirmwareArtifactSource', () => {
|
|
|
86
90
|
sha256: bytesToHex(sha256(bytes)),
|
|
87
91
|
};
|
|
88
92
|
const { reader, read } = createReader(bytes);
|
|
93
|
+
const hasher = sha256.create();
|
|
94
|
+
const update = jest.spyOn(hasher, 'update');
|
|
95
|
+
const digest = jest.spyOn(hasher, 'digest');
|
|
96
|
+
jest.spyOn(sha256, 'create').mockReturnValue(hasher);
|
|
89
97
|
|
|
90
98
|
const source = await openFirmwareByteSource({ artifact: largeArtifact, reader });
|
|
91
99
|
if (!source) {
|
|
@@ -103,6 +111,9 @@ describe('FirmwareArtifactSource', () => {
|
|
|
103
111
|
length: 3,
|
|
104
112
|
},
|
|
105
113
|
]);
|
|
114
|
+
expect(update).toHaveBeenCalledTimes(2);
|
|
115
|
+
expect(update.mock.invocationCallOrder[0]).toBeLessThan(read.mock.invocationCallOrder[1]);
|
|
116
|
+
expect(digest.mock.invocationCallOrder[0]).toBeGreaterThan(update.mock.invocationCallOrder[1]);
|
|
106
117
|
await source.close();
|
|
107
118
|
});
|
|
108
119
|
|
|
@@ -4,7 +4,18 @@ import {
|
|
|
4
4
|
formatLogMethodLabel,
|
|
5
5
|
getLogBlockLabel,
|
|
6
6
|
getSafeLogPayload,
|
|
7
|
+
getSafeLogTask,
|
|
7
8
|
} from '../src/events';
|
|
9
|
+
import { getLog, initLog, setLoggerPostMessage } from '../src/utils/logger';
|
|
10
|
+
import MoneroGetAddress from '../src/api/monero/MoneroGetAddress';
|
|
11
|
+
import ZcashGetAddress from '../src/api/zcash/ZcashGetAddress';
|
|
12
|
+
|
|
13
|
+
import type { Device } from '../src/device/Device';
|
|
14
|
+
|
|
15
|
+
jest.mock('../src/data/config', () => ({
|
|
16
|
+
getSDKVersion: jest.fn(() => '1.0.0'),
|
|
17
|
+
DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
|
|
18
|
+
}));
|
|
8
19
|
|
|
9
20
|
describe('getLogBlockLabel', () => {
|
|
10
21
|
it('keeps existing sensitive UI response blocking', () => {
|
|
@@ -52,11 +63,17 @@ describe('getLogBlockLabel', () => {
|
|
|
52
63
|
}
|
|
53
64
|
);
|
|
54
65
|
|
|
55
|
-
it('
|
|
66
|
+
it.each([false, true])('redacts btcSignMessage logs with debug=%s', enabled => {
|
|
67
|
+
const method = 'btcSignMessage';
|
|
68
|
+
const prefix = `sensitive-btc-sign-message-${enabled}`;
|
|
69
|
+
const logger = initLog(prefix, enabled);
|
|
70
|
+
const postMessage = jest.fn();
|
|
71
|
+
setLoggerPostMessage(postMessage);
|
|
72
|
+
const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
56
73
|
const request = {
|
|
57
|
-
method
|
|
74
|
+
method,
|
|
58
75
|
path: "m/44'/0'/0'/0/0",
|
|
59
|
-
messageHex: '
|
|
76
|
+
messageHex: Buffer.from('synthetic-message-marker').toString('hex'),
|
|
60
77
|
coin: 'Bitcoin',
|
|
61
78
|
};
|
|
62
79
|
const iframeRequest = {
|
|
@@ -66,15 +83,39 @@ describe('getLogBlockLabel', () => {
|
|
|
66
83
|
const response = {
|
|
67
84
|
success: true,
|
|
68
85
|
payload: {
|
|
69
|
-
signature: 'signature-
|
|
70
|
-
address: '
|
|
86
|
+
signature: 'synthetic-signature-marker',
|
|
87
|
+
address: 'synthetic-address-marker',
|
|
71
88
|
},
|
|
72
89
|
};
|
|
90
|
+
const task = { id: 7, method: { name: method, payload: request } };
|
|
91
|
+
const originals = JSON.stringify([request, iframeRequest, response, task]);
|
|
92
|
+
const blockLabel = getLogBlockLabel(iframeRequest);
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
try {
|
|
95
|
+
expect(getLogBlockLabel(request)).toBe(method);
|
|
96
|
+
expect(blockLabel).toBe(method);
|
|
97
|
+
logger.debug('request:', getSafeLogPayload(iframeRequest, blockLabel));
|
|
98
|
+
logger.debug('response:', getSafeLogPayload(response, blockLabel));
|
|
99
|
+
logger.debug('Cancel Api connect task: ', getSafeLogTask(task));
|
|
100
|
+
|
|
101
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
102
|
+
expect(cached.map(entry => entry.message[1])).toEqual([
|
|
103
|
+
{ method, payload: '[REDACTED]' },
|
|
104
|
+
{ method, payload: '[REDACTED]' },
|
|
105
|
+
{ id: task.id, method, payload: '[REDACTED]' },
|
|
106
|
+
]);
|
|
107
|
+
expect(postMessage).toHaveBeenCalledTimes(3);
|
|
108
|
+
expect(consoleLog).toHaveBeenCalledTimes(enabled ? 3 : 0);
|
|
109
|
+
for (const output of [cached, postMessage.mock.calls, consoleLog.mock.calls]) {
|
|
110
|
+
const serialized = JSON.stringify(output);
|
|
111
|
+
expect(serialized).not.toContain('synthetic-');
|
|
112
|
+
expect(serialized).not.toContain(request.messageHex);
|
|
113
|
+
}
|
|
114
|
+
expect(JSON.stringify([request, iframeRequest, response, task])).toBe(originals);
|
|
115
|
+
} finally {
|
|
116
|
+
consoleLog.mockRestore();
|
|
117
|
+
setLoggerPostMessage(jest.fn());
|
|
118
|
+
}
|
|
78
119
|
});
|
|
79
120
|
|
|
80
121
|
it('keeps ordinary API requests and responses visible', () => {
|
|
@@ -96,4 +137,285 @@ describe('getLogBlockLabel', () => {
|
|
|
96
137
|
it('does not classify events without an API method as method calls', () => {
|
|
97
138
|
expect(getLogBlockLabel({ event: 'DEVICE_EVENT', type: 'device-connect' })).toBeUndefined();
|
|
98
139
|
});
|
|
140
|
+
|
|
141
|
+
it.each(['zcashGetAddress', 'zcashGetFullViewingKey', 'zcashSignPczt'])(
|
|
142
|
+
'keeps %s wallet data out of logger caches and events with debug disabled',
|
|
143
|
+
method => {
|
|
144
|
+
const postMessage = jest.fn();
|
|
145
|
+
const prefix = `sensitive-zcash-${method}`;
|
|
146
|
+
const logger = initLog(prefix, false);
|
|
147
|
+
setLoggerPostMessage(postMessage);
|
|
148
|
+
const request = {
|
|
149
|
+
event: 'iframe-call',
|
|
150
|
+
payload: { method, pczt: 'synthetic-pczt-marker', path: 'synthetic-path-marker' },
|
|
151
|
+
};
|
|
152
|
+
const response = {
|
|
153
|
+
success: true,
|
|
154
|
+
payload: [
|
|
155
|
+
{
|
|
156
|
+
ufvk: 'synthetic-viewing-key-marker',
|
|
157
|
+
seedFingerprint: 'synthetic-fingerprint-marker',
|
|
158
|
+
pczt: 'synthetic-signed-pczt-marker',
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
const blockLabel = getLogBlockLabel(request);
|
|
163
|
+
|
|
164
|
+
expect(blockLabel).toBe(method);
|
|
165
|
+
expect(getLogBlockLabel(request.payload)).toBe(method);
|
|
166
|
+
logger.debug('request:', getSafeLogPayload(request, blockLabel));
|
|
167
|
+
logger.debug('response:', getSafeLogPayload(response, blockLabel));
|
|
168
|
+
|
|
169
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
170
|
+
expect(cached.map(entry => entry.message[1])).toEqual([
|
|
171
|
+
{ method, payload: '[REDACTED]' },
|
|
172
|
+
{ method, payload: '[REDACTED]' },
|
|
173
|
+
]);
|
|
174
|
+
expect(postMessage).toHaveBeenCalledTimes(2);
|
|
175
|
+
for (const output of [JSON.stringify(cached), JSON.stringify(postMessage.mock.calls)]) {
|
|
176
|
+
expect(output).not.toContain('synthetic-');
|
|
177
|
+
expect(output).toContain('[REDACTED]');
|
|
178
|
+
}
|
|
179
|
+
expect(response.payload[0].ufvk).toBe('synthetic-viewing-key-marker');
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
it.each(['moneroGetAddress', 'moneroGetWatchKey'])(
|
|
184
|
+
'keeps %s addresses and view keys out of logger caches',
|
|
185
|
+
method => {
|
|
186
|
+
const prefix = `sensitive-monero-${method}`;
|
|
187
|
+
const logger = initLog(prefix, false);
|
|
188
|
+
setLoggerPostMessage(jest.fn());
|
|
189
|
+
const request = { event: 'iframe-call', payload: { method, path: "m/44'/128'/0'" } };
|
|
190
|
+
const response = {
|
|
191
|
+
success: true,
|
|
192
|
+
payload: { address: 'synthetic-address-marker', watchKey: 'synthetic-view-key-marker' },
|
|
193
|
+
};
|
|
194
|
+
const blockLabel = getLogBlockLabel(request);
|
|
195
|
+
|
|
196
|
+
expect(blockLabel).toBe(method);
|
|
197
|
+
logger.debug('response:', getSafeLogPayload(response, blockLabel));
|
|
198
|
+
|
|
199
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
200
|
+
expect(cached.map(entry => entry.message[1])).toEqual([{ method, payload: '[REDACTED]' }]);
|
|
201
|
+
expect(JSON.stringify(cached)).not.toContain('synthetic-');
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
it('keeps moneroKeyImageSync transfers and key images out of logger caches', () => {
|
|
206
|
+
const method = 'moneroKeyImageSync';
|
|
207
|
+
const prefix = 'sensitive-monero-key-image-sync';
|
|
208
|
+
const logger = initLog(prefix, false);
|
|
209
|
+
setLoggerPostMessage(jest.fn());
|
|
210
|
+
const request = {
|
|
211
|
+
event: 'iframe-call',
|
|
212
|
+
payload: {
|
|
213
|
+
method,
|
|
214
|
+
path: "m/44'/128'/0'",
|
|
215
|
+
transfers: [{ outKey: 'synthetic-out-key-marker', txPubKey: 'synthetic-tx-key-marker' }],
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
const response = {
|
|
219
|
+
success: true,
|
|
220
|
+
payload: {
|
|
221
|
+
encKey: 'synthetic-enc-key-marker',
|
|
222
|
+
keyImages: [{ iv: 'synthetic-iv-marker', blob: 'synthetic-key-image-marker' }],
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
const blockLabel = getLogBlockLabel(request);
|
|
226
|
+
|
|
227
|
+
expect(blockLabel).toBe(method);
|
|
228
|
+
logger.debug('request:', getSafeLogPayload(request, blockLabel));
|
|
229
|
+
logger.debug('response:', getSafeLogPayload(response, blockLabel));
|
|
230
|
+
|
|
231
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
232
|
+
expect(cached.map(entry => entry.message[1])).toEqual([
|
|
233
|
+
{ method, payload: '[REDACTED]' },
|
|
234
|
+
{ method, payload: '[REDACTED]' },
|
|
235
|
+
]);
|
|
236
|
+
expect(JSON.stringify(cached)).not.toContain('synthetic-');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// The request reveals the real ring member, masks and amounts; the result
|
|
240
|
+
// carries the key that opens the signatures.
|
|
241
|
+
it('keeps moneroSignTransaction inputs and the opening key out of logger caches', () => {
|
|
242
|
+
const method = 'moneroSignTransaction';
|
|
243
|
+
const prefix = 'sensitive-monero-sign-transaction';
|
|
244
|
+
const logger = initLog(prefix, false);
|
|
245
|
+
setLoggerPostMessage(jest.fn());
|
|
246
|
+
const payload = {
|
|
247
|
+
method,
|
|
248
|
+
path: "m/44'/128'/0'",
|
|
249
|
+
tsxData: { fee: 'synthetic-fee-marker', outputs: [{ amount: 'synthetic-amount-marker' }] },
|
|
250
|
+
inputs: [{ real_output: 'synthetic-real-index-marker', mask: 'synthetic-mask-marker' }],
|
|
251
|
+
};
|
|
252
|
+
const response = {
|
|
253
|
+
success: true,
|
|
254
|
+
payload: {
|
|
255
|
+
signatures: ['synthetic-sealed-signature-marker'],
|
|
256
|
+
openingKey: 'synthetic-opening-key-marker',
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
const blockLabel = getLogBlockLabel({ event: 'iframe-call', payload });
|
|
260
|
+
|
|
261
|
+
expect(blockLabel).toBe(method);
|
|
262
|
+
logger.debug('request:', getSafeLogPayload(payload, blockLabel));
|
|
263
|
+
logger.debug('response:', getSafeLogPayload(response, blockLabel));
|
|
264
|
+
logger.debug('Cancel Api connect task: ', getSafeLogTask({ id: 9, method: { payload } }));
|
|
265
|
+
|
|
266
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
267
|
+
expect(cached.map(entry => entry.message[1])).toEqual([
|
|
268
|
+
{ method, payload: '[REDACTED]' },
|
|
269
|
+
{ method, payload: '[REDACTED]' },
|
|
270
|
+
{ id: 9, method, payload: '[REDACTED]' },
|
|
271
|
+
]);
|
|
272
|
+
expect(JSON.stringify(cached)).not.toContain('synthetic-');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Cancel logs the queued task, which holds the method's raw payload.
|
|
276
|
+
it('redacts a queued Zcash task the same way, keeping its id', () => {
|
|
277
|
+
const postMessage = jest.fn();
|
|
278
|
+
const prefix = 'sensitive-zcash-cancel';
|
|
279
|
+
const logger = initLog(prefix, false);
|
|
280
|
+
setLoggerPostMessage(postMessage);
|
|
281
|
+
const task = {
|
|
282
|
+
id: 7,
|
|
283
|
+
method: {
|
|
284
|
+
name: 'zcashSignPczt',
|
|
285
|
+
payload: { method: 'zcashSignPczt', pczt: 'synthetic-pczt-marker' },
|
|
286
|
+
pczt: 'synthetic-pczt-marker',
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
logger.debug('Cancel Api connect task: ', getSafeLogTask(task));
|
|
291
|
+
|
|
292
|
+
const cached = getLog().filter(entry => entry.prefix === prefix);
|
|
293
|
+
expect(cached.map(entry => entry.message[1])).toEqual([
|
|
294
|
+
{ id: 7, method: 'zcashSignPczt', payload: '[REDACTED]' },
|
|
295
|
+
]);
|
|
296
|
+
expect(JSON.stringify([cached, postMessage.mock.calls])).not.toContain('synthetic-');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('keeps Zcash and Monero results of an all-network call out of logs, others intact', () => {
|
|
300
|
+
const response = {
|
|
301
|
+
success: true,
|
|
302
|
+
payload: [
|
|
303
|
+
{
|
|
304
|
+
network: 'evm',
|
|
305
|
+
path: "m/44'/60'/0'/0/0",
|
|
306
|
+
success: true,
|
|
307
|
+
payload: { address: '0xabc', rootFingerprint: 1 },
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
network: 'zcash',
|
|
311
|
+
path: "m/32'/133'/0'",
|
|
312
|
+
success: true,
|
|
313
|
+
payload: {
|
|
314
|
+
address: 'u1-marker',
|
|
315
|
+
ufvk: 'synthetic-viewing-key-marker',
|
|
316
|
+
rootFingerprint: 1,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
network: 'xmr',
|
|
321
|
+
path: "m/44'/128'/0'",
|
|
322
|
+
success: true,
|
|
323
|
+
payload: { address: 'synthetic-xmr-address-marker', rootFingerprint: 1 },
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
network: 'zcash',
|
|
327
|
+
path: "m/32'/133'/1'",
|
|
328
|
+
success: false,
|
|
329
|
+
payload: { error: 'Device not support', code: 416 },
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const safe = getSafeLogPayload(response) as typeof response;
|
|
335
|
+
|
|
336
|
+
expect(safe.payload[0]).toBe(response.payload[0]);
|
|
337
|
+
expect(safe.payload[1]).toEqual({ ...response.payload[1], payload: '[REDACTED]' });
|
|
338
|
+
expect(safe.payload[2]).toEqual({ ...response.payload[2], payload: '[REDACTED]' });
|
|
339
|
+
expect(safe.payload[3]).toBe(response.payload[3]);
|
|
340
|
+
expect(JSON.stringify(safe)).not.toContain('marker');
|
|
341
|
+
expect(response.payload[1].payload).toHaveProperty('ufvk');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('redacts a privacy network result delivered one by one as a callback', () => {
|
|
345
|
+
const message = {
|
|
346
|
+
event: 'IFRAME_CALLBACK',
|
|
347
|
+
payload: {
|
|
348
|
+
callbackId: 'item',
|
|
349
|
+
data: {
|
|
350
|
+
network: 'zcash',
|
|
351
|
+
success: true,
|
|
352
|
+
payload: { ufvk: 'synthetic-viewing-key-marker' },
|
|
353
|
+
index: 0,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
expect(JSON.stringify(getSafeLogPayload(message))).not.toContain('marker');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it.each([
|
|
362
|
+
{
|
|
363
|
+
method: 'moneroGetAddress',
|
|
364
|
+
create: () =>
|
|
365
|
+
new MoneroGetAddress({
|
|
366
|
+
id: 1,
|
|
367
|
+
payload: { method: 'moneroGetAddress', path: "m/44'/128'/0'" },
|
|
368
|
+
}),
|
|
369
|
+
reply: { address: Buffer.from('synthetic-xmr-address-marker').toString('hex') },
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
method: 'zcashGetAddress',
|
|
373
|
+
create: () =>
|
|
374
|
+
new ZcashGetAddress({
|
|
375
|
+
id: 1,
|
|
376
|
+
payload: { method: 'zcashGetAddress', path: "m/32'/133'/0'" },
|
|
377
|
+
}),
|
|
378
|
+
reply: { address: 'synthetic-zcash-address-marker' },
|
|
379
|
+
},
|
|
380
|
+
])('keeps the $method previous-address event out of logs', async ({ method, create, reply }) => {
|
|
381
|
+
const instance = create();
|
|
382
|
+
instance.init();
|
|
383
|
+
const commands = { typedCall: jest.fn().mockResolvedValue({ message: reply }) };
|
|
384
|
+
instance.device = {
|
|
385
|
+
commands,
|
|
386
|
+
getCommands: () => commands,
|
|
387
|
+
toMessageObject: () => ({}),
|
|
388
|
+
} as unknown as Device;
|
|
389
|
+
const postMessage = jest.fn();
|
|
390
|
+
instance.postMessage = postMessage;
|
|
391
|
+
await instance.run();
|
|
392
|
+
|
|
393
|
+
const [[event]] = postMessage.mock.calls;
|
|
394
|
+
expect(event).toMatchObject({
|
|
395
|
+
type: UI_REQUEST.PREVIOUS_ADDRESS_RESULT,
|
|
396
|
+
payload: { method, data: { address: expect.stringContaining('synthetic-') } },
|
|
397
|
+
});
|
|
398
|
+
const blockLabel = getLogBlockLabel(event);
|
|
399
|
+
expect(blockLabel).toBe(method);
|
|
400
|
+
expect(JSON.stringify(getSafeLogPayload(event, blockLabel))).not.toContain('synthetic-');
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('keeps previous-address events of other methods visible', () => {
|
|
404
|
+
const event = {
|
|
405
|
+
event: 'UI_EVENT',
|
|
406
|
+
type: UI_REQUEST.PREVIOUS_ADDRESS_RESULT,
|
|
407
|
+
payload: { device: {}, method: 'btcGetAddress', data: { address: 'bc1qexample' } },
|
|
408
|
+
};
|
|
409
|
+
expect(getLogBlockLabel(event)).toBeUndefined();
|
|
410
|
+
expect(getSafeLogPayload(event)).toBe(event);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('logs other queued tasks as before', () => {
|
|
414
|
+
const task = {
|
|
415
|
+
id: 8,
|
|
416
|
+
method: { name: 'getDeviceState', payload: { method: 'getDeviceState' } },
|
|
417
|
+
};
|
|
418
|
+
expect(getSafeLogTask(task)).toBe(task);
|
|
419
|
+
expect(getSafeLogTask(undefined)).toBeUndefined();
|
|
420
|
+
});
|
|
99
421
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LoggerNames, enableLog, getLogger } from '../src/utils/logger';
|
|
2
|
+
|
|
3
|
+
describe('logger', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
enableLog(false);
|
|
6
|
+
jest.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('enables method logs that share a prefix with common-connect-sdk', () => {
|
|
10
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
11
|
+
enableLog(true);
|
|
12
|
+
|
|
13
|
+
expect(getLogger(LoggerNames.Method)).toBe(getLogger(LoggerNames.HdCommonConnectSdk));
|
|
14
|
+
getLogger(LoggerNames.Method).debug('shared prefix message');
|
|
15
|
+
|
|
16
|
+
expect(consoleSpy).toHaveBeenCalledWith(LoggerNames.Method, 'shared prefix message');
|
|
17
|
+
});
|
|
18
|
+
});
|