@metamask/eth-ledger-bridge-keyring 12.0.3 → 12.2.0

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 (96) hide show
  1. package/CHANGELOG.md +35 -8
  2. package/dist/dmk/__testhelpers__/mock-error.cjs +24 -0
  3. package/dist/dmk/__testhelpers__/mock-error.cjs.map +1 -0
  4. package/dist/dmk/__testhelpers__/mock-error.d.cts +13 -0
  5. package/dist/dmk/__testhelpers__/mock-error.d.cts.map +1 -0
  6. package/dist/dmk/__testhelpers__/mock-error.d.mts +13 -0
  7. package/dist/dmk/__testhelpers__/mock-error.d.mts.map +1 -0
  8. package/dist/dmk/__testhelpers__/mock-error.mjs +20 -0
  9. package/dist/dmk/__testhelpers__/mock-error.mjs.map +1 -0
  10. package/dist/dmk/dmk-error-translator.cjs +35 -0
  11. package/dist/dmk/dmk-error-translator.cjs.map +1 -0
  12. package/dist/dmk/dmk-error-translator.d.cts +12 -0
  13. package/dist/dmk/dmk-error-translator.d.cts.map +1 -0
  14. package/dist/dmk/dmk-error-translator.d.mts +12 -0
  15. package/dist/dmk/dmk-error-translator.d.mts.map +1 -0
  16. package/dist/dmk/dmk-error-translator.mjs +30 -0
  17. package/dist/dmk/dmk-error-translator.mjs.map +1 -0
  18. package/dist/dmk/eth-get-app-configuration-command.cjs +155 -0
  19. package/dist/dmk/eth-get-app-configuration-command.cjs.map +1 -0
  20. package/dist/dmk/eth-get-app-configuration-command.d.cts +71 -0
  21. package/dist/dmk/eth-get-app-configuration-command.d.cts.map +1 -0
  22. package/dist/dmk/eth-get-app-configuration-command.d.mts +71 -0
  23. package/dist/dmk/eth-get-app-configuration-command.d.mts.map +1 -0
  24. package/dist/dmk/eth-get-app-configuration-command.mjs +151 -0
  25. package/dist/dmk/eth-get-app-configuration-command.mjs.map +1 -0
  26. package/dist/dmk/internal-utils.cjs +81 -0
  27. package/dist/dmk/internal-utils.cjs.map +1 -0
  28. package/dist/dmk/internal-utils.d.cts +44 -0
  29. package/dist/dmk/internal-utils.d.cts.map +1 -0
  30. package/dist/dmk/internal-utils.d.mts +44 -0
  31. package/dist/dmk/internal-utils.d.mts.map +1 -0
  32. package/dist/dmk/internal-utils.mjs +74 -0
  33. package/dist/dmk/internal-utils.mjs.map +1 -0
  34. package/dist/dmk/ledger-dmk-bridge.cjs +379 -0
  35. package/dist/dmk/ledger-dmk-bridge.cjs.map +1 -0
  36. package/dist/dmk/ledger-dmk-bridge.d.cts +157 -0
  37. package/dist/dmk/ledger-dmk-bridge.d.cts.map +1 -0
  38. package/dist/dmk/ledger-dmk-bridge.d.mts +157 -0
  39. package/dist/dmk/ledger-dmk-bridge.d.mts.map +1 -0
  40. package/dist/dmk/ledger-dmk-bridge.mjs +375 -0
  41. package/dist/dmk/ledger-dmk-bridge.mjs.map +1 -0
  42. package/dist/dmk/ledger-dmk-transport-middleware.cjs +170 -0
  43. package/dist/dmk/ledger-dmk-transport-middleware.cjs.map +1 -0
  44. package/dist/dmk/ledger-dmk-transport-middleware.d.cts +99 -0
  45. package/dist/dmk/ledger-dmk-transport-middleware.d.cts.map +1 -0
  46. package/dist/dmk/ledger-dmk-transport-middleware.d.mts +99 -0
  47. package/dist/dmk/ledger-dmk-transport-middleware.d.mts.map +1 -0
  48. package/dist/dmk/ledger-dmk-transport-middleware.mjs +166 -0
  49. package/dist/dmk/ledger-dmk-transport-middleware.mjs.map +1 -0
  50. package/dist/index.cjs +3 -0
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +3 -0
  53. package/dist/index.d.cts.map +1 -1
  54. package/dist/index.d.mts +3 -0
  55. package/dist/index.d.mts.map +1 -1
  56. package/dist/index.mjs +3 -0
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/ledger-bridge.cjs.map +1 -1
  59. package/dist/ledger-bridge.d.cts +12 -0
  60. package/dist/ledger-bridge.d.cts.map +1 -1
  61. package/dist/ledger-bridge.d.mts +12 -0
  62. package/dist/ledger-bridge.d.mts.map +1 -1
  63. package/dist/ledger-bridge.mjs.map +1 -1
  64. package/dist/ledger-iframe-bridge.cjs +3 -0
  65. package/dist/ledger-iframe-bridge.cjs.map +1 -1
  66. package/dist/ledger-iframe-bridge.d.cts +2 -1
  67. package/dist/ledger-iframe-bridge.d.cts.map +1 -1
  68. package/dist/ledger-iframe-bridge.d.mts +2 -1
  69. package/dist/ledger-iframe-bridge.d.mts.map +1 -1
  70. package/dist/ledger-iframe-bridge.mjs +3 -0
  71. package/dist/ledger-iframe-bridge.mjs.map +1 -1
  72. package/dist/ledger-keyring.cjs +59 -3
  73. package/dist/ledger-keyring.cjs.map +1 -1
  74. package/dist/ledger-keyring.d.cts +1 -0
  75. package/dist/ledger-keyring.d.cts.map +1 -1
  76. package/dist/ledger-keyring.d.mts +1 -0
  77. package/dist/ledger-keyring.d.mts.map +1 -1
  78. package/dist/ledger-keyring.mjs +61 -5
  79. package/dist/ledger-keyring.mjs.map +1 -1
  80. package/dist/ledger-mobile-bridge.cjs +3 -0
  81. package/dist/ledger-mobile-bridge.cjs.map +1 -1
  82. package/dist/ledger-mobile-bridge.d.cts +2 -1
  83. package/dist/ledger-mobile-bridge.d.cts.map +1 -1
  84. package/dist/ledger-mobile-bridge.d.mts +2 -1
  85. package/dist/ledger-mobile-bridge.d.mts.map +1 -1
  86. package/dist/ledger-mobile-bridge.mjs +3 -0
  87. package/dist/ledger-mobile-bridge.mjs.map +1 -1
  88. package/dist/v2/ledger-keyring.cjs +92 -1
  89. package/dist/v2/ledger-keyring.cjs.map +1 -1
  90. package/dist/v2/ledger-keyring.d.cts +65 -1
  91. package/dist/v2/ledger-keyring.d.cts.map +1 -1
  92. package/dist/v2/ledger-keyring.d.mts +65 -1
  93. package/dist/v2/ledger-keyring.d.mts.map +1 -1
  94. package/dist/v2/ledger-keyring.mjs +93 -2
  95. package/dist/v2/ledger-keyring.mjs.map +1 -1
  96. package/package.json +9 -5
@@ -0,0 +1,151 @@
1
+ import { ApduBuilder, ApduParser, CommandResultFactory, InvalidStatusWordError } from "@ledgerhq/device-management-kit";
2
+ const CLA = 0xe0;
3
+ const INS = 0x06;
4
+ // Feature flags defined by the Ethereum app `getAppConfiguration` APDU spec.
5
+ // See: https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration
6
+ // 0x01 - arbitrary data signature enabled by user (blind signing)
7
+ // 0x02 - ERC 20 Token information needs to be provided externally
8
+ // 0x10 - Transaction Check enabled (web3 checks)
9
+ // 0x20 - Transaction Check Opt-In done (web3 checks opt-in)
10
+ const BLIND_SIGNING_FLAG = 0x01;
11
+ const WEB3_CHECKS_ENABLED_FLAG = 0x10;
12
+ const WEB3_CHECKS_OPT_IN_FLAG = 0x20;
13
+ const SUCCESS_STATUS_WORD_HI = 0x90;
14
+ const SUCCESS_STATUS_WORD_LO = 0x00;
15
+ /**
16
+ * Discriminator (`_tag`) used by `EthGetAppConfigurationCommand` when raising
17
+ * a {@link DeviceExchangeError}. Exported so tests and translators can
18
+ * reference the canonical string instead of repeating a magic literal.
19
+ */
20
+ export const ETH_APP_COMMAND_ERROR_TAG = 'EthAppCommandError';
21
+ /**
22
+ * Reads the Ethereum app configuration via the Ethereum application's
23
+ * `getAppConfiguration` APDU (CLA `0xE0`, INS `0x06`).
24
+ *
25
+ * ## Wire format
26
+ *
27
+ * Defined by the Ethereum application APDU specification
28
+ * ([`doc/ethapp.adoc`](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration),
29
+ * § *GET APP CONFIGURATION*).
30
+ *
31
+ * The 4-byte response payload is laid out as:
32
+ *
33
+ * ```
34
+ * byte 0 flags bitmask
35
+ * bytes 1..3 app version (major, minor, patch)
36
+ * ```
37
+ *
38
+ * The `flags` byte is defined by the spec as:
39
+ *
40
+ * ```
41
+ * 0x01 arbitrary data signature enabled by user (blind signing)
42
+ * 0x02 ERC 20 Token information needs to be provided externally
43
+ * 0x10 Transaction Check enabled (web3 checks)
44
+ * 0x20 Transaction Check Opt-In done (web3 checks opt-in)
45
+ * ```
46
+ *
47
+ * This command mirrors the implementation in
48
+ * [`@ledgerhq/device-signer-kit-ethereum`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/signer/signer-eth/src/internal/app-binder/command/GetAppConfigurationCommand.ts)
49
+ * (`internal/app-binder/command/GetAppConfigurationCommand`), which is not
50
+ * re-exported from that package's public entry point. Until the signer kit
51
+ * exposes `GetAppConfiguration` publicly, this module is the only way to
52
+ * invoke the Ethereum `getAppConfiguration` APDU through the DMK
53
+ * `sendCommand` API.
54
+ *
55
+ * ## Related APDU docs
56
+ *
57
+ * The other Ethereum APDUs the DMK signer kit sends on our behalf are also
58
+ * defined in the same `ethapp.adoc` document:
59
+ *
60
+ * - `GET ETH PUBLIC ADDRESS` — INS `0x02`
61
+ * - `SIGN ETH TRANSACTION` — INS `0x04`
62
+ * - `SIGN ETH PERSONAL MESSAGE` — INS `0x08`
63
+ * - `SIGN ETH EIP 712` — INS `0x0C`
64
+ * - `SIGN EIP 7702 AUTHORIZATION` — INS `0x34`
65
+ *
66
+ * ## Distinguishing DMK commands
67
+ *
68
+ * Note: this command is distinct from the DMK OS-level
69
+ * [`GetAppAndVersionCommand`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/device-management-kit/src/api/command/os/GetAppAndVersionCommand.ts)
70
+ * (`@ledgerhq/device-management-kit`), which returns only the app's name and
71
+ * version and does not expose Ethereum-specific feature flags.
72
+ */
73
+ export class EthGetAppConfigurationCommand {
74
+ constructor() {
75
+ this.name = 'getAppConfiguration';
76
+ }
77
+ getApdu() {
78
+ return new ApduBuilder({ cla: CLA, ins: INS, p1: 0, p2: 0 }).build();
79
+ }
80
+ parseResponse(response) {
81
+ if (!isSuccessStatusCode(response.statusCode)) {
82
+ return CommandResultFactory({
83
+ error: createEthAppCommandError(response.statusCode),
84
+ });
85
+ }
86
+ const parser = new ApduParser(response);
87
+ const flags = parser.extract8BitUInt();
88
+ if (flags === undefined) {
89
+ return CommandResultFactory({
90
+ error: new InvalidStatusWordError('Cannot extract config flags from response body'),
91
+ });
92
+ }
93
+ const major = parser.extract8BitUInt();
94
+ const minor = parser.extract8BitUInt();
95
+ const patch = parser.extract8BitUInt();
96
+ if (major === undefined || minor === undefined || patch === undefined) {
97
+ return CommandResultFactory({
98
+ error: new InvalidStatusWordError('Cannot extract version bytes from response body'),
99
+ });
100
+ }
101
+ return CommandResultFactory({
102
+ data: {
103
+ blindSigningEnabled: isFlagSet(flags, BLIND_SIGNING_FLAG),
104
+ web3ChecksEnabled: isFlagSet(flags, WEB3_CHECKS_ENABLED_FLAG),
105
+ web3ChecksOptIn: isFlagSet(flags, WEB3_CHECKS_OPT_IN_FLAG),
106
+ version: `${major}.${minor}.${patch}`,
107
+ },
108
+ });
109
+ }
110
+ }
111
+ /**
112
+ * Test whether a single-bit flag is set in a bitmask.
113
+ *
114
+ * Per the Ethereum `getAppConfiguration` APDU spec
115
+ * ([ethapp.adoc](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration)),
116
+ * each supported feature occupies one bit of the `flags` byte. The spec
117
+ * defines *which* bits carry meaning (`0x01`, `0x10`, `0x20`); it does not
118
+ * prescribe an implementation technique for testing them. Bitwise AND is
119
+ * the idiomatic way to test a single bit in any language, and is the
120
+ * same formulation used by Ledger's own
121
+ * [`GetAppConfiguration`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/signer/signer-eth/src/internal/app-binder/command/GetAppConfigurationCommand.ts)
122
+ * implementation upstream.
123
+ *
124
+ * @param flags - The bitmask (the first response byte).
125
+ * @param flag - The single-bit value to test (e.g. `0x01`, `0x10`, `0x20`).
126
+ * @returns `true` if the bit is set, `false` otherwise.
127
+ */
128
+ function isFlagSet(flags, flag) {
129
+ // Bitwise AND is the idiomatic way to test a single-bit flag in a
130
+ // bitmask and matches the upstream Ledger implementation; the
131
+ // `ethapp.adoc` spec defines the flag *values* (0x01, 0x10, 0x20) but
132
+ // leaves the testing technique to the implementer.
133
+ // eslint-disable-next-line no-bitwise
134
+ return (flags & flag) !== 0;
135
+ }
136
+ function isSuccessStatusCode(statusCode) {
137
+ return (statusCode[0] === SUCCESS_STATUS_WORD_HI &&
138
+ statusCode[1] === SUCCESS_STATUS_WORD_LO);
139
+ }
140
+ function createEthAppCommandError(statusCode) {
141
+ const errorCode = Array.from(statusCode)
142
+ .map((byte) => byte.toString(16).padStart(2, '0'))
143
+ .join('');
144
+ return {
145
+ _tag: ETH_APP_COMMAND_ERROR_TAG,
146
+ errorCode,
147
+ message: `Ledger Ethereum app command failed with status 0x${errorCode}.`,
148
+ originalError: undefined,
149
+ };
150
+ }
151
+ //# sourceMappingURL=eth-get-app-configuration-command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eth-get-app-configuration-command.mjs","sourceRoot":"","sources":["../../src/dmk/eth-get-app-configuration-command.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,sBAAsB,EACvB,wCAAwC;AASzC,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,6EAA6E;AAC7E,kGAAkG;AAClG,oEAAoE;AACpE,oEAAoE;AACpE,mDAAmD;AACnD,8DAA8D;AAC9D,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AACpC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,oBAAoB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,OAAO,6BAA6B;IAA1C;QAKW,SAAI,GAAG,qBAAqB,CAAC;IA8CxC,CAAC;IA5CC,OAAO;QACL,OAAO,IAAI,WAAW,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACvE,CAAC;IAED,aAAa,CACX,QAAsB;QAEtB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9C,OAAO,oBAAoB,CAAC;gBAC1B,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,UAAU,CAAC;aACrD,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;QAExC,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,oBAAoB,CAAC;gBAC1B,KAAK,EAAE,IAAI,sBAAsB,CAC/B,gDAAgD,CACjD;aACF,CAAC,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtE,OAAO,oBAAoB,CAAC;gBAC1B,KAAK,EAAE,IAAI,sBAAsB,CAC/B,iDAAiD,CAClD;aACF,CAAC,CAAC;QACL,CAAC;QAED,OAAO,oBAAoB,CAAC;YAC1B,IAAI,EAAE;gBACJ,mBAAmB,EAAE,SAAS,CAAC,KAAK,EAAE,kBAAkB,CAAC;gBACzD,iBAAiB,EAAE,SAAS,CAAC,KAAK,EAAE,wBAAwB,CAAC;gBAC7D,eAAe,EAAE,SAAS,CAAC,KAAK,EAAE,uBAAuB,CAAC;gBAC1D,OAAO,EAAE,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;aACtC;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,SAAS,CAAC,KAAa,EAAE,IAAY;IAC5C,kEAAkE;IAClE,8DAA8D;IAC9D,sEAAsE;IACtE,mDAAmD;IACnD,sCAAsC;IACtC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,mBAAmB,CAAC,UAAsB;IACjD,OAAO,CACL,UAAU,CAAC,CAAC,CAAC,KAAK,sBAAsB;QACxC,UAAU,CAAC,CAAC,CAAC,KAAK,sBAAsB,CACzC,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAC/B,UAAsB;IAEtB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;SACrC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACjD,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO;QACL,IAAI,EAAE,yBAAyB;QAC/B,SAAS;QACT,OAAO,EAAE,oDAAoD,SAAS,GAAG;QACzE,aAAa,EAAE,SAAS;KACzB,CAAC;AACJ,CAAC","sourcesContent":["import type {\n Apdu,\n ApduResponse,\n Command,\n CommandResult,\n DeviceExchangeError,\n} from '@ledgerhq/device-management-kit';\nimport {\n ApduBuilder,\n ApduParser,\n CommandResultFactory,\n InvalidStatusWordError,\n} from '@ledgerhq/device-management-kit';\n\nexport type EthGetAppConfigurationResponse = {\n readonly blindSigningEnabled: boolean;\n readonly web3ChecksEnabled: boolean;\n readonly web3ChecksOptIn: boolean;\n readonly version: string;\n};\n\nconst CLA = 0xe0;\nconst INS = 0x06;\n// Feature flags defined by the Ethereum app `getAppConfiguration` APDU spec.\n// See: https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration\n// 0x01 - arbitrary data signature enabled by user (blind signing)\n// 0x02 - ERC 20 Token information needs to be provided externally\n// 0x10 - Transaction Check enabled (web3 checks)\n// 0x20 - Transaction Check Opt-In done (web3 checks opt-in)\nconst BLIND_SIGNING_FLAG = 0x01;\nconst WEB3_CHECKS_ENABLED_FLAG = 0x10;\nconst WEB3_CHECKS_OPT_IN_FLAG = 0x20;\nconst SUCCESS_STATUS_WORD_HI = 0x90;\nconst SUCCESS_STATUS_WORD_LO = 0x00;\n\n/**\n * Discriminator (`_tag`) used by `EthGetAppConfigurationCommand` when raising\n * a {@link DeviceExchangeError}. Exported so tests and translators can\n * reference the canonical string instead of repeating a magic literal.\n */\nexport const ETH_APP_COMMAND_ERROR_TAG = 'EthAppCommandError';\n\n/**\n * Reads the Ethereum app configuration via the Ethereum application's\n * `getAppConfiguration` APDU (CLA `0xE0`, INS `0x06`).\n *\n * ## Wire format\n *\n * Defined by the Ethereum application APDU specification\n * ([`doc/ethapp.adoc`](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration),\n * § *GET APP CONFIGURATION*).\n *\n * The 4-byte response payload is laid out as:\n *\n * ```\n * byte 0 flags bitmask\n * bytes 1..3 app version (major, minor, patch)\n * ```\n *\n * The `flags` byte is defined by the spec as:\n *\n * ```\n * 0x01 arbitrary data signature enabled by user (blind signing)\n * 0x02 ERC 20 Token information needs to be provided externally\n * 0x10 Transaction Check enabled (web3 checks)\n * 0x20 Transaction Check Opt-In done (web3 checks opt-in)\n * ```\n *\n * This command mirrors the implementation in\n * [`@ledgerhq/device-signer-kit-ethereum`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/signer/signer-eth/src/internal/app-binder/command/GetAppConfigurationCommand.ts)\n * (`internal/app-binder/command/GetAppConfigurationCommand`), which is not\n * re-exported from that package's public entry point. Until the signer kit\n * exposes `GetAppConfiguration` publicly, this module is the only way to\n * invoke the Ethereum `getAppConfiguration` APDU through the DMK\n * `sendCommand` API.\n *\n * ## Related APDU docs\n *\n * The other Ethereum APDUs the DMK signer kit sends on our behalf are also\n * defined in the same `ethapp.adoc` document:\n *\n * - `GET ETH PUBLIC ADDRESS` — INS `0x02`\n * - `SIGN ETH TRANSACTION` — INS `0x04`\n * - `SIGN ETH PERSONAL MESSAGE` — INS `0x08`\n * - `SIGN ETH EIP 712` — INS `0x0C`\n * - `SIGN EIP 7702 AUTHORIZATION` — INS `0x34`\n *\n * ## Distinguishing DMK commands\n *\n * Note: this command is distinct from the DMK OS-level\n * [`GetAppAndVersionCommand`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/device-management-kit/src/api/command/os/GetAppAndVersionCommand.ts)\n * (`@ledgerhq/device-management-kit`), which returns only the app's name and\n * version and does not expose Ethereum-specific feature flags.\n */\nexport class EthGetAppConfigurationCommand implements Command<\n EthGetAppConfigurationResponse,\n void,\n string\n> {\n readonly name = 'getAppConfiguration';\n\n getApdu(): Apdu {\n return new ApduBuilder({ cla: CLA, ins: INS, p1: 0, p2: 0 }).build();\n }\n\n parseResponse(\n response: ApduResponse,\n ): CommandResult<EthGetAppConfigurationResponse, string> {\n if (!isSuccessStatusCode(response.statusCode)) {\n return CommandResultFactory({\n error: createEthAppCommandError(response.statusCode),\n });\n }\n\n const parser = new ApduParser(response);\n\n const flags = parser.extract8BitUInt();\n if (flags === undefined) {\n return CommandResultFactory({\n error: new InvalidStatusWordError(\n 'Cannot extract config flags from response body',\n ),\n });\n }\n\n const major = parser.extract8BitUInt();\n const minor = parser.extract8BitUInt();\n const patch = parser.extract8BitUInt();\n if (major === undefined || minor === undefined || patch === undefined) {\n return CommandResultFactory({\n error: new InvalidStatusWordError(\n 'Cannot extract version bytes from response body',\n ),\n });\n }\n\n return CommandResultFactory({\n data: {\n blindSigningEnabled: isFlagSet(flags, BLIND_SIGNING_FLAG),\n web3ChecksEnabled: isFlagSet(flags, WEB3_CHECKS_ENABLED_FLAG),\n web3ChecksOptIn: isFlagSet(flags, WEB3_CHECKS_OPT_IN_FLAG),\n version: `${major}.${minor}.${patch}`,\n },\n });\n }\n}\n\n/**\n * Test whether a single-bit flag is set in a bitmask.\n *\n * Per the Ethereum `getAppConfiguration` APDU spec\n * ([ethapp.adoc](https://github.com/LedgerHQ/app-ethereum/blob/master/doc/ethapp.adoc#get-app-configuration)),\n * each supported feature occupies one bit of the `flags` byte. The spec\n * defines *which* bits carry meaning (`0x01`, `0x10`, `0x20`); it does not\n * prescribe an implementation technique for testing them. Bitwise AND is\n * the idiomatic way to test a single bit in any language, and is the\n * same formulation used by Ledger's own\n * [`GetAppConfiguration`](https://github.com/LedgerHQ/device-sdk-ts/blob/main/packages/signer/signer-eth/src/internal/app-binder/command/GetAppConfigurationCommand.ts)\n * implementation upstream.\n *\n * @param flags - The bitmask (the first response byte).\n * @param flag - The single-bit value to test (e.g. `0x01`, `0x10`, `0x20`).\n * @returns `true` if the bit is set, `false` otherwise.\n */\nfunction isFlagSet(flags: number, flag: number): boolean {\n // Bitwise AND is the idiomatic way to test a single-bit flag in a\n // bitmask and matches the upstream Ledger implementation; the\n // `ethapp.adoc` spec defines the flag *values* (0x01, 0x10, 0x20) but\n // leaves the testing technique to the implementer.\n // eslint-disable-next-line no-bitwise\n return (flags & flag) !== 0;\n}\n\nfunction isSuccessStatusCode(statusCode: Uint8Array): boolean {\n return (\n statusCode[0] === SUCCESS_STATUS_WORD_HI &&\n statusCode[1] === SUCCESS_STATUS_WORD_LO\n );\n}\n\nfunction createEthAppCommandError(\n statusCode: Uint8Array,\n): DeviceExchangeError<string> {\n const errorCode = Array.from(statusCode)\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n\n return {\n _tag: ETH_APP_COMMAND_ERROR_TAG,\n errorCode,\n message: `Ledger Ethereum app command failed with status 0x${errorCode}.`,\n originalError: undefined,\n };\n}\n"]}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Pure helpers shared by the DMK bridge and its tests. Extracted from
4
+ * `LedgerDmkBridge` so they can be unit-tested in isolation and reused
5
+ * without an instance.
6
+ *
7
+ * @module dmk/internal-utils
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.hexToBytes = exports.toHexString = exports.stripPathPrefix = exports.stripHexPrefix = void 0;
11
+ /**
12
+ * Strip a leading `0x` from a hex string, if present.
13
+ *
14
+ * @param value - The hex string (with or without `0x` prefix).
15
+ * @returns The hex string with no `0x` prefix.
16
+ */
17
+ function stripHexPrefix(value) {
18
+ return value.startsWith('0x') ? value.slice(2) : value;
19
+ }
20
+ exports.stripHexPrefix = stripHexPrefix;
21
+ /**
22
+ * Strip the leading `m/` from a BIP-32 derivation path.
23
+ *
24
+ * DMK / signer-kit accepts paths without the leading `m/`.
25
+ *
26
+ * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).
27
+ * @returns The path with no leading `m/`.
28
+ */
29
+ function stripPathPrefix(path) {
30
+ return path.replace(/^m\//u, '');
31
+ }
32
+ exports.stripPathPrefix = stripPathPrefix;
33
+ /**
34
+ * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex
35
+ * string without `0x` prefix.
36
+ *
37
+ * @param value - The raw value (`bigint`, `number`, or hex string).
38
+ * @returns The hex string with no `0x` prefix.
39
+ */
40
+ function toHexString(value) {
41
+ if (typeof value === 'string') {
42
+ return stripHexPrefix(value);
43
+ }
44
+ return value.toString(16);
45
+ }
46
+ exports.toHexString = toHexString;
47
+ const HEX_PAIR_REGEX = /^[0-9a-fA-F]{2}$/u;
48
+ /**
49
+ * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.
50
+ *
51
+ * Throws on inputs that would silently produce wrong bytes:
52
+ * - odd-length strings (e.g. `'abc'`)
53
+ * - non-hex characters (e.g. `'zz'`)
54
+ *
55
+ * @param value - The hex string (with or without `0x` prefix).
56
+ * @returns The decoded bytes.
57
+ * @throws {Error} If the input has odd length or contains non-hex characters.
58
+ */
59
+ function hexToBytes(value) {
60
+ const normalized = stripHexPrefix(value);
61
+ if (normalized.length === 0) {
62
+ return Uint8Array.from([]);
63
+ }
64
+ if (normalized.length % 2 !== 0) {
65
+ throw new Error('Hex string must have an even number of characters (got ' +
66
+ `${normalized.length}): "${normalized}"`);
67
+ }
68
+ const pairs = normalized.match(/.{1,2}/gu);
69
+ if (!pairs) {
70
+ // Should be unreachable given the length check above; defensive.
71
+ return Uint8Array.from([]);
72
+ }
73
+ for (const pair of pairs) {
74
+ if (!HEX_PAIR_REGEX.test(pair)) {
75
+ throw new Error(`Hex string contains non-hex characters: "${pair}" in "${normalized}"`);
76
+ }
77
+ }
78
+ return Uint8Array.from(pairs.map((pair) => parseInt(pair, 16)));
79
+ }
80
+ exports.hexToBytes = hexToBytes;
81
+ //# sourceMappingURL=internal-utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-utils.cjs","sourceRoot":"","sources":["../../src/dmk/internal-utils.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC;AAFD,wCAEC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAFD,0CAEC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,KAA+B;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC5B,CAAC;AALD,kCAKC;AAED,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,yDAAyD;YACvD,GAAG,UAAU,CAAC,MAAM,OAAO,UAAU,GAAG,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,iEAAiE;QACjE,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACb,4CAA4C,IAAI,SAAS,UAAU,GAAG,CACvE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AA3BD,gCA2BC","sourcesContent":["/**\n * Pure helpers shared by the DMK bridge and its tests. Extracted from\n * `LedgerDmkBridge` so they can be unit-tested in isolation and reused\n * without an instance.\n *\n * @module dmk/internal-utils\n */\n\n/**\n * Strip a leading `0x` from a hex string, if present.\n *\n * @param value - The hex string (with or without `0x` prefix).\n * @returns The hex string with no `0x` prefix.\n */\nexport function stripHexPrefix(value: string): string {\n return value.startsWith('0x') ? value.slice(2) : value;\n}\n\n/**\n * Strip the leading `m/` from a BIP-32 derivation path.\n *\n * DMK / signer-kit accepts paths without the leading `m/`.\n *\n * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).\n * @returns The path with no leading `m/`.\n */\nexport function stripPathPrefix(path: string): string {\n return path.replace(/^m\\//u, '');\n}\n\n/**\n * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex\n * string without `0x` prefix.\n *\n * @param value - The raw value (`bigint`, `number`, or hex string).\n * @returns The hex string with no `0x` prefix.\n */\nexport function toHexString(value: bigint | number | string): string {\n if (typeof value === 'string') {\n return stripHexPrefix(value);\n }\n return value.toString(16);\n}\n\nconst HEX_PAIR_REGEX = /^[0-9a-fA-F]{2}$/u;\n\n/**\n * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.\n *\n * Throws on inputs that would silently produce wrong bytes:\n * - odd-length strings (e.g. `'abc'`)\n * - non-hex characters (e.g. `'zz'`)\n *\n * @param value - The hex string (with or without `0x` prefix).\n * @returns The decoded bytes.\n * @throws {Error} If the input has odd length or contains non-hex characters.\n */\nexport function hexToBytes(value: string): Uint8Array {\n const normalized = stripHexPrefix(value);\n if (normalized.length === 0) {\n return Uint8Array.from([]);\n }\n if (normalized.length % 2 !== 0) {\n throw new Error(\n 'Hex string must have an even number of characters (got ' +\n `${normalized.length}): \"${normalized}\"`,\n );\n }\n\n const pairs = normalized.match(/.{1,2}/gu);\n if (!pairs) {\n // Should be unreachable given the length check above; defensive.\n return Uint8Array.from([]);\n }\n\n for (const pair of pairs) {\n if (!HEX_PAIR_REGEX.test(pair)) {\n throw new Error(\n `Hex string contains non-hex characters: \"${pair}\" in \"${normalized}\"`,\n );\n }\n }\n\n return Uint8Array.from(pairs.map((pair) => parseInt(pair, 16)));\n}\n"]}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure helpers shared by the DMK bridge and its tests. Extracted from
3
+ * `LedgerDmkBridge` so they can be unit-tested in isolation and reused
4
+ * without an instance.
5
+ *
6
+ * @module dmk/internal-utils
7
+ */
8
+ /**
9
+ * Strip a leading `0x` from a hex string, if present.
10
+ *
11
+ * @param value - The hex string (with or without `0x` prefix).
12
+ * @returns The hex string with no `0x` prefix.
13
+ */
14
+ export declare function stripHexPrefix(value: string): string;
15
+ /**
16
+ * Strip the leading `m/` from a BIP-32 derivation path.
17
+ *
18
+ * DMK / signer-kit accepts paths without the leading `m/`.
19
+ *
20
+ * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).
21
+ * @returns The path with no leading `m/`.
22
+ */
23
+ export declare function stripPathPrefix(path: string): string;
24
+ /**
25
+ * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex
26
+ * string without `0x` prefix.
27
+ *
28
+ * @param value - The raw value (`bigint`, `number`, or hex string).
29
+ * @returns The hex string with no `0x` prefix.
30
+ */
31
+ export declare function toHexString(value: bigint | number | string): string;
32
+ /**
33
+ * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.
34
+ *
35
+ * Throws on inputs that would silently produce wrong bytes:
36
+ * - odd-length strings (e.g. `'abc'`)
37
+ * - non-hex characters (e.g. `'zz'`)
38
+ *
39
+ * @param value - The hex string (with or without `0x` prefix).
40
+ * @returns The decoded bytes.
41
+ * @throws {Error} If the input has odd length or contains non-hex characters.
42
+ */
43
+ export declare function hexToBytes(value: string): Uint8Array;
44
+ //# sourceMappingURL=internal-utils.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-utils.d.cts","sourceRoot":"","sources":["../../src/dmk/internal-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAKnE;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CA2BpD"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure helpers shared by the DMK bridge and its tests. Extracted from
3
+ * `LedgerDmkBridge` so they can be unit-tested in isolation and reused
4
+ * without an instance.
5
+ *
6
+ * @module dmk/internal-utils
7
+ */
8
+ /**
9
+ * Strip a leading `0x` from a hex string, if present.
10
+ *
11
+ * @param value - The hex string (with or without `0x` prefix).
12
+ * @returns The hex string with no `0x` prefix.
13
+ */
14
+ export declare function stripHexPrefix(value: string): string;
15
+ /**
16
+ * Strip the leading `m/` from a BIP-32 derivation path.
17
+ *
18
+ * DMK / signer-kit accepts paths without the leading `m/`.
19
+ *
20
+ * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).
21
+ * @returns The path with no leading `m/`.
22
+ */
23
+ export declare function stripPathPrefix(path: string): string;
24
+ /**
25
+ * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex
26
+ * string without `0x` prefix.
27
+ *
28
+ * @param value - The raw value (`bigint`, `number`, or hex string).
29
+ * @returns The hex string with no `0x` prefix.
30
+ */
31
+ export declare function toHexString(value: bigint | number | string): string;
32
+ /**
33
+ * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.
34
+ *
35
+ * Throws on inputs that would silently produce wrong bytes:
36
+ * - odd-length strings (e.g. `'abc'`)
37
+ * - non-hex characters (e.g. `'zz'`)
38
+ *
39
+ * @param value - The hex string (with or without `0x` prefix).
40
+ * @returns The decoded bytes.
41
+ * @throws {Error} If the input has odd length or contains non-hex characters.
42
+ */
43
+ export declare function hexToBytes(value: string): Uint8Array;
44
+ //# sourceMappingURL=internal-utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-utils.d.mts","sourceRoot":"","sources":["../../src/dmk/internal-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAKnE;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CA2BpD"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Pure helpers shared by the DMK bridge and its tests. Extracted from
3
+ * `LedgerDmkBridge` so they can be unit-tested in isolation and reused
4
+ * without an instance.
5
+ *
6
+ * @module dmk/internal-utils
7
+ */
8
+ /**
9
+ * Strip a leading `0x` from a hex string, if present.
10
+ *
11
+ * @param value - The hex string (with or without `0x` prefix).
12
+ * @returns The hex string with no `0x` prefix.
13
+ */
14
+ export function stripHexPrefix(value) {
15
+ return value.startsWith('0x') ? value.slice(2) : value;
16
+ }
17
+ /**
18
+ * Strip the leading `m/` from a BIP-32 derivation path.
19
+ *
20
+ * DMK / signer-kit accepts paths without the leading `m/`.
21
+ *
22
+ * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).
23
+ * @returns The path with no leading `m/`.
24
+ */
25
+ export function stripPathPrefix(path) {
26
+ return path.replace(/^m\//u, '');
27
+ }
28
+ /**
29
+ * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex
30
+ * string without `0x` prefix.
31
+ *
32
+ * @param value - The raw value (`bigint`, `number`, or hex string).
33
+ * @returns The hex string with no `0x` prefix.
34
+ */
35
+ export function toHexString(value) {
36
+ if (typeof value === 'string') {
37
+ return stripHexPrefix(value);
38
+ }
39
+ return value.toString(16);
40
+ }
41
+ const HEX_PAIR_REGEX = /^[0-9a-fA-F]{2}$/u;
42
+ /**
43
+ * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.
44
+ *
45
+ * Throws on inputs that would silently produce wrong bytes:
46
+ * - odd-length strings (e.g. `'abc'`)
47
+ * - non-hex characters (e.g. `'zz'`)
48
+ *
49
+ * @param value - The hex string (with or without `0x` prefix).
50
+ * @returns The decoded bytes.
51
+ * @throws {Error} If the input has odd length or contains non-hex characters.
52
+ */
53
+ export function hexToBytes(value) {
54
+ const normalized = stripHexPrefix(value);
55
+ if (normalized.length === 0) {
56
+ return Uint8Array.from([]);
57
+ }
58
+ if (normalized.length % 2 !== 0) {
59
+ throw new Error('Hex string must have an even number of characters (got ' +
60
+ `${normalized.length}): "${normalized}"`);
61
+ }
62
+ const pairs = normalized.match(/.{1,2}/gu);
63
+ if (!pairs) {
64
+ // Should be unreachable given the length check above; defensive.
65
+ return Uint8Array.from([]);
66
+ }
67
+ for (const pair of pairs) {
68
+ if (!HEX_PAIR_REGEX.test(pair)) {
69
+ throw new Error(`Hex string contains non-hex characters: "${pair}" in "${normalized}"`);
70
+ }
71
+ }
72
+ return Uint8Array.from(pairs.map((pair) => parseInt(pair, 16)));
73
+ }
74
+ //# sourceMappingURL=internal-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-utils.mjs","sourceRoot":"","sources":["../../src/dmk/internal-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAA+B;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,yDAAyD;YACvD,GAAG,UAAU,CAAC,MAAM,OAAO,UAAU,GAAG,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,iEAAiE;QACjE,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACb,4CAA4C,IAAI,SAAS,UAAU,GAAG,CACvE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * Pure helpers shared by the DMK bridge and its tests. Extracted from\n * `LedgerDmkBridge` so they can be unit-tested in isolation and reused\n * without an instance.\n *\n * @module dmk/internal-utils\n */\n\n/**\n * Strip a leading `0x` from a hex string, if present.\n *\n * @param value - The hex string (with or without `0x` prefix).\n * @returns The hex string with no `0x` prefix.\n */\nexport function stripHexPrefix(value: string): string {\n return value.startsWith('0x') ? value.slice(2) : value;\n}\n\n/**\n * Strip the leading `m/` from a BIP-32 derivation path.\n *\n * DMK / signer-kit accepts paths without the leading `m/`.\n *\n * @param path - The BIP-32 path (e.g. `m/44'/60'/0'/0/0`).\n * @returns The path with no leading `m/`.\n */\nexport function stripPathPrefix(path: string): string {\n return path.replace(/^m\\//u, '');\n}\n\n/**\n * Normalize a signature `v` (or any numeric/string hex-ish value) to a hex\n * string without `0x` prefix.\n *\n * @param value - The raw value (`bigint`, `number`, or hex string).\n * @returns The hex string with no `0x` prefix.\n */\nexport function toHexString(value: bigint | number | string): string {\n if (typeof value === 'string') {\n return stripHexPrefix(value);\n }\n return value.toString(16);\n}\n\nconst HEX_PAIR_REGEX = /^[0-9a-fA-F]{2}$/u;\n\n/**\n * Convert a hex string to a `Uint8Array`. Strips a leading `0x` if present.\n *\n * Throws on inputs that would silently produce wrong bytes:\n * - odd-length strings (e.g. `'abc'`)\n * - non-hex characters (e.g. `'zz'`)\n *\n * @param value - The hex string (with or without `0x` prefix).\n * @returns The decoded bytes.\n * @throws {Error} If the input has odd length or contains non-hex characters.\n */\nexport function hexToBytes(value: string): Uint8Array {\n const normalized = stripHexPrefix(value);\n if (normalized.length === 0) {\n return Uint8Array.from([]);\n }\n if (normalized.length % 2 !== 0) {\n throw new Error(\n 'Hex string must have an even number of characters (got ' +\n `${normalized.length}): \"${normalized}\"`,\n );\n }\n\n const pairs = normalized.match(/.{1,2}/gu);\n if (!pairs) {\n // Should be unreachable given the length check above; defensive.\n return Uint8Array.from([]);\n }\n\n for (const pair of pairs) {\n if (!HEX_PAIR_REGEX.test(pair)) {\n throw new Error(\n `Hex string contains non-hex characters: \"${pair}\" in \"${normalized}\"`,\n );\n }\n }\n\n return Uint8Array.from(pairs.map((pair) => parseInt(pair, 16)));\n}\n"]}