ox 1.0.4 → 1.1.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 (230) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/core/Base64.d.ts +10 -21
  3. package/dist/core/Base64.d.ts.map +1 -1
  4. package/dist/core/Base64.js +8 -127
  5. package/dist/core/Base64.js.map +1 -1
  6. package/dist/core/BinaryStateTree.d.ts.map +1 -1
  7. package/dist/core/BinaryStateTree.js +2 -2
  8. package/dist/core/BinaryStateTree.js.map +1 -1
  9. package/dist/core/Bls.d.ts.map +1 -1
  10. package/dist/core/Bls.js +19 -44
  11. package/dist/core/Bls.js.map +1 -1
  12. package/dist/core/Ed25519.d.ts.map +1 -1
  13. package/dist/core/Ed25519.js +7 -6
  14. package/dist/core/Ed25519.js.map +1 -1
  15. package/dist/core/Engine.d.ts +124 -0
  16. package/dist/core/Engine.d.ts.map +1 -0
  17. package/dist/core/Engine.js +180 -0
  18. package/dist/core/Engine.js.map +1 -0
  19. package/dist/core/Hash.d.ts +4 -4
  20. package/dist/core/Hash.d.ts.map +1 -1
  21. package/dist/core/Hash.js +9 -12
  22. package/dist/core/Hash.js.map +1 -1
  23. package/dist/core/Keystore.d.ts.map +1 -1
  24. package/dist/core/Keystore.js +15 -10
  25. package/dist/core/Keystore.js.map +1 -1
  26. package/dist/core/Mnemonic.d.ts.map +1 -1
  27. package/dist/core/Mnemonic.js +3 -2
  28. package/dist/core/Mnemonic.js.map +1 -1
  29. package/dist/core/P256.d.ts.map +1 -1
  30. package/dist/core/P256.js +8 -9
  31. package/dist/core/P256.js.map +1 -1
  32. package/dist/core/Secp256k1.d.ts.map +1 -1
  33. package/dist/core/Secp256k1.js +8 -9
  34. package/dist/core/Secp256k1.js.map +1 -1
  35. package/dist/core/WebCryptoP256.d.ts.map +1 -1
  36. package/dist/core/WebCryptoP256.js +2 -2
  37. package/dist/core/WebCryptoP256.js.map +1 -1
  38. package/dist/core/X25519.d.ts.map +1 -1
  39. package/dist/core/X25519.js +4 -3
  40. package/dist/core/X25519.js.map +1 -1
  41. package/dist/core/internal/bls.d.ts +12 -0
  42. package/dist/core/internal/bls.d.ts.map +1 -0
  43. package/dist/core/internal/bls.js +79 -0
  44. package/dist/core/internal/bls.js.map +1 -0
  45. package/dist/core/internal/codec/base64.d.ts +84 -0
  46. package/dist/core/internal/codec/base64.d.ts.map +1 -0
  47. package/dist/core/internal/codec/base64.js +177 -0
  48. package/dist/core/internal/codec/base64.js.map +1 -0
  49. package/dist/core/internal/codec/hex.d.ts +49 -4
  50. package/dist/core/internal/codec/hex.d.ts.map +1 -1
  51. package/dist/core/internal/codec/hex.js +109 -35
  52. package/dist/core/internal/codec/hex.js.map +1 -1
  53. package/dist/core/internal/curves.d.ts +17 -0
  54. package/dist/core/internal/curves.d.ts.map +1 -0
  55. package/dist/core/internal/curves.js +17 -0
  56. package/dist/core/internal/curves.js.map +1 -0
  57. package/dist/core/internal/ed25519.d.ts +14 -0
  58. package/dist/core/internal/ed25519.d.ts.map +1 -0
  59. package/dist/core/internal/ed25519.js +29 -0
  60. package/dist/core/internal/ed25519.js.map +1 -0
  61. package/dist/core/internal/engine.d.ts +278 -0
  62. package/dist/core/internal/engine.d.ts.map +1 -0
  63. package/dist/core/internal/engine.js +131 -0
  64. package/dist/core/internal/engine.js.map +1 -0
  65. package/dist/core/internal/hash.d.ts +12 -0
  66. package/dist/core/internal/hash.d.ts.map +1 -0
  67. package/dist/core/internal/hash.js +30 -0
  68. package/dist/core/internal/hash.js.map +1 -0
  69. package/dist/core/internal/keystore.d.ts +14 -0
  70. package/dist/core/internal/keystore.d.ts.map +1 -0
  71. package/dist/core/internal/keystore.js +32 -0
  72. package/dist/core/internal/keystore.js.map +1 -0
  73. package/dist/core/internal/mnemonic.d.ts +4 -0
  74. package/dist/core/internal/mnemonic.d.ts.map +1 -0
  75. package/dist/core/internal/mnemonic.js +14 -0
  76. package/dist/core/internal/mnemonic.js.map +1 -0
  77. package/dist/core/internal/p256.d.ts +14 -0
  78. package/dist/core/internal/p256.d.ts.map +1 -0
  79. package/dist/core/internal/p256.js +36 -0
  80. package/dist/core/internal/p256.js.map +1 -0
  81. package/dist/core/internal/secp256k1.d.ts +14 -0
  82. package/dist/core/internal/secp256k1.d.ts.map +1 -0
  83. package/dist/core/internal/secp256k1.js +36 -0
  84. package/dist/core/internal/secp256k1.js.map +1 -0
  85. package/dist/core/internal/x25519.d.ts +8 -0
  86. package/dist/core/internal/x25519.d.ts.map +1 -0
  87. package/dist/core/internal/x25519.js +20 -0
  88. package/dist/core/internal/x25519.js.map +1 -0
  89. package/dist/index.d.ts +35 -0
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.docs.d.ts +2 -0
  92. package/dist/index.docs.d.ts.map +1 -1
  93. package/dist/index.docs.js +2 -0
  94. package/dist/index.docs.js.map +1 -1
  95. package/dist/index.js +35 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/node/Engine.d.ts +55 -0
  98. package/dist/node/Engine.d.ts.map +1 -0
  99. package/dist/node/Engine.js +51 -0
  100. package/dist/node/Engine.js.map +1 -0
  101. package/dist/node/Hash.d.ts +39 -0
  102. package/dist/node/Hash.d.ts.map +1 -0
  103. package/dist/node/Hash.js +37 -0
  104. package/dist/node/Hash.js.map +1 -0
  105. package/dist/node/index.d.ts +39 -0
  106. package/dist/node/index.d.ts.map +1 -0
  107. package/dist/node/index.js +39 -0
  108. package/dist/node/index.js.map +1 -0
  109. package/dist/tempo/VirtualMaster.d.ts +2 -0
  110. package/dist/tempo/VirtualMaster.d.ts.map +1 -1
  111. package/dist/tempo/VirtualMaster.js +19 -5
  112. package/dist/tempo/VirtualMaster.js.map +1 -1
  113. package/dist/tempo/ZoneId.d.ts +35 -17
  114. package/dist/tempo/ZoneId.d.ts.map +1 -1
  115. package/dist/tempo/ZoneId.js +41 -16
  116. package/dist/tempo/ZoneId.js.map +1 -1
  117. package/dist/tempo/index.d.ts +5 -6
  118. package/dist/tempo/index.d.ts.map +1 -1
  119. package/dist/tempo/index.js +5 -6
  120. package/dist/tempo/index.js.map +1 -1
  121. package/dist/tempo/internal/mine.wasm.d.ts +2 -4
  122. package/dist/tempo/internal/mine.wasm.d.ts.map +1 -1
  123. package/dist/tempo/internal/mine.wasm.js +7 -12
  124. package/dist/tempo/internal/mine.wasm.js.map +1 -1
  125. package/dist/tempo/internal/virtualMasterPool.js +4 -1
  126. package/dist/tempo/internal/virtualMasterPool.js.map +1 -1
  127. package/dist/wasm/Engine.d.ts +62 -0
  128. package/dist/wasm/Engine.d.ts.map +1 -0
  129. package/dist/wasm/Engine.js +57 -0
  130. package/dist/wasm/Engine.js.map +1 -0
  131. package/dist/wasm/Hash.d.ts +49 -0
  132. package/dist/wasm/Hash.d.ts.map +1 -0
  133. package/dist/wasm/Hash.js +70 -0
  134. package/dist/wasm/Hash.js.map +1 -0
  135. package/dist/wasm/index.d.ts +46 -0
  136. package/dist/wasm/index.d.ts.map +1 -0
  137. package/dist/wasm/index.js +46 -0
  138. package/dist/wasm/index.js.map +1 -0
  139. package/dist/wasm/internal/hash.d.ts +9 -0
  140. package/dist/wasm/internal/hash.d.ts.map +1 -0
  141. package/dist/wasm/internal/hash.js +26 -0
  142. package/dist/wasm/internal/hash.js.map +1 -0
  143. package/dist/wasm/internal/hashes.wasm.d.ts +5 -0
  144. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -0
  145. package/dist/wasm/internal/hashes.wasm.js +12 -0
  146. package/dist/wasm/internal/hashes.wasm.js.map +1 -0
  147. package/dist/wasm/internal/instantiate.d.ts +56 -0
  148. package/dist/wasm/internal/instantiate.d.ts.map +1 -0
  149. package/dist/wasm/internal/instantiate.js +92 -0
  150. package/dist/wasm/internal/instantiate.js.map +1 -0
  151. package/dist/zod/TypedData.d.ts +12 -12
  152. package/package.json +36 -1
  153. package/src/core/Base64.ts +19 -166
  154. package/src/core/BinaryStateTree.ts +2 -3
  155. package/src/core/Bls.ts +27 -77
  156. package/src/core/Ed25519.ts +7 -7
  157. package/src/core/Engine.ts +235 -0
  158. package/src/core/Hash.ts +9 -12
  159. package/src/core/Keystore.ts +17 -17
  160. package/src/core/Mnemonic.ts +3 -6
  161. package/src/core/P256.ts +15 -24
  162. package/src/core/Rlp_tx.bench.ts +2 -4
  163. package/src/core/Secp256k1.ts +9 -14
  164. package/src/core/TxEnvelopeEip1559.bench.ts +0 -5
  165. package/src/core/WebCryptoP256.ts +2 -2
  166. package/src/core/X25519.ts +4 -3
  167. package/src/core/_test/AbiParameters.adversarial.fuzz.ts +1 -2
  168. package/src/core/_test/AbiParameters.fuzz.ts +1 -2
  169. package/src/core/_test/Base.fuzz.ts +1 -2
  170. package/src/core/_test/Cbor.fuzz.ts +1 -2
  171. package/src/core/_test/Engine.fuzz.ts +293 -0
  172. package/src/core/_test/Engine.test-d.ts +84 -0
  173. package/src/core/_test/Engine.test.ts +523 -0
  174. package/src/core/_test/Hash.vectors.test.ts +56 -0
  175. package/src/core/_test/Hex.fuzz.ts +1 -2
  176. package/src/core/_test/Rlp.fuzz.ts +1 -2
  177. package/src/core/_test/Signature.fuzz.ts +1 -2
  178. package/src/core/_test/curves.test.ts +7 -0
  179. package/src/core/_test/index.test.ts +1 -0
  180. package/src/core/internal/bls.ts +121 -0
  181. package/src/core/internal/codec/_test/base64.conformance.ts +156 -0
  182. package/src/core/internal/codec/_test/hex.conformance.ts +204 -0
  183. package/src/core/internal/codec/_test/hex.fuzz.ts +234 -0
  184. package/src/core/internal/codec/_test/hex.test.ts +124 -0
  185. package/src/core/internal/codec/base64.ts +222 -0
  186. package/src/core/internal/codec/hex.ts +115 -34
  187. package/src/core/internal/curves.ts +18 -0
  188. package/src/core/internal/ed25519.ts +64 -0
  189. package/src/core/internal/engine.ts +410 -0
  190. package/src/core/internal/hash.ts +48 -0
  191. package/src/core/internal/keystore.ts +107 -0
  192. package/src/core/internal/mnemonic.ts +18 -0
  193. package/src/core/internal/p256.ts +91 -0
  194. package/src/core/internal/secp256k1.ts +91 -0
  195. package/src/core/internal/x25519.ts +40 -0
  196. package/src/index.docs.ts +2 -0
  197. package/src/index.ts +36 -0
  198. package/src/node/Engine.ts +69 -0
  199. package/src/node/Hash.ts +56 -0
  200. package/src/node/_test/Engine.test.ts +62 -0
  201. package/src/node/_test/Hash.test-d.ts +26 -0
  202. package/src/node/_test/Hash.test.ts +102 -0
  203. package/src/node/index.ts +40 -0
  204. package/src/tempo/VirtualMaster.test.ts +75 -0
  205. package/src/tempo/VirtualMaster.ts +28 -6
  206. package/src/tempo/ZoneId.test-d.ts +16 -0
  207. package/src/tempo/ZoneId.test.ts +48 -13
  208. package/src/tempo/ZoneId.ts +55 -19
  209. package/src/tempo/index.ts +5 -6
  210. package/src/tempo/internal/mine.wasm.ts +7 -13
  211. package/src/tempo/internal/virtualMasterPool.ts +4 -1
  212. package/src/tsconfig.json +2 -1
  213. package/src/version.ts +1 -1
  214. package/src/wasm/Engine.ts +76 -0
  215. package/src/wasm/Hash.bench.ts +60 -0
  216. package/src/wasm/Hash.ts +109 -0
  217. package/src/wasm/_test/Engine.test.ts +62 -0
  218. package/src/wasm/_test/Hash.browser.test.ts +50 -0
  219. package/src/wasm/_test/Hash.fuzz.ts +188 -0
  220. package/src/wasm/_test/Hash.test-d.ts +28 -0
  221. package/src/wasm/_test/Hash.test.ts +211 -0
  222. package/src/wasm/_test/Hash.vectors.test.ts +117 -0
  223. package/src/wasm/_test/Runtime.test.ts +21 -0
  224. package/src/wasm/_test/runtime.wasm.ts +11 -0
  225. package/src/wasm/index.ts +47 -0
  226. package/src/wasm/internal/hash.ts +50 -0
  227. package/src/wasm/internal/hashes.wasm.ts +14 -0
  228. package/src/wasm/internal/instantiate.ts +135 -0
  229. package/src/zod/tempo/_test/ZoneId.test.ts +2 -2
  230. package/src/tempo/internal/mine.c +0 -182
@@ -19,40 +19,92 @@ const nibbleTable = /*#__PURE__*/ (() => {
19
19
  return table
20
20
  })()
21
21
 
22
- // Phase 2 native fast-path detection: `Uint8Array.prototype.toHex` lands in
23
- // Node 22+, Safari 18+, Firefox 133+. Falls back to `Buffer` on Node, then to
24
- // the JS loop.
22
+ // Native fast-path detection. `Uint8Array.prototype.toHex` ships in Chromium
23
+ // 145, Safari 18.2+ and Firefox 133+, but not in Node 24, which reaches
24
+ // `Buffer` instead.
25
+ //
26
+ // Only *encoding* delegates to it. Encoding is total, so a native method has
27
+ // nothing to get wrong; decoding has to reject, and the native decoders do not
28
+ // do so reliably -- Chromium 145 reads U+C230 as the digit `0`, masking the
29
+ // code unit to 8 bits exactly as `Buffer` does, and only rejects it from 149
30
+ // on. test262 misses this because none of its illegal characters mask onto the
31
+ // hex alphabet. Guarding `fromHex` costs an ASCII scan that leaves it only
32
+ // 10-25% ahead of the loop, so it is not used at all.
25
33
  const _Buffer: typeof globalThis.Buffer | undefined = (
26
34
  globalThis as typeof globalThis & { Buffer?: typeof globalThis.Buffer }
27
35
  ).Buffer
28
36
  const nativeToHex: ((this: Uint8Array) => string) | undefined = (
29
37
  Uint8Array.prototype as Uint8Array & { toHex?: () => string }
30
38
  ).toHex
31
- const nativeFromHex: ((value: string) => Uint8Array) | undefined = (
32
- Uint8Array as typeof Uint8Array & { fromHex?: (value: string) => Uint8Array }
33
- ).fromHex
39
+
40
+ /**
41
+ * Byte count below which {@link hexToBytesLoop} beats `Buffer.from(…, 'hex')`,
42
+ * whose fixed per-call cost short inputs cannot amortize. Measured on Node 24
43
+ * (V8 13.6): the crossover sits between 32 and 48 bytes, and 32 covers the
44
+ * sizes Ethereum work is made of -- 20-byte addresses and 32-byte words.
45
+ *
46
+ * `Buffer` is the only tier this applies to; where it is absent the loop
47
+ * decodes at every size. Re-measure before moving it.
48
+ *
49
+ * @internal
50
+ */
51
+ const loopDecodeMaxBytes = 32
34
52
 
35
53
  /**
36
54
  * Encodes a `Uint8Array` into a `0x`-prefixed lowercase hex string. Uses the
37
- * native `Uint8Array.prototype.toHex` (Node 22+, Safari 18+, Firefox 133+) or
38
- * Node's `Buffer` when available; otherwise a tight JS loop.
55
+ * native `Uint8Array.prototype.toHex` or Node's `Buffer` when available;
56
+ * otherwise a tight JS loop.
39
57
  *
40
58
  * @internal
41
59
  */
42
60
  export function bytesToHex(value: Uint8Array): Hex {
43
- if (nativeToHex) return `0x${nativeToHex.call(value)}` as Hex
44
- if (_Buffer)
45
- return `0x${_Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('hex')}` as Hex
61
+ if (bytesToHexNative) return bytesToHexNative(value)
62
+ if (bytesToHexBuffer) return bytesToHexBuffer(value)
63
+ return bytesToHexLoop(value)
64
+ }
65
+
66
+ /**
67
+ * Encodes via `Uint8Array.prototype.toHex`, where the runtime has it.
68
+ * `undefined` otherwise, so callers and the conformance suite can both ask
69
+ * whether this tier exists rather than inferring it.
70
+ *
71
+ * @internal
72
+ */
73
+ export const bytesToHexNative: ((value: Uint8Array) => Hex) | undefined =
74
+ nativeToHex && ((value) => `0x${nativeToHex.call(value)}` as Hex)
75
+
76
+ /**
77
+ * Encodes via Node's `Buffer`, where the runtime has it.
78
+ *
79
+ * @internal
80
+ */
81
+ export const bytesToHexBuffer: ((value: Uint8Array) => Hex) | undefined =
82
+ _Buffer &&
83
+ ((value) =>
84
+ `0x${_Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('hex')}` as Hex)
85
+
86
+ /**
87
+ * Encodes in plain JavaScript, for runtimes with neither the native method nor
88
+ * `Buffer`: browsers predating `Uint8Array.prototype.toHex`, which is recent
89
+ * enough that a meaningful share of installed browsers still land here.
90
+ *
91
+ * Appending to a string beats collecting substrings and joining them: V8 builds
92
+ * a rope and flattens it once, where `join` over an `Array.from({ length })`
93
+ * pays for a holey array as well as the concatenation. Measured at ~6.5x on a
94
+ * 32-byte input, counting the flatten.
95
+ *
96
+ * @internal
97
+ */
98
+ export function bytesToHexLoop(value: Uint8Array): Hex {
46
99
  const length = value.length
47
- const parts = Array.from<string>({ length })
48
- for (let i = 0; i < length; i++) parts[i] = hexes[value[i]!]!
49
- return `0x${parts.join('')}` as Hex
100
+ let hex = '0x'
101
+ for (let i = 0; i < length; i++) hex += hexes[value[i]!]
102
+ return hex as Hex
50
103
  }
51
104
 
52
105
  /**
53
106
  * Strictly decodes a `0x`-prefixed even-length hex string into a `Uint8Array`.
54
- * Uses the native `Uint8Array.fromHex` (Node 22+, Safari 18+, Firefox 133+) or
55
- * Node's `Buffer` when available; otherwise a tight JS loop.
107
+ * Uses a JS loop for short inputs and Node's `Buffer` for longer ones.
56
108
  *
57
109
  * @internal
58
110
  */
@@ -64,43 +116,72 @@ export function hexToBytes(value: string): Uint8Array {
64
116
  value.charCodeAt(1) !== 120 /* 'x' */
65
117
  )
66
118
  throw new InvalidHexValueError(value)
67
- const body = value.length === 2 ? '' : (value as string).slice(2)
68
- if ((body.length & 1) !== 0) throw new InvalidLengthError(value as Hex)
119
+ const nibbles = value.length - 2
120
+ if ((nibbles & 1) !== 0) throw new InvalidLengthError(value as Hex)
121
+ const length = nibbles >> 1
69
122
 
70
- if (nativeFromHex) {
71
- try {
72
- return nativeFromHex(body)
73
- } catch {
123
+ if (length <= loopDecodeMaxBytes) return hexToBytesLoop(value, length)
124
+ if (hexToBytesBuffer) return hexToBytesBuffer(value, length)
125
+ return hexToBytesLoop(value, length)
126
+ }
127
+
128
+ /**
129
+ * Decodes via Node's `Buffer`, where the runtime has it.
130
+ *
131
+ * @internal
132
+ */
133
+ export const hexToBytesBuffer:
134
+ | ((value: string, length: number) => Uint8Array)
135
+ | undefined =
136
+ _Buffer &&
137
+ ((value, length) => {
138
+ const body = value.slice(2)
139
+ // `Buffer.from(…, 'hex')` masks each UTF-16 code unit to 8 bits, so a
140
+ // character above U+00FF can alias a hex digit: U+C230 masks to 0x30, the
141
+ // digit `0`, and `'0x숰0'` decodes to `0x00` instead of being rejected.
142
+ // Masking cannot fabricate a digit from pure ASCII, and a string is pure
143
+ // ASCII exactly when its UTF-8 length matches its code-unit count.
144
+ if (_Buffer.byteLength(body, 'utf8') !== body.length)
74
145
  throw new InvalidHexValueError(value)
75
- }
76
- }
77
- if (_Buffer && body.length > 0) {
78
146
  // Buffer.from with 'hex' silently truncates on invalid chars; verify
79
147
  // byteLength matches expectations to detect malformed input.
80
- const expected = body.length >> 1
81
148
  const buf = _Buffer.from(body, 'hex')
82
- if (buf.length !== expected) throw new InvalidHexValueError(value)
149
+ if (buf.length !== length) throw new InvalidHexValueError(value)
83
150
  // Copy out of Buffer pool: callers may rely on `.buffer` being a
84
151
  // standalone ArrayBuffer (e.g. WebAuthn attestationObject round-trips).
85
152
  const out = new Uint8Array(buf.byteLength)
86
153
  out.set(buf)
87
154
  return out
88
- }
155
+ })
89
156
 
90
- const length = body.length >> 1
157
+ /**
158
+ * Decodes in plain JavaScript, reading nibbles straight out of `value` from
159
+ * index 2 so no substring is materialized.
160
+ *
161
+ * Invalidity is accumulated rather than branched on per nibble: a valid nibble
162
+ * is at most `0x0f`, so only the `0xff` sentinel can set bit 7, and one check
163
+ * after the loop covers every character. `nibbleTable` only spans Latin-1, so a
164
+ * code unit above it reads `undefined` and has to be mapped to the sentinel --
165
+ * without that, a non-ASCII character would OR in as zero and decode silently.
166
+ *
167
+ * @internal
168
+ */
169
+ export function hexToBytesLoop(value: string, length: number): Uint8Array {
91
170
  const out = new Uint8Array(length)
92
- for (let i = 0, j = 0; i < length; i++) {
93
- const hi = nibbleTable[body.charCodeAt(j++)]!
94
- const lo = nibbleTable[body.charCodeAt(j++)]!
95
- if (hi === 0xff || lo === 0xff) throw new InvalidHexValueError(value)
171
+ let invalid = 0
172
+ for (let i = 0, j = 2; i < length; i++) {
173
+ const hi = nibbleTable[value.charCodeAt(j++)] ?? 0xff
174
+ const lo = nibbleTable[value.charCodeAt(j++)] ?? 0xff
175
+ invalid |= hi | lo
96
176
  out[i] = (hi << 4) | lo
97
177
  }
178
+ if (invalid & 0x80) throw new InvalidHexValueError(value)
98
179
  return out
99
180
  }
100
181
 
101
182
  /** @internal */
102
183
  export function charCodeToBase16(char: number): number | undefined {
103
- const v = nibbleTable[char]!
184
+ const v = nibbleTable[char] ?? 0xff
104
185
  return v === 0xff ? undefined : v
105
186
  }
106
187
 
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Curve constants, inlined so that modules needing nothing but an order do not
3
+ * pull a curve implementation into the bundle.
4
+ *
5
+ * These are fixed by the curve specifications and are asserted against
6
+ * `@noble/curves` in `src/core/_test/curves.test.ts`.
7
+ *
8
+ * @internal
9
+ */
10
+
11
+ /**
12
+ * Order of the secp256r1 (P-256) curve.
13
+ *
14
+ * @see https://www.secg.org/sec2-v2.pdf
15
+ * @internal
16
+ */
17
+ export const p256N =
18
+ 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n
@@ -0,0 +1,64 @@
1
+ import { ed25519 } from '@noble/curves/ed25519.js'
2
+ import { type Complete, type Eddsa, overrides } from './engine.js'
3
+
4
+ /**
5
+ * Resolvers for the `Ed25519` slot, and ox's defaults for it, backed by
6
+ * `@noble/curves`.
7
+ *
8
+ * Declaring the defaults against the slot contract is what keeps them honest: a
9
+ * default that goes missing, or whose signature drifts, fails to compile rather
10
+ * than failing at the call site.
11
+ */
12
+
13
+ const getPublicKeyDefault: Complete<Eddsa>['getPublicKey'] = (privateKey) =>
14
+ ed25519.getPublicKey(privateKey)
15
+
16
+ const randomSecretKeyDefault: Complete<Eddsa>['randomSecretKey'] = () =>
17
+ ed25519.utils.randomSecretKey()
18
+
19
+ const signDefault: Complete<Eddsa>['sign'] = (payload, privateKey) =>
20
+ ed25519.sign(payload, privateKey)
21
+
22
+ const toMontgomeryDefault: Complete<Eddsa>['toMontgomery'] = (publicKey) =>
23
+ ed25519.utils.toMontgomery(publicKey)
24
+
25
+ const toMontgomerySecretDefault: Complete<Eddsa>['toMontgomerySecret'] = (
26
+ privateKey,
27
+ ) => ed25519.utils.toMontgomerySecret(privateKey)
28
+
29
+ const verifyDefault: Complete<Eddsa>['verify'] = (
30
+ signature,
31
+ payload,
32
+ publicKey,
33
+ ) => ed25519.verify(signature, payload, publicKey)
34
+
35
+ /** @internal */
36
+ export const getPublicKey: Complete<Eddsa>['getPublicKey'] = (privateKey) =>
37
+ (overrides.Ed25519?.getPublicKey ?? getPublicKeyDefault)(privateKey)
38
+
39
+ /** @internal */
40
+ export const randomSecretKey: Complete<Eddsa>['randomSecretKey'] = () =>
41
+ (overrides.Ed25519?.randomSecretKey ?? randomSecretKeyDefault)()
42
+
43
+ /** @internal */
44
+ export const sign: Complete<Eddsa>['sign'] = (payload, privateKey) =>
45
+ (overrides.Ed25519?.sign ?? signDefault)(payload, privateKey)
46
+
47
+ /** @internal */
48
+ export const toMontgomery: Complete<Eddsa>['toMontgomery'] = (publicKey) =>
49
+ (overrides.Ed25519?.toMontgomery ?? toMontgomeryDefault)(publicKey)
50
+
51
+ /** @internal */
52
+ export const toMontgomerySecret: Complete<Eddsa>['toMontgomerySecret'] = (
53
+ privateKey,
54
+ ) =>
55
+ (overrides.Ed25519?.toMontgomerySecret ?? toMontgomerySecretDefault)(
56
+ privateKey,
57
+ )
58
+
59
+ /** @internal */
60
+ export const verify: Complete<Eddsa>['verify'] = (
61
+ signature,
62
+ payload,
63
+ publicKey,
64
+ ) => (overrides.Ed25519?.verify ?? verifyDefault)(signature, payload, publicKey)
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Engine slot contracts and the override registry.
3
+ *
4
+ * This module intentionally has **no imports**. It is pulled into the module
5
+ * graph of every crypto module in ox, so it must not drag any dependency along
6
+ * with it. That constraint is also why the contracts speak raw `Uint8Array`
7
+ * rather than `Bytes.Bytes`/`Hex.Hex` -- coercion stays in the public module,
8
+ * and the engine boundary is plain bytes in, plain bytes out.
9
+ *
10
+ * The registry holds **overrides only**. Defaults are never registered here,
11
+ * because a registered default has to live on a complete slot object, and a
12
+ * slot object is a single reachable value: esbuild cannot drop the properties
13
+ * of one, so every default's `@noble/*` import survives into every bundle that
14
+ * touches the slot. Measured on a keccak256-only bundle, that costs 5 kB gzip.
15
+ * Instead each slot has a resolver module under `internal/` holding one binding
16
+ * per primitive, and the fallback is chosen there, per primitive.
17
+ *
18
+ * @internal
19
+ */
20
+
21
+ /**
22
+ * Hash primitives. One-shot, synchronous, bytes in / bytes out.
23
+ *
24
+ * @internal
25
+ */
26
+ export type Hash = {
27
+ /** BLAKE3, 32-byte digest. */
28
+ blake3?: ((input: Uint8Array) => Uint8Array) | undefined
29
+ /** HMAC-SHA256. */
30
+ hmacSha256?:
31
+ | ((key: Uint8Array, message: Uint8Array) => Uint8Array)
32
+ | undefined
33
+ /** Keccak256 (not FIPS-202 SHA3-256). */
34
+ keccak256?: ((input: Uint8Array) => Uint8Array) | undefined
35
+ /** RIPEMD-160. */
36
+ ripemd160?: ((input: Uint8Array) => Uint8Array) | undefined
37
+ /** SHA-256. */
38
+ sha256?: ((input: Uint8Array) => Uint8Array) | undefined
39
+ }
40
+
41
+ /**
42
+ * Short-Weierstrass ECDSA. Shared by the `Secp256k1` and `P256` slots.
43
+ *
44
+ * Signatures are always low-S normalized and always returned in 65-byte
45
+ * recovered form -- the contract mandates it, so an engine cannot silently opt
46
+ * out.
47
+ *
48
+ * @internal
49
+ */
50
+ export type Ecdsa = {
51
+ /** Derives an uncompressed (65-byte) public key. */
52
+ getPublicKey?: ((privateKey: Uint8Array) => Uint8Array) | undefined
53
+ /**
54
+ * Computes a compressed (33-byte) ECDH shared secret.
55
+ *
56
+ * `publicKey` arrives in whichever SEC1 form the caller supplied, so an
57
+ * implementation has to accept both the compressed 33-byte and uncompressed
58
+ * 65-byte encodings.
59
+ */
60
+ getSharedSecret?:
61
+ | ((privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array)
62
+ | undefined
63
+ /**
64
+ * Recovers an uncompressed public key from a 65-byte `yParity || r || s`
65
+ * signature, the same layout {@link Ecdsa.sign} returns.
66
+ */
67
+ recoverPublicKey?:
68
+ | ((signature: Uint8Array, payload: Uint8Array) => Uint8Array)
69
+ | undefined
70
+ /** Generates a random private key. */
71
+ randomSecretKey?: (() => Uint8Array) | undefined
72
+ /** Signs a payload, returning 65 bytes of `yParity || r || s`. */
73
+ sign?:
74
+ | ((
75
+ payload: Uint8Array,
76
+ privateKey: Uint8Array,
77
+ options: {
78
+ /** Extra entropy for hedged signing, or `true` for random. */
79
+ extraEntropy: boolean | Uint8Array
80
+ /** Whether to hash the payload before signing. */
81
+ prehash: boolean
82
+ },
83
+ ) => Uint8Array)
84
+ | undefined
85
+ /** Verifies a 64-byte compact (`r || s`) signature. */
86
+ verify?:
87
+ | ((
88
+ signature: Uint8Array,
89
+ payload: Uint8Array,
90
+ publicKey: Uint8Array,
91
+ options: {
92
+ /** Whether to hash the payload before verifying. */
93
+ prehash: boolean
94
+ },
95
+ ) => boolean)
96
+ | undefined
97
+ }
98
+
99
+ /**
100
+ * Edwards-curve signatures (Ed25519).
101
+ *
102
+ * @internal
103
+ */
104
+ export type Eddsa = {
105
+ /** Derives a 32-byte public key. */
106
+ getPublicKey?: ((privateKey: Uint8Array) => Uint8Array) | undefined
107
+ /** Generates a random private key. */
108
+ randomSecretKey?: (() => Uint8Array) | undefined
109
+ /** Signs a payload, returning a 64-byte signature. */
110
+ sign?:
111
+ | ((payload: Uint8Array, privateKey: Uint8Array) => Uint8Array)
112
+ | undefined
113
+ /** Converts an Ed25519 public key to its X25519 (Montgomery) form. */
114
+ toMontgomery?: ((publicKey: Uint8Array) => Uint8Array) | undefined
115
+ /** Converts an Ed25519 private key to its X25519 (Montgomery) form. */
116
+ toMontgomerySecret?: ((privateKey: Uint8Array) => Uint8Array) | undefined
117
+ /** Verifies a 64-byte signature. */
118
+ verify?:
119
+ | ((
120
+ signature: Uint8Array,
121
+ payload: Uint8Array,
122
+ publicKey: Uint8Array,
123
+ ) => boolean)
124
+ | undefined
125
+ }
126
+
127
+ /**
128
+ * Montgomery-curve key agreement (X25519).
129
+ *
130
+ * @internal
131
+ */
132
+ export type Ecdh = {
133
+ /** Derives a 32-byte public key. */
134
+ getPublicKey?: ((privateKey: Uint8Array) => Uint8Array) | undefined
135
+ /** Computes a 32-byte shared secret. */
136
+ getSharedSecret?:
137
+ | ((privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array)
138
+ | undefined
139
+ /** Generates a random private key. */
140
+ randomSecretKey?: (() => Uint8Array) | undefined
141
+ }
142
+
143
+ /**
144
+ * Key derivation and symmetric primitives used by keystores.
145
+ *
146
+ * The `*Async` functions resolve independently of their synchronous twins --
147
+ * an engine that supplies only `scrypt` leaves `scryptAsync` on the default
148
+ * implementation, because wrapping a long synchronous call in a promise is
149
+ * worse than a genuinely yielding one.
150
+ *
151
+ * @internal
152
+ */
153
+ export type Keystore = {
154
+ /** AES-CTR decryption (key length selects AES-128/192/256). */
155
+ aesCtrDecrypt?:
156
+ | ((key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array)
157
+ | undefined
158
+ /** AES-CTR encryption (key length selects AES-128/192/256). */
159
+ aesCtrEncrypt?:
160
+ | ((key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array)
161
+ | undefined
162
+ /** PBKDF2-HMAC-SHA256. */
163
+ pbkdf2Sha256?:
164
+ | ((
165
+ password: Uint8Array,
166
+ salt: Uint8Array,
167
+ options: { c: number; dkLen: number },
168
+ ) => Uint8Array)
169
+ | undefined
170
+ /** Asynchronous PBKDF2-HMAC-SHA256. */
171
+ pbkdf2Sha256Async?:
172
+ | ((
173
+ password: Uint8Array,
174
+ salt: Uint8Array,
175
+ options: { c: number; dkLen: number },
176
+ ) => Promise<Uint8Array>)
177
+ | undefined
178
+ /** scrypt. */
179
+ scrypt?:
180
+ | ((
181
+ password: Uint8Array,
182
+ salt: Uint8Array,
183
+ options: { N: number; dkLen: number; p: number; r: number },
184
+ ) => Uint8Array)
185
+ | undefined
186
+ /** Asynchronous scrypt. */
187
+ scryptAsync?:
188
+ | ((
189
+ password: Uint8Array,
190
+ salt: Uint8Array,
191
+ options: { N: number; dkLen: number; p: number; r: number },
192
+ ) => Promise<Uint8Array>)
193
+ | undefined
194
+ }
195
+
196
+ /**
197
+ * BIP-39 seed derivation (PBKDF2-HMAC-SHA512, 2048 iterations).
198
+ *
199
+ * Mnemonic generation, validation, and the wordlists themselves are plain data
200
+ * and string handling, so they are not part of the engine boundary.
201
+ *
202
+ * @internal
203
+ */
204
+ export type Mnemonic = {
205
+ /** Derives a 64-byte seed from a mnemonic phrase and optional passphrase. */
206
+ toSeed?: ((mnemonic: string, passphrase?: string) => Uint8Array) | undefined
207
+ }
208
+
209
+ /**
210
+ * BLS12-381 operations.
211
+ *
212
+ * Points cross this boundary in their compressed serialized form (48 bytes for
213
+ * G1, 96 for G2), never as projective coordinate triples -- an engine backed by
214
+ * a native library cannot be expected to reproduce another library's internal
215
+ * point representation.
216
+ *
217
+ * @internal
218
+ */
219
+ export type Bls = {
220
+ /** Aggregates compressed points of the same group. */
221
+ aggregate?:
222
+ | ((points: readonly Uint8Array[], group: 'G1' | 'G2') => Uint8Array)
223
+ | undefined
224
+ /** Derives a compressed public key in the given group. */
225
+ getPublicKey?:
226
+ | ((privateKey: Uint8Array, group: 'G1' | 'G2') => Uint8Array)
227
+ | undefined
228
+ /** Generates a random private key. */
229
+ randomSecretKey?: (() => Uint8Array) | undefined
230
+ /** Signs a payload, returning a compressed point in the opposite group. */
231
+ sign?:
232
+ | ((
233
+ payload: Uint8Array,
234
+ privateKey: Uint8Array,
235
+ options: {
236
+ /** Domain separation tag for hash-to-curve. */
237
+ dst?: Uint8Array | undefined
238
+ /** Group of the signature. */
239
+ group: 'G1' | 'G2'
240
+ },
241
+ ) => Uint8Array)
242
+ | undefined
243
+ /** Verifies a compressed signature against a compressed public key. */
244
+ verify?:
245
+ | ((
246
+ signature: Uint8Array,
247
+ payload: Uint8Array,
248
+ publicKey: Uint8Array,
249
+ options: {
250
+ /** Domain separation tag for hash-to-curve. */
251
+ dst?: Uint8Array | undefined
252
+ /** Group of the signature. */
253
+ signatureGroup: 'G1' | 'G2'
254
+ },
255
+ ) => boolean)
256
+ | undefined
257
+ }
258
+
259
+ /**
260
+ * Root engine type. Slots and functions are optional; omissions preserve
261
+ * earlier overrides, or use Ox's default when none exists.
262
+ *
263
+ * @internal
264
+ */
265
+ export type Engine = {
266
+ /** Implementation for {@link ox#Bls}. */
267
+ Bls?: Bls | undefined
268
+ /** Implementation for {@link ox#Ed25519}. */
269
+ Ed25519?: Eddsa | undefined
270
+ /** Implementation for {@link ox#Hash}. */
271
+ Hash?: Hash | undefined
272
+ /** Implementation for {@link ox#Keystore}. */
273
+ Keystore?: Keystore | undefined
274
+ /** Implementation for {@link ox#Mnemonic}. */
275
+ Mnemonic?: Mnemonic | undefined
276
+ /** Implementation for {@link ox#P256}. */
277
+ P256?: Ecdsa | undefined
278
+ /** Implementation for {@link ox#Secp256k1}. */
279
+ Secp256k1?: Ecdsa | undefined
280
+ /** Implementation for {@link ox#X25519}. */
281
+ X25519?: Ecdh | undefined
282
+ }
283
+
284
+ /**
285
+ * A slot with every function present.
286
+ *
287
+ * Slots are optional all the way down so that an engine can fill in as little
288
+ * as it likes. ox's own default implementations are held to the opposite
289
+ * standard: they back every function ox routes, so they are declared against
290
+ * this instead, and a default that goes missing or drifts from the contract
291
+ * fails to compile.
292
+ *
293
+ * @internal
294
+ */
295
+ export type Complete<slot> = { [key in keyof slot]-?: NonNullable<slot[key]> }
296
+
297
+ /**
298
+ * Recognized slot names, in the order they appear on {@link Engine}.
299
+ *
300
+ * @internal
301
+ */
302
+ export const slots = [
303
+ 'Bls',
304
+ 'Ed25519',
305
+ 'Hash',
306
+ 'Keystore',
307
+ 'Mnemonic',
308
+ 'P256',
309
+ 'Secp256k1',
310
+ 'X25519',
311
+ ] as const satisfies readonly (keyof Engine)[]
312
+
313
+ /**
314
+ * Recognized primitive names, per slot.
315
+ *
316
+ * An override stored under a name no resolver reads is invisible: the default
317
+ * keeps running and nothing is reported, so a typo looks exactly like success.
318
+ * That is worst for the callers who can least afford it -- someone installing a
319
+ * policy-constrained or hardware-backed implementation silently gets ox's
320
+ * default instead. {@link Engine.set} checks against this to make it loud.
321
+ *
322
+ * @internal
323
+ */
324
+ export const primitives = {
325
+ Bls: ['aggregate', 'getPublicKey', 'randomSecretKey', 'sign', 'verify'],
326
+ Ed25519: [
327
+ 'getPublicKey',
328
+ 'randomSecretKey',
329
+ 'sign',
330
+ 'toMontgomery',
331
+ 'toMontgomerySecret',
332
+ 'verify',
333
+ ],
334
+ Hash: ['blake3', 'hmacSha256', 'keccak256', 'ripemd160', 'sha256'],
335
+ Keystore: [
336
+ 'aesCtrDecrypt',
337
+ 'aesCtrEncrypt',
338
+ 'pbkdf2Sha256',
339
+ 'pbkdf2Sha256Async',
340
+ 'scrypt',
341
+ 'scryptAsync',
342
+ ],
343
+ Mnemonic: ['toSeed'],
344
+ P256: [
345
+ 'getPublicKey',
346
+ 'getSharedSecret',
347
+ 'recoverPublicKey',
348
+ 'randomSecretKey',
349
+ 'sign',
350
+ 'verify',
351
+ ],
352
+ Secp256k1: [
353
+ 'getPublicKey',
354
+ 'getSharedSecret',
355
+ 'recoverPublicKey',
356
+ 'randomSecretKey',
357
+ 'sign',
358
+ 'verify',
359
+ ],
360
+ X25519: ['getPublicKey', 'getSharedSecret', 'randomSecretKey'],
361
+ } as const satisfies {
362
+ [key in keyof Engine]-?: readonly (keyof Complete<NonNullable<Engine[key]>>)[]
363
+ }
364
+
365
+ /**
366
+ * The installed overrides. Empty by default -- a bare object literal, so
367
+ * bundlers treat it as side-effect free.
368
+ *
369
+ * @internal
370
+ */
371
+ export const overrides: Engine = {}
372
+
373
+ /**
374
+ * Merges overrides into the registry, one level deep. A slot set to `undefined`
375
+ * is removed.
376
+ *
377
+ * @internal
378
+ */
379
+ export function merge(value: Engine) {
380
+ for (const key of Object.keys(value) as (keyof Engine)[]) {
381
+ const slot = value[key]
382
+ if (slot === undefined) {
383
+ delete overrides[key]
384
+ continue
385
+ }
386
+ // A member set to `undefined` clears that primitive. Spreading would keep
387
+ // it as an own property: calls still fall through to the default, but
388
+ // `get` would report an override that is not there.
389
+ const merged: Record<string, unknown> = { ...overrides[key] }
390
+ for (const [name, fn] of Object.entries(slot)) {
391
+ if (fn === undefined) delete merged[name]
392
+ else merged[name] = fn
393
+ }
394
+ // An empty slot stays: `set({ Hash: {} })` is a no-op a caller wrote
395
+ // deliberately, and `get` has always reported it.
396
+ overrides[key] = merged as never
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Removes one slot, or every slot when `slot` is omitted.
402
+ *
403
+ * @internal
404
+ */
405
+ export function reset(slot?: keyof Engine) {
406
+ if (slot) delete overrides[slot]
407
+ else
408
+ for (const key of Object.keys(overrides) as (keyof Engine)[])
409
+ delete overrides[key]
410
+ }