@onekeyfe/hd-transport 1.2.8-alpha.13 → 1.2.8-alpha.130

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.
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS,
3
+ PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW,
3
4
  PROTOCOL_V2_PACKET_SRC_COMMAND,
4
5
  } from '../../constants';
5
6
  import { ProtocolV2FrameAssembler, concatUint8Arrays } from './frame-assembler';
@@ -9,13 +10,18 @@ import {
9
10
  createProtocolV2LinkDisabledError,
10
11
  isProtocolV2LinkDisabledError,
11
12
  isProtocolV2LinkDisabledFailure,
13
+ isProtocolV2LinkError,
12
14
  } from './errors';
13
15
  import { ProtocolV2 } from '..';
14
16
  import * as check from '../../utils/highlevel-checks';
15
17
  import { LogBlockCommand } from '../../utils/logBlockCommand';
16
18
 
17
19
  import type { Root } from 'protobufjs/light';
18
- import type { MessageFromOneKey, TransportWriteMetrics } from '../../types';
20
+ import type {
21
+ MessageFromOneKey,
22
+ ProtocolV2FileWritePipeline,
23
+ TransportWriteMetrics,
24
+ } from '../../types';
19
25
 
20
26
  export * from './errors';
21
27
 
@@ -62,6 +68,7 @@ export type ProtocolV2CallOptions = {
62
68
  returnAfterWrite?: boolean;
63
69
  onResponseAfterWrite?: (response: MessageFromOneKey) => void;
64
70
  writeWithResponse?: boolean;
71
+ fileWritePipeline?: ProtocolV2FileWritePipeline;
65
72
  };
66
73
 
67
74
  export { concatUint8Arrays, ProtocolV2FrameAssembler };
@@ -214,6 +221,30 @@ export async function withProtocolTimeout<T>(
214
221
  }
215
222
  }
216
223
 
224
+ const resolveProtocolV2CallTimeoutMs = (timeoutMs?: number) =>
225
+ timeoutMs && timeoutMs > 0 ? timeoutMs : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
226
+
227
+ function parseFileWriteChunk(request: Record<string, unknown>) {
228
+ const file = request.file as
229
+ | { path?: unknown; offset?: unknown; total_size?: unknown; data?: { byteLength?: unknown } }
230
+ | undefined;
231
+ const offset = Number(file?.offset);
232
+ const totalSize = Number(file?.total_size);
233
+ const length = Number(file?.data?.byteLength);
234
+ if (
235
+ typeof file?.path !== 'string' ||
236
+ !Number.isSafeInteger(offset) ||
237
+ offset < 0 ||
238
+ !Number.isSafeInteger(totalSize) ||
239
+ !Number.isSafeInteger(length) ||
240
+ length <= 0 ||
241
+ offset + length > totalSize
242
+ ) {
243
+ throw new Error('Protocol V2 file write pipeline chunk is invalid');
244
+ }
245
+ return { path: file.path, offset, end: offset + length, totalSize };
246
+ }
247
+
217
248
  export class ProtocolV2Session {
218
249
  private readonly options: ProtocolV2SessionOptions;
219
250
 
@@ -293,28 +324,46 @@ export class ProtocolV2Session {
293
324
  return result;
294
325
  }
295
326
 
327
+ private reportWriteCompleted(
328
+ callOptions: ProtocolV2CallOptions,
329
+ writeStartedAt: number,
330
+ frameBytes: number
331
+ ) {
332
+ try {
333
+ callOptions.onWriteCompleted?.({
334
+ elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
335
+ frameBytes,
336
+ });
337
+ } catch (error) {
338
+ const { logger, logPrefix = 'ProtocolV2' } = this.options;
339
+ logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
340
+ }
341
+ }
342
+
343
+ private createCallTimeoutError(name: string, timeoutMs: number): Error {
344
+ const { createTimeoutError } = this.options;
345
+ return createTimeoutError
346
+ ? createTimeoutError(name, timeoutMs)
347
+ : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`);
348
+ }
349
+
296
350
  private executeCall(
297
351
  name: string,
298
352
  data: Record<string, unknown>,
299
353
  callOptions: ProtocolV2CallOptions
300
354
  ): Promise<MessageFromOneKey> {
355
+ if (callOptions.fileWritePipeline) {
356
+ return this.executeFileWritePipeline(name, data, callOptions, callOptions.fileWritePipeline);
357
+ }
301
358
  const {
302
- schemas,
303
- router,
304
- packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
305
- maxFrameBytes,
306
359
  prepareCall,
307
360
  writeFrame,
308
361
  readFrame,
309
362
  logger,
310
363
  logPrefix = 'ProtocolV2',
311
- createTimeoutError,
312
364
  generation = 0,
313
365
  } = this.options;
314
- const timeoutMs =
315
- callOptions.timeoutMs && callOptions.timeoutMs > 0
316
- ? callOptions.timeoutMs
317
- : PROTOCOL_V2_DEFAULT_RESPONSE_TIMEOUT_MS;
366
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
318
367
  const abortController = new AbortController();
319
368
 
320
369
  const shouldReduceDebug = shouldReduceProtocolV2Debug(name);
@@ -335,29 +384,11 @@ export class ProtocolV2Session {
335
384
  if (!this.pendingResponseAfterWrite) {
336
385
  await prepareCall?.(baseCallContext);
337
386
  }
338
- const protoSeq = this.sequenceCursor.next();
339
- const frame = ProtocolV2.encodeFrame(schemas, name, data, {
340
- packetSrc,
341
- router,
342
- seq: protoSeq,
343
- });
344
-
345
- if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
346
- throw new Error(
347
- `Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
348
- );
349
- }
387
+ const { seq: protoSeq, frame } = this.encodeRequestFrame(name, data);
350
388
 
351
389
  const writeStartedAt = Date.now();
352
390
  await this.serializeWrite(() => writeFrame(frame, baseCallContext));
353
- try {
354
- callOptions.onWriteCompleted?.({
355
- elapsedMs: Math.max(Date.now() - writeStartedAt, 0),
356
- frameBytes: frame.byteLength,
357
- });
358
- } catch (error) {
359
- logger?.error?.(`${logPrefix} write metrics callback failed: ${String(error)}`);
360
- }
391
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
361
392
 
362
393
  if (callOptions.returnAfterWrite) {
363
394
  if (callOptions.expectedTypes?.length) {
@@ -376,68 +407,8 @@ export class ProtocolV2Session {
376
407
  const rxFrame = await readFrame(baseCallContext);
377
408
  if (abortController.signal.aborted) break;
378
409
 
379
- let header: ReturnType<typeof ProtocolV2.inspectFrameHeader>;
380
- try {
381
- header = ProtocolV2.inspectFrameHeader(rxFrame);
382
- } catch (cause) {
383
- throw new ProtocolV2LinkError(
384
- 'frame',
385
- `Protocol V2 frame validation failed: ${getErrorMessage(cause)}`,
386
- cause
387
- );
388
- }
389
- if (header.router !== router) {
390
- throw new ProtocolV2LinkError(
391
- 'router',
392
- `Protocol V2 router mismatch: expected ${router}, got ${header.router}`
393
- );
394
- }
395
- if (header.packetSrc !== packetSrc) {
396
- throw new ProtocolV2LinkError(
397
- 'packet-source',
398
- `Protocol V2 packet source mismatch: expected ${packetSrc}, got ${header.packetSrc}`
399
- );
400
- }
401
-
402
- let isAck: boolean;
403
- try {
404
- isAck = ProtocolV2.isAckFrame(rxFrame);
405
- } catch (cause) {
406
- throw new ProtocolV2LinkError(
407
- 'frame',
408
- `Protocol V2 ACK validation failed: ${getErrorMessage(cause)}`,
409
- cause
410
- );
411
- }
412
- if (isAck) {
413
- if (header.seq !== protoSeq) {
414
- throw new ProtocolV2LinkError(
415
- 'ack-sequence',
416
- `Protocol V2 ACK sequence mismatch: expected ${protoSeq}, got ${header.seq}`
417
- );
418
- }
419
- } else {
420
- let decoded: ReturnType<typeof ProtocolV2.decodeFrame>;
421
- try {
422
- decoded = ProtocolV2.decodeFrame(schemas, rxFrame);
423
- } catch (cause) {
424
- throw new ProtocolV2LinkError(
425
- 'frame',
426
- `Protocol V2 frame decode failed: ${getErrorMessage(cause)}`,
427
- cause
428
- );
429
- }
430
- // Firmware owns one TX sequence across all channels and packet sources,
431
- // so frames routed elsewhere create valid gaps in this session.
432
- if (this.lastResponseSequence === decoded.seq) {
433
- throw new ProtocolV2LinkError(
434
- 'response-sequence',
435
- `Protocol V2 duplicate response sequence: ${decoded.seq}`
436
- );
437
- }
438
- this.lastResponseSequence = decoded.seq;
439
-
440
- const response = check.call(decoded);
410
+ const response = this.parseResponseFrame(rxFrame, [protoSeq]);
411
+ if (response) {
441
412
  const { pendingResponseAfterWrite } = this;
442
413
  let belongsToWriteOnlyCall = false;
443
414
  if (pendingResponseAfterWrite) {
@@ -477,15 +448,274 @@ export class ProtocolV2Session {
477
448
  return withProtocolTimeout(
478
449
  runCall(),
479
450
  timeoutMs,
480
- () =>
481
- createTimeoutError
482
- ? createTimeoutError(name, timeoutMs)
483
- : new Error(`Protocol V2 response timeout after ${timeoutMs}ms for ${name}`),
451
+ () => this.createCallTimeoutError(name, timeoutMs),
484
452
  () => {
485
453
  abortController.abort();
486
454
  }
487
455
  );
488
456
  }
457
+
458
+ private encodeRequestFrame(name: string, data: Record<string, unknown>) {
459
+ const {
460
+ schemas,
461
+ router,
462
+ packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND,
463
+ maxFrameBytes,
464
+ } = this.options;
465
+ const seq = this.sequenceCursor.next();
466
+ const frame = ProtocolV2.encodeFrame(schemas, name, data, { packetSrc, router, seq });
467
+ if (maxFrameBytes !== undefined && frame.length > maxFrameBytes) {
468
+ throw new Error(
469
+ `Protocol V2 frame too large for transport: ${frame.length} > ${maxFrameBytes}`
470
+ );
471
+ }
472
+ return { seq, frame };
473
+ }
474
+
475
+ /**
476
+ * Validates one received frame for this session. Returns undefined for an ACK of one of
477
+ * `ackSequences`, otherwise the decoded response.
478
+ */
479
+ private parseResponseFrame(
480
+ rxFrame: Uint8Array,
481
+ ackSequences: readonly number[]
482
+ ): MessageFromOneKey | undefined {
483
+ const { schemas, router, packetSrc = PROTOCOL_V2_PACKET_SRC_COMMAND } = this.options;
484
+ let header: ReturnType<typeof ProtocolV2.inspectFrameHeader>;
485
+ try {
486
+ header = ProtocolV2.inspectFrameHeader(rxFrame);
487
+ } catch (cause) {
488
+ throw new ProtocolV2LinkError(
489
+ 'frame',
490
+ `Protocol V2 frame validation failed: ${getErrorMessage(cause)}`,
491
+ cause
492
+ );
493
+ }
494
+ if (header.router !== router) {
495
+ throw new ProtocolV2LinkError(
496
+ 'router',
497
+ `Protocol V2 router mismatch: expected ${router}, got ${header.router}`
498
+ );
499
+ }
500
+ if (header.packetSrc !== packetSrc) {
501
+ throw new ProtocolV2LinkError(
502
+ 'packet-source',
503
+ `Protocol V2 packet source mismatch: expected ${packetSrc}, got ${header.packetSrc}`
504
+ );
505
+ }
506
+
507
+ let isAck: boolean;
508
+ try {
509
+ isAck = ProtocolV2.isAckFrame(rxFrame);
510
+ } catch (cause) {
511
+ throw new ProtocolV2LinkError(
512
+ 'frame',
513
+ `Protocol V2 ACK validation failed: ${getErrorMessage(cause)}`,
514
+ cause
515
+ );
516
+ }
517
+ if (isAck) {
518
+ if (!ackSequences.includes(header.seq)) {
519
+ throw new ProtocolV2LinkError(
520
+ 'ack-sequence',
521
+ `Protocol V2 ACK sequence mismatch: expected ${ackSequences.join('|')}, got ${header.seq}`
522
+ );
523
+ }
524
+ return undefined;
525
+ }
526
+
527
+ let decoded: ReturnType<typeof ProtocolV2.decodeFrame>;
528
+ try {
529
+ decoded = ProtocolV2.decodeFrame(schemas, rxFrame);
530
+ } catch (cause) {
531
+ throw new ProtocolV2LinkError(
532
+ 'frame',
533
+ `Protocol V2 frame decode failed: ${getErrorMessage(cause)}`,
534
+ cause
535
+ );
536
+ }
537
+ // Firmware owns one TX sequence across all channels and packet sources,
538
+ // so frames routed elsewhere create valid gaps in this session.
539
+ if (this.lastResponseSequence === decoded.seq) {
540
+ throw new ProtocolV2LinkError(
541
+ 'response-sequence',
542
+ `Protocol V2 duplicate response sequence: ${decoded.seq}`
543
+ );
544
+ }
545
+ this.lastResponseSequence = decoded.seq;
546
+ return check.call(decoded);
547
+ }
548
+
549
+ /**
550
+ * Writes one file as ordered FilesystemFileWrite chunks while keeping up to
551
+ * PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW chunks in flight. Firmware still processes and
552
+ * answers every chunk in order, so responses are matched to chunks by position. Upstream
553
+ * bootloaders append at their own cursor without checking the request offset, so the echoed
554
+ * offset is what exposes a lost chunk. A device Failure, or `next` returning undefined, stops
555
+ * sending; the remaining in-flight responses are drained so the link stays usable.
556
+ */
557
+ private async executeFileWritePipeline(
558
+ name: string,
559
+ data: Record<string, unknown>,
560
+ callOptions: ProtocolV2CallOptions,
561
+ pipeline: ProtocolV2FileWritePipeline
562
+ ): Promise<MessageFromOneKey> {
563
+ if (name !== 'FilesystemFileWrite') {
564
+ throw new Error(`Protocol V2 file write pipeline does not support ${name}`);
565
+ }
566
+ if (this.pendingResponseAfterWrite) {
567
+ throw new Error('Protocol V2 file write pipeline cannot start while a response is pending');
568
+ }
569
+ const { prepareCall, writeFrame, readFrame, generation = 0 } = this.options;
570
+ const timeoutMs = resolveProtocolV2CallTimeoutMs(callOptions.timeoutMs);
571
+ const abortController = new AbortController();
572
+ const context: ProtocolV2CallContext = {
573
+ messageName: name,
574
+ timeoutMs,
575
+ highThroughput: true,
576
+ ...(callOptions.writeWithResponse === undefined
577
+ ? {}
578
+ : { writeWithResponse: callOptions.writeWithResponse }),
579
+ generation,
580
+ signal: abortController.signal,
581
+ };
582
+ // Transport errors and timeouts propagate unchanged: each transport classifies its own.
583
+ let transportFailure: unknown;
584
+ // One file can take far longer than one chunk, so the watchdog bounds each step instead.
585
+ const withStepTimeout = <T>(promise: Promise<T>, stepTimeoutMs = timeoutMs) =>
586
+ withProtocolTimeout(
587
+ promise,
588
+ stepTimeoutMs,
589
+ () => this.createCallTimeoutError(name, timeoutMs),
590
+ () => {
591
+ abortController.abort();
592
+ }
593
+ ).catch((error: unknown) => {
594
+ transportFailure = error;
595
+ throw error;
596
+ });
597
+ // ACKs and skipped frames must not extend the wait for an in-flight chunk's response.
598
+ let progressDeadline = 0;
599
+ const markProgress = () => {
600
+ progressDeadline = Date.now() + timeoutMs;
601
+ };
602
+
603
+ const inFlight: Array<{ seq: number; offset: number; end: number }> = [];
604
+ let path: string | undefined;
605
+ let totalSize: number | undefined;
606
+ let nextOffset: number | undefined;
607
+ let sourceDone = false;
608
+ let failure: MessageFromOneKey | undefined;
609
+ let lastConfirmed: MessageFromOneKey | undefined;
610
+
611
+ const sendChunk = async (request: Record<string, unknown>) => {
612
+ const chunk = parseFileWriteChunk(request);
613
+ if (path !== undefined && (chunk.path !== path || chunk.totalSize !== totalSize)) {
614
+ throw new Error('Protocol V2 file write pipeline chunk belongs to a different file');
615
+ }
616
+ if (nextOffset !== undefined && chunk.offset !== nextOffset) {
617
+ throw new Error(
618
+ `Protocol V2 file write pipeline chunk out of order: expected offset ${nextOffset}, got ${chunk.offset}`
619
+ );
620
+ }
621
+ path = chunk.path;
622
+ totalSize = chunk.totalSize;
623
+ nextOffset = chunk.end;
624
+
625
+ const { seq, frame } = this.encodeRequestFrame(name, request);
626
+ const writeStartedAt = Date.now();
627
+ await withStepTimeout(this.serializeWrite(() => writeFrame(frame, context)));
628
+ inFlight.push({ seq, offset: chunk.offset, end: chunk.end });
629
+ markProgress();
630
+ this.reportWriteCompleted(callOptions, writeStartedAt, frame.byteLength);
631
+ };
632
+ const sendNextChunk = async () => {
633
+ const request = await pipeline.next();
634
+ if (!request) {
635
+ sourceDone = true;
636
+ return;
637
+ }
638
+ await sendChunk(request);
639
+ };
640
+
641
+ await withStepTimeout(Promise.resolve().then(() => prepareCall?.(context)));
642
+ try {
643
+ await sendChunk(data);
644
+ while (!sourceDone && inFlight.length < PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW) {
645
+ await sendNextChunk();
646
+ }
647
+
648
+ while (inFlight.length > 0) {
649
+ // Floor at 1 ms: withProtocolTimeout treats 0 as "no timeout", so an expired deadline
650
+ // must still time out.
651
+ const rxFrame = await withStepTimeout(
652
+ readFrame(context),
653
+ Math.max(progressDeadline - Date.now(), 1)
654
+ );
655
+ const response = this.parseResponseFrame(
656
+ rxFrame,
657
+ inFlight.map(item => item.seq)
658
+ );
659
+ if (!response) {
660
+ // ACK of an in-flight chunk; its FilesystemFile response follows.
661
+ } else if (response.type === 'FilesystemFile') {
662
+ const [expected] = inFlight;
663
+ const { message } = response;
664
+ if (
665
+ Number(message.processed_byte) !== expected.end ||
666
+ message.path !== path ||
667
+ Number(message.offset) !== expected.offset ||
668
+ Number(message.total_size) !== totalSize
669
+ ) {
670
+ throw new ProtocolV2LinkError(
671
+ 'frame',
672
+ `Protocol V2 file write response does not match chunk ${expected.offset}-${
673
+ expected.end
674
+ }: got offset ${String(message.offset)} processed_byte ${String(
675
+ message.processed_byte
676
+ )}`
677
+ );
678
+ }
679
+ inFlight.shift();
680
+ markProgress();
681
+ if (!failure) {
682
+ lastConfirmed = response;
683
+ pipeline.onResponse(response);
684
+ if (!sourceDone) {
685
+ await sendNextChunk();
686
+ }
687
+ }
688
+ } else if (response.type === 'Failure') {
689
+ inFlight.shift();
690
+ markProgress();
691
+ failure = failure ?? response;
692
+ sourceDone = true;
693
+ } else if (COMMON_TERMINAL_RESPONSE_TYPES.has(response.type)) {
694
+ throw new ProtocolV2LinkError(
695
+ 'frame',
696
+ `Protocol V2 file write pipeline received unexpected ${response.type}`
697
+ );
698
+ }
699
+ }
700
+ } catch (error) {
701
+ // Unread responses of in-flight chunks would answer the next call on this link, so a
702
+ // session or caller error raised with chunks in flight must invalidate it.
703
+ if (inFlight.length === 0 || error === transportFailure || isProtocolV2LinkError(error)) {
704
+ throw error;
705
+ }
706
+ throw new ProtocolV2LinkError(
707
+ 'frame',
708
+ `Protocol V2 file write pipeline aborted with chunks in flight: ${getErrorMessage(error)}`,
709
+ error
710
+ );
711
+ }
712
+
713
+ const terminal = failure ?? lastConfirmed;
714
+ if (!terminal) {
715
+ throw new ProtocolV2LinkError('frame', 'Protocol V2 file write pipeline ended unconfirmed');
716
+ }
717
+ return terminal;
718
+ }
489
719
  }
490
720
 
491
721
  export async function probeProtocolV2({
@@ -4680,6 +4680,33 @@ export type UiAnimationRequest = {
4680
4680
  type?: UiAnimationType;
4681
4681
  };
4682
4682
 
4683
+ // ZcashSignPczt
4684
+ export type ZcashSignPczt = {
4685
+ pczt_length: number;
4686
+ pczt_initial_chunk: string;
4687
+ };
4688
+
4689
+ // ZcashPcztChunkRequest
4690
+ export type ZcashPcztChunkRequest = {
4691
+ chunk_length: number;
4692
+ };
4693
+
4694
+ // ZcashPcztChunkAck
4695
+ export type ZcashPcztChunkAck = {
4696
+ data_chunk: string;
4697
+ };
4698
+
4699
+ // ZcashSignedPczt
4700
+ export type ZcashSignedPczt = {
4701
+ pczt_length: number;
4702
+ pczt_initial_chunk: string;
4703
+ };
4704
+
4705
+ // ZcashSignedPcztChunkRequest
4706
+ export type ZcashSignedPcztChunkRequest = {
4707
+ chunk_length: number;
4708
+ };
4709
+
4683
4710
  // experimental_message
4684
4711
  export type experimental_message = {};
4685
4712
 
@@ -5374,6 +5401,35 @@ export type ViewVerifyPage = {
5374
5401
  content?: ViewContentPreview;
5375
5402
  };
5376
5403
 
5404
+ export enum ZcashAddressType {
5405
+ UNIFIED_ORCHARD_P2PKH = 0,
5406
+ TRANSPARENT_P2PKH = 1,
5407
+ UNIFIED_ORCHARD = 2,
5408
+ }
5409
+
5410
+ export enum ZcashAddressScope {
5411
+ EXTERNAL = 0,
5412
+ INTERNAL = 1,
5413
+ }
5414
+
5415
+ // ZcashGetAddress
5416
+ export type ZcashGetAddress = {
5417
+ address_n: number[];
5418
+ show_display?: boolean;
5419
+ include_seed_fingerprint?: boolean;
5420
+ include_ufvk?: boolean;
5421
+ diversifier_index?: number;
5422
+ address_type?: ZcashAddressType;
5423
+ scope?: ZcashAddressScope;
5424
+ };
5425
+
5426
+ // ZcashAddress
5427
+ export type ZcashAddress = {
5428
+ address: string;
5429
+ ufvk?: string;
5430
+ seed_fingerprint?: string;
5431
+ };
5432
+
5377
5433
  export enum ProtocolV2FailureType {
5378
5434
  Failure_InvalidMessage = 1,
5379
5435
  Failure_UndefinedError = 2,
@@ -5988,6 +6044,11 @@ export type MessageType = {
5988
6044
  UnlockPath: UnlockPath;
5989
6045
  UnlockedPathRequest: UnlockedPathRequest;
5990
6046
  UiAnimationRequest: UiAnimationRequest;
6047
+ ZcashSignPczt: ZcashSignPczt;
6048
+ ZcashPcztChunkRequest: ZcashPcztChunkRequest;
6049
+ ZcashPcztChunkAck: ZcashPcztChunkAck;
6050
+ ZcashSignedPczt: ZcashSignedPczt;
6051
+ ZcashSignedPcztChunkRequest: ZcashSignedPcztChunkRequest;
5991
6052
  experimental_message: experimental_message;
5992
6053
  experimental_field: experimental_field;
5993
6054
  ProtocolInfoRequest: ProtocolInfoRequest;
@@ -6064,6 +6125,8 @@ export type MessageType = {
6064
6125
  ViewSignPage: ViewSignPage;
6065
6126
  ViewWarningPage: ViewWarningPage;
6066
6127
  ViewVerifyPage: ViewVerifyPage;
6128
+ ZcashGetAddress: ZcashGetAddress;
6129
+ ZcashAddress: ZcashAddress;
6067
6130
  };
6068
6131
 
6069
6132
  export type MessageKey = keyof MessageType;
@@ -34,6 +34,12 @@ export type OneKeyDeviceInfoWithSession = OneKeyUsbDeviceInfo & {
34
34
  export type OneKeyMobileDeviceInfo = {
35
35
  id: string;
36
36
  name: string | null;
37
+ /**
38
+ * Color letter of the serial number ('A'-'Z') from the Pro2 / Neo BLE advertisement,
39
+ * possibly seen in an earlier scan. Set by the React Native and Electron BLE
40
+ * transports; absent when unknown.
41
+ */
42
+ bleColorCode?: string;
37
43
  };
38
44
 
39
45
  export type OneKeyDeviceInfoBase = {
@@ -70,6 +76,19 @@ export type TransportWriteMetrics = {
70
76
  frameBytes: number;
71
77
  };
72
78
 
79
+ /**
80
+ * Supplies the remaining chunks of one file for an ordered FilesystemFileWrite pipeline.
81
+ * The call's request data is the first chunk; `next` returns the following chunk in offset
82
+ * order, or undefined to stop. The call then resolves with the last confirmation (or the
83
+ * device Failure), so the caller must check `processed_byte` for completeness. Callbacks
84
+ * should not throw: an error escapes with chunks still in flight and invalidates the link.
85
+ */
86
+ export type ProtocolV2FileWritePipeline = {
87
+ next: () => Promise<Record<string, unknown> | undefined>;
88
+ /** Called for each chunk after the device confirms it, in offset order. */
89
+ onResponse: (response: MessageFromOneKey) => void;
90
+ };
91
+
73
92
  export type TransportCallOptions = {
74
93
  timeoutMs?: number;
75
94
  expectedTypes?: string[];
@@ -83,6 +102,12 @@ export type TransportCallOptions = {
83
102
  onResponseAfterWrite?: (response: MessageFromOneKey) => void;
84
103
  /** Prefer acknowledged BLE characteristic writes for this call when supported. */
85
104
  writeWithResponse?: boolean;
105
+ /**
106
+ * Protocol V2 only: keep up to PROTOCOL_V2_FILE_WRITE_PIPELINE_WINDOW ordered
107
+ * FilesystemFileWrite frames of one file in flight within this call. `timeoutMs`
108
+ * bounds each frame write and each response instead of the whole file.
109
+ */
110
+ fileWritePipeline?: ProtocolV2FileWritePipeline;
86
111
  };
87
112
 
88
113
  type ITransportInitFn = (
@@ -4,29 +4,39 @@ const REDACTED = '[REDACTED]';
4
4
 
5
5
  const SENSITIVE_LOG_KEYS = new Set([
6
6
  'accesstoken',
7
+ 'additionaltxpubkeys',
7
8
  'address',
8
9
  'apikey',
9
10
  'connectid',
11
+ 'datachunk',
10
12
  'devicestate',
11
13
  'deviceid',
14
+ 'enckey',
12
15
  'entropy',
13
16
  'expectedpassphrasestate',
14
17
  'mnemonic',
18
+ 'outkey',
15
19
  'passphrase',
16
20
  'passphrasestate',
17
21
  'password',
22
+ 'pczt',
18
23
  'pin',
19
24
  'privatekey',
20
25
  'publickey',
21
26
  'seed',
22
27
  'secret',
28
+ 'seedfingerprint',
23
29
  'serialnumber',
24
30
  'session',
25
31
  'sessionid',
26
32
  'signature',
33
+ 'signedpczt',
27
34
  'token',
35
+ 'txpubkey',
36
+ 'ufvk',
28
37
  'uuid',
29
38
  'walletsessionid',
39
+ 'watchkey',
30
40
  'xprv',
31
41
  'xpub',
32
42
  ]);
@@ -45,7 +55,11 @@ const isSensitiveLogKey = (key: string) => {
45
55
  };
46
56
 
47
57
  const isSigningMessage = (messageName?: string) =>
48
- Boolean(messageName && /(?:sign|txack|transaction)/i.test(messageName));
58
+ Boolean(messageName && /(?:sign|txack|transaction|pczt)/i.test(messageName));
59
+
60
+ // Key image and tx key exchanges carry per-output secrets under generic field names (iv, blob, salt).
61
+ const isMoneroSecretMessage = (messageName?: string) =>
62
+ Boolean(messageName && /^Monero(?:KeyImage|GetTxKey|LiveRefresh)/.test(messageName));
49
63
 
50
64
  const redactLogValue = (value: unknown, seen: WeakSet<object>): unknown => {
51
65
  if (ArrayBuffer.isView(value)) {
@@ -78,7 +92,7 @@ const redactLogValue = (value: unknown, seen: WeakSet<object>): unknown => {
78
92
  };
79
93
 
80
94
  export function getSafeTransportLogPayload(value: unknown, messageName?: string): unknown {
81
- if (isSigningMessage(messageName)) {
95
+ if (isSigningMessage(messageName) || isMoneroSecretMessage(messageName)) {
82
96
  return REDACTED;
83
97
  }
84
98
  return redactLogValue(value, new WeakSet());