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

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 (75) hide show
  1. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +31 -0
  2. package/__tests__/core-dispose.test.ts +3 -3
  3. package/__tests__/device-lifecycle-events.test.ts +52 -0
  4. package/__tests__/device-pool-state.test.ts +14 -2
  5. package/__tests__/device-settings.test.ts +22 -0
  6. package/__tests__/device-state-mapper.test.ts +13 -2
  7. package/__tests__/device-state-projector.test.ts +2 -2
  8. package/__tests__/device-utils.test.ts +1 -0
  9. package/__tests__/device-wallet-session-store.test.ts +64 -17
  10. package/__tests__/kaspaSignTransaction.test.ts +9 -0
  11. package/__tests__/known-device-public-types.type-test.ts +3 -0
  12. package/__tests__/logBlockEvent.test.ts +89 -5
  13. package/__tests__/open-wallet-session.test.ts +334 -30
  14. package/__tests__/pro2Wallpaper.test.ts +1 -1
  15. package/__tests__/protocol-v2.test.ts +708 -75
  16. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  17. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  18. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  19. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  20. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  21. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  22. package/dist/core/index.d.ts.map +1 -1
  23. package/dist/device/Device.d.ts +7 -2
  24. package/dist/device/Device.d.ts.map +1 -1
  25. package/dist/device/DeviceConnector.d.ts +1 -1
  26. package/dist/device/DeviceConnector.d.ts.map +1 -1
  27. package/dist/device/DevicePool.d.ts +1 -0
  28. package/dist/device/DevicePool.d.ts.map +1 -1
  29. package/dist/device/DeviceStateMapper.d.ts.map +1 -1
  30. package/dist/device/DeviceStateProjector.d.ts.map +1 -1
  31. package/dist/device/DeviceWalletSessionStore.d.ts +8 -0
  32. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  33. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
  34. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts +7 -0
  35. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -0
  36. package/dist/events/logBlockEvent.d.ts +2 -0
  37. package/dist/events/logBlockEvent.d.ts.map +1 -1
  38. package/dist/index.d.ts +19 -7
  39. package/dist/index.js +456 -222
  40. package/dist/protocols/protocol-v2/walletSession.d.ts +0 -10
  41. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  42. package/dist/types/api/openWalletSession.d.ts +1 -1
  43. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  44. package/dist/types/device.d.ts +4 -2
  45. package/dist/types/device.d.ts.map +1 -1
  46. package/dist/types/params.d.ts.map +1 -1
  47. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  48. package/dist/utils/deviceFeaturesUtils.d.ts +0 -5
  49. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  50. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  51. package/package.json +4 -4
  52. package/src/api/CheckAllFirmwareRelease.ts +11 -81
  53. package/src/api/FirmwareUpdateV4.ts +33 -17
  54. package/src/api/GetPassphraseState.ts +1 -3
  55. package/src/api/OpenWalletSession.ts +3 -3
  56. package/src/api/device/DeviceSettings.ts +8 -1
  57. package/src/api/kaspa/KaspaSignTransaction.ts +7 -0
  58. package/src/core/index.ts +7 -5
  59. package/src/device/Device.ts +37 -9
  60. package/src/device/DeviceConnector.ts +10 -7
  61. package/src/device/DevicePool.ts +5 -3
  62. package/src/device/DeviceStateMapper.ts +5 -3
  63. package/src/device/DeviceStateProjector.ts +0 -7
  64. package/src/device/DeviceWalletSessionStore.ts +57 -4
  65. package/src/deviceProfile/buildDeviceFeatures.ts +10 -3
  66. package/src/deviceProfile/protocolV2DeviceIdentity.ts +32 -0
  67. package/src/events/logBlockEvent.ts +84 -1
  68. package/src/protocols/protocol-v2/deviceSession.ts +1 -1
  69. package/src/protocols/protocol-v2/walletSession.ts +116 -34
  70. package/src/types/api/openWalletSession.ts +1 -1
  71. package/src/types/device.ts +6 -2
  72. package/src/types/params.ts +2 -1
  73. package/src/utils/deviceFeaturesCompat.ts +8 -1
  74. package/src/utils/pro2Wallpaper.ts +3 -9
  75. package/tsconfig.type-tests.json +5 -1
@@ -8,6 +8,21 @@ import { isDeviceLockedError } from './lockedError';
8
8
  import type { DeviceSessionGet } from '@onekeyfe/hd-transport';
9
9
  import type { Device } from '../../device/Device';
10
10
 
11
+ const HOST_PASSPHRASE_MAX_BYTES = 50;
12
+
13
+ const utf8ByteLength = (value: string): number | undefined => {
14
+ let length = 0;
15
+ for (const character of value) {
16
+ const codePoint = character.codePointAt(0) ?? 0;
17
+ if (codePoint >= 0xd800 && codePoint <= 0xdfff) return undefined;
18
+ if (codePoint <= 0x7f) length += 1;
19
+ else if (codePoint <= 0x7ff) length += 2;
20
+ else if (codePoint <= 0xffff) length += 3;
21
+ else length += 4;
22
+ }
23
+ return length;
24
+ };
25
+
11
26
  export async function requestProtocolV2DeviceStatus(device: Device) {
12
27
  const { message } = await device.commands.typedCall('DeviceStatusGet', 'DeviceStatus', {});
13
28
  return message;
@@ -28,7 +43,7 @@ const getDeviceSession = async (device: Device, request: DeviceSessionGet) => {
28
43
  try {
29
44
  return await device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
30
45
  } catch (error) {
31
- if (!isDeviceLockedError(error)) {
46
+ if (!isDeviceLockedError(error) || !request.session_id) {
32
47
  throw error;
33
48
  }
34
49
  await device.unlockDevice(DeviceSessionPinType.Main);
@@ -44,32 +59,47 @@ const askDevicePassphrase = async (device: Device, passphrase?: string) => {
44
59
  passphrase ? { passphrase, on_device: false } : { on_device: true }
45
60
  );
46
61
  try {
47
- return await request();
62
+ await request();
48
63
  } catch (error) {
49
64
  if (!isDeviceLockedError(error)) {
50
65
  throw error;
51
66
  }
52
67
  await device.unlockDevice(DeviceSessionPinType.Main);
53
- return request();
68
+ await request();
54
69
  }
70
+ await refreshProtocolV2DeviceStatus(device);
55
71
  };
56
72
 
57
- const selectDeviceSession = async (device: Device) => {
73
+ const selectDeviceSession = async (device: Device, expectedPassphraseState?: string) => {
58
74
  const existsAttachPinUser = device.features?.attachToPinEnabled === true;
59
75
  const metadata = {
60
76
  source: 'wallet-session-coordinator' as const,
61
- reason: 'open-wallet' as const,
77
+ reason: expectedPassphraseState ? ('session-recovery' as const) : ('open-wallet' as const),
78
+ ...(expectedPassphraseState ? { expectedPassphraseState } : {}),
62
79
  };
63
80
  const response = await device.commands.promptPassphrase(
64
81
  {
65
82
  existsAttachPinUser,
66
- deviceOnly: true,
83
+ deviceOnly: false,
67
84
  ...metadata,
68
85
  },
69
86
  { cancelDeviceOnReject: false }
70
87
  );
71
- const hasHostPassphrase =
72
- typeof response.passphrase === 'string' && response.passphrase.length > 0;
88
+ const hostPassphrase =
89
+ typeof response.passphrase === 'string' ? response.passphrase.normalize('NFKD') : undefined;
90
+ const hasHostPassphrase = typeof hostPassphrase === 'string' && hostPassphrase.length > 0;
91
+ const hostPassphraseByteLength = hasHostPassphrase ? utf8ByteLength(hostPassphrase) : undefined;
92
+ if (
93
+ hasHostPassphrase &&
94
+ (hostPassphrase.includes('\0') ||
95
+ hostPassphraseByteLength === undefined ||
96
+ hostPassphraseByteLength > HOST_PASSPHRASE_MAX_BYTES)
97
+ ) {
98
+ throw ERRORS.TypedError(
99
+ HardwareErrorCode.RuntimeError,
100
+ `Host passphrase must contain 1 to ${HOST_PASSPHRASE_MAX_BYTES} valid UTF-8 bytes without NUL.`
101
+ );
102
+ }
73
103
  const selections = [
74
104
  hasHostPassphrase,
75
105
  response.passphraseOnDevice === true,
@@ -96,7 +126,7 @@ const selectDeviceSession = async (device: Device) => {
96
126
  }
97
127
 
98
128
  if (hasHostPassphrase) {
99
- await askDevicePassphrase(device, response.passphrase);
129
+ await askDevicePassphrase(device, hostPassphrase);
100
130
  return getDeviceSession(device, {});
101
131
  }
102
132
 
@@ -115,7 +145,11 @@ export async function getProtocolV2WalletSession(
115
145
  }
116
146
  ) {
117
147
  if (options?.initSession) {
118
- device.clearInternalState();
148
+ if (options.onlyMainPin) {
149
+ device.clearStandardInternalState?.();
150
+ } else {
151
+ device.clearInternalState();
152
+ }
119
153
  device.passphraseState = undefined;
120
154
  }
121
155
 
@@ -124,29 +158,42 @@ export async function getProtocolV2WalletSession(
124
158
  if (options?.onlyMainPin) {
125
159
  device.passphraseState = undefined;
126
160
  }
127
- const expectedPassphraseState = options?.onlyMainPin
161
+ let expectedPassphraseState = options?.onlyMainPin
128
162
  ? undefined
129
163
  : options?.expectedPassphraseState ?? device.passphraseState;
130
164
 
131
- if (device.features?.unlocked === false) {
132
- await device.unlockDevice(DeviceSessionPinType.Main);
133
- }
134
-
135
- if (options?.onlyMainPin || device.getCurrentPassphraseProtection() === false) {
136
- return {
137
- passphraseState: undefined,
138
- newSession: undefined,
139
- unlockedAttachPin: device.features?.unlockedAttachPin ?? false,
140
- resumed: false,
141
- };
142
- }
143
-
165
+ const cachedStandardSession = options?.onlyMainPin
166
+ ? device.getStandardInternalState?.()
167
+ : undefined;
144
168
  const cachedSessionId =
145
- typeof device.getInternalState === 'function' ? device.getInternalState() : undefined;
169
+ !options?.onlyMainPin && typeof device.getInternalState === 'function'
170
+ ? device.getInternalState()
171
+ : undefined;
146
172
  let response;
147
173
  let resumed = false;
148
174
 
149
- if (cachedSessionId && expectedPassphraseState) {
175
+ if (options?.onlyMainPin) {
176
+ expectedPassphraseState = cachedStandardSession?.passphraseState;
177
+ if (cachedStandardSession) {
178
+ try {
179
+ if (device.features?.unlockedAttachPin === true) {
180
+ await device.unlockDevice(DeviceSessionPinType.Main);
181
+ }
182
+ response = await getDeviceSession(device, {
183
+ session_id: cachedStandardSession.sessionId,
184
+ });
185
+ resumed = true;
186
+ } catch (error) {
187
+ device.clearStandardInternalState?.();
188
+ throw error;
189
+ }
190
+ }
191
+
192
+ if (!response) {
193
+ await device.unlockDevice(DeviceSessionPinType.Main);
194
+ response = await getDeviceSession(device, {});
195
+ }
196
+ } else if (cachedSessionId && expectedPassphraseState) {
150
197
  try {
151
198
  response = await getDeviceSession(device, { session_id: cachedSessionId });
152
199
  resumed = true;
@@ -161,28 +208,63 @@ export async function getProtocolV2WalletSession(
161
208
  device.clearInternalState();
162
209
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
163
210
  }
164
- response = await selectDeviceSession(device);
211
+ response = await selectDeviceSession(device, expectedPassphraseState);
165
212
  }
166
213
 
167
- const { message } = response;
214
+ let { message } = response;
168
215
  try {
169
216
  assertCompleteDeviceSession(message);
170
217
  } catch (error) {
171
- device.clearInternalState();
218
+ if (options?.onlyMainPin) {
219
+ device.clearStandardInternalState?.();
220
+ } else {
221
+ device.clearInternalState();
222
+ }
172
223
  throw error;
173
224
  }
174
225
  if (expectedPassphraseState && expectedPassphraseState !== message.btc_test_address) {
175
- device.clearInternalState();
176
- throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
226
+ resumed = false;
227
+ if (options?.onlyMainPin) {
228
+ device.clearStandardInternalState?.();
229
+ await device.unlockDevice(DeviceSessionPinType.Main);
230
+ response = await getDeviceSession(device, {});
231
+ } else {
232
+ device.clearInternalState();
233
+ response = await selectDeviceSession(device, expectedPassphraseState);
234
+ }
235
+ message = response.message;
236
+ try {
237
+ assertCompleteDeviceSession(message);
238
+ } catch (error) {
239
+ if (options?.onlyMainPin) {
240
+ device.clearStandardInternalState?.();
241
+ } else {
242
+ device.clearInternalState();
243
+ }
244
+ throw error;
245
+ }
246
+ if (expectedPassphraseState !== message.btc_test_address) {
247
+ if (options?.onlyMainPin) {
248
+ device.clearStandardInternalState?.();
249
+ } else {
250
+ device.clearInternalState();
251
+ }
252
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
253
+ }
177
254
  }
178
255
 
179
- device.updateInternalState(
256
+ const internalStateArgs = [
180
257
  true,
181
258
  message.btc_test_address,
182
259
  device.getCurrentDeviceId(),
183
260
  message.session_id,
184
- options?.initSession ? null : cachedSessionId ?? null
185
- );
261
+ options?.initSession ? null : cachedSessionId ?? null,
262
+ ] as const;
263
+ if (options?.onlyMainPin) {
264
+ device.updateInternalState(...internalStateArgs, 'standard');
265
+ } else {
266
+ device.updateInternalState(...internalStateArgs);
267
+ }
186
268
 
187
269
  return {
188
270
  passphraseState: message.btc_test_address,
@@ -36,7 +36,7 @@ export type OpenWalletSessionPayload = OpenWalletSessionPayloadBase &
36
36
  (
37
37
  | {
38
38
  walletType: 'standard';
39
- passphraseState: null;
39
+ passphraseState: string | null;
40
40
  }
41
41
  | {
42
42
  walletType: 'hidden';
@@ -1,4 +1,4 @@
1
- import { EDeviceType, type EFirmwareType } from '@onekeyfe/hd-shared';
1
+ import { EDeviceType, type EFirmwareType, type HardwareConnectProtocol } from '@onekeyfe/hd-shared';
2
2
 
3
3
  import type { IVersionArray } from './settings';
4
4
  import type { PROTO } from '../constants';
@@ -28,6 +28,8 @@ export type UnavailableCapabilities = { [key: string]: UnavailableCapability };
28
28
 
29
29
  export type KnownDevice = {
30
30
  connectId: string | null;
31
+ /** Protocol family confirmed by the transport's active probe. */
32
+ connectProtocol?: HardwareConnectProtocol;
31
33
  /**
32
34
  * Stable physical-device identity. Current SDK responses always include this
33
35
  * value; it remains optional in the type during the uuid compatibility window.
@@ -46,7 +48,7 @@ export type KnownDevice = {
46
48
  /** Current SDK/transport usage state for connection indicators. */
47
49
  status?: DeviceStatus;
48
50
  mode: EOneKeyDeviceMode;
49
- features?: Features;
51
+ features: Features;
50
52
  /** Unified SDK device-state snapshot; features remains a legacy projection. */
51
53
  state?: DeviceState;
52
54
  unavailableCapabilities: UnavailableCapabilities;
@@ -61,6 +63,8 @@ export type KnownDevice = {
61
63
 
62
64
  export type SearchDevice = {
63
65
  connectId: string | null;
66
+ /** Protocol family confirmed by the transport's active probe. */
67
+ connectProtocol?: HardwareConnectProtocol;
64
68
  /**
65
69
  * Available after device initialization. BLE discovery does not connect to the
66
70
  * device, so it returns null until a later initialized device response. Current
@@ -60,7 +60,8 @@ export interface CommonParams {
60
60
  usePreInitialize?: boolean;
61
61
 
62
62
  /**
63
- * Expected transport protocol. If omitted, SDK probes Protocol V1 then Protocol V2.
63
+ * Strictly expected transport protocol. The SDK actively verifies this value and
64
+ * rejects a mismatch. If omitted, a cached protocol only influences probe order.
64
65
  */
65
66
  connectProtocol?: HardwareConnectProtocol;
66
67
  }
@@ -122,11 +122,18 @@ export const resolveDeviceBleName = (features?: DeviceFeaturesInput): string | n
122
122
  export const resolveDeviceFirmwareVersion = (features?: DeviceFeaturesInput): string | null => {
123
123
  if (!features) return null;
124
124
  const compatible = asCompatibleFeatures(features);
125
+ const bootloaderMode = compatible.bootloaderMode ?? compatible.bootloader_mode;
125
126
  return firstMeaningfulVersion(
126
127
  compatible.firmwareVersion,
127
128
  compatible.onekey_firmware_version,
128
129
  compatible.onekey_version,
129
- versionFromParts(compatible.major_version, compatible.minor_version, compatible.patch_version)
130
+ bootloaderMode
131
+ ? null
132
+ : versionFromParts(
133
+ compatible.major_version,
134
+ compatible.minor_version,
135
+ compatible.patch_version
136
+ )
130
137
  );
131
138
  };
132
139
 
@@ -85,15 +85,9 @@ export function encodePro2Wallpaper(options: {
85
85
  for (let x = 0; x < width; x += 1) {
86
86
  const sourceOffset = (y * width + x) * 4;
87
87
  const thresholdIndex = ((y & 7) << 3) + (x & 7);
88
- let red = Math.min(rgba[sourceOffset] + RED_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
89
- let green = Math.min(rgba[sourceOffset + 1] + GREEN_THRESHOLD[thresholdIndex], 0xff) & 0xfc;
90
- let blue = Math.min(rgba[sourceOffset + 2] + BLUE_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
91
- if (!hasTransparency) {
92
- const alpha = rgba[sourceOffset + 3];
93
- red = (red * alpha) >> 8;
94
- green = (green * alpha) >> 8;
95
- blue = (blue * alpha) >> 8;
96
- }
88
+ const red = Math.min(rgba[sourceOffset] + RED_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
89
+ const green = Math.min(rgba[sourceOffset + 1] + GREEN_THRESHOLD[thresholdIndex], 0xff) & 0xfc;
90
+ const blue = Math.min(rgba[sourceOffset + 2] + BLUE_THRESHOLD[thresholdIndex], 0xff) & 0xf8;
97
91
  const rgb565 = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
98
92
  const rgbOffset = 12 + y * stride + x * 2;
99
93
  data[rgbOffset] = rgb565 & 0xff;
@@ -3,6 +3,10 @@
3
3
  "compilerOptions": {
4
4
  "noEmit": true
5
5
  },
6
- "include": ["./src", "./__tests__/kaspa-public-types.type-test.ts"],
6
+ "include": [
7
+ "./src",
8
+ "./__tests__/kaspa-public-types.type-test.ts",
9
+ "./__tests__/known-device-public-types.type-test.ts"
10
+ ],
7
11
  "exclude": ["./dist", "../../node_modules"]
8
12
  }