@onekeyfe/hd-core 1.2.8-alpha.2 → 1.2.8-alpha.20

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/README.md +27 -0
  2. package/__tests__/btc-derive-context-hash.test.ts +200 -0
  3. package/__tests__/device-lifecycle-events.test.ts +51 -7
  4. package/__tests__/device-pool-state.test.ts +50 -0
  5. package/__tests__/protocol-v2-bootloader-mode.test.ts +12 -0
  6. package/__tests__/usb-identity-probe.test.ts +293 -0
  7. package/dist/api/SearchDevices.d.ts +21 -1
  8. package/dist/api/SearchDevices.d.ts.map +1 -1
  9. package/dist/api/btc/BTCDeriveContextHash.d.ts +16 -0
  10. package/dist/api/btc/BTCDeriveContextHash.d.ts.map +1 -0
  11. package/dist/api/index.d.ts +1 -0
  12. package/dist/api/index.d.ts.map +1 -1
  13. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -1
  14. package/dist/core/index.d.ts +3 -1
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/device/Device.d.ts +2 -0
  17. package/dist/device/Device.d.ts.map +1 -1
  18. package/dist/device/DevicePool.d.ts.map +1 -1
  19. package/dist/events/logBlockEvent.d.ts.map +1 -1
  20. package/dist/index.d.ts +45 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2788 -2522
  23. package/dist/inject.d.ts.map +1 -1
  24. package/dist/types/api/btcDeriveContextHash.d.ts +10 -0
  25. package/dist/types/api/btcDeriveContextHash.d.ts.map +1 -0
  26. package/dist/types/api/export.d.ts +1 -0
  27. package/dist/types/api/export.d.ts.map +1 -1
  28. package/dist/types/api/index.d.ts +2 -0
  29. package/dist/types/api/index.d.ts.map +1 -1
  30. package/dist/types/device.d.ts +8 -0
  31. package/dist/types/device.d.ts.map +1 -1
  32. package/dist/utils/patch.d.ts +1 -1
  33. package/dist/utils/patch.d.ts.map +1 -1
  34. package/package.json +4 -4
  35. package/src/api/SearchDevices.ts +163 -7
  36. package/src/api/btc/BTCDeriveContextHash.ts +90 -0
  37. package/src/api/index.ts +1 -0
  38. package/src/api/protocol-v2/DeviceReboot.ts +2 -0
  39. package/src/core/index.ts +23 -7
  40. package/src/data/messages/messages.json +51 -0
  41. package/src/device/Device.ts +10 -0
  42. package/src/device/DevicePool.ts +12 -2
  43. package/src/events/logBlockEvent.ts +1 -0
  44. package/src/index.ts +1 -0
  45. package/src/inject.ts +2 -0
  46. package/src/types/api/btcDeriveContextHash.ts +15 -0
  47. package/src/types/api/export.ts +1 -0
  48. package/src/types/api/index.ts +2 -0
  49. package/src/types/device.ts +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/hd-core",
3
- "version": "1.2.8-alpha.2",
3
+ "version": "1.2.8-alpha.20",
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.8-alpha.2",
29
- "@onekeyfe/hd-transport": "1.2.8-alpha.2",
28
+ "@onekeyfe/hd-shared": "1.2.8-alpha.20",
29
+ "@onekeyfe/hd-transport": "1.2.8-alpha.20",
30
30
  "axios": "1.15.2",
31
31
  "bignumber.js": "^9.0.2",
32
32
  "buffer": "^6.0.3",
@@ -46,5 +46,5 @@
46
46
  "@types/w3c-web-usb": "^1.0.10",
47
47
  "@types/web-bluetooth": "^0.0.21"
48
48
  },
49
- "gitHead": "74ac64464df3806ef44ad54d313378754c3eaed2"
49
+ "gitHead": "973f059fe93a58fdcf098ddb32ba63f621a2c78e"
50
50
  }
@@ -5,10 +5,20 @@ import TransportManager from '../data-manager/TransportManager';
5
5
  import { DataManager } from '../data-manager';
6
6
  import { LoggerNames, getDeviceTypeByBleName, getLogger } from '../utils';
7
7
  import { DevicePool } from '../device/DevicePool';
8
+ import { buildProtocolV1FeaturesPayload } from '../deviceProfile/buildDeviceFeatures';
9
+ import { mapProtocolV2DeviceInfoToState } from '../device/DeviceStateMapper';
10
+ import { getSupportProtocolV1MessageSchema } from '../utils/deviceFeaturesUtils';
11
+ import { PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST } from '../protocols/protocol-v2/features';
8
12
 
9
- import type { SearchDevice } from '../types/device';
13
+ import type { SearchDevice, UsbDeviceIdentity } from '../types/device';
14
+ import type { PROTO } from '../constants';
10
15
  import type DeviceConnector from '../device/DeviceConnector';
11
- import type { OneKeyDeviceInfo as DeviceDescriptor } from '@onekeyfe/hd-transport';
16
+ import type {
17
+ AcquireInput,
18
+ OneKeyDeviceInfo as DeviceDescriptor,
19
+ ProtocolV2DeviceInfo,
20
+ Transport,
21
+ } from '@onekeyfe/hd-transport';
12
22
  import type { Device } from '../device/Device';
13
23
 
14
24
  const Log = getLogger(LoggerNames.DevicePool);
@@ -18,7 +28,10 @@ type RequestQueueLookup = {
18
28
  getTask: (requestId: number) => { method: { device?: Device } } | undefined;
19
29
  };
20
30
 
21
- const getDescriptorKeys = (descriptor: DeviceDescriptor) =>
31
+ export type UsbIdentityProbeDescriptor = Pick<DeviceDescriptor, 'path' | 'commType'> &
32
+ Partial<Pick<DeviceDescriptor, 'id' | 'session'>>;
33
+
34
+ const getDescriptorKeys = (descriptor: UsbIdentityProbeDescriptor) =>
22
35
  [descriptor.path, descriptor.id].filter(
23
36
  (key): key is string => typeof key === 'string' && key.length > 0
24
37
  );
@@ -33,11 +46,11 @@ const isOwnedByActiveWebUsbRequest = (
33
46
  );
34
47
  };
35
48
 
36
- const getUsbHandle = (descriptor?: DeviceDescriptor) =>
49
+ const getUsbHandle = (descriptor?: UsbIdentityProbeDescriptor) =>
37
50
  (descriptor as { device?: unknown } | undefined)?.device;
38
51
 
39
52
  const getOwningRequestDevice = (
40
- descriptor: DeviceDescriptor,
53
+ descriptor: UsbIdentityProbeDescriptor,
41
54
  requestQueue?: RequestQueueLookup
42
55
  ) => {
43
56
  if (!requestQueue) return undefined;
@@ -60,6 +73,149 @@ const getOwningRequestDevice = (
60
73
  return undefined;
61
74
  };
62
75
 
76
+ export const getOwnedUsbDeviceIdentity = (
77
+ descriptor: UsbIdentityProbeDescriptor,
78
+ requestQueue: RequestQueueLookup
79
+ ): UsbDeviceIdentity | undefined => {
80
+ const owner = getOwningRequestDevice(descriptor, requestQueue);
81
+ if (!owner || descriptor.commType !== 'webusb' || owner.originalDescriptor?.commType !== 'webusb')
82
+ return undefined;
83
+ return {
84
+ path: descriptor.path,
85
+ serialNo: owner.getCurrentSerialNo() || null,
86
+ deviceId: owner.getCurrentDeviceId() || null,
87
+ deviceType: owner.getCurrentDeviceType(),
88
+ connectProtocol: owner.getProtocol(),
89
+ commType: 'webusb',
90
+ };
91
+ };
92
+
93
+ export type UsbIdentityProbeTransport = Pick<
94
+ Transport,
95
+ 'release' | 'call' | 'getProtocolType' | 'configureProtocolV2'
96
+ > & {
97
+ init: (logger: object) => unknown;
98
+ enumerate: () => Promise<UsbIdentityProbeDescriptor[]>;
99
+ acquire: (input: AcquireInput) => Promise<string | undefined>;
100
+ configure: (signedData: string) => void | Promise<void>;
101
+ };
102
+
103
+ // Identity discovery must not publish device events or log raw Features/session data.
104
+ const identityProbeLogger = {
105
+ debug: () => undefined,
106
+ log: () => undefined,
107
+ info: () => undefined,
108
+ warn: () => undefined,
109
+ error: () => undefined,
110
+ };
111
+
112
+ /** Runs on a separate transport; never initializes or switches the current Core. */
113
+ export const probeUsbDeviceIdentitiesWithTransport = async (
114
+ transport: UsbIdentityProbeTransport,
115
+ commType: UsbDeviceIdentity['commType'],
116
+ getOwnedIdentity?: (descriptor: UsbIdentityProbeDescriptor) => UsbDeviceIdentity | undefined
117
+ ): Promise<UsbDeviceIdentity[]> => {
118
+ if (commType !== 'webusb') return [];
119
+ await transport.init(identityProbeLogger);
120
+ const defaultMessages = JSON.stringify(DataManager.getProtobufMessages());
121
+ await transport.configure(defaultMessages);
122
+ await transport.configureProtocolV2?.(
123
+ JSON.stringify(DataManager.getProtobufMessages('v2Schema'))
124
+ );
125
+ const descriptors = await transport.enumerate();
126
+ const identities: UsbDeviceIdentity[] = [];
127
+ const readIdentity = async (
128
+ descriptor: UsbIdentityProbeDescriptor
129
+ ): Promise<UsbDeviceIdentity | undefined> => {
130
+ const { path } = descriptor;
131
+ if (!path || descriptor.commType !== commType) return undefined;
132
+ if (descriptors.filter(candidate => candidate.path === path).length !== 1) return undefined;
133
+ const owner = getOwnedIdentity?.(descriptor);
134
+ if (owner) {
135
+ return owner;
136
+ }
137
+ // Do not borrow a browser handle or replace a Bridge session owned by another call.
138
+ if (descriptor.session || (getUsbHandle(descriptor) as USBDevice | undefined)?.opened)
139
+ return undefined;
140
+
141
+ let session: string | undefined;
142
+ let identified: UsbDeviceIdentity | undefined;
143
+ try {
144
+ await transport.configure(defaultMessages);
145
+ session = await transport.acquire({ path, previous: null, forceProtocolDetection: true });
146
+ if (!session) return undefined;
147
+ const protocol = transport.getProtocolType(path);
148
+ if (protocol === 'V1') {
149
+ const response = await transport.call(session, 'GetFeatures', {}, { timeoutMs: 3000 });
150
+ if (response.type !== 'Features') return undefined;
151
+ let features = buildProtocolV1FeaturesPayload(response.message as PROTO.Features);
152
+ if (!features.serialNo) {
153
+ const { messages, protocolV1MessageSchema } = getSupportProtocolV1MessageSchema(features);
154
+ // Legacy V1 has no OnekeyGetFeatures; keep the identity confirmed by GetFeatures.
155
+ if (protocolV1MessageSchema === 'v1CurrentSchema') {
156
+ if (messages) await transport.configure(JSON.stringify(messages));
157
+ const vendor = await transport.call(
158
+ session,
159
+ 'OnekeyGetFeatures',
160
+ {},
161
+ { timeoutMs: 3000 }
162
+ );
163
+ if (vendor.type === 'OnekeyFeatures') {
164
+ features = buildProtocolV1FeaturesPayload({
165
+ ...response.message,
166
+ ...vendor.message,
167
+ } as PROTO.Features);
168
+ }
169
+ }
170
+ }
171
+ identified = {
172
+ path,
173
+ serialNo: features.serialNo || null,
174
+ deviceId: features.deviceId || null,
175
+ deviceType: features.deviceType,
176
+ connectProtocol: 'V1',
177
+ commType,
178
+ };
179
+ } else if (protocol === 'V2') {
180
+ const response = await transport.call(
181
+ session,
182
+ 'DeviceInfoGet',
183
+ PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
184
+ { timeoutMs: 3000 }
185
+ );
186
+ if (response.type !== 'DeviceInfo') return undefined;
187
+ const { identity } = mapProtocolV2DeviceInfoToState(
188
+ response.message as ProtocolV2DeviceInfo
189
+ );
190
+ identified = {
191
+ path,
192
+ serialNo: identity?.serialNo || null,
193
+ deviceId: identity?.deviceId || null,
194
+ deviceType: identity?.deviceType ?? EDeviceType.Unknown,
195
+ connectProtocol: 'V2',
196
+ commType,
197
+ };
198
+ }
199
+ } catch {
200
+ // Discovery is best effort; a busy/disconnected device cannot confirm identity.
201
+ } finally {
202
+ if (session) {
203
+ try {
204
+ await transport.release(session, false);
205
+ } catch {
206
+ identified = undefined;
207
+ }
208
+ }
209
+ }
210
+ return identified;
211
+ };
212
+ for (const descriptor of descriptors) {
213
+ const identity = await readIdentity(descriptor);
214
+ if (identity) identities.push(identity);
215
+ }
216
+ return identities;
217
+ };
218
+
63
219
  // A USB path is a routing key (the USB serial, a synthesized usb-vid-pid-name, or the
64
220
  // bootloader placeholder), not a hardware identity, so it never fills uuid/serialNo.
65
221
  const toSearchDeviceFromDescriptor = (descriptor: DeviceDescriptor): SearchDevice => {
@@ -81,7 +237,7 @@ const toSearchDevice = (device: Device): SearchDevice | null => {
81
237
  };
82
238
 
83
239
  export default class SearchDevices extends BaseMethod {
84
- connector?: DeviceConnector;
240
+ connector?: DeviceConnector = undefined;
85
241
 
86
242
  init() {
87
243
  this.useDevice = false;
@@ -130,7 +286,7 @@ export default class SearchDevices extends BaseMethod {
130
286
  // BLE discovery cannot provide a physical-device identity before initialization.
131
287
  uuid: '',
132
288
  deviceId: null,
133
- name: bleName || device.name,
289
+ name: bleName || device.name || '',
134
290
  deviceType: getDeviceTypeByBleName(bleName),
135
291
  });
136
292
  }
@@ -0,0 +1,90 @@
1
+ import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
+ import { Buffer } from 'buffer';
3
+
4
+ import { BaseMethod } from '../BaseMethod';
5
+ import { invalidParameter, validateParams } from '../helpers/paramsValidator';
6
+ import { getScriptType, validatePath } from '../helpers/pathUtils';
7
+
8
+ import type { BabylonDeriveContextHash, CanonicalBitcoinNetwork } from '@onekeyfe/hd-transport';
9
+ import type { BTCDeriveContextHashParams } from '../../types';
10
+
11
+ const NETWORKS: Record<BTCDeriveContextHashParams['network'], CanonicalBitcoinNetwork> = {
12
+ 'bitcoin-mainnet': 'BITCOIN_MAINNET',
13
+ 'bitcoin-testnet': 'BITCOIN_TESTNET',
14
+ 'bitcoin-signet': 'BITCOIN_SIGNET',
15
+ 'bitcoin-regtest': 'BITCOIN_REGTEST',
16
+ };
17
+
18
+ export default class BTCDeriveContextHash extends BaseMethod<BabylonDeriveContextHash> {
19
+ strictCheckDeviceSupport = true;
20
+
21
+ init() {
22
+ this.checkDeviceId = true;
23
+ this.allowUsePreInitialize = true;
24
+
25
+ validateParams(this.payload, [
26
+ { name: 'path', required: true },
27
+ { name: 'appName', type: 'string', required: true },
28
+ { name: 'context', type: 'string', required: true },
29
+ { name: 'network', type: 'string', required: true },
30
+ ]);
31
+
32
+ const { path, appName, context, network } = this.payload;
33
+ if (typeof appName !== 'string' || !/^[a-z0-9-]{1,64}$/.test(appName)) {
34
+ throw invalidParameter(
35
+ 'appName must contain 1..64 ASCII lowercase letters, digits or hyphens'
36
+ );
37
+ }
38
+ if (typeof context !== 'string' || !/^(?:[0-9a-f]{2}){1,1024}$/.test(context)) {
39
+ throw invalidParameter('context must be 1..1024 bytes of lowercase hex without a prefix');
40
+ }
41
+ if (typeof network !== 'string' || !Object.prototype.hasOwnProperty.call(NETWORKS, network)) {
42
+ throw invalidParameter('Unsupported Bitcoin network');
43
+ }
44
+
45
+ // Reject malformed or overflowing indices before validatePath can truncate them.
46
+ if (
47
+ (typeof path === 'string' &&
48
+ (!/^m(?:\/\d+'?)+$/i.test(path) ||
49
+ path
50
+ .split('/')
51
+ .slice(1)
52
+ .some(index => Number(index.replace(/'/g, '')) >= 0x80000000))) ||
53
+ (Array.isArray(path) &&
54
+ path.findIndex(index => !Number.isInteger(index) || index < 0 || index > 0xffffffff) !== -1)
55
+ ) {
56
+ throw invalidParameter('Not a valid BIP-32 path');
57
+ }
58
+ const addressN = validatePath(path, 3);
59
+
60
+ this.params = {
61
+ address_n: addressN,
62
+ script_type: getScriptType(addressN),
63
+ app_name: Buffer.from(appName, 'ascii').toString('hex'),
64
+ context,
65
+ network: NETWORKS[network as BTCDeriveContextHashParams['network']],
66
+ };
67
+ }
68
+
69
+ getVersionRange() {
70
+ return {
71
+ classic1s: { min: '3.21.0' },
72
+ classicpure: { min: '3.21.0' },
73
+ };
74
+ }
75
+
76
+ async run() {
77
+ const { message } = await this.device.commands.typedCall(
78
+ 'BabylonDeriveContextHash',
79
+ 'BabylonDerivedContextHash',
80
+ this.params
81
+ );
82
+ if (typeof message.secret !== 'string' || !/^[0-9a-f]{64}$/.test(message.secret)) {
83
+ throw ERRORS.TypedError(
84
+ HardwareErrorCode.CallMethodError,
85
+ 'Invalid Babylon context hash response'
86
+ );
87
+ }
88
+ return message;
89
+ }
90
+ }
package/src/api/index.ts CHANGED
@@ -60,6 +60,7 @@ export { default as allNetworkGetAddressByLoop } from './allnetwork/AllNetworkGe
60
60
 
61
61
  export { default as btcGetAddress } from './btc/BTCGetAddress';
62
62
  export { default as btcGetPublicKey } from './btc/BTCGetPublicKey';
63
+ export { default as btcDeriveContextHash } from './btc/BTCDeriveContextHash';
63
64
  export { default as btcSignMessage } from './btc/BTCSignMessage';
64
65
  export { default as btcSignPsbt } from './btc/BTCSignPsbt';
65
66
  export { default as btcSignTransaction } from './btc/BTCSignTransaction';
@@ -1,5 +1,6 @@
1
1
  import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
2
 
3
+ import { UI_REQUEST } from '../../constants/ui-request';
3
4
  import { BaseMethod } from '../BaseMethod';
4
5
  import { normalizeRebootType } from './helpers';
5
6
 
@@ -11,6 +12,7 @@ export default class DeviceReboot extends BaseMethod<DeviceRebootParams> {
11
12
  }
12
13
 
13
14
  init() {
15
+ this.allowDeviceMode.push(UI_REQUEST.BOOTLOADER);
14
16
  this.skipForceUpdateCheck = true;
15
17
  this.useDevicePassphraseState = false;
16
18
  this.unlockPolicy = 'unlock-before-run';
package/src/core/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import semver from 'semver';
2
+ import { getOwnedUsbDeviceIdentity } from '../api/SearchDevices';
2
3
  import EventEmitter from 'events';
3
4
  import {
4
5
  DeviceSessionPinType,
@@ -1589,7 +1590,7 @@ const onDeviceConnectHandler = (device: Device) => {
1589
1590
 
1590
1591
  const onDeviceDisconnectHandler = (device: Device) => {
1591
1592
  device.clearPreInitialized();
1592
- const deviceObject = device.toMessageObject();
1593
+ const deviceObject = device.toDisconnectMessageObject();
1593
1594
  if (!deviceObject) return;
1594
1595
  postMessage(createDeviceMessage(DEVICE.DISCONNECT, { device: deviceObject }));
1595
1596
  };
@@ -1826,6 +1827,10 @@ export default class Core extends EventEmitter {
1826
1827
 
1827
1828
  private requestQueue = new RequestQueue();
1828
1829
 
1830
+ getOwnedUsbDeviceIdentity(descriptor: Parameters<typeof getOwnedUsbDeviceIdentity>[0]) {
1831
+ return getOwnedUsbDeviceIdentity(descriptor, this.requestQueue);
1832
+ }
1833
+
1829
1834
  private disposePromise?: Promise<void>;
1830
1835
 
1831
1836
  // background task
@@ -2038,6 +2043,8 @@ export const init = async (
2038
2043
  }
2039
2044
  };
2040
2045
 
2046
+ let pendingTransportSwitch: Promise<void> = Promise.resolve();
2047
+
2041
2048
  export const switchTransport = ({
2042
2049
  env,
2043
2050
  Transport,
@@ -2047,10 +2054,19 @@ export const switchTransport = ({
2047
2054
  Transport: any;
2048
2055
  plugin?: LowlevelTransportSharedPlugin;
2049
2056
  }) => {
2050
- DataManager.updateEnv(env);
2051
- TransportManager.setTransport(Transport, plugin);
2052
- _deviceList = undefined;
2053
- DevicePool.resetState();
2054
- _connector = undefined;
2055
- initConnector();
2057
+ // Preserve request order while native transport cleanup is pending.
2058
+ const switching = pendingTransportSwitch
2059
+ .catch(() => undefined)
2060
+ .then(async () => {
2061
+ _connector?.stop();
2062
+ await TransportManager.getTransport()?.stop?.();
2063
+ DataManager.updateEnv(env);
2064
+ TransportManager.setTransport(Transport, plugin);
2065
+ _deviceList = undefined;
2066
+ DevicePool.resetState();
2067
+ _connector = undefined;
2068
+ initConnector();
2069
+ });
2070
+ pendingTransportSwitch = switching;
2071
+ return switching;
2056
2072
  };
@@ -12485,6 +12485,8 @@
12485
12485
  "(has_bitcoin_only_values)": true
12486
12486
  },
12487
12487
  "values": {
12488
+ "MessageType_BabylonDeriveContextHash": 10054,
12489
+ "MessageType_BabylonDerivedContextHash": 10055,
12488
12490
  "MessageType_Initialize": 0,
12489
12491
  "MessageType_Ping": 1,
12490
12492
  "MessageType_Success": 2,
@@ -12949,6 +12951,55 @@
12949
12951
  "MessageType_UpgradeFileHeader": 10050
12950
12952
  }
12951
12953
  },
12954
+ "CanonicalBitcoinNetwork": {
12955
+ "values": {
12956
+ "BITCOIN_MAINNET": 0,
12957
+ "BITCOIN_TESTNET": 1,
12958
+ "BITCOIN_SIGNET": 2,
12959
+ "BITCOIN_REGTEST": 3
12960
+ }
12961
+ },
12962
+ "BabylonDeriveContextHash": {
12963
+ "fields": {
12964
+ "address_n": {
12965
+ "rule": "repeated",
12966
+ "type": "uint32",
12967
+ "id": 1,
12968
+ "options": {
12969
+ "packed": false
12970
+ }
12971
+ },
12972
+ "script_type": {
12973
+ "rule": "required",
12974
+ "type": "InputScriptType",
12975
+ "id": 2
12976
+ },
12977
+ "app_name": {
12978
+ "rule": "required",
12979
+ "type": "bytes",
12980
+ "id": 3
12981
+ },
12982
+ "context": {
12983
+ "rule": "required",
12984
+ "type": "bytes",
12985
+ "id": 4
12986
+ },
12987
+ "network": {
12988
+ "rule": "required",
12989
+ "type": "CanonicalBitcoinNetwork",
12990
+ "id": 5
12991
+ }
12992
+ }
12993
+ },
12994
+ "BabylonDerivedContextHash": {
12995
+ "fields": {
12996
+ "secret": {
12997
+ "rule": "required",
12998
+ "type": "bytes",
12999
+ "id": 1
13000
+ }
13001
+ }
13002
+ },
12952
13003
  "google": {
12953
13004
  "nested": {
12954
13005
  "protobuf": {
@@ -248,6 +248,9 @@ export class Device extends EventEmitter {
248
248
  /** Canonical device-state cache; legacy Features is a compatibility projection. */
249
249
  private stateStore = new DeviceStateStore();
250
250
 
251
+ /** Retain the old public identity only for disconnect notifications after wipe. */
252
+ private deviceBeforeWipe: DeviceTyped | null = null;
253
+
251
254
  /** Force the next initialization to reload DeviceInfo after reconnect or reboot. */
252
255
  private protocolV2StateNeedsReload = false;
253
256
 
@@ -351,6 +354,8 @@ export class Device extends EventEmitter {
351
354
  toMessageObject(): DeviceTyped | null {
352
355
  if (this.isUnacquired() || !this.features) return null;
353
356
 
357
+ this.deviceBeforeWipe = null;
358
+
354
359
  const env = DataManager.getSettings('env');
355
360
  const deviceType = this.getCurrentDeviceType();
356
361
 
@@ -395,6 +400,10 @@ export class Device extends EventEmitter {
395
400
  };
396
401
  }
397
402
 
403
+ toDisconnectMessageObject(): DeviceTyped | null {
404
+ return this.toMessageObject() ?? this.deviceBeforeWipe;
405
+ }
406
+
398
407
  /**
399
408
  * Device connect
400
409
  * @returns {Promise<boolean>}
@@ -1364,6 +1373,7 @@ export class Device extends EventEmitter {
1364
1373
  }
1365
1374
 
1366
1375
  invalidateAfterWipe() {
1376
+ this.deviceBeforeWipe = this.toMessageObject() ?? this.deviceBeforeWipe;
1367
1377
  const deviceId = this.getCurrentDeviceId();
1368
1378
  if (deviceId) {
1369
1379
  deviceWalletSessionStore.deleteDevice(deviceId);
@@ -114,7 +114,11 @@ export class DevicePool extends EventEmitter {
114
114
  const exist = canPathIdentifyDevice(cachedPath)
115
115
  ? descriptorList.find(d => d.path === cachedPath)
116
116
  : undefined;
117
- if (exist && !initOptions?.forceProtocolDetection) {
117
+ if (
118
+ exist &&
119
+ !initOptions?.forceProtocolDetection &&
120
+ !(device.needReloadDevice && device.isUnacquired())
121
+ ) {
118
122
  // Log.debug('find existed Device: ', connectId);
119
123
  device.updateDescriptor(exist, true);
120
124
  await this._refreshRuntimeState(device, initOptions);
@@ -170,7 +174,13 @@ export class DevicePool extends EventEmitter {
170
174
  const forceProtocolDetection = initOptions?.forceProtocolDetection === true;
171
175
  const isNewDevice = !cachedDevice;
172
176
  const device = cachedDevice ?? Device.fromDescriptor(descriptor);
173
- if (isNewDevice || forceProtocolDetection) {
177
+ if (
178
+ isNewDevice ||
179
+ forceProtocolDetection ||
180
+ (device.needReloadDevice && device.isUnacquired())
181
+ ) {
182
+ // Wipe invalidates state while the same USB path can remain discoverable.
183
+ if (cachedDevice) device.updateDescriptor(descriptor, true);
174
184
  device.deviceConnector = this.connector;
175
185
  // Search/discovery acquires outside Device.run(). Start a new attempt so
176
186
  // a previous user cancel does not hide this device from later searches.
@@ -11,6 +11,7 @@ export const LogBlockEvent: Set<string> = new Set([
11
11
  // These resource/file APIs did not exist in 1.1.32. Skip the payload so
12
12
  // the log layer does not copy huge Base64 or binary data.
13
13
  const LogPayloadBlockMethod: Set<string> = new Set([
14
+ 'btcDeriveContextHash',
14
15
  'deviceUploadNft',
15
16
  'deviceUploadWallpaper',
16
17
  'uploadPortfolio',
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export { executeCallback, cleanupCallback };
21
21
  export { preloadSessionCache } from './device/Device';
22
22
  export { projectFeatures as projectDeviceStateFeatures } from './device/DeviceStateProjector';
23
23
  export { getMethodSupportedProtocols } from './api/utils';
24
+ export { probeUsbDeviceIdentitiesWithTransport } from './api/SearchDevices';
24
25
  export {
25
26
  getFirmwareUpdateHostBindingGeneration,
26
27
  registerFirmwareUpdateHostBinding,
package/src/inject.ts CHANGED
@@ -309,6 +309,8 @@ export const createCoreApi = (
309
309
  call({ ...params, connectId, deviceId, method: 'btcGetAddress' }),
310
310
  btcGetPublicKey: (connectId, deviceId, params) =>
311
311
  call({ ...params, connectId, deviceId, method: 'btcGetPublicKey' }),
312
+ btcDeriveContextHash: (connectId, deviceId, params) =>
313
+ call({ ...params, connectId, deviceId, method: 'btcDeriveContextHash' }),
312
314
  btcSignMessage: (connectId, deviceId, params) =>
313
315
  call({ ...params, connectId, deviceId, method: 'btcSignMessage' }),
314
316
  btcSignPsbt: (connectId, deviceId, params) =>
@@ -0,0 +1,15 @@
1
+ import type { BabylonDerivedContextHash } from '@onekeyfe/hd-transport';
2
+ import type { CommonParams, Response } from '../params';
3
+
4
+ export type BTCDeriveContextHashParams = {
5
+ path: string | number[];
6
+ appName: string;
7
+ context: string;
8
+ network: 'bitcoin-mainnet' | 'bitcoin-testnet' | 'bitcoin-signet' | 'bitcoin-regtest';
9
+ };
10
+
11
+ export declare function btcDeriveContextHash(
12
+ connectId: string,
13
+ deviceId: string,
14
+ params: CommonParams & BTCDeriveContextHashParams
15
+ ): Response<BabylonDerivedContextHash>;
@@ -1,5 +1,6 @@
1
1
  export type { BTCAddress, BTCGetAddressParams } from './btcGetAddress';
2
2
  export type { BTCPublicKey, BTCGetPublicKeyParams } from './btcGetPublicKey';
3
+ export type { BTCDeriveContextHashParams } from './btcDeriveContextHash';
3
4
  export type { BTCSignMessageParams } from './btcSignMessage';
4
5
  export type { BTCVerifyMessageParams } from './btcVerifyMessage';
5
6
  export type {
@@ -73,6 +73,7 @@ import type { evmSignTypedData } from './evmSignTypedData';
73
73
  import type { evmVerifyMessage } from './evmVerifyMessage';
74
74
  import type { btcGetAddress } from './btcGetAddress';
75
75
  import type { btcGetPublicKey } from './btcGetPublicKey';
76
+ import type { btcDeriveContextHash } from './btcDeriveContextHash';
76
77
  import type { btcSignMessage } from './btcSignMessage';
77
78
  import type { btcSignPsbt } from './btcSignPsbt';
78
79
  import type { btcSignTransaction } from './btcSignTransaction';
@@ -286,6 +287,7 @@ export type CoreApi = {
286
287
  */
287
288
  btcGetAddress: typeof btcGetAddress;
288
289
  btcGetPublicKey: typeof btcGetPublicKey;
290
+ btcDeriveContextHash: typeof btcDeriveContextHash;
289
291
  btcSignMessage: typeof btcSignMessage;
290
292
  btcSignPsbt: typeof btcSignPsbt;
291
293
  btcSignTransaction: typeof btcSignTransaction;
@@ -85,6 +85,16 @@ export type SearchDevice = {
85
85
  commType: OneKeyDeviceCommType;
86
86
  };
87
87
 
88
+ /** Identity confirmed over USB. The descriptor path is only a transport address. */
89
+ export type UsbDeviceIdentity = {
90
+ path: string;
91
+ serialNo: string | null;
92
+ deviceId: string | null;
93
+ deviceType: IDeviceType;
94
+ connectProtocol: HardwareConnectProtocol;
95
+ commType: 'webusb' | 'bridge';
96
+ };
97
+
88
98
  // export type UnknownDevice = {
89
99
  // type: 'unacquired';
90
100
  // id?: null;