@onekeyfe/hd-core 1.2.0-alpha.25 → 1.2.0-alpha.26

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.
@@ -200,6 +200,42 @@ describe('DeviceUploadWallpaper', () => {
200
200
  });
201
201
 
202
202
  describe('UploadPortfolio', () => {
203
+ test('writes and applies the portfolio while the device is locked without unlocking', async () => {
204
+ const packageBytes = new Uint8Array([1, 2, 3]);
205
+ const typedCall = jest
206
+ .fn()
207
+ .mockResolvedValueOnce({ message: { processed_byte: 3 } })
208
+ .mockResolvedValueOnce({ message: { message: 'Portfolio updated' } });
209
+ const unlockDevice = jest.fn().mockResolvedValue(undefined);
210
+ const device = stubDevice({
211
+ features: { unlocked: false },
212
+ commands: { typedCall },
213
+ isProtocolV2: () => true,
214
+ unlockDevice,
215
+ });
216
+ const method = new UploadPortfolio({
217
+ id: 1,
218
+ payload: {
219
+ method: 'uploadPortfolio',
220
+ packageBytes,
221
+ },
222
+ });
223
+ (method as any).device = device;
224
+
225
+ method.init();
226
+ await runMethodWithUnlockRetry(method, device as any);
227
+
228
+ expect(unlockDevice).not.toHaveBeenCalled();
229
+ expect(typedCall).toHaveBeenNthCalledWith(
230
+ 1,
231
+ 'FilesystemFileWrite',
232
+ 'FilesystemFile',
233
+ expect.any(Object),
234
+ { timeoutMs: undefined }
235
+ );
236
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'PortfolioUpdate', 'Success', {});
237
+ });
238
+
203
239
  test('stages the complete package before applying PortfolioUpdate', async () => {
204
240
  const packageBytes = new Uint8Array([1, 2, 3]);
205
241
  const typedCall = jest
@@ -219,7 +255,7 @@ describe('UploadPortfolio', () => {
219
255
  method.init();
220
256
  const result = await method.run();
221
257
 
222
- expect(method.unlockPolicy).toBe('unlock-before-run');
258
+ expect(method.unlockPolicy).toBe('none');
223
259
  expect(method.protocolV2UiMode).toBe('none');
224
260
  expect(method.protocolV2UiInteraction).toBeUndefined();
225
261
  expect(method.payload.emitProgress).toBe(false);
@@ -5359,6 +5395,93 @@ describe('Protocol V2 protected method execution', () => {
5359
5395
  ]);
5360
5396
  });
5361
5397
 
5398
+ test('restores the expected hidden-wallet session after pre-unlock without selecting Attach PIN', async () => {
5399
+ const calls: string[] = [];
5400
+ const method = {
5401
+ name: 'evmSignMessage',
5402
+ payload: { passphraseState: 'hidden-state' },
5403
+ useDevicePassphraseState: true,
5404
+ unlockPolicy: 'retry-on-locked',
5405
+ run: jest.fn(() => {
5406
+ calls.push('run');
5407
+ return Promise.resolve({ message: 'ok' });
5408
+ }),
5409
+ };
5410
+ const typedCall = jest.fn((requestType: string, _responseType: string, request: any) => {
5411
+ if (requestType === 'ProtocolInfoRequest') {
5412
+ calls.push('negotiate-session');
5413
+ return Promise.resolve({ message: { version: 2 } });
5414
+ }
5415
+ if (requestType === 'DeviceSessionGet') {
5416
+ calls.push('resume-hidden-session');
5417
+ expect(request).toEqual({ session_id: 'hidden-session' });
5418
+ return Promise.resolve({
5419
+ message: {
5420
+ session_id: 'hidden-session',
5421
+ btc_test_address: 'hidden-state',
5422
+ },
5423
+ });
5424
+ }
5425
+ throw new Error(`Unexpected request: ${requestType}`);
5426
+ });
5427
+ const features = {
5428
+ unlocked: false,
5429
+ unlockedAttachPin: true,
5430
+ passphraseProtection: true,
5431
+ };
5432
+ const device = {
5433
+ features,
5434
+ passphraseState: 'hidden-state',
5435
+ commands: { typedCall },
5436
+ isProtocolV2: () => true,
5437
+ unlockDevice: jest.fn(() => {
5438
+ calls.push('unlock-main');
5439
+ features.unlocked = true;
5440
+ features.unlockedAttachPin = false;
5441
+ return Promise.resolve();
5442
+ }),
5443
+ getCurrentPassphraseProtection: () => true,
5444
+ getInternalState: () => 'hidden-session',
5445
+ clearInternalState: jest.fn(),
5446
+ getCurrentDeviceId: () => 'wallet-device-id',
5447
+ updateInternalState: jest.fn(() => calls.push('validate-hidden-session')),
5448
+ };
5449
+
5450
+ await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
5451
+ message: 'ok',
5452
+ });
5453
+ expect(device.unlockDevice).toHaveBeenCalledWith();
5454
+ expect(calls).toEqual([
5455
+ 'unlock-main',
5456
+ 'negotiate-session',
5457
+ 'resume-hidden-session',
5458
+ 'validate-hidden-session',
5459
+ 'run',
5460
+ ]);
5461
+ });
5462
+
5463
+ test('does not restore a hidden-wallet session for a standard-wallet pre-unlock', async () => {
5464
+ const method = {
5465
+ name: 'evmGetAddress',
5466
+ payload: { useEmptyPassphrase: true },
5467
+ useDevicePassphraseState: true,
5468
+ unlockPolicy: 'retry-on-locked',
5469
+ run: jest.fn().mockResolvedValue({ address: 'standard-wallet-address' }),
5470
+ };
5471
+ const device = {
5472
+ features: { unlocked: false },
5473
+ passphraseState: 'stale-hidden-state',
5474
+ isProtocolV2: () => true,
5475
+ unlockDevice: jest.fn().mockResolvedValue(undefined),
5476
+ };
5477
+
5478
+ await expect(runMethodWithUnlockRetry(method as any, device as any)).resolves.toEqual({
5479
+ address: 'standard-wallet-address',
5480
+ });
5481
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
5482
+ expect(method.run).toHaveBeenCalledTimes(1);
5483
+ });
5484
+
5362
5485
  test('unlocks before showing the method interaction when cached status is locked', async () => {
5363
5486
  const calls: string[] = [];
5364
5487
  const method = {
@@ -5444,10 +5567,10 @@ describe('Protocol V2 protected method execution', () => {
5444
5567
  }
5445
5568
  });
5446
5569
 
5447
- test('keeps auto unlock but suppresses all synthesized UI for eventless methods', async () => {
5570
+ test('runs lock-free eventless methods without unlocking or synthesized UI', async () => {
5448
5571
  const method = {
5449
5572
  name: 'uploadPortfolio',
5450
- unlockPolicy: 'unlock-before-run',
5573
+ unlockPolicy: 'none',
5451
5574
  protocolV2UiMode: 'none',
5452
5575
  run: jest.fn().mockResolvedValue({ message: 'ok' }),
5453
5576
  };
@@ -5465,17 +5588,17 @@ describe('Protocol V2 protected method execution', () => {
5465
5588
  await expect(
5466
5589
  runMethodWithUnlockRetry(method as any, device as any, uiCoordinator as any)
5467
5590
  ).resolves.toEqual({ message: 'ok' });
5468
- expect(device.unlockDevice).toHaveBeenCalledTimes(1);
5591
+ expect(device.unlockDevice).not.toHaveBeenCalled();
5469
5592
  expect(uiCoordinator.enterMethodInteraction).not.toHaveBeenCalled();
5470
5593
  expect(uiCoordinator.enterUnlockInteraction).not.toHaveBeenCalled();
5471
5594
  expect(uiCoordinator.resumeMethodInteraction).not.toHaveBeenCalled();
5472
5595
  });
5473
5596
 
5474
- test('does not replay a state-changing method after a locked response', async () => {
5597
+ test('does not unlock or replay a lock-free state-changing method after a locked response', async () => {
5475
5598
  const error = deviceLockedError();
5476
5599
  const method = {
5477
5600
  name: 'uploadPortfolio',
5478
- unlockPolicy: 'unlock-before-run',
5601
+ unlockPolicy: 'none',
5479
5602
  run: jest.fn().mockRejectedValue(error),
5480
5603
  };
5481
5604
  const device = {
@@ -43,4 +43,32 @@ describe('TronSignMessage legacy message validation', () => {
43
43
  }
44
44
  }
45
45
  );
46
+
47
+ test('allows Protocol V2 message signing on Pro2', async () => {
48
+ const method = new TronSignMessage({
49
+ id: 1,
50
+ payload: {
51
+ method: 'tronSignMessage',
52
+ path: "m/44'/195'/0'/0/0",
53
+ messageHex: '00',
54
+ messageType: 'V2',
55
+ },
56
+ });
57
+ method.init();
58
+
59
+ const typedCall = jest.fn().mockResolvedValue({ message: { signature: 'signature' } });
60
+ method.device = {
61
+ commands: { typedCall },
62
+ getCurrentFirmwareVersionString: jest.fn(() => '0.0.0'),
63
+ getCurrentMethodVersionRange: jest.fn(selector => selector('pro2')),
64
+ getCurrentFirmwareType: jest.fn(() => EFirmwareType.Universal),
65
+ } as unknown as Device;
66
+
67
+ await expect(method.run()).resolves.toEqual({ signature: 'signature' });
68
+ expect(typedCall).toHaveBeenCalledWith('TronSignMessage', 'TronMessageSignature', {
69
+ address_n: [2147483692, 2147483843, 2147483648, 0, 0],
70
+ message: '00',
71
+ message_type: 2,
72
+ });
73
+ });
46
74
  });
@@ -13,6 +13,9 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
13
13
  };
14
14
  };
15
15
  getMessageV2VersionRange(): {
16
+ pro2: {
17
+ min: string;
18
+ };
16
19
  pro: {
17
20
  min: string;
18
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TronSignMessage.d.ts","sourceRoot":"","sources":["../../../src/api/tron/TronSignMessage.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,IAAI,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,uBAAuB,CAAC;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,qBAAqB;IAIrB,IAAI;IA4BJ,eAAe;;;;;;;;IAWf,wBAAwB;;;;;;;;;;;;;;IAiBlB,GAAG;CA0BV"}
1
+ {"version":3,"file":"TronSignMessage.d.ts","sourceRoot":"","sources":["../../../src/api/tron/TronSignMessage.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,IAAI,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,UAAU,CAAC,uBAAuB,CAAC;IAC9E,OAAO,CAAC,mBAAmB,CAAS;IAEpC,qBAAqB;IAIrB,IAAI;IA4BJ,eAAe;;;;;;;;IAWf,wBAAwB;;;;;;;;;;;;;;;;;IAoBlB,GAAG;CA0BV"}
package/dist/index.js CHANGED
@@ -51421,7 +51421,7 @@ class UploadPortfolio extends FileWrite {
51421
51421
  const { packageBytes, timeoutMs } = this.payload;
51422
51422
  this.payload = Object.assign(Object.assign({}, this.payload), { path: PORTFOLIO_PENDING_PATH, offset: 0, data: packageBytes, chunkSize: PORTFOLIO_CHUNK_SIZE, overwrite: true, append: false, emitProgress: false, timeoutMs });
51423
51423
  super.init();
51424
- this.unlockPolicy = 'unlock-before-run';
51424
+ this.unlockPolicy = 'none';
51425
51425
  this.protocolV2UiMode = 'none';
51426
51426
  }
51427
51427
  run() {
@@ -55761,6 +55761,9 @@ class TronSignMessage extends BaseMethod {
55761
55761
  }
55762
55762
  getMessageV2VersionRange() {
55763
55763
  return {
55764
+ pro2: {
55765
+ min: '0.0.0',
55766
+ },
55764
55767
  pro: {
55765
55768
  min: '4.16.0',
55766
55769
  },
@@ -61507,8 +61510,21 @@ class ProtocolV2UiInteractionCoordinator {
61507
61510
  }
61508
61511
 
61509
61512
  const Log$1 = getLogger(exports.LoggerNames.Core);
61513
+ const restoreExpectedWalletSessionAfterUnlock = (method, device) => __awaiter(void 0, void 0, void 0, function* () {
61514
+ var _a, _b, _c;
61515
+ const expectedPassphraseState = ((_a = method.payload) === null || _a === void 0 ? void 0 : _a.useEmptyPassphrase)
61516
+ ? undefined
61517
+ : (_c = (_b = method.payload) === null || _b === void 0 ? void 0 : _b.passphraseState) !== null && _c !== void 0 ? _c : device.passphraseState;
61518
+ if (!method.useDevicePassphraseState ||
61519
+ typeof expectedPassphraseState !== 'string' ||
61520
+ expectedPassphraseState.length === 0) {
61521
+ return;
61522
+ }
61523
+ yield restoreProtocolV2WalletSession(device, expectedPassphraseState);
61524
+ Log$1.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
61525
+ });
61510
61526
  function runMethodWithUnlockRetry(method, device, uiCoordinator) {
61511
- var _a, _b, _c, _d;
61527
+ var _a;
61512
61528
  return __awaiter(this, void 0, void 0, function* () {
61513
61529
  const shouldEmitUi = isProtocolV2UiEnabled(method);
61514
61530
  const shouldUnlockBeforeRun = device.isProtocolV2() && method.unlockPolicy !== 'none' && ((_a = device.features) === null || _a === void 0 ? void 0 : _a.unlocked) === false;
@@ -61518,6 +61534,7 @@ function runMethodWithUnlockRetry(method, device, uiCoordinator) {
61518
61534
  }
61519
61535
  yield device.unlockDevice();
61520
61536
  Log$1.debug('Protocol V2 pre-unlock completed', { method: method.name });
61537
+ yield restoreExpectedWalletSessionAfterUnlock(method, device);
61521
61538
  if (shouldEmitUi) {
61522
61539
  uiCoordinator === null || uiCoordinator === void 0 ? void 0 : uiCoordinator.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
61523
61540
  }
@@ -61541,15 +61558,7 @@ function runMethodWithUnlockRetry(method, device, uiCoordinator) {
61541
61558
  }
61542
61559
  yield device.unlockDevice();
61543
61560
  Log$1.debug('Protocol V2 unlock completed', { method: method.name });
61544
- const expectedPassphraseState = ((_b = method.payload) === null || _b === void 0 ? void 0 : _b.useEmptyPassphrase)
61545
- ? undefined
61546
- : (_d = (_c = method.payload) === null || _c === void 0 ? void 0 : _c.passphraseState) !== null && _d !== void 0 ? _d : device.passphraseState;
61547
- if (method.useDevicePassphraseState &&
61548
- typeof expectedPassphraseState === 'string' &&
61549
- expectedPassphraseState.length > 0) {
61550
- yield restoreProtocolV2WalletSession(device, expectedPassphraseState);
61551
- Log$1.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
61552
- }
61561
+ yield restoreExpectedWalletSessionAfterUnlock(method, device);
61553
61562
  if (shouldEmitUi) {
61554
61563
  uiCoordinator === null || uiCoordinator === void 0 ? void 0 : uiCoordinator.resumeMethodInteraction();
61555
61564
  }
@@ -1 +1 @@
1
- {"version":3,"file":"unlockRetry.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/unlockRetry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,iBAAiB,CAAC;AAI1E,KAAK,cAAc,GAAG,IAAI,CACxB,UAAU,EACR,KAAK,GACL,cAAc,GACd,yBAAyB,GACzB,kBAAkB,GAClB,QAAQ,GACR,SAAS,GACT,0BAA0B,CAC7B,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtB,KAAK,wBAAwB,GAAG,IAAI,CAClC,kCAAkC,EAClC,wBAAwB,GAAG,wBAAwB,GAAG,yBAAyB,CAChF,CAAC;AAEF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,wBAAwB,gBA6DzC"}
1
+ {"version":3,"file":"unlockRetry.d.ts","sourceRoot":"","sources":["../../../src/protocols/protocol-v2/unlockRetry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,iBAAiB,CAAC;AAI1E,KAAK,cAAc,GAAG,IAAI,CACxB,UAAU,EACR,KAAK,GACL,cAAc,GACd,yBAAyB,GACzB,kBAAkB,GAClB,QAAQ,GACR,SAAS,GACT,0BAA0B,CAC7B,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACtB,KAAK,wBAAwB,GAAG,IAAI,CAClC,kCAAkC,EAClC,wBAAwB,GAAG,wBAAwB,GAAG,yBAAyB,CAChF,CAAC;AAkBF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,wBAAwB,gBAoDzC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.0-alpha.25",
3
+ "version": "1.2.0-alpha.26",
4
4
  "description": "Core processes and APIs for communicating with OneKey hardware devices.",
5
5
  "author": "OneKey",
6
6
  "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
@@ -25,8 +25,8 @@
25
25
  "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@onekeyfe/hd-shared": "1.2.0-alpha.25",
29
- "@onekeyfe/hd-transport": "1.2.0-alpha.25",
28
+ "@onekeyfe/hd-shared": "1.2.0-alpha.26",
29
+ "@onekeyfe/hd-transport": "1.2.0-alpha.26",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "bytebuffer": "^5.0.1",
@@ -44,5 +44,5 @@
44
44
  "@types/w3c-web-usb": "^1.0.10",
45
45
  "@types/web-bluetooth": "^0.0.21"
46
46
  },
47
- "gitHead": "20bb98b8530c509299b9e109d2b5ada7c6a6d034"
47
+ "gitHead": "1401fef3eb93f5a83ed5e8375b99181b7aaa6054"
48
48
  }
@@ -23,7 +23,7 @@ export default class UploadPortfolio extends FileWrite {
23
23
  timeoutMs,
24
24
  };
25
25
  super.init();
26
- this.unlockPolicy = 'unlock-before-run';
26
+ this.unlockPolicy = 'none';
27
27
  // Portfolio is a background write/apply flow and never synthesizes UI events.
28
28
  this.protocolV2UiMode = 'none';
29
29
  }
@@ -57,6 +57,9 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
57
57
 
58
58
  getMessageV2VersionRange() {
59
59
  return {
60
+ pro2: {
61
+ min: '0.0.0',
62
+ },
60
63
  pro: {
61
64
  min: '4.16.0',
62
65
  },
@@ -24,6 +24,22 @@ type UiInteractionCoordinator = Pick<
24
24
  'enterMethodInteraction' | 'enterUnlockInteraction' | 'resumeMethodInteraction'
25
25
  >;
26
26
 
27
+ const restoreExpectedWalletSessionAfterUnlock = async (method: RunnableMethod, device: Device) => {
28
+ const expectedPassphraseState = method.payload?.useEmptyPassphrase
29
+ ? undefined
30
+ : method.payload?.passphraseState ?? device.passphraseState;
31
+ if (
32
+ !method.useDevicePassphraseState ||
33
+ typeof expectedPassphraseState !== 'string' ||
34
+ expectedPassphraseState.length === 0
35
+ ) {
36
+ return;
37
+ }
38
+
39
+ await restoreProtocolV2WalletSession(device, expectedPassphraseState);
40
+ Log.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
41
+ };
42
+
27
43
  export async function runMethodWithUnlockRetry(
28
44
  method: RunnableMethod,
29
45
  device: Device,
@@ -39,6 +55,7 @@ export async function runMethodWithUnlockRetry(
39
55
  }
40
56
  await device.unlockDevice();
41
57
  Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
58
+ await restoreExpectedWalletSessionAfterUnlock(method, device);
42
59
  if (shouldEmitUi) {
43
60
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
44
61
  }
@@ -65,17 +82,7 @@ export async function runMethodWithUnlockRetry(
65
82
  }
66
83
  await device.unlockDevice();
67
84
  Log.debug('Protocol V2 unlock completed', { method: method.name });
68
- const expectedPassphraseState = method.payload?.useEmptyPassphrase
69
- ? undefined
70
- : method.payload?.passphraseState ?? device.passphraseState;
71
- if (
72
- method.useDevicePassphraseState &&
73
- typeof expectedPassphraseState === 'string' &&
74
- expectedPassphraseState.length > 0
75
- ) {
76
- await restoreProtocolV2WalletSession(device, expectedPassphraseState);
77
- Log.debug('Protocol V2 wallet session restored after unlock', { method: method.name });
78
- }
85
+ await restoreExpectedWalletSessionAfterUnlock(method, device);
79
86
  if (shouldEmitUi) {
80
87
  uiCoordinator?.resumeMethodInteraction();
81
88
  }