@onekeyfe/hwk-ledger-adapter 1.1.34-alpha.2 → 1.1.34-alpha.4

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.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _onekeyfe_hwk_adapter_core from '@onekeyfe/hwk-adapter-core';
2
- import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
2
+ import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, DeviceAuthenticityParams, DeviceAuthenticityResult, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, ConnectorConfig, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
3
3
  import { DeviceActionState as DeviceActionState$1, DiscoveredDevice, ExecuteDeviceActionReturnType, DeviceManagementKit } from '@ledgerhq/device-management-kit';
4
4
  import { Address, Signature, SignerEth as SignerEth$1, TypedData } from '@ledgerhq/device-signer-kit-ethereum';
5
5
  import { ContextModule } from '@ledgerhq/context-module';
@@ -93,6 +93,19 @@ interface LedgerDeviceInfo extends FirmwareVersion {
93
93
 
94
94
  type LedgerCallParams<T> = NullableCallArg<IHardwareCallParams<T>>;
95
95
  type LedgerCommonParams = NullableCallArg<IHardwareCommonCallParams>;
96
+ type LedgerAttestationBridgeDevice = {
97
+ id: string;
98
+ modelId: 'nanoS' | 'nanoSP' | 'nanoX' | 'stax' | 'flex' | 'apexp';
99
+ name?: string;
100
+ connectionType?: 'USB' | 'BLE';
101
+ };
102
+ type LedgerAttestationApduBridge = {
103
+ device: LedgerAttestationBridgeDevice;
104
+ exchangeApdu: (apduHex: string, timeoutMs?: number) => Promise<{
105
+ dataHex: string;
106
+ statusCodeHex: string;
107
+ }>;
108
+ };
96
109
  declare class LedgerAdapter implements IHardwareWallet {
97
110
  readonly vendor: "ledger";
98
111
  private readonly connector;
@@ -102,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
102
115
  private readonly _uiRegistry;
103
116
  private _btcHighIndexConfirmedThisSession;
104
117
  private readonly _jobQueue;
118
+ private _deviceAuthenticityQueueTail;
105
119
  private _doConnectAbortController;
106
120
  private readonly _defaultAutoInstallApp;
107
121
  constructor(connector: IConnector, options?: {
@@ -162,6 +176,25 @@ declare class LedgerAdapter implements IHardwareWallet {
162
176
  listAvailableApps(connectId: string): Promise<Response<AppMetadata[]>>;
163
177
  getLedgerFirmwareVersion(connectId: string): Promise<Response<FirmwareVersion>>;
164
178
  getLedgerDeviceInfo(connectId: string): Promise<Response<LedgerDeviceInfo>>;
179
+ /**
180
+ * Reserves the existing physical Ledger session while a server-owned DMK
181
+ * Genuine Check drives it. The callback can only exchange raw APDUs; the
182
+ * authoritative verdict remains in the server state machine.
183
+ */
184
+ runDeviceAttestationApduBridge<T>(connectId: string, run: (bridge: LedgerAttestationApduBridge) => Promise<T>): Promise<Response<T>>;
185
+ /**
186
+ * Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
187
+ * SAME secure-channel backend as app install
188
+ * (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
189
+ * HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
190
+ * attestation public key, which DMK reads inside that session. The id survives
191
+ * wipe/recovery and cannot be forged from a seed.
192
+ *
193
+ * Requires network access to Ledger's backend and an on-device
194
+ * "Allow secure connection" confirmation the first time.
195
+ */
196
+ verifyDeviceAuthenticity(connectId: string, params?: DeviceAuthenticityParams): Promise<Response<DeviceAuthenticityResult>>;
197
+ private _verifyDeviceAuthenticityExclusive;
165
198
  on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
166
199
  on(event: string, listener: DeviceEventListener): void;
167
200
  off<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
@@ -410,8 +443,14 @@ declare class LedgerConnectorBase implements IConnector {
410
443
  private readonly _eventHandlers;
411
444
  private readonly _providedDmk;
412
445
  private readonly _createTransport;
446
+ private _ledgerGenuineCheckWebSocketUrl;
413
447
  readonly connectionType: ConnectionType;
414
448
  private readonly _cancellers;
449
+ /**
450
+ * A server-owned attestation relay temporarily owns the raw APDU stream.
451
+ * The stored function re-enables DMK's session refresher when ownership ends.
452
+ */
453
+ private readonly _attestationBridgeReleasers;
415
454
  private readonly _sessionStateSubs;
416
455
  /**
417
456
  * Resolves a Ledger signer kit module by package name.
@@ -471,11 +510,15 @@ declare class LedgerConnectorBase implements IConnector {
471
510
  on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
472
511
  off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
473
512
  reset(): void;
513
+ configure(config: ConnectorConfig): Promise<void>;
474
514
  /**
475
515
  * Lazily create or return the DMK instance.
476
516
  * If a DMK was provided via constructor, it is used directly.
477
517
  * Otherwise, one is created via the transport factory.
478
518
  */
519
+ private _capturedGenuineCertPubKeys;
520
+ private _tapTransportForCert;
521
+ private _tapConnectedDevice;
479
522
  protected _getOrCreateDmk(): Promise<DeviceManagementKit>;
480
523
  private _initManagers;
481
524
  private _getDeviceManager;
@@ -497,6 +540,8 @@ declare class LedgerConnectorBase implements IConnector {
497
540
  */
498
541
  private _resetSignersAndSessions;
499
542
  private _resetAll;
543
+ private _releaseAttestationBridge;
544
+ private _releaseAllAttestationBridges;
500
545
  protected _emit<K extends ConnectorEventType>(event: K, data: ConnectorEventMap[K]): void;
501
546
  /**
502
547
  * Return a per-call ctx with the chain's Ledger app name pre-bound to
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _onekeyfe_hwk_adapter_core from '@onekeyfe/hwk-adapter-core';
2
- import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
2
+ import { IHardwareWallet, IConnector, TransportType, UiResponseEvent, SearchDevicesOptions, DeviceInfo, Response, ChainCapability, NullableCallArg, IHardwareCallParams, EvmGetAddressParams, EvmAddress, EvmSignTxLedgerParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignPsbtParams, BtcSignedPsbt, BtcSignMsgParams, BtcSignature, IHardwareCommonCallParams, SolGetAddressParams, SolAddress, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, TronGetAddressParams, TronAddress, TronSignTxParams, TronSignedTx, TronSignMsgParams, TronSignature, DeviceAuthenticityParams, DeviceAuthenticityResult, HardwareEventMap, DeviceEventListener, ChainForFingerprint, DeviceDescriptor, DeviceChangeEvent, ConnectionType, ConnectorDevice, ConnectorSession, ConnectorCallResult, ConnectorEventType, ConnectorEventMap, ConnectorConfig, Failure, HardwareErrorCode } from '@onekeyfe/hwk-adapter-core';
3
3
  import { DeviceActionState as DeviceActionState$1, DiscoveredDevice, ExecuteDeviceActionReturnType, DeviceManagementKit } from '@ledgerhq/device-management-kit';
4
4
  import { Address, Signature, SignerEth as SignerEth$1, TypedData } from '@ledgerhq/device-signer-kit-ethereum';
5
5
  import { ContextModule } from '@ledgerhq/context-module';
@@ -93,6 +93,19 @@ interface LedgerDeviceInfo extends FirmwareVersion {
93
93
 
94
94
  type LedgerCallParams<T> = NullableCallArg<IHardwareCallParams<T>>;
95
95
  type LedgerCommonParams = NullableCallArg<IHardwareCommonCallParams>;
96
+ type LedgerAttestationBridgeDevice = {
97
+ id: string;
98
+ modelId: 'nanoS' | 'nanoSP' | 'nanoX' | 'stax' | 'flex' | 'apexp';
99
+ name?: string;
100
+ connectionType?: 'USB' | 'BLE';
101
+ };
102
+ type LedgerAttestationApduBridge = {
103
+ device: LedgerAttestationBridgeDevice;
104
+ exchangeApdu: (apduHex: string, timeoutMs?: number) => Promise<{
105
+ dataHex: string;
106
+ statusCodeHex: string;
107
+ }>;
108
+ };
96
109
  declare class LedgerAdapter implements IHardwareWallet {
97
110
  readonly vendor: "ledger";
98
111
  private readonly connector;
@@ -102,6 +115,7 @@ declare class LedgerAdapter implements IHardwareWallet {
102
115
  private readonly _uiRegistry;
103
116
  private _btcHighIndexConfirmedThisSession;
104
117
  private readonly _jobQueue;
118
+ private _deviceAuthenticityQueueTail;
105
119
  private _doConnectAbortController;
106
120
  private readonly _defaultAutoInstallApp;
107
121
  constructor(connector: IConnector, options?: {
@@ -162,6 +176,25 @@ declare class LedgerAdapter implements IHardwareWallet {
162
176
  listAvailableApps(connectId: string): Promise<Response<AppMetadata[]>>;
163
177
  getLedgerFirmwareVersion(connectId: string): Promise<Response<FirmwareVersion>>;
164
178
  getLedgerDeviceInfo(connectId: string): Promise<Response<LedgerDeviceInfo>>;
179
+ /**
180
+ * Reserves the existing physical Ledger session while a server-owned DMK
181
+ * Genuine Check drives it. The callback can only exchange raw APDUs; the
182
+ * authoritative verdict remains in the server state machine.
183
+ */
184
+ runDeviceAttestationApduBridge<T>(connectId: string, run: (bridge: LedgerAttestationApduBridge) => Promise<T>): Promise<Response<T>>;
185
+ /**
186
+ * Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
187
+ * SAME secure-channel backend as app install
188
+ * (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
189
+ * HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
190
+ * attestation public key, which DMK reads inside that session. The id survives
191
+ * wipe/recovery and cannot be forged from a seed.
192
+ *
193
+ * Requires network access to Ledger's backend and an on-device
194
+ * "Allow secure connection" confirmation the first time.
195
+ */
196
+ verifyDeviceAuthenticity(connectId: string, params?: DeviceAuthenticityParams): Promise<Response<DeviceAuthenticityResult>>;
197
+ private _verifyDeviceAuthenticityExclusive;
165
198
  on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
166
199
  on(event: string, listener: DeviceEventListener): void;
167
200
  off<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
@@ -410,8 +443,14 @@ declare class LedgerConnectorBase implements IConnector {
410
443
  private readonly _eventHandlers;
411
444
  private readonly _providedDmk;
412
445
  private readonly _createTransport;
446
+ private _ledgerGenuineCheckWebSocketUrl;
413
447
  readonly connectionType: ConnectionType;
414
448
  private readonly _cancellers;
449
+ /**
450
+ * A server-owned attestation relay temporarily owns the raw APDU stream.
451
+ * The stored function re-enables DMK's session refresher when ownership ends.
452
+ */
453
+ private readonly _attestationBridgeReleasers;
415
454
  private readonly _sessionStateSubs;
416
455
  /**
417
456
  * Resolves a Ledger signer kit module by package name.
@@ -471,11 +510,15 @@ declare class LedgerConnectorBase implements IConnector {
471
510
  on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
472
511
  off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
473
512
  reset(): void;
513
+ configure(config: ConnectorConfig): Promise<void>;
474
514
  /**
475
515
  * Lazily create or return the DMK instance.
476
516
  * If a DMK was provided via constructor, it is used directly.
477
517
  * Otherwise, one is created via the transport factory.
478
518
  */
519
+ private _capturedGenuineCertPubKeys;
520
+ private _tapTransportForCert;
521
+ private _tapConnectedDevice;
479
522
  protected _getOrCreateDmk(): Promise<DeviceManagementKit>;
480
523
  private _initManagers;
481
524
  private _getDeviceManager;
@@ -497,6 +540,8 @@ declare class LedgerConnectorBase implements IConnector {
497
540
  */
498
541
  private _resetSignersAndSessions;
499
542
  private _resetAll;
543
+ private _releaseAttestationBridge;
544
+ private _releaseAllAttestationBridges;
500
545
  protected _emit<K extends ConnectorEventType>(event: K, data: ConnectorEventMap[K]): void;
501
546
  /**
502
547
  * Return a per-call ctx with the chain's Ledger app name pre-bound to
package/dist/index.js CHANGED
@@ -772,6 +772,10 @@ var _LedgerAdapter = class _LedgerAdapter {
772
772
  // The Ledger device itself still requires a per-call confirmation — that's
773
773
  // the Ledger app's safety boundary, not ours to bypass.
774
774
  this._btcHighIndexConfirmedThisSession = false;
775
+ // Runtime relay configuration mutates connector-wide DMK state. Serialize
776
+ // the complete configure → genuine check → clear lifecycle so concurrent
777
+ // callers cannot reset or overwrite each other's one-shot relay.
778
+ this._deviceAuthenticityQueueTail = Promise.resolve();
775
779
  // Shared across concurrent callers — only `cancel()` aborts.
776
780
  this._doConnectAbortController = null;
777
781
  this._installProgressLastEmittedValue = -Infinity;
@@ -1322,6 +1326,137 @@ var _LedgerAdapter = class _LedgerAdapter {
1322
1326
  return this.errorToFailure(err);
1323
1327
  }
1324
1328
  }
1329
+ /**
1330
+ * Reserves the existing physical Ledger session while a server-owned DMK
1331
+ * Genuine Check drives it. The callback can only exchange raw APDUs; the
1332
+ * authoritative verdict remains in the server state machine.
1333
+ */
1334
+ async runDeviceAttestationApduBridge(connectId, run) {
1335
+ const queueKey = connectId || "__ledger_default__";
1336
+ try {
1337
+ const payload = await this._jobQueue.enqueue(
1338
+ queueKey,
1339
+ async (signal) => {
1340
+ const device = await this._runConnectorCall(
1341
+ connectId,
1342
+ "startDeviceAttestationApduBridge",
1343
+ {},
1344
+ signal
1345
+ );
1346
+ const sessionId = this._sessions.get(connectId) ?? (this._sessions.size === 1 ? this._sessions.values().next().value : void 0);
1347
+ if (!sessionId) {
1348
+ throw new Error(
1349
+ "Ledger attestation bridge started without an active device session"
1350
+ );
1351
+ }
1352
+ try {
1353
+ return await run({
1354
+ device,
1355
+ exchangeApdu: async (apduHex, timeoutMs) => await this._callConnector(
1356
+ sessionId,
1357
+ "exchangeDeviceAttestationApdu",
1358
+ { apduHex, timeoutMs },
1359
+ signal
1360
+ )
1361
+ });
1362
+ } finally {
1363
+ try {
1364
+ await this._callConnector(
1365
+ sessionId,
1366
+ "stopDeviceAttestationApduBridge",
1367
+ {}
1368
+ );
1369
+ } catch {
1370
+ this.connector.reset();
1371
+ this.resetState();
1372
+ }
1373
+ }
1374
+ },
1375
+ {
1376
+ label: "ledgerDeviceAttestationApduBridge",
1377
+ rejectIfBusy: true,
1378
+ busyError: _LedgerAdapter._createDeviceBusyError(
1379
+ "ledgerDeviceAttestationApduBridge"
1380
+ )
1381
+ }
1382
+ );
1383
+ return (0, import_hwk_adapter_core3.success)(payload);
1384
+ } catch (error) {
1385
+ return this.errorToFailure(error);
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Runs Ledger's official genuine check (DMK GenuineCheckDeviceAction) over the
1390
+ * SAME secure-channel backend as app install
1391
+ * (wss://scriptrunner.api.live.ledger.com/update/genuine). It returns Ledger's
1392
+ * HSM verdict (`verified`) and a stable per-device id = sha3-256 of the device
1393
+ * attestation public key, which DMK reads inside that session. The id survives
1394
+ * wipe/recovery and cannot be forged from a seed.
1395
+ *
1396
+ * Requires network access to Ledger's backend and an on-device
1397
+ * "Allow secure connection" confirmation the first time.
1398
+ */
1399
+ async verifyDeviceAuthenticity(connectId, params = {}) {
1400
+ const waitForPrevious = this._deviceAuthenticityQueueTail;
1401
+ let releaseQueue = () => void 0;
1402
+ this._deviceAuthenticityQueueTail = new Promise((resolve) => {
1403
+ releaseQueue = resolve;
1404
+ });
1405
+ await waitForPrevious;
1406
+ try {
1407
+ return await this._verifyDeviceAuthenticityExclusive(connectId, params);
1408
+ } finally {
1409
+ releaseQueue();
1410
+ }
1411
+ }
1412
+ async _verifyDeviceAuthenticityExclusive(connectId, params) {
1413
+ const relayUrl = params.ledgerGenuineCheckWebSocketUrl;
1414
+ try {
1415
+ if (relayUrl) {
1416
+ if (!this.connector.configure) {
1417
+ return (0, import_hwk_adapter_core3.failure)(
1418
+ import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1419
+ "This Ledger connector does not support genuine-check relay configuration"
1420
+ );
1421
+ }
1422
+ await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
1423
+ this.resetState();
1424
+ }
1425
+ const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
1426
+ if (!result.isGenuine) {
1427
+ return (0, import_hwk_adapter_core3.success)({
1428
+ vendor: "ledger",
1429
+ verified: false,
1430
+ note: "Ledger genuine-check returned NOT genuine."
1431
+ });
1432
+ }
1433
+ if (!result.deviceId) {
1434
+ return (0, import_hwk_adapter_core3.failure)(
1435
+ import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
1436
+ `Genuine check completed (isGenuine=${result.isGenuine}) but no deviceId was captured`
1437
+ );
1438
+ }
1439
+ return (0, import_hwk_adapter_core3.success)({
1440
+ vendor: "ledger",
1441
+ verified: result.isGenuine,
1442
+ deviceId: result.deviceId,
1443
+ attestationPubKey: result.attestationPubKey,
1444
+ note: "Verified by Ledger genuine-check backend; deviceId = sha3_256(attestation pubkey)."
1445
+ });
1446
+ } catch (err) {
1447
+ return this.errorToFailure(err);
1448
+ } finally {
1449
+ if (relayUrl) {
1450
+ try {
1451
+ await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
1452
+ this.resetState();
1453
+ } catch {
1454
+ this.connector.reset();
1455
+ this.resetState();
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1325
1460
  on(event, listener) {
1326
1461
  this.emitter.on(event, listener);
1327
1462
  }
@@ -2294,6 +2429,7 @@ var LedgerAdapter = _LedgerAdapter;
2294
2429
 
2295
2430
  // src/connector/LedgerConnectorBase.ts
2296
2431
  var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
2432
+ var sha3 = __toESM(require("@noble/hashes/sha3"));
2297
2433
 
2298
2434
  // src/device/LedgerDeviceManager.ts
2299
2435
  var LedgerDeviceManager = class {
@@ -4053,6 +4189,25 @@ var DeviceAppsManager = class {
4053
4189
  };
4054
4190
 
4055
4191
  // src/connector/LedgerConnectorBase.ts
4192
+ function extractCertPubKey(data) {
4193
+ try {
4194
+ const buf = Buffer.from(data);
4195
+ let offset = 0;
4196
+ if (offset >= buf.length) return null;
4197
+ const headerLen = buf[offset];
4198
+ offset += 1 + headerLen;
4199
+ if (offset >= buf.length) return null;
4200
+ const keyLen = buf[offset];
4201
+ offset += 1;
4202
+ if (keyLen === 0 || offset + keyLen > buf.length) return null;
4203
+ return buf.subarray(offset, offset + keyLen).toString("hex");
4204
+ } catch {
4205
+ return null;
4206
+ }
4207
+ }
4208
+ function sha3HexOf(pubKeyHex) {
4209
+ return Buffer.from(sha3.sha3_256(Uint8Array.from(Buffer.from(pubKeyHex, "hex")))).toString("hex");
4210
+ }
4056
4211
  var METHOD_PREFIX_TO_APP_NAME = {
4057
4212
  evm: "Ethereum",
4058
4213
  btc: "Bitcoin",
@@ -4109,6 +4264,11 @@ var LedgerConnectorBase = class {
4109
4264
  // unsubscribes the observable and releases DMK's IntentQueue slot.
4110
4265
  // ---------------------------------------------------------------------------
4111
4266
  this._cancellers = /* @__PURE__ */ new Map();
4267
+ /**
4268
+ * A server-owned attestation relay temporarily owns the raw APDU stream.
4269
+ * The stored function re-enables DMK's session refresher when ownership ends.
4270
+ */
4271
+ this._attestationBridgeReleasers = /* @__PURE__ */ new Map();
4112
4272
  // ---------------------------------------------------------------------------
4113
4273
  // Per-session DMK state subscriptions
4114
4274
  //
@@ -4121,6 +4281,17 @@ var LedgerConnectorBase = class {
4121
4281
  // reset / observable completion to avoid leaks.
4122
4282
  // ---------------------------------------------------------------------------
4123
4283
  this._sessionStateSubs = /* @__PURE__ */ new Map();
4284
+ // ---------------------------------------------------------------------------
4285
+ // Private -- DMK / Manager lifecycle
4286
+ // ---------------------------------------------------------------------------
4287
+ /**
4288
+ * Lazily create or return the DMK instance.
4289
+ * If a DMK was provided via constructor, it is used directly.
4290
+ * Otherwise, one is created via the transport factory.
4291
+ */
4292
+ // Attestation public keys (hex) seen in GET CERTIFICATE (E0 52) responses on
4293
+ // the transport. Only populated during a genuine check; cleared per call.
4294
+ this._capturedGenuineCertPubKeys = [];
4124
4295
  this._createTransport = createTransport;
4125
4296
  this.connectionType = options?.connectionType ?? "usb";
4126
4297
  this._providedDmk = options?.dmk;
@@ -4343,6 +4514,7 @@ var LedgerConnectorBase = class {
4343
4514
  }
4344
4515
  }
4345
4516
  async disconnect(sessionId) {
4517
+ this._releaseAttestationBridge(sessionId);
4346
4518
  if (!this._deviceManager) return;
4347
4519
  const deviceId = this._deviceManager.getDeviceId(sessionId);
4348
4520
  this._signerManager?.invalidate(sessionId);
@@ -4404,6 +4576,7 @@ var LedgerConnectorBase = class {
4404
4576
  externalConnectId
4405
4577
  );
4406
4578
  this._unwatchSessionState(sessionId);
4579
+ this._releaseAttestationBridge(sessionId);
4407
4580
  this._signerManager?.invalidate(sessionId);
4408
4581
  this._cancellers.get(sessionId)?.({
4409
4582
  code: import_hwk_adapter_core13.HardwareErrorCode.DeviceDisconnected,
@@ -4584,6 +4757,92 @@ var LedgerConnectorBase = class {
4584
4757
  ctx.clearCanceller(sessionId);
4585
4758
  }
4586
4759
  }
4760
+ case "getDeviceGenuineCheck": {
4761
+ try {
4762
+ this._capturedGenuineCertPubKeys = [];
4763
+ const dmk = await ctx.getOrCreateDmk();
4764
+ const kit = await ctx.importLedgerKit("@ledgerhq/device-management-kit");
4765
+ const action = dmk.executeDeviceAction({
4766
+ sessionId,
4767
+ deviceAction: new kit.GenuineCheckDeviceAction({ input: {} })
4768
+ });
4769
+ let deviceId;
4770
+ const output = await deviceActionToPromise(
4771
+ action,
4772
+ (interaction) => ctx.emit("ui-event", {
4773
+ type: collapseSignerInteraction(interaction),
4774
+ payload: { sessionId }
4775
+ }),
4776
+ // Generous timeout: covers websocket round-trips + the on-device
4777
+ // "Allow secure connection" tap (the idle watchdog does not pause
4778
+ // for it, only for unlock-device).
4779
+ 5 * 6e4,
4780
+ (cancel) => ctx.registerCanceller(sessionId, cancel),
4781
+ (intermediateValue) => {
4782
+ const iv = intermediateValue;
4783
+ if (iv?.deviceId instanceof Uint8Array && !deviceId) {
4784
+ deviceId = Buffer.from(iv.deviceId).toString("hex");
4785
+ }
4786
+ }
4787
+ );
4788
+ const attestationPubKey = deviceId && this._capturedGenuineCertPubKeys.length > 0 ? this._capturedGenuineCertPubKeys.find((pk) => sha3HexOf(pk) === deviceId) : void 0;
4789
+ this._capturedGenuineCertPubKeys = [];
4790
+ return { isGenuine: output.isGenuine, deviceId, attestationPubKey };
4791
+ } catch (err) {
4792
+ ctx.invalidateSession(sessionId);
4793
+ throw ctx.wrapError(err);
4794
+ } finally {
4795
+ ctx.clearCanceller(sessionId);
4796
+ }
4797
+ }
4798
+ case "startDeviceAttestationApduBridge": {
4799
+ if (this._attestationBridgeReleasers.has(sessionId)) {
4800
+ throw new Error("Ledger device attestation APDU bridge is already active");
4801
+ }
4802
+ const dmk = await ctx.getOrCreateDmk();
4803
+ const release = dmk.disableDeviceSessionRefresher({
4804
+ sessionId,
4805
+ blockerId: "onekey-ledger-attestation-relay"
4806
+ });
4807
+ try {
4808
+ const device = dmk.getConnectedDevice({ sessionId });
4809
+ this._attestationBridgeReleasers.set(sessionId, release);
4810
+ return {
4811
+ id: device.id,
4812
+ modelId: device.modelId,
4813
+ name: device.name,
4814
+ connectionType: device.type
4815
+ };
4816
+ } catch (error) {
4817
+ release();
4818
+ throw error;
4819
+ }
4820
+ }
4821
+ case "exchangeDeviceAttestationApdu": {
4822
+ if (!this._attestationBridgeReleasers.has(sessionId)) {
4823
+ throw new Error("Ledger device attestation APDU bridge is not active");
4824
+ }
4825
+ const p = params;
4826
+ if (typeof p.apduHex !== "string" || p.apduHex.length < 8 || p.apduHex.length % 2 !== 0 || !/^[0-9a-f]+$/i.test(p.apduHex) || p.apduHex.length / 2 > 8 * 1024) {
4827
+ throw new Error("Invalid Ledger device attestation APDU");
4828
+ }
4829
+ const requestedTimeout = typeof p.timeoutMs === "number" && Number.isFinite(p.timeoutMs) ? p.timeoutMs : 3e4;
4830
+ const timeoutMs = Math.max(1e3, Math.min(requestedTimeout, 6e4));
4831
+ const dmk = await ctx.getOrCreateDmk();
4832
+ const response = await dmk.sendApdu({
4833
+ sessionId,
4834
+ apdu: Uint8Array.from(Buffer.from(p.apduHex, "hex")),
4835
+ abortTimeout: timeoutMs,
4836
+ triggersDisconnection: false
4837
+ });
4838
+ return {
4839
+ dataHex: Buffer.from(response.data).toString("hex"),
4840
+ statusCodeHex: Buffer.from(response.statusCode).toString("hex")
4841
+ };
4842
+ }
4843
+ case "stopDeviceAttestationApduBridge":
4844
+ this._releaseAttestationBridge(sessionId);
4845
+ return void 0;
4587
4846
  default:
4588
4847
  throw new Error(`LedgerConnector: unknown method "${method}"`);
4589
4848
  }
@@ -4616,16 +4875,80 @@ var LedgerConnectorBase = class {
4616
4875
  // IConnector -- Reset
4617
4876
  // ---------------------------------------------------------------------------
4618
4877
  reset() {
4878
+ this._ledgerGenuineCheckWebSocketUrl = void 0;
4619
4879
  this._resetAll();
4620
4880
  }
4621
- // ---------------------------------------------------------------------------
4622
- // Private -- DMK / Manager lifecycle
4623
- // ---------------------------------------------------------------------------
4624
- /**
4625
- * Lazily create or return the DMK instance.
4626
- * If a DMK was provided via constructor, it is used directly.
4627
- * Otherwise, one is created via the transport factory.
4628
- */
4881
+ async configure(config) {
4882
+ const nextUrl = config.ledgerGenuineCheckWebSocketUrl;
4883
+ if (nextUrl) {
4884
+ let parsed;
4885
+ try {
4886
+ parsed = new URL(nextUrl);
4887
+ } catch {
4888
+ throw new Error("Ledger genuine-check relay URL is invalid");
4889
+ }
4890
+ if (parsed.protocol !== "wss:") {
4891
+ throw new Error("Ledger genuine-check relay URL must use wss");
4892
+ }
4893
+ if (this._providedDmk) {
4894
+ throw new Error("Cannot change Ledger genuine-check relay on a pre-built DMK");
4895
+ }
4896
+ }
4897
+ if (nextUrl === this._ledgerGenuineCheckWebSocketUrl) {
4898
+ return;
4899
+ }
4900
+ this._resetAll();
4901
+ this._ledgerGenuineCheckWebSocketUrl = nextUrl;
4902
+ }
4903
+ // Wrap a TransportFactory so every connected device's `sendApdu` is tapped:
4904
+ // when DMK's genuine-check secure channel reads the device attestation
4905
+ // certificate (E0 52), we capture the raw public key from the response. The
4906
+ // tap is best-effort and can NEVER disturb the real APDU flow (it always
4907
+ // returns the original result, all capture logic is in try/catch).
4908
+ _tapTransportForCert(factory) {
4909
+ return (args) => {
4910
+ const transport = factory(args);
4911
+ const originalConnect = transport?.connect;
4912
+ if (typeof originalConnect === "function") {
4913
+ transport.connect = async (params) => {
4914
+ const either = await originalConnect.call(transport, params);
4915
+ try {
4916
+ if (either && typeof either.map === "function") {
4917
+ return either.map((device) => this._tapConnectedDevice(device));
4918
+ }
4919
+ } catch {
4920
+ }
4921
+ return either;
4922
+ };
4923
+ }
4924
+ return transport;
4925
+ };
4926
+ }
4927
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4928
+ _tapConnectedDevice(device) {
4929
+ const originalSendApdu = device?.sendApdu;
4930
+ if (typeof originalSendApdu !== "function") return device;
4931
+ const wrapped = async (apdu, ...rest) => {
4932
+ const result = await originalSendApdu.call(device, apdu, ...rest);
4933
+ try {
4934
+ if (apdu?.[0] === 224 && apdu?.[1] === 82 && result && typeof result.isRight === "function" && result.isRight()) {
4935
+ const resp = result.extract();
4936
+ if (resp && Buffer.from(resp.statusCode).toString("hex") === "9000") {
4937
+ const pubKey = extractCertPubKey(resp.data);
4938
+ if (pubKey) this._capturedGenuineCertPubKeys.push(pubKey);
4939
+ }
4940
+ }
4941
+ } catch {
4942
+ }
4943
+ return result;
4944
+ };
4945
+ return new Proxy(device, {
4946
+ get(target, prop, receiver) {
4947
+ if (prop === "sendApdu") return wrapped;
4948
+ return Reflect.get(target, prop, receiver);
4949
+ }
4950
+ });
4951
+ }
4629
4952
  async _getOrCreateDmk() {
4630
4953
  debugLog(
4631
4954
  "[DMK] _getOrCreateDmk called, _dmk exists:",
@@ -4641,9 +4964,13 @@ var LedgerConnectorBase = class {
4641
4964
  const { DeviceManagementKitBuilder } = await this._importLedgerKit(
4642
4965
  "@ledgerhq/device-management-kit"
4643
4966
  );
4644
- const transportFactory = await this._createTransport();
4967
+ const transportFactory = this._tapTransportForCert(await this._createTransport());
4645
4968
  debugLog("[DMK] _getOrCreateDmk: transportFactory type:", typeof transportFactory);
4646
- const dmk = new DeviceManagementKitBuilder().addTransport(transportFactory).build();
4969
+ const builder = new DeviceManagementKitBuilder().addTransport(transportFactory);
4970
+ if (this._ledgerGenuineCheckWebSocketUrl) {
4971
+ builder.addConfig({ webSocketUrl: this._ledgerGenuineCheckWebSocketUrl });
4972
+ }
4973
+ const dmk = builder.build();
4647
4974
  this._dmk = dmk;
4648
4975
  debugLog("[DMK] _getOrCreateDmk: DMK created");
4649
4976
  return dmk;
@@ -4711,6 +5038,7 @@ var LedgerConnectorBase = class {
4711
5038
  */
4712
5039
  _resetSignersAndSessions() {
4713
5040
  debugLog("[DMK] _resetSignersAndSessions called");
5041
+ this._releaseAllAttestationBridges();
4714
5042
  this._signerManager?.clearAll();
4715
5043
  this._signerManager = null;
4716
5044
  this._deviceAppsManager?.clearAll();
@@ -4720,6 +5048,7 @@ var LedgerConnectorBase = class {
4720
5048
  }
4721
5049
  _resetAll() {
4722
5050
  debugLog("[DMK] _resetAll called");
5051
+ this._releaseAllAttestationBridges();
4723
5052
  for (const cancel of this._cancellers.values()) {
4724
5053
  try {
4725
5054
  cancel();
@@ -4742,6 +5071,20 @@ var LedgerConnectorBase = class {
4742
5071
  this._deviceAppsManager = null;
4743
5072
  this._dmk = null;
4744
5073
  }
5074
+ _releaseAttestationBridge(sessionId) {
5075
+ const release = this._attestationBridgeReleasers.get(sessionId);
5076
+ if (!release) return;
5077
+ this._attestationBridgeReleasers.delete(sessionId);
5078
+ try {
5079
+ release();
5080
+ } catch {
5081
+ }
5082
+ }
5083
+ _releaseAllAttestationBridges() {
5084
+ for (const sessionId of [...this._attestationBridgeReleasers.keys()]) {
5085
+ this._releaseAttestationBridge(sessionId);
5086
+ }
5087
+ }
4745
5088
  // ---------------------------------------------------------------------------
4746
5089
  // Private -- Events
4747
5090
  // ---------------------------------------------------------------------------