@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.36

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 (91) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +74 -0
  2. package/__tests__/DeviceCommands.test.ts +84 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/device-settings.test.ts +3 -0
  5. package/__tests__/device-wallet-session-store.test.ts +80 -7
  6. package/__tests__/evmSignTransaction.test.ts +69 -0
  7. package/__tests__/get-device-state.test.ts +180 -40
  8. package/__tests__/open-wallet-session.test.ts +314 -55
  9. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  10. package/__tests__/protocol-v2.test.ts +768 -139
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  14. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  15. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  16. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  17. package/dist/api/evm/protocol.d.ts +3 -0
  18. package/dist/api/evm/protocol.d.ts.map +1 -0
  19. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  20. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  21. package/dist/core/deviceEventRegistration.d.ts +2 -0
  22. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/device/Device.d.ts +25 -6
  25. package/dist/device/Device.d.ts.map +1 -1
  26. package/dist/device/DeviceCommands.d.ts.map +1 -1
  27. package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
  28. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  29. package/dist/events/device.d.ts +4 -0
  30. package/dist/events/device.d.ts.map +1 -1
  31. package/dist/events/ui-request.d.ts +27 -2
  32. package/dist/events/ui-request.d.ts.map +1 -1
  33. package/dist/index.d.ts +63 -12
  34. package/dist/index.js +1164 -904
  35. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  36. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  37. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  38. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  40. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  41. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  42. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  43. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  45. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  46. package/dist/types/api/openWalletSession.d.ts +1 -1
  47. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  48. package/dist/types/api/protocolV2.d.ts +2 -2
  49. package/dist/types/api/protocolV2.d.ts.map +1 -1
  50. package/dist/types/device.d.ts +2 -1
  51. package/dist/types/device.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/patch.d.ts +1 -1
  55. package/dist/utils/patch.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/BaseMethod.ts +1 -1
  58. package/src/api/FirmwareUpdateV4.ts +39 -14
  59. package/src/api/GetPassphraseState.ts +17 -0
  60. package/src/api/OpenWalletSession.ts +31 -8
  61. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
  62. package/src/api/device/DeviceUnlock.ts +1 -1
  63. package/src/api/evm/EVMGetAddress.ts +2 -2
  64. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  65. package/src/api/evm/EVMSignMessage.ts +2 -2
  66. package/src/api/evm/EVMSignTransaction.ts +2 -2
  67. package/src/api/evm/EVMSignTypedData.ts +35 -45
  68. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  69. package/src/api/evm/protocol.ts +6 -0
  70. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  71. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  72. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  73. package/src/core/deviceEventRegistration.ts +4 -0
  74. package/src/core/index.ts +46 -4
  75. package/src/data/messages/messages-protocol-v2.json +408 -485
  76. package/src/device/Device.ts +266 -43
  77. package/src/device/DeviceCommands.ts +12 -2
  78. package/src/device/DeviceWalletSessionStore.ts +0 -14
  79. package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
  80. package/src/events/device.ts +4 -0
  81. package/src/events/ui-request.ts +32 -2
  82. package/src/protocols/protocol-v2/features.ts +65 -16
  83. package/src/protocols/protocol-v2/index.ts +5 -0
  84. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  85. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  86. package/src/protocols/protocol-v2/unlockRetry.ts +51 -18
  87. package/src/protocols/protocol-v2/walletSession.ts +172 -64
  88. package/src/types/api/openWalletSession.ts +1 -1
  89. package/src/types/api/protocolV2.ts +2 -2
  90. package/src/types/device.ts +2 -0
  91. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -4,6 +4,7 @@ import type {
4
4
  DeviceInfoGet,
5
5
  DeviceSEInfo,
6
6
  DeviceStatus,
7
+ ProtocolInfo,
7
8
  ProtocolV2DeviceInfo,
8
9
  } from '@onekeyfe/hd-transport';
9
10
  import type { DeviceCommands } from '../../device/DeviceCommands';
@@ -53,27 +54,54 @@ export const getProtocolV2SeState = (se?: DeviceSEInfo): ProtocolV2SeStateLabel
53
54
  export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
54
55
  normalizeEnumValue(DeviceSeType, se?.type);
55
56
 
56
- export type ProtocolV2RuntimeMode = 'normal' | 'bootloader';
57
+ export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
57
58
 
58
59
  /**
59
- * TODO: Once firmware-pro2 exposes ProtocolInfo.build_fingerprint, parse the running
60
- * binary name and remove the temporary DeviceStatusGet-failure heuristic. romloader
61
- * is a separate recovery flow and is excluded until its contract is defined.
62
- *
63
- * The temporary contract has two states: DeviceStatusGet success means normal and
64
- * failure means bootloader. Do not infer mode from application/SE/romloader fields.
60
+ * Protocol V2 fingerprints use:
61
+ * <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
65
62
  */
66
- export const getProtocolV2RuntimeMode = ({
67
- deviceInfo: _deviceInfo,
68
- deviceStatusAvailable,
69
- }: {
70
- deviceInfo?: ProtocolV2DeviceInfo | null;
71
- deviceStatusAvailable: boolean;
72
- }): ProtocolV2RuntimeMode => {
73
- if (deviceStatusAvailable) return 'normal';
74
- return 'bootloader';
63
+ export type ProtocolV2BuildFingerprint = {
64
+ binary: 'application' | 'bootloader' | 'romloader';
65
+ version: string;
66
+ commit: string;
67
+ environment: 'PROD' | 'DEV';
68
+ buildType: 'DEBUG' | 'RELEASE';
75
69
  };
76
70
 
71
+ export const parseProtocolV2BuildFingerprint = (
72
+ buildFingerprint: string | null | undefined
73
+ ): ProtocolV2BuildFingerprint | null => {
74
+ if (!buildFingerprint) return null;
75
+ const [binary, version, commit, environment, buildType, ...extra] = buildFingerprint.split('__');
76
+ if (
77
+ extra.length > 0 ||
78
+ (binary !== 'application' && binary !== 'bootloader' && binary !== 'romloader') ||
79
+ !version ||
80
+ !commit ||
81
+ (environment !== 'PROD' && environment !== 'DEV') ||
82
+ (buildType !== 'DEBUG' && buildType !== 'RELEASE')
83
+ ) {
84
+ return null;
85
+ }
86
+ return { binary, version, commit, environment, buildType };
87
+ };
88
+
89
+ export const getProtocolV2RuntimeMode = (
90
+ protocolInfo: ProtocolInfo
91
+ ): ProtocolV2RuntimeMode | undefined => {
92
+ const binary = parseProtocolV2BuildFingerprint(protocolInfo.build_fingerprint)?.binary;
93
+ if (binary === 'application') return 'normal';
94
+ return binary;
95
+ };
96
+
97
+ // MessageType_DeviceStatusGet in the Protocol V2 protobuf registry.
98
+ export const PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE = 60602;
99
+
100
+ export const supportsProtocolV2Message = (
101
+ protocolInfo: ProtocolInfo,
102
+ messageType: number
103
+ ): boolean => protocolInfo.supported_messages.includes(messageType);
104
+
77
105
  export const PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST = {
78
106
  targets: {
79
107
  hw: true,
@@ -131,6 +159,27 @@ export const PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST = {
131
159
  export const PROTOCOL_V2_DEVICE_INFO_REQUEST = PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST;
132
160
  export const PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS = 10 * 1000;
133
161
 
162
+ export async function requestProtocolV2ProtocolInfo({
163
+ commands,
164
+ timeoutMs,
165
+ }: {
166
+ commands: DeviceCommands;
167
+ timeoutMs?: number;
168
+ }): Promise<ProtocolInfo> {
169
+ const response =
170
+ timeoutMs === undefined
171
+ ? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
172
+ eventless_wallet_session: true,
173
+ })
174
+ : await commands.typedCall(
175
+ 'ProtocolInfoRequest',
176
+ 'ProtocolInfo',
177
+ { eventless_wallet_session: true },
178
+ { timeoutMs }
179
+ );
180
+ return response.message;
181
+ }
182
+
134
183
  export async function requestProtocolV2DeviceInfo({
135
184
  commands,
136
185
  timeoutMs = PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
@@ -1,13 +1,18 @@
1
1
  export {
2
2
  PROTOCOL_V2_DEVICE_INFO_REQUEST,
3
3
  PROTOCOL_V2_DEVICE_INFO_TIMEOUT_MS,
4
+ PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE,
4
5
  PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
5
6
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
6
7
  getProtocolV2RuntimeMode,
7
8
  getProtocolV2SeState,
8
9
  getProtocolV2SeType,
10
+ parseProtocolV2BuildFingerprint,
11
+ requestProtocolV2ProtocolInfo,
12
+ supportsProtocolV2Message,
9
13
  } from './features';
10
14
  export type {
15
+ ProtocolV2BuildFingerprint,
11
16
  ProtocolV2DeviceInfo,
12
17
  ProtocolV2FirmwareImageInfo,
13
18
  ProtocolV2SEInfo,
@@ -2,14 +2,19 @@ import type { DeviceSettings, DeviceSettingsPage } from '@onekeyfe/hd-transport'
2
2
  import type { UnlockPolicy } from '../../api/BaseMethod';
3
3
  import type { ProtocolV2InteractionDescriptor } from './uiInteraction';
4
4
 
5
- const LOCK_FREE_DEVICE_SETTINGS = new Set<keyof DeviceSettings>([
5
+ export const PROTOCOL_V2_LOCK_FREE_DEVICE_SETTINGS = [
6
6
  'language',
7
7
  'brightness',
8
+ 'animation_enable',
9
+ 'tap_to_wake',
8
10
  'haptic_feedback',
9
- ]);
11
+ 'device_name_display_enabled',
12
+ ] as const satisfies readonly (keyof DeviceSettings)[];
13
+
14
+ const lockFreeDeviceSettings = new Set<keyof DeviceSettings>(PROTOCOL_V2_LOCK_FREE_DEVICE_SETTINGS);
10
15
 
11
16
  export const getProtocolV2SettingsUnlockPolicy = (settings: DeviceSettings): UnlockPolicy =>
12
- Object.keys(settings).every(key => LOCK_FREE_DEVICE_SETTINGS.has(key as keyof DeviceSettings))
17
+ Object.keys(settings).every(key => lockFreeDeviceSettings.has(key as keyof DeviceSettings))
13
18
  ? 'none'
14
19
  : 'unlock-before-run';
15
20
 
@@ -1,6 +1,6 @@
1
1
  import { UI_REQUEST, createUiMessage } from '../../events';
2
2
 
3
- import type { CoreMessage, ProtocolV2UiCompletion } from '../../events';
3
+ import type { CoreMessage, HardwareUiInteractionMeta, ProtocolV2UiCompletion } from '../../events';
4
4
  import type { Device } from '../../device/Device';
5
5
  import type { KnownDevice } from '../../types';
6
6
 
@@ -28,7 +28,15 @@ type ProtocolV2InteractionMethod = {
28
28
  protocolV2UiInteraction?: ProtocolV2InteractionDescriptor;
29
29
  };
30
30
 
31
- type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'>;
31
+ type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'> &
32
+ Partial<
33
+ Pick<
34
+ Device,
35
+ | 'createProtocolV2UiPhaseMetadata'
36
+ | 'finishProtocolV2UiInteraction'
37
+ | 'hasOpenProtocolV2UiInteraction'
38
+ >
39
+ >;
32
40
  type PostMessage = (message: CoreMessage) => void;
33
41
 
34
42
  export const isProtocolV2UiEnabled = (method: { protocolV2UiMode?: 'auto' | 'none' }) =>
@@ -156,12 +164,17 @@ export class ProtocolV2UiInteractionCoordinator {
156
164
  this.methodInteraction = interaction;
157
165
  if (!interaction) return;
158
166
  const { request, ...metadata } = interaction;
167
+ const interactionMeta = this.device.createProtocolV2UiPhaseMetadata?.(
168
+ request === 'pin' ? 'pin' : 'button',
169
+ 'start'
170
+ );
159
171
 
160
172
  this.emit(
161
173
  request === 'pin' ? UI_REQUEST.REQUEST_PIN : UI_REQUEST.REQUEST_BUTTON,
162
174
  {
163
175
  device: this.device.toMessageObject() as KnownDevice,
164
176
  ...metadata,
177
+ ...(interactionMeta ? { interaction: interactionMeta } : {}),
165
178
  },
166
179
  `method:${request}:${interaction.reason}:${interaction.page ?? ''}:${
167
180
  interaction.operation ?? ''
@@ -169,7 +182,8 @@ export class ProtocolV2UiInteractionCoordinator {
169
182
  );
170
183
  }
171
184
 
172
- enterUnlockInteraction(method?: string) {
185
+ enterUnlockInteraction(method?: string): HardwareUiInteractionMeta | undefined {
186
+ const interaction = this.device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
173
187
  this.emit(
174
188
  UI_REQUEST.REQUEST_PIN,
175
189
  {
@@ -178,9 +192,11 @@ export class ProtocolV2UiInteractionCoordinator {
178
192
  reason: 'device-locked',
179
193
  deviceOnly: true,
180
194
  method,
195
+ ...(interaction ? { interaction } : {}),
181
196
  },
182
197
  `unlock:${method ?? ''}`
183
198
  );
199
+ return interaction;
184
200
  }
185
201
 
186
202
  resumeMethodInteraction() {
@@ -188,9 +204,19 @@ export class ProtocolV2UiInteractionCoordinator {
188
204
  }
189
205
 
190
206
  close() {
191
- if (!this.device.isProtocolV2() || !this.opened || this.closed) return;
207
+ if (
208
+ !this.device.isProtocolV2() ||
209
+ (!this.opened && !this.device.hasOpenProtocolV2UiInteraction?.()) ||
210
+ this.closed
211
+ )
212
+ return;
192
213
  this.closed = true;
193
- this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW));
214
+ const interaction = this.device.finishProtocolV2UiInteraction?.();
215
+ this.postMessage(
216
+ interaction
217
+ ? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, interaction)
218
+ : createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
219
+ );
194
220
  }
195
221
 
196
222
  private emit(
@@ -1,10 +1,11 @@
1
1
  import { LoggerNames, getLogger } from '../../utils';
2
2
  import { isDeviceLockedError } from './lockedError';
3
3
  import { isProtocolV2UiEnabled, resolveProtocolV2UiInteraction } from './uiInteraction';
4
- import { restoreProtocolV2WalletSession } from './walletSession';
4
+ import { refreshProtocolV2DeviceStatus, restoreProtocolV2WalletSession } from './walletSession';
5
5
 
6
6
  import type { BaseMethod } from '../../api/BaseMethod';
7
7
  import type { Device } from '../../device/Device';
8
+ import type { HardwareUiInteractionMeta } from '../../events';
8
9
  import type { ProtocolV2UiInteractionCoordinator } from './uiInteraction';
9
10
 
10
11
  const Log = getLogger(LoggerNames.Core);
@@ -46,44 +47,76 @@ export async function runMethodWithUnlockRetry(
46
47
  uiCoordinator?: UiInteractionCoordinator
47
48
  ) {
48
49
  const shouldEmitUi = isProtocolV2UiEnabled(method);
50
+ const shouldCoordinateUi = shouldEmitUi && uiCoordinator !== undefined;
51
+ const isProtocolV2 = device.isProtocolV2();
52
+ const requiresFreshStatus =
53
+ isProtocolV2 &&
54
+ method.unlockPolicy === 'unlock-before-run' &&
55
+ !device.isBootloader?.() &&
56
+ !device.isRomloader?.();
57
+
58
+ if (requiresFreshStatus) {
59
+ await refreshProtocolV2DeviceStatus(device);
60
+ }
61
+
49
62
  const shouldUnlockBeforeRun =
50
- device.isProtocolV2() && method.unlockPolicy !== 'none' && device.features?.unlocked === false;
63
+ isProtocolV2 &&
64
+ method.unlockPolicy !== 'none' &&
65
+ !device.isBootloader?.() &&
66
+ !device.isRomloader?.() &&
67
+ device.features?.unlocked === false;
51
68
 
52
69
  if (shouldUnlockBeforeRun) {
53
- if (shouldEmitUi) {
54
- uiCoordinator?.enterUnlockInteraction(method.name);
55
- }
56
- await device.unlockDevice();
70
+ const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
71
+ ? uiCoordinator?.enterUnlockInteraction(method.name)
72
+ : undefined;
73
+ await device.unlockDevice(
74
+ undefined,
75
+ shouldCoordinateUi
76
+ ? { emitUiEvent: false, interaction: unlockInteraction }
77
+ : {
78
+ source: 'unlock-coordinator',
79
+ reason: 'device-locked',
80
+ deviceOnly: true,
81
+ method: method.name,
82
+ }
83
+ );
57
84
  Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
58
85
  await restoreExpectedWalletSessionAfterUnlock(method, device);
59
- if (shouldEmitUi) {
86
+ if (shouldCoordinateUi) {
60
87
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
61
88
  }
62
89
  return method.run();
63
90
  }
64
91
 
65
- if (shouldEmitUi) {
92
+ if (shouldCoordinateUi) {
66
93
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
67
94
  }
68
95
  try {
69
96
  return await method.run();
70
97
  } catch (error) {
71
- if (
72
- !device.isProtocolV2() ||
73
- method.unlockPolicy !== 'retry-on-locked' ||
74
- !isDeviceLockedError(error)
75
- ) {
98
+ if (!isProtocolV2 || method.unlockPolicy !== 'retry-on-locked' || !isDeviceLockedError(error)) {
76
99
  throw error;
77
100
  }
78
101
 
79
102
  Log.debug('Protocol V2 unlock retry triggered', { method: method.name });
80
- if (shouldEmitUi) {
81
- uiCoordinator?.enterUnlockInteraction(method.name);
82
- }
83
- await device.unlockDevice();
103
+ const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
104
+ ? uiCoordinator?.enterUnlockInteraction(method.name)
105
+ : undefined;
106
+ await device.unlockDevice(
107
+ undefined,
108
+ shouldCoordinateUi
109
+ ? { emitUiEvent: false, interaction: unlockInteraction }
110
+ : {
111
+ source: 'unlock-coordinator',
112
+ reason: 'device-locked',
113
+ deviceOnly: true,
114
+ method: method.name,
115
+ }
116
+ );
84
117
  Log.debug('Protocol V2 unlock completed', { method: method.name });
85
118
  await restoreExpectedWalletSessionAfterUnlock(method, device);
86
- if (shouldEmitUi) {
119
+ if (shouldCoordinateUi) {
87
120
  uiCoordinator?.resumeMethodInteraction();
88
121
  }
89
122
  try {