@onekeyfe/hd-core 1.2.0-alpha.183 → 1.2.0-alpha.185

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 (49) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +13 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
  4. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
  5. package/__tests__/device-lifecycle-events.test.ts +19 -0
  6. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +102 -2
  7. package/__tests__/open-wallet-session.test.ts +127 -1
  8. package/__tests__/protocol-v2-resources.test.ts +0 -4
  9. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
  10. package/__tests__/protocol-v2.test.ts +155 -2
  11. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  12. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  13. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  14. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  15. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  16. package/dist/api/firmware/getBinary.d.ts +2 -2
  17. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  18. package/dist/core/index.d.ts +1 -0
  19. package/dist/core/index.d.ts.map +1 -1
  20. package/dist/device/Device.d.ts +1 -1
  21. package/dist/device/Device.d.ts.map +1 -1
  22. package/dist/events/ui-request.d.ts +3 -0
  23. package/dist/events/ui-request.d.ts.map +1 -1
  24. package/dist/index.d.ts +9 -4
  25. package/dist/index.js +644 -347
  26. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
  27. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  28. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  29. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  30. package/dist/types/settings.d.ts +2 -2
  31. package/dist/types/settings.d.ts.map +1 -1
  32. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  33. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  34. package/dist/utils/patch.d.ts +1 -1
  35. package/dist/utils/patch.d.ts.map +1 -1
  36. package/package.json +4 -4
  37. package/src/api/FirmwareUpdateV4.ts +193 -21
  38. package/src/api/OpenWalletSession.ts +39 -9
  39. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +2 -1
  40. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  41. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  42. package/src/core/index.ts +16 -2
  43. package/src/data/messages/messages-protocol-v2.json +111 -1
  44. package/src/device/Device.ts +3 -1
  45. package/src/events/ui-request.ts +3 -0
  46. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
  47. package/src/protocols/protocol-v2/walletSession.ts +65 -22
  48. package/src/types/settings.ts +4 -2
  49. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -446,6 +446,9 @@
446
446
  "MessageType_DeviceCertificateSignature": 60423,
447
447
  "MessageType_DeviceCertificateSign": 60424,
448
448
  "MessageType_DeviceMiscUsbMscControl": 60440,
449
+ "MessageType_DeviceFindMyTokenState": 60450,
450
+ "MessageType_DeviceFindMyTokenUpdate": 60451,
451
+ "MessageType_DeviceFindMyTokenStateGet": 60452,
449
452
  "MessageType_DeviceInfoGet": 60600,
450
453
  "MessageType_DeviceInfo": 60601,
451
454
  "MessageType_DeviceStatusGet": 60602,
@@ -5404,6 +5407,10 @@
5404
5407
  "request_id": {
5405
5408
  "type": "bytes",
5406
5409
  "id": 5
5410
+ },
5411
+ "source_fingerprint": {
5412
+ "type": "uint32",
5413
+ "id": 6
5407
5414
  }
5408
5415
  }
5409
5416
  },
@@ -5539,6 +5546,14 @@
5539
5546
  "tx_type": {
5540
5547
  "type": "uint32",
5541
5548
  "id": 10
5549
+ },
5550
+ "expected_address": {
5551
+ "type": "bytes",
5552
+ "id": 12
5553
+ },
5554
+ "source_fingerprint": {
5555
+ "type": "uint32",
5556
+ "id": 13
5542
5557
  }
5543
5558
  }
5544
5559
  },
@@ -5619,6 +5634,14 @@
5619
5634
  "rule": "repeated",
5620
5635
  "type": "EthereumAccessListOneKey",
5621
5636
  "id": 11
5637
+ },
5638
+ "expected_address": {
5639
+ "type": "bytes",
5640
+ "id": 12
5641
+ },
5642
+ "source_fingerprint": {
5643
+ "type": "uint32",
5644
+ "id": 13
5622
5645
  }
5623
5646
  }
5624
5647
  },
@@ -5795,6 +5818,10 @@
5795
5818
  "chain_id": {
5796
5819
  "type": "uint64",
5797
5820
  "id": 3
5821
+ },
5822
+ "source_fingerprint": {
5823
+ "type": "uint32",
5824
+ "id": 4
5798
5825
  }
5799
5826
  }
5800
5827
  },
@@ -9474,6 +9501,10 @@
9474
9501
  "rule": "required",
9475
9502
  "type": "bytes",
9476
9503
  "id": 2
9504
+ },
9505
+ "source_fingerprint": {
9506
+ "type": "uint32",
9507
+ "id": 3
9477
9508
  }
9478
9509
  }
9479
9510
  },
@@ -9518,6 +9549,10 @@
9518
9549
  "application_domain": {
9519
9550
  "type": "bytes",
9520
9551
  "id": 5
9552
+ },
9553
+ "source_fingerprint": {
9554
+ "type": "uint32",
9555
+ "id": 6
9521
9556
  }
9522
9557
  }
9523
9558
  },
@@ -9535,6 +9570,10 @@
9535
9570
  "rule": "required",
9536
9571
  "type": "bytes",
9537
9572
  "id": 2
9573
+ },
9574
+ "source_fingerprint": {
9575
+ "type": "uint32",
9576
+ "id": 3
9538
9577
  }
9539
9578
  }
9540
9579
  },
@@ -11764,6 +11803,27 @@
11764
11803
  }
11765
11804
  }
11766
11805
  },
11806
+ "DeviceFindMyTokenUpdate": {
11807
+ "fields": {
11808
+ "token": {
11809
+ "rule": "required",
11810
+ "type": "bytes",
11811
+ "id": 1
11812
+ }
11813
+ }
11814
+ },
11815
+ "DeviceFindMyTokenStateGet": {
11816
+ "fields": {}
11817
+ },
11818
+ "DeviceFindMyTokenState": {
11819
+ "fields": {
11820
+ "burned": {
11821
+ "rule": "required",
11822
+ "type": "bool",
11823
+ "id": 1
11824
+ }
11825
+ }
11826
+ },
11767
11827
  "DeviceFactoryAck": {
11768
11828
  "values": {
11769
11829
  "FACTORY_ACK_SUCCESS": 0,
@@ -11893,6 +11953,13 @@
11893
11953
  "FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS": 10
11894
11954
  }
11895
11955
  },
11956
+ "DeviceFirmwareUpdatePhase": {
11957
+ "values": {
11958
+ "FW_MGMT_UPDATER_PHASE_PREPARE": 0,
11959
+ "FW_MGMT_UPDATER_PHASE_INSTALL": 1,
11960
+ "FW_MGMT_UPDATER_PHASE_VERIFY": 2
11961
+ }
11962
+ },
11896
11963
  "DeviceFirmwareTarget": {
11897
11964
  "fields": {
11898
11965
  "target_id": {
@@ -11917,7 +11984,26 @@
11917
11984
  }
11918
11985
  },
11919
11986
  "DeviceFirmwareUpdateRequest": {
11920
- "fields": {}
11987
+ "fields": {
11988
+ "reboot_after_update": {
11989
+ "type": "bool",
11990
+ "id": 1
11991
+ }
11992
+ }
11993
+ },
11994
+ "DeviceFirmwareUpdatePhaseInfo": {
11995
+ "fields": {
11996
+ "phase": {
11997
+ "rule": "required",
11998
+ "type": "DeviceFirmwareUpdatePhase",
11999
+ "id": 1
12000
+ },
12001
+ "progress_percent": {
12002
+ "rule": "required",
12003
+ "type": "uint32",
12004
+ "id": 2
12005
+ }
12006
+ }
11921
12007
  },
11922
12008
  "DeviceFirmwareUpdateRecord": {
11923
12009
  "fields": {
@@ -11930,6 +12016,14 @@
11930
12016
  "type": "DeviceFirmwareUpdateTaskStatus",
11931
12017
  "id": 10
11932
12018
  },
12019
+ "progress_percent": {
12020
+ "type": "uint32",
12021
+ "id": 11
12022
+ },
12023
+ "phase_info": {
12024
+ "type": "DeviceFirmwareUpdatePhaseInfo",
12025
+ "id": 12
12026
+ },
11933
12027
  "payload_version": {
11934
12028
  "type": "uint32",
11935
12029
  "id": 20
@@ -11946,6 +12040,14 @@
11946
12040
  "type": "bool",
11947
12041
  "id": 10
11948
12042
  },
12043
+ "progress_percent": {
12044
+ "type": "bool",
12045
+ "id": 11
12046
+ },
12047
+ "phase_info": {
12048
+ "type": "bool",
12049
+ "id": 12
12050
+ },
11949
12051
  "payload_version": {
11950
12052
  "type": "bool",
11951
12053
  "id": 20
@@ -12713,6 +12815,10 @@
12713
12815
  "text_id": {
12714
12816
  "type": "uint32",
12715
12817
  "id": 3
12818
+ },
12819
+ "text_arg": {
12820
+ "type": "string",
12821
+ "id": 4
12716
12822
  }
12717
12823
  }
12718
12824
  },
@@ -12780,6 +12886,10 @@
12780
12886
  "title_id": {
12781
12887
  "type": "uint32",
12782
12888
  "id": 8
12889
+ },
12890
+ "title_arg": {
12891
+ "type": "string",
12892
+ "id": 9
12783
12893
  }
12784
12894
  }
12785
12895
  },
@@ -2005,7 +2005,8 @@ export class Device extends EventEmitter {
2005
2005
  passphraseState?: string,
2006
2006
  useEmptyPassphrase?: boolean,
2007
2007
  skipPassphraseCheck?: boolean,
2008
- deriveCardano?: boolean
2008
+ deriveCardano?: boolean,
2009
+ mainPinSelected?: boolean
2009
2010
  ) {
2010
2011
  if (this.isUnacquired()) return false;
2011
2012
 
@@ -2016,6 +2017,7 @@ export class Device extends EventEmitter {
2016
2017
  onlyMainPin: useEmptyPassphrase,
2017
2018
  deriveCardano,
2018
2019
  rejectAttachPinForMainWallet: useEmptyPassphrase === true,
2020
+ mainPinSelected,
2019
2021
  });
2020
2022
 
2021
2023
  // Main wallet and unlock Attach Pin, throw safe error
@@ -189,6 +189,9 @@ export interface FirmwareProgress {
189
189
  totalBytes?: number;
190
190
  rateBytesPerSecond?: number;
191
191
  elapsedMs?: number;
192
+ installTargetId?: number;
193
+ installPhase?: 'prepare' | 'install' | 'verify';
194
+ installPhaseProgress?: number;
192
195
  };
193
196
  }
194
197
 
@@ -14,10 +14,14 @@ const Log = getLogger(LoggerNames.Core);
14
14
 
15
15
  export type ProtocolV2UnlockContext = {
16
16
  preflightCompleted: boolean;
17
+ preflightStatusRefreshed?: boolean;
18
+ preflightMainPinSelected?: boolean;
17
19
  };
18
20
 
19
21
  export const createProtocolV2UnlockContext = (): ProtocolV2UnlockContext => ({
20
22
  preflightCompleted: false,
23
+ preflightStatusRefreshed: false,
24
+ preflightMainPinSelected: false,
21
25
  });
22
26
 
23
27
  type RunnableMethod = Pick<
@@ -100,11 +104,12 @@ export async function runMethodWithUnlockPolicy<T = unknown>(
100
104
  await afterStatusBeforeUnlock?.();
101
105
 
102
106
  if (!status.unlocked) {
107
+ const preUnlockPinType = resolvePreUnlockPinType(method);
103
108
  const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
104
109
  ? uiCoordinator.enterUnlockInteraction(method.name)
105
110
  : undefined;
106
111
  const unlockedStatus = await device.unlockDevice(
107
- resolvePreUnlockPinType(method),
112
+ preUnlockPinType,
108
113
  shouldCoordinateUi
109
114
  ? { emitUiEvent: false, interaction: unlockInteraction }
110
115
  : {
@@ -120,9 +125,11 @@ export async function runMethodWithUnlockPolicy<T = unknown>(
120
125
  'Protocol V2 device remained locked after the unlock flow.'
121
126
  );
122
127
  }
128
+ context.preflightMainPinSelected = preUnlockPinType === DeviceSessionPinType.Main;
123
129
  Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
124
130
  }
125
131
 
132
+ context.preflightStatusRefreshed = true;
126
133
  context.preflightCompleted = true;
127
134
  }
128
135
 
@@ -83,15 +83,21 @@ const buildDeviceSessionGetRequest = ({
83
83
  ],
84
84
  });
85
85
 
86
- const askDevicePassphrase = async (device: Device, requestPayload: DeviceSessionAskPassphrase) => {
86
+ const askDevicePassphrase = async (
87
+ device: Device,
88
+ requestPayload: DeviceSessionAskPassphrase,
89
+ onStatusRefreshed?: () => void
90
+ ) => {
87
91
  await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', requestPayload);
88
92
  await refreshProtocolV2DeviceStatus(device);
93
+ onStatusRefreshed?.();
89
94
  };
90
95
 
91
96
  const selectDeviceSession = async (
92
97
  device: Device,
93
98
  expectedPassphraseState?: string,
94
- deriveCardano?: boolean
99
+ deriveCardano?: boolean,
100
+ onStatusRefreshed?: () => void
95
101
  ) => {
96
102
  const existsAttachPinUser = device.features?.attachToPinEnabled === true;
97
103
  const metadata = {
@@ -153,14 +159,19 @@ const selectDeviceSession = async (
153
159
  emitUiEvent: false,
154
160
  interaction: attachPinInteraction,
155
161
  });
162
+ onStatusRefreshed?.();
156
163
  return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
157
164
  }
158
165
 
159
166
  if (hasHostPassphrase) {
160
- await askDevicePassphrase(device, {
161
- passphrase: hostPassphrase,
162
- on_device: false,
163
- });
167
+ await askDevicePassphrase(
168
+ device,
169
+ {
170
+ passphrase: hostPassphrase,
171
+ on_device: false,
172
+ },
173
+ onStatusRefreshed
174
+ );
164
175
  return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
165
176
  }
166
177
 
@@ -172,7 +183,7 @@ const selectDeviceSession = async (
172
183
  ...metadata,
173
184
  ...(passphraseOnDeviceInteraction ? { interaction: passphraseOnDeviceInteraction } : {}),
174
185
  });
175
- await askDevicePassphrase(device, { on_device: true });
186
+ await askDevicePassphrase(device, { on_device: true }, onStatusRefreshed);
176
187
  return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
177
188
  };
178
189
 
@@ -191,6 +202,8 @@ export async function getProtocolV2WalletSession(
191
202
  deriveCardano?: boolean;
192
203
  /** Read the wallet already selected by Attach PIN without reopening wallet selection. */
193
204
  readCurrentAttachPinSession?: boolean;
205
+ /** The current call already selected the Main PIN during its unlock preflight. */
206
+ mainPinSelected?: boolean;
194
207
  }
195
208
  ) {
196
209
  const forceWalletSelection =
@@ -227,10 +240,16 @@ export async function getProtocolV2WalletSession(
227
240
  : undefined;
228
241
  let response;
229
242
  let resumed = false;
230
- let mainPinSelected =
231
- options?.onlyMainPin === true &&
232
- device.features?.unlocked === true &&
233
- device.features?.unlockedAttachPin === false;
243
+ let walletStatusRefreshed = false;
244
+ const markWalletStatusRefreshed = () => {
245
+ walletStatusRefreshed = true;
246
+ };
247
+ let mainPinAuthenticated =
248
+ options?.mainPinSelected === true ||
249
+ (options?.onlyMainPin === true &&
250
+ device.features?.unlocked === true &&
251
+ device.features?.unlockedAttachPin === false);
252
+ let standardWalletSelected = options?.mainPinSelected === true;
234
253
 
235
254
  const clearCurrentWalletSession = () => {
236
255
  if (options?.onlyMainPin) {
@@ -242,6 +261,7 @@ export async function getProtocolV2WalletSession(
242
261
 
243
262
  const rejectMismatchedAttachPinWallet = async () => {
244
263
  const features = await refreshProtocolV2DeviceStatus(device);
264
+ markWalletStatusRefreshed();
245
265
  if (features.unlockedAttachPin !== true) {
246
266
  return;
247
267
  }
@@ -265,30 +285,42 @@ export async function getProtocolV2WalletSession(
265
285
  }
266
286
 
267
287
  const selectMainPin = async (force = false) => {
268
- if (force || !mainPinSelected) {
288
+ if (force || !mainPinAuthenticated) {
269
289
  await device.unlockDevice(DeviceSessionPinType.Main, {
270
290
  source: 'wallet-session-coordinator',
271
291
  reason: expectedPassphraseState ? 'session-recovery' : 'open-wallet',
272
292
  deviceOnly: true,
273
293
  });
274
- mainPinSelected = true;
294
+ markWalletStatusRefreshed();
295
+ mainPinAuthenticated = true;
296
+ standardWalletSelected = true;
275
297
  }
276
298
  };
277
299
 
278
300
  const selectStandardWallet = async () => {
279
- // Main PIN authenticates the device; an empty host passphrase selects the standard derivation.
280
- await selectMainPin();
281
301
  if (device.features?.passphraseProtection === true) {
282
- await askDevicePassphrase(device, {
283
- passphrase: '',
284
- on_device: false,
285
- });
302
+ // Main PIN authenticates the device; an empty host passphrase selects the standard derivation.
303
+ await selectMainPin();
304
+ await askDevicePassphrase(
305
+ device,
306
+ {
307
+ passphrase: '',
308
+ on_device: false,
309
+ },
310
+ markWalletStatusRefreshed
311
+ );
312
+ standardWalletSelected = true;
313
+ } else if (!standardWalletSelected) {
314
+ // Without passphrase protection there is no empty-passphrase selector.
315
+ // Main PIN selection is the only authoritative switch back to the standard wallet.
316
+ await selectMainPin(true);
286
317
  }
287
318
  };
288
319
 
289
320
  if (readCurrentAttachPinSession) {
290
321
  const status = await requestProtocolV2DeviceStatus(device);
291
322
  device.updateProtocolV2Status(status);
323
+ markWalletStatusRefreshed();
292
324
  if (
293
325
  status.unlocked !== true ||
294
326
  status.passphrase_enabled !== true ||
@@ -372,7 +404,12 @@ export async function getProtocolV2WalletSession(
372
404
  device.clearInternalState();
373
405
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
374
406
  }
375
- response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
407
+ response = await selectDeviceSession(
408
+ device,
409
+ expectedPassphraseState,
410
+ options?.deriveCardano,
411
+ markWalletStatusRefreshed
412
+ );
376
413
  }
377
414
 
378
415
  let { message } = response;
@@ -402,7 +439,12 @@ export async function getProtocolV2WalletSession(
402
439
  );
403
440
  } else {
404
441
  device.clearInternalState();
405
- response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
442
+ response = await selectDeviceSession(
443
+ device,
444
+ expectedPassphraseState,
445
+ options?.deriveCardano,
446
+ markWalletStatusRefreshed
447
+ );
406
448
  }
407
449
  message = response.message;
408
450
  try {
@@ -438,7 +480,7 @@ export async function getProtocolV2WalletSession(
438
480
  let unlockedAttachPin: boolean | undefined;
439
481
  if (readCurrentAttachPinSession) {
440
482
  unlockedAttachPin = true;
441
- } else if (mainPinSelected) {
483
+ } else if (mainPinAuthenticated) {
442
484
  unlockedAttachPin = false;
443
485
  }
444
486
 
@@ -447,6 +489,7 @@ export async function getProtocolV2WalletSession(
447
489
  newSession: message.session_id,
448
490
  unlockedAttachPin,
449
491
  resumed,
492
+ walletStatusRefreshed,
450
493
  };
451
494
  }
452
495
 
@@ -77,7 +77,8 @@ export type IProtocolV2Resources = {
77
77
  /** STM32 firmware config */
78
78
  export type IFirmwareReleaseInfo = {
79
79
  required: boolean;
80
- url: string;
80
+ /** Legacy release artifact URL. Protocol V2 releases use component URLs. */
81
+ url?: string;
81
82
  /**
82
83
  * Firmware type (bitcoinonly or universal)
83
84
  * This field is not present in the remote config, but will be inferred from the firmware field name
@@ -106,7 +107,8 @@ export type IFirmwareReleaseInfo = {
106
107
  bootloaderChangelog?: {
107
108
  [k in ILocale]: string;
108
109
  };
109
- fingerprint: string;
110
+ /** Legacy release artifact SHA-256. Protocol V2 releases use component fingerprints. */
111
+ fingerprint?: string;
110
112
  expectedSize?: number;
111
113
  version: IVersionArray;
112
114
  changelog: {
@@ -110,6 +110,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
110
110
  initSession?: boolean;
111
111
  deriveCardano?: boolean;
112
112
  rejectAttachPinForMainWallet?: boolean;
113
+ mainPinSelected?: boolean;
113
114
  }
114
115
  ) => {
115
116
  if (device.isProtocolV2()) {
@@ -127,6 +128,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
127
128
  onlyMainPin: options?.onlyMainPin,
128
129
  deriveCardano: options?.deriveCardano,
129
130
  rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
131
+ mainPinSelected: options?.mainPinSelected,
130
132
  });
131
133
  }
132
134
 
@@ -194,6 +196,7 @@ export const getPassphraseState = async (
194
196
  initSession?: boolean;
195
197
  deriveCardano?: boolean;
196
198
  rejectAttachPinForMainWallet?: boolean;
199
+ mainPinSelected?: boolean;
197
200
  }
198
201
  ): Promise<{
199
202
  passphraseState: string | undefined;
@@ -216,6 +219,7 @@ export const getPassphraseState = async (
216
219
  onlyMainPin: options?.onlyMainPin,
217
220
  deriveCardano: options?.deriveCardano,
218
221
  rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
222
+ mainPinSelected: options?.mainPinSelected,
219
223
  });
220
224
  }
221
225