@onekeyfe/hd-core 1.2.0-alpha.136 → 1.2.0-alpha.140

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 (30) hide show
  1. package/__tests__/DeviceCommands.test.ts +1 -1
  2. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +48 -0
  3. package/__tests__/device-lifecycle-events.test.ts +21 -17
  4. package/__tests__/device-settings.test.ts +44 -8
  5. package/__tests__/device-state-mapper.test.ts +38 -0
  6. package/__tests__/device-wallet-session-store.test.ts +21 -79
  7. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +37 -1
  8. package/__tests__/protocol-v2-unlock-policy.test.ts +97 -1
  9. package/__tests__/protocol-v2.test.ts +102 -21
  10. package/dist/api/FirmwareUpdateV4.d.ts +1 -1
  11. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  12. package/dist/api/device/DeviceChangePin.d.ts.map +1 -1
  13. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  14. package/dist/api/device/DeviceVerify.d.ts +1 -0
  15. package/dist/api/device/DeviceVerify.d.ts.map +1 -1
  16. package/dist/api/device/DeviceWipe.d.ts.map +1 -1
  17. package/dist/api/firmware/protocolV2Release.d.ts.map +1 -1
  18. package/dist/device/Device.d.ts.map +1 -1
  19. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  20. package/dist/index.js +93 -82
  21. package/package.json +4 -4
  22. package/src/api/FirmwareUpdateV4.ts +52 -78
  23. package/src/api/device/DeviceChangePin.ts +4 -1
  24. package/src/api/device/DeviceSettings.ts +13 -0
  25. package/src/api/device/DeviceVerify.ts +25 -0
  26. package/src/api/device/DeviceWipe.ts +4 -1
  27. package/src/api/firmware/protocolV2Release.ts +7 -2
  28. package/src/device/Device.ts +17 -42
  29. package/src/device/DeviceCommands.ts +1 -1
  30. package/src/device/DeviceStateMapper.ts +11 -7
@@ -2,6 +2,7 @@ import { EDeviceType, ERRORS, HardwareError, HardwareErrorCode, wait } from '@on
2
2
  import JSZip from 'jszip';
3
3
  import {
4
4
  DeviceRebootType,
5
+ DeviceSessionPinType,
5
6
  PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
6
7
  PROTOCOL_V2_BLE_FILE_READ_CHUNK_SIZE,
7
8
  PROTOCOL_V2_BLE_FIRMWARE_FILE_CHUNK_SIZE,
@@ -90,9 +91,8 @@ const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 90 * 1000;
90
91
  const PROTOCOL_V2_FINAL_RECONNECT_TIMEOUT = 3 * 60 * 1000;
91
92
  const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
92
93
  const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
93
- const PROTOCOL_V2_INSTALL_TIMEOUT = 8 * 60 * 1000;
94
+ const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
94
95
  const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
95
- const PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT = 30 * 1000;
96
96
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
97
97
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
98
98
  const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
@@ -106,6 +106,8 @@ const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
106
106
  const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 30 * 1000;
107
107
  const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
108
108
  const PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE = 'FirmwareInstallStatusConflict';
109
+ const PROTOCOL_V2_INSTALL_FAILED_CODE = 'FirmwareInstallFailed';
110
+ const PROTOCOL_V2_INSTALL_TIMEOUT_CODE = 'FirmwareInstallTimeout';
109
111
  const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
110
112
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
111
113
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
@@ -391,7 +393,8 @@ const isProtocolV2FirmwareStatusEndpointUnavailable = (error: unknown) => {
391
393
 
392
394
  const isProtocolV2TerminalInstallStatusError = (error: unknown) =>
393
395
  error instanceof HardwareError &&
394
- (error.errorCode === HardwareErrorCode.FirmwareError ||
396
+ (error.errorCode === HardwareErrorCode.ActionCancelled ||
397
+ error.errorCode === HardwareErrorCode.FirmwareError ||
395
398
  error.errorCode === HardwareErrorCode.FirmwareVerificationFailed ||
396
399
  error.params?.firmwareUpdateCode === PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE);
397
400
 
@@ -587,9 +590,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
587
590
 
588
591
  private protocolV2CompletedTargetVersions = new Map<number, number>();
589
592
 
590
- private protocolV2LatestFinalFeatures?: Features;
593
+ private protocolV2CompletedTargetIds = new Set<number>();
591
594
 
592
- private protocolV2FinalStatusVerified = false;
595
+ private protocolV2LatestFinalFeatures?: Features;
593
596
 
594
597
  private protocolV2InstallBaselineVersions = new Map<number, string>();
595
598
 
@@ -599,6 +602,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
599
602
  this.allowDeviceMode = [UI_REQUEST.BOOTLOADER, UI_REQUEST.NOT_INITIALIZE];
600
603
  this.requireDeviceMode = [];
601
604
  this.unlockPolicy = 'unlock-before-run';
605
+ // Protocol V2 device-management actions are not wallet-scoped, so either
606
+ // the main PIN or an Attach PIN may authorize them.
607
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
602
608
  this.useDevicePassphraseState = false;
603
609
  this.skipForceUpdateCheck = true;
604
610
 
@@ -1623,9 +1629,17 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1623
1629
  Math.floor(statusVersion / 0x100) % 0x100
1624
1630
  }.${statusVersion % 0x100}`;
1625
1631
  if (!observedVersion) {
1626
- if (this.protocolV2FinalStatusVerified) {
1632
+ const requestedTargetIds = requestedTargets.flatMap(requestedTarget => {
1633
+ if (requestedTarget === 'resource' || requestedTarget === 'boot_resources') return [];
1634
+ const installTarget = PROTOCOL_V2_INSTALL_TARGET_BY_UPDATE_TARGET.get(requestedTarget);
1635
+ return installTarget ? [installTarget.targetId] : [];
1636
+ });
1637
+ const hasCompleteTargetEvidence =
1638
+ requestedTargetIds.length > 0 &&
1639
+ requestedTargetIds.every(targetId => this.protocolV2CompletedTargetIds.has(targetId));
1640
+ if (hasCompleteTargetEvidence) {
1627
1641
  Log.warn(
1628
- `Protocol V2 target ${target} has no observable final version; completed target status is authoritative`
1642
+ `Protocol V2 target ${target} has no observable final version; install completion is authoritative`
1629
1643
  );
1630
1644
  } else {
1631
1645
  throw ERRORS.TypedError(
@@ -1665,14 +1679,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1665
1679
  }
1666
1680
 
1667
1681
  private async getProtocolV2DeviceFeatures(): Promise<Features> {
1668
- if (this.device.features) {
1669
- return this.device.features;
1670
- }
1682
+ let { features } = this.device;
1671
1683
  if (typeof this.device.getFeatures === 'function') {
1672
- const features = await this.device.getFeatures();
1673
- if (features) return features;
1684
+ features ??= await this.device.getFeatures();
1674
1685
  }
1675
- throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
1686
+ if (!features) {
1687
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
1688
+ }
1689
+ const deviceType = this.device.getCurrentDeviceType();
1690
+ if (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo) {
1691
+ return features;
1692
+ }
1693
+ return {
1694
+ ...features,
1695
+ deviceType,
1696
+ firmwareType: this.device.getCurrentFirmwareType(),
1697
+ };
1676
1698
  }
1677
1699
 
1678
1700
  private getProtocolV2SerialNumber(deviceInfo: ProtocolV2DeviceInfo) {
@@ -1965,6 +1987,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1965
1987
  ? PROTOCOL_V2_UPDATE_TARGET_BY_TARGET_ID.get(target.targetId)
1966
1988
  : undefined;
1967
1989
  if (updateTarget && targetsToUpdate.has(updateTarget)) {
1990
+ if (component.version) {
1991
+ this.params.expectedTargetVersions ??= {};
1992
+ this.params.expectedTargetVersions[updateTarget] = component.version.join('.');
1993
+ }
1968
1994
  const explicitInstallItem = explicitInstallItemByTargetId.get(target.targetId);
1969
1995
  const installItem =
1970
1996
  explicitInstallItem ?? (await this.downloadRemoteProtocolV2Component(key, component));
@@ -2499,14 +2525,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2499
2525
  path: failedTarget.path,
2500
2526
  });
2501
2527
  Log.error(`[FirmwareUpdateV4] firmware install failed target=${failedTargetDetails}`);
2502
- throw ERRORS.TypedError(
2503
- HardwareErrorCode.FirmwareError,
2504
- `Protocol V2 firmware target failed: target=${failedTarget.target_id} status=${
2505
- failedTarget.status ?? 'unknown'
2506
- } payloadVersion=${failedTarget.payload_version ?? 'unknown'} path=${
2507
- failedTarget.path ?? 'unknown'
2508
- }`
2509
- );
2528
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
2529
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_FAILED_CODE,
2530
+ });
2510
2531
  }
2511
2532
 
2512
2533
  const matchingTargets = statusTargets.filter(target =>
@@ -2520,13 +2541,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2520
2541
  seenTargetIds.has(targetId) ||
2521
2542
  (target.path && expectedPaths.get(targetId) && target.path !== expectedPaths.get(targetId))
2522
2543
  ) {
2523
- throw ERRORS.TypedError(
2524
- HardwareErrorCode.RuntimeError,
2525
- `Protocol V2 install status conflicts with target ${target.target_id}`,
2526
- {
2527
- firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
2528
- }
2529
- );
2544
+ Log.error(`[FirmwareUpdateV4] install status conflicts with target=${target.target_id}`);
2545
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
2546
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_STATUS_CONFLICT_CODE,
2547
+ });
2530
2548
  }
2531
2549
  seenTargetIds.add(targetId);
2532
2550
  }
@@ -2538,6 +2556,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2538
2556
  const targetId = normalizeProtocolV2TargetId(target.target_id);
2539
2557
  if (targetId !== undefined) {
2540
2558
  completedTargetIds.add(targetId);
2559
+ this.protocolV2CompletedTargetIds.add(targetId);
2541
2560
  }
2542
2561
  });
2543
2562
  if (completedTargetIds.size === expectedTargetIds.size && expectedTargetIds.size > 0) {
@@ -2642,7 +2661,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2642
2661
  targets: Array<{ target_id: number; path: string }>,
2643
2662
  requireCurrentInstallStatus = false
2644
2663
  ) {
2645
- this.protocolV2FinalStatusVerified = false;
2646
2664
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
2647
2665
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
2648
2666
  const startTime = Date.now();
@@ -2651,15 +2669,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2651
2669
  // Reconnecting here probes DeviceInfo, which loaders reject while installation is active.
2652
2670
  let shouldReconnect = false;
2653
2671
  let deviceInfo: ProtocolV2DeviceInfo | undefined;
2654
- let missingTargetStatusSince: number | undefined;
2655
- let missingTargetStatusKey: string | undefined;
2656
- let normalModeWithoutInstallEvidenceSince: number | undefined;
2657
2672
  let installEvidenceObserved = false;
2658
2673
  let currentInstallStatusObserved = false;
2659
- const resetMissingTargetStatusGrace = () => {
2660
- missingTargetStatusSince = undefined;
2661
- missingTargetStatusKey = undefined;
2662
- };
2663
2674
 
2664
2675
  while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
2665
2676
  // A transport release caused by an explicit workflow cancellation must not
@@ -2687,7 +2698,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2687
2698
  );
2688
2699
  if (statusResponse.type === 'Success') {
2689
2700
  installEvidenceObserved = true;
2690
- resetMissingTargetStatusGrace();
2691
2701
  lastError = new Error(
2692
2702
  'Protocol V2 firmware install acknowledged; waiting for target status'
2693
2703
  );
@@ -2716,7 +2726,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2716
2726
  (!requireCurrentInstallStatus || currentInstallStatusObserved)
2717
2727
  ) {
2718
2728
  installEvidenceObserved = true;
2719
- normalModeWithoutInstallEvidenceSince = undefined;
2720
2729
  }
2721
2730
  const matchingStatusTargets = statusTargets.filter(target => {
2722
2731
  const targetId = normalizeProtocolV2TargetId(target.target_id);
@@ -2728,7 +2737,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2728
2737
  shouldVerifyTargetCompletion &&
2729
2738
  this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds, expectedPaths)
2730
2739
  ) {
2731
- this.protocolV2FinalStatusVerified = true;
2732
2740
  return;
2733
2741
  }
2734
2742
 
@@ -2758,7 +2766,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2758
2766
  }
2759
2767
 
2760
2768
  if (statusTargets.length === 0) {
2761
- resetMissingTargetStatusGrace();
2762
2769
  if (statusResponse.type !== 'Success') {
2763
2770
  lastError = new Error(
2764
2771
  'Protocol V2 firmware update is waiting for user confirmation or target status'
@@ -2770,30 +2777,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2770
2777
  expectedTargetIds
2771
2778
  );
2772
2779
  if (missingTargetIds.length > 0) {
2773
- const now = Date.now();
2774
2780
  const missingKey = missingTargetIds.join(',');
2775
- if (missingTargetStatusSince === undefined || missingTargetStatusKey !== missingKey) {
2776
- missingTargetStatusSince = now;
2777
- missingTargetStatusKey = missingKey;
2778
- } else if (
2779
- now - missingTargetStatusSince >=
2780
- PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
2781
- ) {
2782
- const reportedTargetIds = statusTargets
2783
- .map(target => normalizeProtocolV2TargetId(target.target_id))
2784
- .filter((targetId): targetId is number => targetId !== undefined);
2785
- throw ERRORS.TypedError(
2786
- HardwareErrorCode.FirmwareError,
2787
- `Protocol V2 firmware status is missing requested records: targetIds=${missingKey} reportedTargetIds=${reportedTargetIds.join(
2788
- ','
2789
- )}`
2790
- );
2791
- }
2792
2781
  lastError = new Error(
2793
2782
  `Protocol V2 firmware status is temporarily missing targetIds=${missingKey}`
2794
2783
  );
2795
2784
  } else {
2796
- resetMissingTargetStatusGrace();
2797
2785
  lastError = new Error('Protocol V2 firmware targets are still installing');
2798
2786
  }
2799
2787
  }
@@ -2801,9 +2789,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2801
2789
  if (isProtocolV2TerminalInstallStatusError(error)) {
2802
2790
  throw error;
2803
2791
  }
2804
- // Only consecutive full status dumps can prove that a record is absent.
2805
- // A reboot or transport interruption starts a fresh grace window.
2806
- resetMissingTargetStatusGrace();
2807
2792
  // App firmware does not register DeviceFirmwareUpdateStatusGet. Treat the
2808
2793
  // missing endpoint as completion only after the runtime probe confirms App mode.
2809
2794
  if (isProtocolV2FirmwareStatusEndpointUnavailable(error)) {
@@ -2826,22 +2811,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2826
2811
  return;
2827
2812
  }
2828
2813
  if (isNormalMode) {
2829
- const now = Date.now();
2830
- normalModeWithoutInstallEvidenceSince ??= now;
2831
- if (
2832
- now - normalModeWithoutInstallEvidenceSince >=
2833
- PROTOCOL_V2_MISSING_TARGET_STATUS_GRACE_TIMEOUT
2834
- ) {
2835
- throw ERRORS.TypedError(
2836
- HardwareErrorCode.FirmwareError,
2837
- 'Protocol V2 device returned to normal mode without install ACK, target status, or version change'
2838
- );
2839
- }
2840
2814
  lastError = new Error(
2841
2815
  'Protocol V2 device is in normal mode but installation is not yet confirmed'
2842
2816
  );
2843
2817
  } else {
2844
- normalModeWithoutInstallEvidenceSince = undefined;
2845
2818
  lastError = new Error(
2846
2819
  'Protocol V2 firmware status endpoint is unavailable while the device remains in loader mode'
2847
2820
  );
@@ -2872,18 +2845,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2872
2845
  }
2873
2846
  shouldReconnect = true;
2874
2847
  deviceInfo = undefined;
2875
- resetMissingTargetStatusGrace();
2876
2848
  Log.log('Protocol V2 firmware install device readiness probe failed: ', error);
2877
2849
  }
2878
2850
  await wait(1000);
2879
2851
  }
2880
2852
 
2881
- throw ERRORS.TypedError(
2882
- HardwareErrorCode.RuntimeError,
2883
- `Protocol V2 firmware update status not complete within ${
2853
+ Log.error(
2854
+ `[FirmwareUpdateV4] install timed out after ${
2884
2855
  PROTOCOL_V2_INSTALL_TIMEOUT / 1000
2885
2856
  }s: ${this.normalizeErrorMessage(lastError)}`
2886
2857
  );
2858
+ throw ERRORS.TypedError(HardwareErrorCode.FirmwareError, undefined, {
2859
+ firmwareUpdateCode: PROTOCOL_V2_INSTALL_TIMEOUT_CODE,
2860
+ });
2887
2861
  }
2888
2862
 
2889
2863
  private async exitProtocolV2BootloaderToNormal() {
@@ -1,4 +1,4 @@
1
- import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
1
+ import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
2
2
 
3
3
  import { BaseMethod } from '../BaseMethod';
4
4
  import { validateParams } from '../helpers/paramsValidator';
@@ -13,6 +13,9 @@ export default class DeviceChangePin extends BaseMethod<ChangePin> {
13
13
 
14
14
  init() {
15
15
  this.unlockPolicy = 'unlock-before-run';
16
+ // Protocol V2 device-management actions are not wallet-scoped, so either
17
+ // the main PIN or an Attach PIN may authorize them.
18
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
16
19
  this.useDevicePassphraseState = false;
17
20
 
18
21
  // check payload
@@ -200,6 +200,19 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
200
200
  const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
201
201
  page: DeviceSettingsPage.DevicePassphrase,
202
202
  });
203
+ // A successful page response confirms the device-side toggle. When an Attach PIN
204
+ // session disables passphrase protection, the firmware locks immediately and omits
205
+ // private passphrase fields from subsequent reads, so persist the confirmed value
206
+ // before refreshing the remaining device state.
207
+ this.device.updateState(
208
+ {
209
+ status: {
210
+ passphraseProtection: requestedPassphrase,
211
+ ...(requestedPassphrase === false ? { unlockedAttachPin: false } : undefined),
212
+ },
213
+ },
214
+ 'settings-write'
215
+ );
203
216
  await refreshStatusAndSettings();
204
217
  return res.message;
205
218
  }
@@ -1,5 +1,6 @@
1
1
  import { sha256 } from '@noble/hashes/sha256';
2
2
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
3
+ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
3
4
  import { bytesToHex } from '@noble/hashes/utils';
4
5
 
5
6
  import { formatAnyHex } from '../helpers/hexUtils';
@@ -11,7 +12,15 @@ import type { BixinVerifyDeviceRequest } from '@onekeyfe/hd-transport';
11
12
  import type { DeviceVerifySignature } from '../../types';
12
13
 
13
14
  export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
15
+ getSupportedProtocols() {
16
+ return ['V1', 'V2'] as const;
17
+ }
18
+
14
19
  init() {
20
+ this.unlockPolicy = 'unlock-before-run';
21
+ // Protocol V2 device-management actions are not wallet-scoped, so either
22
+ // the main PIN or an Attach PIN may authorize them.
23
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
15
24
  this.useDevicePassphraseState = false;
16
25
 
17
26
  // check payload
@@ -38,6 +47,22 @@ export default class DeviceVerify extends BaseMethod<BixinVerifyDeviceRequest> {
38
47
  }
39
48
  );
40
49
  response = res.message;
50
+ } else if (this.device.isProtocolV2()) {
51
+ const signatureRes = await this.device.commands.typedCall(
52
+ 'DeviceCertificateSign',
53
+ 'DeviceCertificateSignature',
54
+ {
55
+ data: this.params.data,
56
+ }
57
+ );
58
+ const certRes = await this.device.commands.typedCall(
59
+ 'DeviceCertificateRead',
60
+ 'DeviceCertificate'
61
+ );
62
+ response = {
63
+ cert: certRes.message.cert_and_pubkey,
64
+ signature: signatureRes.message.data,
65
+ };
41
66
  } else {
42
67
  const signatureRes = await this.device.commands.typedCall(
43
68
  'SESignMessage',
@@ -1,4 +1,4 @@
1
- import { DeviceSettingsPage } from '@onekeyfe/hd-transport';
1
+ import { DeviceSessionPinType, DeviceSettingsPage } from '@onekeyfe/hd-transport';
2
2
 
3
3
  import { BaseMethod } from '../BaseMethod';
4
4
 
@@ -11,6 +11,9 @@ export default class DeviceWipe extends BaseMethod<WipeDevice> {
11
11
 
12
12
  init() {
13
13
  this.unlockPolicy = 'unlock-before-run';
14
+ // Protocol V2 device-management actions are not wallet-scoped, so either
15
+ // the main PIN or an Attach PIN may authorize them.
16
+ this.protocolV2PreUnlockPinType = DeviceSessionPinType.Any;
14
17
  this.useDevicePassphraseState = false;
15
18
  this.protocolV2UiInteraction = {
16
19
  request: 'button',
@@ -77,11 +77,16 @@ export async function loadProtocolV2FirmwareReleaseContext({
77
77
  }
78
78
 
79
79
  const firmwareType = firmwareTypeParam ?? state.identity.firmwareType ?? EFirmwareType.Universal;
80
+ const normalizedFeatures: Features = {
81
+ ...features,
82
+ deviceType: state.identity.deviceType,
83
+ firmwareType,
84
+ };
80
85
  return {
81
86
  state: state as ProtocolV2FirmwareReleaseContext['state'],
82
- features,
87
+ features: normalizedFeatures,
83
88
  firmwareType,
84
- release: DataManager.getFirmwareLatestRelease(features, firmwareType),
89
+ release: DataManager.getFirmwareLatestRelease(normalizedFeatures, firmwareType),
85
90
  };
86
91
  }
87
92
 
@@ -930,6 +930,17 @@ export class Device extends EventEmitter {
930
930
  };
931
931
 
932
932
  const expectedDeviceId = options?.deviceId;
933
+ if (expectedDeviceId) {
934
+ // 先只读校验物理设备身份;钱包上下文仍由下方携带完整参数的
935
+ // Initialize 选择,避免标准钱包请求复用此前的隐藏钱包上下文。
936
+ this.passphraseState = undefined;
937
+ const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
938
+ this._updateFeatures(message);
939
+ await TransportManager.reconfigure(this.features);
940
+ if (!this.checkDeviceId(expectedDeviceId)) {
941
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
942
+ }
943
+ }
933
944
 
934
945
  this.passphraseState = options?.passphraseState;
935
946
 
@@ -949,46 +960,7 @@ export class Device extends EventEmitter {
949
960
  payload.derive_cardano = true;
950
961
  }
951
962
 
952
- if (this.features) {
953
- // Re-sync the V1 message schema for THIS device before encoding
954
- // Initialize: the process-global schema may still reflect another
955
- // device (e.g. legacy-firmware Touch/Mini) on multi-device setups, and
956
- // a stale legacy schema would silently strip passphrase_state /
957
- // is_contains_attach from the wire message. Local operation, no wire
958
- // I/O; a no-op when the schema is unchanged.
959
- await TransportManager.reconfigure(this.features);
960
- }
961
-
962
- // Initialize's own Features response carries device_id, so the physical
963
- // device identity is validated on the same round trip instead of via a
964
- // separate read-only GetFeatures preflight (which doubled the wire cost
965
- // of every deviceId-carrying call). The method fn has not run yet, so a
966
- // mismatch still fails before any wallet data can be derived, with the
967
- // same DeviceCheckDeviceIdError. Wallet-context selection is unchanged:
968
- // it is decided by the Initialize payload above either way.
969
- const assertExpectedDeviceIdentity = () => {
970
- if (expectedDeviceId && !this.checkDeviceId(expectedDeviceId)) {
971
- // The mismatched Initialize may have cached a session under the wrong
972
- // device's identity; drop it so no wallet context survives from it.
973
- // (This also evicts any session the wrong device legitimately cached
974
- // under the same passphraseState — a deliberate conservative purge
975
- // after a physical-swap event, consistent with
976
- // reconcileDeviceIdentity purging the previous device's sessions.)
977
- this.clearInternalState();
978
- throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckDeviceIdError);
979
- }
980
- };
981
-
982
- try {
983
- await callInitialize(payload, options?.initSession);
984
- } catch (error) {
985
- // callInitialize can fail AFTER the wire call cached the session (e.g.
986
- // TransportManager.reconfigure rejecting); the identity check must
987
- // still run so a wrong-device session never survives the error path.
988
- assertExpectedDeviceIdentity();
989
- throw error;
990
- }
991
- assertExpectedDeviceIdentity();
963
+ await callInitialize(payload, options?.initSession);
992
964
  } catch (error) {
993
965
  Log.error('Initialization failed:', error);
994
966
  throw error;
@@ -1558,15 +1530,18 @@ export class Device extends EventEmitter {
1558
1530
  const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
1559
1531
  const cleanupPromise = this.runCleanupPromise;
1560
1532
  const { cancelableAction } = this;
1533
+ const env = DataManager.getSettings('env');
1561
1534
  if (cancelableAction) {
1562
1535
  await cancelableAction(error);
1563
1536
  } else if (
1564
1537
  this.isProtocolV2() &&
1565
- DataManager.isBleConnect(DataManager.getSettings('env')) &&
1538
+ (DataManager.isBleConnect(env) ||
1539
+ DataManager.isBrowserWebUsb(env) ||
1540
+ DataManager.isDesktopWebUsb(env)) &&
1566
1541
  this.hasDeviceAcquire()
1567
1542
  ) {
1568
1543
  await this.commands?.cancelDevice?.().catch(cancelError => {
1569
- Log.debug('Protocol V2 BLE fallback cancel error', cancelError);
1544
+ Log.debug('Protocol V2 fallback cancel error', cancelError);
1570
1545
  });
1571
1546
  }
1572
1547
  await this.commands?.cancel();
@@ -69,7 +69,7 @@ const DEVICE_CONTROL_CALLS = new Set([
69
69
  // Older Protocol V2 firmware may omit the cancellation subcode, so retain a
70
70
  // narrow message fallback for compatibility.
71
71
  const isProtocolV2ActionCancelledMessage = (message: string) =>
72
- /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
72
+ /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|update cancel(?:led|ed)|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
73
73
  message
74
74
  );
75
75
 
@@ -347,8 +347,10 @@ export const mapProtocolV2DeviceStatusToState = (status: DeviceStatus): DeviceSt
347
347
  unlocked: status.unlocked,
348
348
  passphraseProtection: status.unlocked === true ? status.passphrase_enabled ?? null : undefined,
349
349
  backupRequired: status.backup_required,
350
- attachToPinEnabled: status.attach_to_pin_enabled,
351
- unlockedAttachPin: status.unlocked_by_attach_to_pin,
350
+ attachToPinEnabled:
351
+ status.unlocked === true ? status.attach_to_pin_enabled ?? undefined : undefined,
352
+ unlockedAttachPin:
353
+ status.unlocked === true ? status.unlocked_by_attach_to_pin ?? null : undefined,
352
354
  }),
353
355
  raw: { protocolV2DeviceStatus: status },
354
356
  });
@@ -375,21 +377,23 @@ export const mapApplySettingsToState = (settings: ApplySettings): DeviceStatePat
375
377
  };
376
378
 
377
379
  export const mapDeviceSettingsToState = (settings: DeviceSettings): DeviceStatePatch => {
378
- const identity = definedEntries({ label: settings.label });
379
- const status = definedEntries({ passphraseProtection: settings.passphrase_enable });
380
+ const identity = definedEntries({ label: settings.label ?? undefined });
381
+ const status = definedEntries({
382
+ passphraseProtection: settings.passphrase_enable ?? undefined,
383
+ });
380
384
  const stateSettings = definedEntries({
381
385
  bleEnabled: settings.bt_enable,
382
386
  language: mapLanguageFromProtocolV2(settings.language),
383
387
  wallpaperPath: settings.wallpaper_path,
384
388
  brightness: settings.brightness,
385
- autoLockDelayMs: settings.autolock_delay_ms,
386
- autoShutdownDelayMs: settings.autoshutdown_delay_ms,
389
+ autoLockDelayMs: settings.autolock_delay_ms ?? undefined,
390
+ autoShutdownDelayMs: settings.autoshutdown_delay_ms ?? undefined,
387
391
  animationEnabled: settings.animation_enable,
388
392
  tapToWake: settings.tap_to_wake,
389
393
  hapticFeedback: settings.haptic_feedback,
390
394
  deviceNameDisplayEnabled: settings.device_name_display_enabled,
391
395
  airgapMode: settings.airgap_mode,
392
- fidoEnabled: settings.fido_enabled,
396
+ fidoEnabled: settings.fido_enabled ?? undefined,
393
397
  usbLockEnabled: settings.usb_lock_enable,
394
398
  randomKeypad: settings.random_keypad,
395
399
  });