@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
@@ -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('logs signing requests and responses as-is', () => {
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: 'btcSignMessage',
74
+ method,
58
75
  path: "m/44'/0'/0'/0/0",
59
- messageHex: '68656c6c6f',
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-bytes',
70
- address: 'bc1qexample',
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
- expect(getLogBlockLabel(request)).toBeUndefined();
75
- expect(getLogBlockLabel(iframeRequest)).toBeUndefined();
76
- expect(getSafeLogPayload(request)).toBe(request);
77
- expect(getSafeLogPayload(response)).toBe(response);
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
+ });
@@ -0,0 +1,147 @@
1
+ import MoneroGetAddress from '../src/api/monero/MoneroGetAddress';
2
+ import MoneroGetWatchKey from '../src/api/monero/MoneroGetWatchKey';
3
+ import { toHardened } from '../src/api/helpers/pathUtils';
4
+
5
+ import type { Device } from '../src/device/Device';
6
+
7
+ jest.mock('../src/data/config', () => ({
8
+ getSDKVersion: jest.fn(() => '1.0.0'),
9
+ DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
10
+ }));
11
+
12
+ // firmware-pro tests/device_tests/monero/test_getwatchkey.py, account 0.
13
+ const ADDRESS =
14
+ '4Ahp23WfMrMFK3wYL2hLWQFGt87ZTeRkufS6JoQZu6MEFDokAQeGWmu9MA3GFq1yVLSJQbKJqVAn9F9DLYGpRzRAEXqAXKM';
15
+ const WATCH_KEY = '8722520a581e2a50cc1adab4a1692401effd37b0d63b9d9b60fd7f34ea2b950e';
16
+ const ascii = (value: string) => Buffer.from(value, 'utf-8').toString('hex');
17
+ const ACCOUNT_0 = [toHardened(44), toHardened(128), toHardened(0)];
18
+
19
+ const attachDevice = (method: MoneroGetAddress | MoneroGetWatchKey, typedCall: jest.Mock) => {
20
+ const commands = { typedCall };
21
+ method.device = {
22
+ commands,
23
+ getCommands: () => commands,
24
+ toMessageObject: () => ({}),
25
+ } as unknown as Device;
26
+ method.postMessage = jest.fn();
27
+ };
28
+
29
+ const createAddressMethod = (payload: Record<string, unknown>) =>
30
+ new MoneroGetAddress({ id: 1, payload: { method: 'moneroGetAddress', ...payload } });
31
+ const createWatchKeyMethod = (payload: Record<string, unknown>) =>
32
+ new MoneroGetWatchKey({ id: 1, payload: { method: 'moneroGetWatchKey', ...payload } });
33
+
34
+ describe('MoneroGetAddress', () => {
35
+ test('asks for the account address and decodes the ASCII reply', async () => {
36
+ const method = createAddressMethod({ path: "m/44'/128'/0'" });
37
+ method.init();
38
+ const typedCall = jest
39
+ .fn()
40
+ .mockResolvedValue({ type: 'MoneroAddress', message: { address: ascii(ADDRESS) } });
41
+ attachDevice(method, typedCall);
42
+
43
+ await expect(method.run()).resolves.toEqual({ path: "m/44'/128'/0'", address: ADDRESS });
44
+ expect(typedCall).toHaveBeenCalledWith('MoneroGetAddress', 'MoneroAddress', {
45
+ address_n: ACCOUNT_0,
46
+ network_type: 0,
47
+ show_display: true,
48
+ });
49
+ });
50
+
51
+ test('returns one entry per bundle item', async () => {
52
+ const method = createAddressMethod({
53
+ bundle: [
54
+ { path: "m/44'/128'/0'", showOnOneKey: false },
55
+ { path: "m/44'/128'/1'", showOnOneKey: false },
56
+ ],
57
+ });
58
+ method.init();
59
+ const typedCall = jest
60
+ .fn()
61
+ .mockResolvedValue({ type: 'MoneroAddress', message: { address: ascii(ADDRESS) } });
62
+ attachDevice(method, typedCall);
63
+
64
+ const result = await method.run();
65
+ expect(result).toHaveLength(2);
66
+ expect(typedCall.mock.calls[1][2]).toMatchObject({ show_display: false });
67
+ });
68
+
69
+ test('leaves which paths are allowed to the device', () => {
70
+ const method = createAddressMethod({ path: "m/44'/128'/0'/0/0" });
71
+ method.init();
72
+ expect(method.params[0].address_n).toEqual([...ACCOUNT_0, 0, 0]);
73
+ });
74
+
75
+ test('rejects an unknown network type', () => {
76
+ expect(() => createAddressMethod({ path: "m/44'/128'/0'", networkType: 3 }).init()).toThrow();
77
+ });
78
+
79
+ // Each of these would otherwise be sent as account 0'.
80
+ test.each([
81
+ ["m/44'/128'/2147483648'"],
82
+ ["m/44'/128'/4294967296'"],
83
+ ["m/44'/128'/0.5'"],
84
+ ["m/44'/128'/0x0'"],
85
+ ["m/44'/128'/0h"],
86
+ ["m/44'/128'/2147483648"],
87
+ [[...ACCOUNT_0.slice(0, 2), 0x100000000 + toHardened(0)]],
88
+ [[...ACCOUNT_0.slice(0, 2), toHardened(0) + 0.5]],
89
+ ])('rejects the account path %j instead of aliasing it to another account', path => {
90
+ for (const method of [createAddressMethod({ path }), createWatchKeyMethod({ path })]) {
91
+ expect(() => method.init()).toThrow('Not a valid path');
92
+ }
93
+ });
94
+
95
+ test.each([
96
+ ["m/44'/128'/2147483647'", [...ACCOUNT_0.slice(0, 2), 0xffffffff]],
97
+ [
98
+ [...ACCOUNT_0.slice(0, 2), 0xffffffff],
99
+ [...ACCOUNT_0.slice(0, 2), 0xffffffff],
100
+ ],
101
+ ])('accepts the largest hardened account %j', (path, addressN) => {
102
+ const method = createWatchKeyMethod({ path });
103
+ method.init();
104
+ expect(method.params[0].address_n).toEqual(addressN);
105
+ });
106
+
107
+ test('fails when the device omits the address', async () => {
108
+ const method = createAddressMethod({ path: "m/44'/128'/0'" });
109
+ method.init();
110
+ attachDevice(method, jest.fn().mockResolvedValue({ type: 'MoneroAddress', message: {} }));
111
+
112
+ await expect(method.run()).rejects.toThrow();
113
+ });
114
+ });
115
+
116
+ describe('MoneroGetWatchKey', () => {
117
+ test('returns the view key and the decoded address', async () => {
118
+ const method = createWatchKeyMethod({ path: "m/44'/128'/0'" });
119
+ method.init();
120
+ const typedCall = jest.fn().mockResolvedValue({
121
+ type: 'MoneroWatchKey',
122
+ message: { watch_key: WATCH_KEY, address: ascii(ADDRESS) },
123
+ });
124
+ attachDevice(method, typedCall);
125
+
126
+ await expect(method.run()).resolves.toEqual({
127
+ path: "m/44'/128'/0'",
128
+ watchKey: WATCH_KEY,
129
+ address: ADDRESS,
130
+ });
131
+ expect(typedCall).toHaveBeenCalledWith('MoneroGetWatchKey', 'MoneroWatchKey', {
132
+ address_n: ACCOUNT_0,
133
+ network_type: 0,
134
+ });
135
+ });
136
+
137
+ test('fails when the device omits the view key', async () => {
138
+ const method = createWatchKeyMethod({ path: "m/44'/128'/0'" });
139
+ method.init();
140
+ attachDevice(
141
+ method,
142
+ jest.fn().mockResolvedValue({ type: 'MoneroWatchKey', message: { address: ascii(ADDRESS) } })
143
+ );
144
+
145
+ await expect(method.run()).rejects.toThrow();
146
+ });
147
+ });