ox 1.2.0 → 1.3.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 (187) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/core/AbiFunction.d.ts +1 -1
  3. package/dist/core/AbiFunction.d.ts.map +1 -1
  4. package/dist/core/AbiFunction.js +6 -2
  5. package/dist/core/AbiFunction.js.map +1 -1
  6. package/dist/core/AbiParameters.d.ts +1 -1
  7. package/dist/core/AbiParameters.d.ts.map +1 -1
  8. package/dist/core/AbiParameters.js +1 -13
  9. package/dist/core/AbiParameters.js.map +1 -1
  10. package/dist/core/AesGcm.d.ts +36 -1
  11. package/dist/core/AesGcm.d.ts.map +1 -1
  12. package/dist/core/AesGcm.js +44 -0
  13. package/dist/core/AesGcm.js.map +1 -1
  14. package/dist/core/Ed25519.d.ts +46 -1
  15. package/dist/core/Ed25519.d.ts.map +1 -1
  16. package/dist/core/Ed25519.js +43 -0
  17. package/dist/core/Ed25519.js.map +1 -1
  18. package/dist/core/Engine.d.ts +1 -1
  19. package/dist/core/Engine.d.ts.map +1 -1
  20. package/dist/core/Engine.js.map +1 -1
  21. package/dist/core/Hash.d.ts +164 -1
  22. package/dist/core/Hash.d.ts.map +1 -1
  23. package/dist/core/Hash.js +172 -0
  24. package/dist/core/Hash.js.map +1 -1
  25. package/dist/core/Hex.d.ts.map +1 -1
  26. package/dist/core/Hex.js +1 -4
  27. package/dist/core/Hex.js.map +1 -1
  28. package/dist/core/Rlp.d.ts +32 -0
  29. package/dist/core/Rlp.d.ts.map +1 -1
  30. package/dist/core/Rlp.js +146 -0
  31. package/dist/core/Rlp.js.map +1 -1
  32. package/dist/core/Secp256k1.d.ts +46 -1
  33. package/dist/core/Secp256k1.d.ts.map +1 -1
  34. package/dist/core/Secp256k1.js +48 -0
  35. package/dist/core/Secp256k1.js.map +1 -1
  36. package/dist/core/WebAuthn.d.ts +224 -0
  37. package/dist/core/WebAuthn.d.ts.map +1 -0
  38. package/dist/core/WebAuthn.js +343 -0
  39. package/dist/core/WebAuthn.js.map +1 -0
  40. package/dist/core/WebAuthnP256.d.ts +46 -50
  41. package/dist/core/WebAuthnP256.d.ts.map +1 -1
  42. package/dist/core/WebAuthnP256.js +44 -50
  43. package/dist/core/WebAuthnP256.js.map +1 -1
  44. package/dist/core/internal/abiParameters.d.ts +7 -87
  45. package/dist/core/internal/abiParameters.d.ts.map +1 -1
  46. package/dist/core/internal/abiParameters.js +339 -194
  47. package/dist/core/internal/abiParameters.js.map +1 -1
  48. package/dist/core/internal/engine.d.ts +35 -2
  49. package/dist/core/internal/engine.d.ts.map +1 -1
  50. package/dist/core/internal/engine.js +12 -1
  51. package/dist/core/internal/engine.js.map +1 -1
  52. package/dist/core/internal/hash.d.ts +10 -0
  53. package/dist/core/internal/hash.d.ts.map +1 -1
  54. package/dist/core/internal/hash.js +26 -1
  55. package/dist/core/internal/hash.js.map +1 -1
  56. package/dist/index.d.ts +23 -14
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +23 -14
  59. package/dist/index.js.map +1 -1
  60. package/dist/node/Engine.d.ts +1 -1
  61. package/dist/node/Engine.d.ts.map +1 -1
  62. package/dist/node/Engine.js.map +1 -1
  63. package/dist/node/Hash.d.ts +6 -4
  64. package/dist/node/Hash.d.ts.map +1 -1
  65. package/dist/node/Hash.js +35 -3
  66. package/dist/node/Hash.js.map +1 -1
  67. package/dist/tempo/AuthorizationTempo.d.ts +6 -6
  68. package/dist/tempo/AuthorizationTempo.js +6 -6
  69. package/dist/tempo/KeyAuthorization.d.ts +3 -8
  70. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  71. package/dist/tempo/KeyAuthorization.js +3 -8
  72. package/dist/tempo/KeyAuthorization.js.map +1 -1
  73. package/dist/tempo/SignatureEnvelope.d.ts +8 -8
  74. package/dist/tempo/SignatureEnvelope.js +8 -8
  75. package/dist/wasm/Engine.d.ts +1 -1
  76. package/dist/wasm/Engine.d.ts.map +1 -1
  77. package/dist/wasm/Engine.js.map +1 -1
  78. package/dist/wasm/Hash.d.ts +1 -1
  79. package/dist/wasm/Hash.d.ts.map +1 -1
  80. package/dist/wasm/Hash.js +5 -0
  81. package/dist/wasm/Hash.js.map +1 -1
  82. package/dist/wasm/internal/blake3.d.ts +10 -1
  83. package/dist/wasm/internal/blake3.d.ts.map +1 -1
  84. package/dist/wasm/internal/blake3.js +12 -1
  85. package/dist/wasm/internal/blake3.js.map +1 -1
  86. package/dist/wasm/internal/blake3.wasm.d.ts +3 -1
  87. package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -1
  88. package/dist/wasm/internal/blake3.wasm.js +3 -1
  89. package/dist/wasm/internal/blake3.wasm.js.map +1 -1
  90. package/dist/wasm/internal/hash.d.ts +9 -0
  91. package/dist/wasm/internal/hash.d.ts.map +1 -1
  92. package/dist/wasm/internal/hash.js +13 -1
  93. package/dist/wasm/internal/hash.js.map +1 -1
  94. package/dist/wasm/internal/hashState.d.ts +25 -0
  95. package/dist/wasm/internal/hashState.d.ts.map +1 -0
  96. package/dist/wasm/internal/hashState.js +105 -0
  97. package/dist/wasm/internal/hashState.js.map +1 -0
  98. package/dist/wasm/internal/hashes.d.ts +12 -1
  99. package/dist/wasm/internal/hashes.d.ts.map +1 -1
  100. package/dist/wasm/internal/hashes.js +18 -1
  101. package/dist/wasm/internal/hashes.js.map +1 -1
  102. package/dist/wasm/internal/hashes.wasm.d.ts +11 -3
  103. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
  104. package/dist/wasm/internal/hashes.wasm.js +11 -3
  105. package/dist/wasm/internal/hashes.wasm.js.map +1 -1
  106. package/dist/webauthn/Authentication.d.ts +2 -0
  107. package/dist/webauthn/Authentication.d.ts.map +1 -1
  108. package/dist/webauthn/Authentication.js +2 -1
  109. package/dist/webauthn/Authentication.js.map +1 -1
  110. package/dist/webauthn/Registration.d.ts.map +1 -1
  111. package/dist/webauthn/Registration.js +2 -1
  112. package/dist/webauthn/Registration.js.map +1 -1
  113. package/dist/webauthn/Types.d.ts +27 -6
  114. package/dist/webauthn/Types.d.ts.map +1 -1
  115. package/dist/webauthn/internal/utils.d.ts.map +1 -1
  116. package/dist/webauthn/internal/utils.js +35 -9
  117. package/dist/webauthn/internal/utils.js.map +1 -1
  118. package/dist/zod/TypedData.d.ts +2 -2
  119. package/package.json +9 -1
  120. package/src/benchmarks/Ox.bench.ts +6 -0
  121. package/src/benchmarks/OxNode.bench.ts +8 -0
  122. package/src/benchmarks/OxV0.bench.ts +4 -0
  123. package/src/benchmarks/OxWasm.bench.ts +22 -0
  124. package/src/core/AbiFunction.ts +5 -5
  125. package/src/core/AbiParameters.ts +8 -20
  126. package/src/core/AesGcm.ts +86 -1
  127. package/src/core/Ed25519.ts +87 -1
  128. package/src/core/Engine.ts +1 -0
  129. package/src/core/Hash.ts +262 -1
  130. package/src/core/Hex.ts +1 -4
  131. package/src/core/Rlp.ts +172 -5
  132. package/src/core/Secp256k1.ts +92 -1
  133. package/src/core/WebAuthn.ts +579 -0
  134. package/src/core/WebAuthnP256.ts +47 -50
  135. package/src/core/_test/AbiFunction.test.ts +9 -0
  136. package/src/core/_test/AbiParameters.differential.fuzz.ts +156 -0
  137. package/src/core/_test/AbiParameters.encode.test.ts +22 -0
  138. package/src/core/_test/AbiParameters.fuzz.ts +4 -0
  139. package/src/core/_test/AesGcm.test.ts +76 -0
  140. package/src/core/_test/Ed25519.test-d.ts +13 -1
  141. package/src/core/_test/Ed25519.test.ts +120 -1
  142. package/src/core/_test/Engine.test-d.ts +9 -0
  143. package/src/core/_test/Engine.test.ts +1 -1
  144. package/src/core/_test/Hash.test-d.ts +23 -0
  145. package/src/core/_test/Hash.test.ts +179 -1
  146. package/src/core/_test/Hex.test.ts +2 -0
  147. package/src/core/_test/Rlp.test-d.ts +18 -0
  148. package/src/core/_test/Rlp.test.ts +109 -0
  149. package/src/core/_test/Secp256k1.test-d.ts +14 -0
  150. package/src/core/_test/Secp256k1.test.ts +91 -1
  151. package/src/core/_test/WebAuthn.test-d.ts +142 -0
  152. package/src/core/_test/WebAuthn.test.ts +766 -0
  153. package/src/core/_test/index.test.ts +1 -0
  154. package/src/core/internal/abiParameters.ts +466 -326
  155. package/src/core/internal/engine.ts +47 -2
  156. package/src/core/internal/hash.ts +53 -1
  157. package/src/index.ts +24 -14
  158. package/src/node/Engine.ts +1 -0
  159. package/src/node/Hash.ts +43 -6
  160. package/src/node/_test/Engine.test-d.ts +1 -0
  161. package/src/node/_test/Engine.test.ts +2 -0
  162. package/src/node/_test/Hash.test-d.ts +10 -0
  163. package/src/node/_test/Hash.test.ts +46 -0
  164. package/src/tempo/AuthorizationTempo.ts +6 -6
  165. package/src/tempo/KeyAuthorization.ts +3 -8
  166. package/src/tempo/SignatureEnvelope.ts +8 -8
  167. package/src/version.ts +1 -1
  168. package/src/wasm/Engine.ts +1 -0
  169. package/src/wasm/Hash.ts +10 -0
  170. package/src/wasm/_test/Engine.test-d.ts +1 -0
  171. package/src/wasm/_test/Engine.test.ts +5 -0
  172. package/src/wasm/_test/Hash.browser.test.ts +28 -0
  173. package/src/wasm/_test/Hash.test-d.ts +16 -0
  174. package/src/wasm/_test/Hash.test.ts +250 -1
  175. package/src/wasm/internal/blake3.ts +23 -2
  176. package/src/wasm/internal/blake3.wasm.ts +4 -1
  177. package/src/wasm/internal/hash.ts +27 -1
  178. package/src/wasm/internal/hashState.ts +154 -0
  179. package/src/wasm/internal/hashes.ts +56 -15
  180. package/src/wasm/internal/hashes.wasm.ts +15 -3
  181. package/src/webauthn/Authentication.ts +4 -0
  182. package/src/webauthn/Registration.ts +2 -0
  183. package/src/webauthn/Types.ts +32 -2
  184. package/src/webauthn/_test/Authentication.test.ts +123 -1
  185. package/src/webauthn/_test/Registration.test.ts +26 -6
  186. package/src/webauthn/_test/WebAuthn.browser.test.ts +28 -0
  187. package/src/webauthn/internal/utils.ts +41 -8
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
4
  "type": "module",
5
- "version": "1.2.0",
5
+ "version": "1.3.0",
6
6
  "main": "./dist/index.js",
7
7
  "sideEffects": false,
8
8
  "license": "MIT",
@@ -417,6 +417,11 @@
417
417
  "types": "./dist/core/Value.d.ts",
418
418
  "default": "./dist/core/Value.js"
419
419
  },
420
+ "./WebAuthn": {
421
+ "src": "./src/core/WebAuthn.ts",
422
+ "types": "./dist/core/WebAuthn.d.ts",
423
+ "default": "./dist/core/WebAuthn.js"
424
+ },
420
425
  "./WebAuthnP256": {
421
426
  "src": "./src/core/WebAuthnP256.ts",
422
427
  "types": "./dist/core/WebAuthnP256.d.ts",
@@ -1057,6 +1062,9 @@
1057
1062
  "types": "./dist/index.d.ts",
1058
1063
  "typesVersions": {
1059
1064
  "*": {
1065
+ "_types/*": [
1066
+ "./dist/*.d.ts"
1067
+ ],
1060
1068
  "*": [
1061
1069
  "./dist/*",
1062
1070
  "./dist/*/index.d.ts",
@@ -0,0 +1,6 @@
1
+ import { Engine } from 'ox'
2
+ import { register } from '../../test/benchmarks/comparison.js'
3
+ import { createOperations } from '../../test/benchmarks/comparison.current.js'
4
+
5
+ Engine.reset()
6
+ register('ox', createOperations())
@@ -0,0 +1,8 @@
1
+ import { Engine } from 'ox'
2
+ import { Engine as NodeEngine } from 'ox/node'
3
+ import { register } from '../../test/benchmarks/comparison.js'
4
+ import { createOperations } from '../../test/benchmarks/comparison.current.js'
5
+
6
+ Engine.reset()
7
+ Engine.set(await NodeEngine.engine())
8
+ register('ox/node', createOperations())
@@ -0,0 +1,4 @@
1
+ import { register } from '../../test/benchmarks/comparison.js'
2
+ import { createOperations } from '../../test/benchmarks/comparison.v0.js'
3
+
4
+ register('ox v0', createOperations())
@@ -0,0 +1,22 @@
1
+ import { Engine } from 'ox'
2
+ import {
3
+ Engine as WasmEngine,
4
+ Keystore as WasmKeystore,
5
+ Secp256k1 as WasmSecp256k1,
6
+ } from 'ox/wasm'
7
+ import { register } from '../../test/benchmarks/comparison.js'
8
+ import { createOperations } from '../../test/benchmarks/comparison.current.js'
9
+
10
+ const [wasm, keystore, secp256k1] = await Promise.all([
11
+ WasmEngine.engine(),
12
+ WasmKeystore.engine(),
13
+ WasmSecp256k1.engine(),
14
+ ])
15
+
16
+ Engine.reset()
17
+ Engine.set({
18
+ ...wasm,
19
+ Keystore: keystore,
20
+ Secp256k1: secp256k1,
21
+ })
22
+ register('ox/wasm', createOperations())
@@ -6,7 +6,7 @@ import type * as Errors from './Errors.js'
6
6
  import * as Hex from './Hex.js'
7
7
  import type * as internal from './internal/abiFunction.js'
8
8
  import type * as AbiItem_internal from './internal/abiItem.js'
9
- import type * as AbiParameters_internal from './internal/abiParameters.js'
9
+ import * as AbiParameters_internal from './internal/abiParameters.js'
10
10
  import * as formatAbiItem from './internal/human-readable/formatAbiItem.js'
11
11
  import type { IsNarrowable } from './internal/types.js'
12
12
 
@@ -670,10 +670,10 @@ export function encodeData(
670
670
 
671
671
  const selector = getSelector(item)
672
672
 
673
- const data =
674
- args.length > 0 ? AbiParameters.encode(item.inputs, args) : undefined
675
-
676
- return data ? Hex.concat(selector, data) : selector
673
+ if (args.length === 0) return selector
674
+ return AbiParameters_internal.encodeParameters(item.inputs, args, {
675
+ prefix: selector,
676
+ })
677
677
  }
678
678
 
679
679
  export declare namespace encodeData {
@@ -212,29 +212,17 @@ export function encode<
212
212
  options?: encode.Options,
213
213
  ): Hex.Hex {
214
214
  const { checksumAddress = false } = options ?? {}
215
-
216
- if (parameters.length !== values.length)
217
- throw new LengthMismatchError({
218
- expectedLength: parameters.length as number,
219
- givenLength: values.length as any,
220
- })
221
- // Prepare the parameters to determine dynamic types to encode.
222
- const preparedParameters = internal.prepareParameters({
223
- checksumAddress,
224
- parameters: parameters as readonly Parameter[],
225
- values: values as any,
226
- })
227
- const data = internal.encode(preparedParameters)
228
- if (data.length === 0) return '0x'
229
- return data
215
+ return internal.encodeParameters(
216
+ parameters as readonly Parameter[],
217
+ values as readonly unknown[],
218
+ {
219
+ checksumAddress,
220
+ },
221
+ )
230
222
  }
231
223
 
232
224
  export declare namespace encode {
233
- type ErrorType =
234
- | LengthMismatchError
235
- | internal.encode.ErrorType
236
- | internal.prepareParameters.ErrorType
237
- | Errors.GlobalErrorType
225
+ type ErrorType = internal.encodeParameters.ErrorType | Errors.GlobalErrorType
238
226
 
239
227
  type Options = {
240
228
  /**
@@ -1,5 +1,5 @@
1
1
  import * as Bytes from './Bytes.js'
2
- import type * as Errors from './Errors.js'
2
+ import * as Errors from './Errors.js'
3
3
  import * as Hex from './Hex.js'
4
4
 
5
5
  export const ivLength = 16
@@ -134,6 +134,70 @@ export declare namespace encrypt {
134
134
  | Errors.GlobalErrorType
135
135
  }
136
136
 
137
+ /**
138
+ * Derives an AES-256-GCM key from a 32-byte WebAuthn PRF output.
139
+ *
140
+ * The permanent derivation contract uses the PRF output as the HMAC-SHA256
141
+ * key. Its message is the UTF-8 bytes of `ox.aesGcm.fromPrf.v1` followed by
142
+ * a 32-bit big-endian counter set to zero.
143
+ *
144
+ * @example
145
+ * ```ts twoslash
146
+ * import { AesGcm } from 'ox'
147
+ *
148
+ * const key = await AesGcm.fromPrf(
149
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
150
+ * )
151
+ * ```
152
+ *
153
+ * @param value - A 32-byte WebAuthn PRF output.
154
+ * @returns A nonextractable AES-256-GCM key for encryption and decryption.
155
+ */
156
+ export async function fromPrf(
157
+ value: Hex.Hex | Bytes.Bytes,
158
+ ): Promise<CryptoKey> {
159
+ const bytes = Bytes.from(value)
160
+ if (bytes.length !== 32) throw new InvalidPrfSizeError({ size: bytes.length })
161
+
162
+ const prfKey = await globalThis.crypto.subtle.importKey(
163
+ 'raw',
164
+ bytes,
165
+ {
166
+ name: 'HMAC',
167
+ hash: 'SHA-256',
168
+ },
169
+ false,
170
+ ['sign'],
171
+ )
172
+ const key = new Uint8Array(
173
+ await globalThis.crypto.subtle.sign(
174
+ 'HMAC',
175
+ prfKey,
176
+ Bytes.concat(fromPrfLabel, Bytes.fromNumber(0, { size: 4 })),
177
+ ),
178
+ )
179
+ try {
180
+ return await globalThis.crypto.subtle.importKey(
181
+ 'raw',
182
+ key,
183
+ { name: 'AES-GCM' },
184
+ false,
185
+ ['encrypt', 'decrypt'],
186
+ )
187
+ } finally {
188
+ key.fill(0)
189
+ }
190
+ }
191
+
192
+ export declare namespace fromPrf {
193
+ type ErrorType =
194
+ | Bytes.concat.ErrorType
195
+ | Bytes.from.ErrorType
196
+ | Bytes.fromNumber.ErrorType
197
+ | InvalidPrfSizeError
198
+ | Errors.GlobalErrorType
199
+ }
200
+
137
201
  /**
138
202
  * Derives an AES-GCM key from a password using PBKDF2.
139
203
  *
@@ -206,3 +270,24 @@ export function randomSalt(size = 32): Bytes.Bytes {
206
270
  export declare namespace randomSalt {
207
271
  type ErrorType = Bytes.random.ErrorType | Errors.GlobalErrorType
208
272
  }
273
+
274
+ /** Thrown when a WebAuthn PRF output is not 32 bytes. */
275
+ export class InvalidPrfSizeError extends Errors.BaseError {
276
+ override readonly name = 'AesGcm.InvalidPrfSizeError'
277
+
278
+ constructor(options: InvalidPrfSizeError.Options) {
279
+ super(
280
+ `PRF output must be exactly 32 bytes. Received ${options.size} bytes.`,
281
+ )
282
+ }
283
+ }
284
+
285
+ export declare namespace InvalidPrfSizeError {
286
+ /** Options for {@link ox#AesGcm.InvalidPrfSizeError}. */
287
+ type Options = {
288
+ /** Received PRF output size. */
289
+ size: number
290
+ }
291
+ }
292
+
293
+ const fromPrfLabel = Bytes.fromString('ox.aesGcm.fromPrf.v1')
@@ -1,6 +1,7 @@
1
1
  import { ed25519 } from '@noble/curves/ed25519.js'
2
2
  import * as Bytes from './Bytes.js'
3
- import type * as Errors from './Errors.js'
3
+ import * as Errors from './Errors.js'
4
+ import * as Hash from './Hash.js'
4
5
  import * as Hex from './Hex.js'
5
6
  import * as engine from './internal/ed25519.js'
6
7
 
@@ -58,6 +59,70 @@ export declare namespace createKeyPair {
58
59
  | Errors.GlobalErrorType
59
60
  }
60
61
 
62
+ /**
63
+ * Derives an Ed25519 private key from a 32-byte WebAuthn PRF output.
64
+ *
65
+ * The permanent derivation contract uses the PRF output as the HMAC-SHA256
66
+ * key. Its message is the UTF-8 bytes of `ox.ed25519.fromPrf.v1` followed by
67
+ * a 32-bit big-endian counter set to zero.
68
+ *
69
+ * @example
70
+ * ```ts twoslash
71
+ * import { Ed25519 } from 'ox'
72
+ *
73
+ * const privateKey = Ed25519.fromPrf(
74
+ * '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
75
+ * )
76
+ * ```
77
+ *
78
+ * @param value - A 32-byte WebAuthn PRF output.
79
+ * @param options - Options.
80
+ * @returns An Ed25519 private key.
81
+ */
82
+ export function fromPrf<as extends 'Hex' | 'Bytes' = 'Hex'>(
83
+ value: Hex.Hex | Bytes.Bytes,
84
+ options: fromPrf.Options<as> = {},
85
+ ): fromPrf.ReturnType<as> {
86
+ const { as = 'Hex' } = options
87
+ const bytes = Bytes.from(value)
88
+ if (bytes.length !== 32) throw new InvalidPrfSizeError({ size: bytes.length })
89
+
90
+ const privateKey = Hash.hmac256(
91
+ bytes,
92
+ Bytes.concat(fromPrfLabel, Bytes.fromNumber(0, { size: 4 })),
93
+ { as: 'Bytes' },
94
+ )
95
+ if (as === 'Hex') {
96
+ const value = Hex.fromBytes(privateKey)
97
+ privateKey.fill(0)
98
+ return value as never
99
+ }
100
+ return privateKey as never
101
+ }
102
+
103
+ export declare namespace fromPrf {
104
+ type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
105
+ /**
106
+ * Format of the returned private key.
107
+ * @default 'Hex'
108
+ */
109
+ as?: as | 'Hex' | 'Bytes' | undefined
110
+ }
111
+
112
+ type ReturnType<as extends 'Hex' | 'Bytes'> =
113
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
114
+ | (as extends 'Hex' ? Hex.Hex : never)
115
+
116
+ type ErrorType =
117
+ | Bytes.concat.ErrorType
118
+ | Bytes.from.ErrorType
119
+ | Bytes.fromNumber.ErrorType
120
+ | Hash.hmac256.ErrorType
121
+ | Hex.fromBytes.ErrorType
122
+ | InvalidPrfSizeError
123
+ | Errors.GlobalErrorType
124
+ }
125
+
61
126
  /**
62
127
  * Computes the Ed25519 public key from a provided private key.
63
128
  *
@@ -345,3 +410,24 @@ export declare namespace toX25519PrivateKey {
345
410
  | Hex.fromBytes.ErrorType
346
411
  | Errors.GlobalErrorType
347
412
  }
413
+
414
+ /** Thrown when a WebAuthn PRF output is not 32 bytes. */
415
+ export class InvalidPrfSizeError extends Errors.BaseError {
416
+ override readonly name = 'Ed25519.InvalidPrfSizeError'
417
+
418
+ constructor(options: InvalidPrfSizeError.Options) {
419
+ super(
420
+ `PRF output must be exactly 32 bytes. Received ${options.size} bytes.`,
421
+ )
422
+ }
423
+ }
424
+
425
+ export declare namespace InvalidPrfSizeError {
426
+ /** Options for {@link ox#Ed25519.InvalidPrfSizeError}. */
427
+ type Options = {
428
+ /** Received PRF output size. */
429
+ size: number
430
+ }
431
+ }
432
+
433
+ const fromPrfLabel = Bytes.fromString('ox.ed25519.fromPrf.v1')
@@ -9,6 +9,7 @@ export type {
9
9
  Eddsa,
10
10
  Engine,
11
11
  Hash,
12
+ HashState,
12
13
  Keystore,
13
14
  Mnemonic,
14
15
  } from './internal/engine.js'
package/src/core/Hash.ts CHANGED
@@ -1,8 +1,187 @@
1
1
  import * as Bytes from './Bytes.js'
2
- import type * as Errors from './Errors.js'
2
+ import * as Errors from './Errors.js'
3
3
  import * as Hex from './Hex.js'
4
+ import type { HashState } from './internal/engine.js'
4
5
  import * as engine from './internal/hash.js'
5
6
 
7
+ /**
8
+ * Incremental hash state.
9
+ *
10
+ * A hasher accepts any number of chunks. Calling `digest` or `digestInto`
11
+ * consumes the state. Call `clone` before finalizing to branch from the same
12
+ * prefix.
13
+ */
14
+ export type Hasher = {
15
+ /** Creates an independent copy of the current state. */
16
+ clone(): Hasher
17
+ /** Destroys the state. Calling `destroy` more than once has no effect. */
18
+ destroy(): void
19
+ /** Finalizes the digest and consumes the state. */
20
+ digest<as extends 'Hex' | 'Bytes' = 'Hex'>(
21
+ options?: Hasher.DigestOptions<as>,
22
+ ): Hasher.DigestReturnType<as>
23
+ /**
24
+ * Finalizes into the start of `output` and consumes the state.
25
+ *
26
+ * `output` may be larger than the digest. Bytes after the digest are left
27
+ * unchanged.
28
+ */
29
+ digestInto(output: Bytes.Bytes): void
30
+ /** Absorbs a message chunk. */
31
+ update(value: Hex.Hex | Bytes.Bytes): Hasher
32
+ }
33
+
34
+ export declare namespace Hasher {
35
+ /** Options for `Hasher.digest`. */
36
+ type DigestOptions<as extends 'Hex' | 'Bytes' = 'Hex'> = {
37
+ /** The return type. @default 'Hex' */
38
+ as?: as | 'Hex' | 'Bytes' | undefined
39
+ }
40
+
41
+ /** Return type for `Hasher.digest`. */
42
+ type DigestReturnType<as extends 'Hex' | 'Bytes' = 'Hex'> =
43
+ | (as extends 'Bytes' ? Bytes.Bytes : never)
44
+ | (as extends 'Hex' ? Hex.Hex : never)
45
+
46
+ /** Errors thrown by an incremental hash state. */
47
+ type ErrorType =
48
+ | HasherDestroyedError
49
+ | InvalidDigestSizeError
50
+ | Bytes.from.ErrorType
51
+ | Hex.fromBytes.ErrorType
52
+ | Errors.GlobalErrorType
53
+ }
54
+
55
+ /**
56
+ * Creates an incremental BLAKE3 hasher.
57
+ *
58
+ * The installed Engine provider is captured when this function is called.
59
+ *
60
+ * @example
61
+ * ```ts twoslash
62
+ * import { Hash } from 'ox'
63
+ *
64
+ * const hash = Hash.createBlake3()
65
+ * hash.update('0xdead')
66
+ * hash.update('0xbeef')
67
+ * hash.digest()
68
+ * // @log: '0x53147f3ce49ed4f60dfa5b9654c36ba6103c11f5737df3dabd4cbd296c4161bd'
69
+ * ```
70
+ *
71
+ * @returns An incremental BLAKE3 hasher.
72
+ */
73
+ export function createBlake3(): Hasher {
74
+ return fromState(engine.createBlake3(), 32)
75
+ }
76
+
77
+ export declare namespace createBlake3 {
78
+ type ErrorType = Hasher.ErrorType
79
+ }
80
+
81
+ /**
82
+ * Creates an incremental HMAC-SHA256 hasher.
83
+ *
84
+ * The installed Engine provider is captured when this function is called.
85
+ *
86
+ * @example
87
+ * ```ts twoslash
88
+ * import { Hash, Hex } from 'ox'
89
+ *
90
+ * const hash = Hash.createHmac256(Hex.fromString('key'))
91
+ * hash.update('0xdead')
92
+ * hash.update('0xbeef')
93
+ * hash.digest()
94
+ * ```
95
+ *
96
+ * @param key - HMAC key.
97
+ * @returns An incremental HMAC-SHA256 hasher.
98
+ */
99
+ export function createHmac256(key: Hex.Hex | Bytes.Bytes): Hasher {
100
+ return fromState(engine.createHmacSha256(Bytes.from(key)), 32)
101
+ }
102
+
103
+ export declare namespace createHmac256 {
104
+ type ErrorType = Hasher.ErrorType
105
+ }
106
+
107
+ /**
108
+ * Creates an incremental Keccak256 hasher.
109
+ *
110
+ * The installed Engine provider is captured when this function is called.
111
+ *
112
+ * @example
113
+ * ```ts twoslash
114
+ * import { Hash } from 'ox'
115
+ *
116
+ * const hash = Hash.createKeccak256()
117
+ * hash.update('0xdead')
118
+ * hash.update('0xbeef')
119
+ * hash.digest()
120
+ * // @log: '0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1'
121
+ * ```
122
+ *
123
+ * @returns An incremental Keccak256 hasher.
124
+ */
125
+ export function createKeccak256(): Hasher {
126
+ return fromState(engine.createKeccak256(), 32)
127
+ }
128
+
129
+ export declare namespace createKeccak256 {
130
+ type ErrorType = Hasher.ErrorType
131
+ }
132
+
133
+ /**
134
+ * Creates an incremental RIPEMD-160 hasher.
135
+ *
136
+ * The installed Engine provider is captured when this function is called.
137
+ *
138
+ * @example
139
+ * ```ts twoslash
140
+ * import { Hash } from 'ox'
141
+ *
142
+ * const hash = Hash.createRipemd160()
143
+ * hash.update('0xdead')
144
+ * hash.update('0xbeef')
145
+ * hash.digest()
146
+ * // @log: '0x226821c2f5423e11fe9af68bd285c249db2e4b5a'
147
+ * ```
148
+ *
149
+ * @returns An incremental RIPEMD-160 hasher.
150
+ */
151
+ export function createRipemd160(): Hasher {
152
+ return fromState(engine.createRipemd160(), 20)
153
+ }
154
+
155
+ export declare namespace createRipemd160 {
156
+ type ErrorType = Hasher.ErrorType
157
+ }
158
+
159
+ /**
160
+ * Creates an incremental SHA-256 hasher.
161
+ *
162
+ * The installed Engine provider is captured when this function is called.
163
+ *
164
+ * @example
165
+ * ```ts twoslash
166
+ * import { Hash } from 'ox'
167
+ *
168
+ * const hash = Hash.createSha256()
169
+ * hash.update('0xdead')
170
+ * hash.update('0xbeef')
171
+ * hash.digest()
172
+ * // @log: '0x5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953'
173
+ * ```
174
+ *
175
+ * @returns An incremental SHA-256 hasher.
176
+ */
177
+ export function createSha256(): Hasher {
178
+ return fromState(engine.createSha256(), 32)
179
+ }
180
+
181
+ export declare namespace createSha256 {
182
+ type ErrorType = Hasher.ErrorType
183
+ }
184
+
6
185
  /**
7
186
  * Calculates the [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) hash of a {@link ox#Bytes.Bytes} or {@link ox#Hex.Hex} value.
8
187
  *
@@ -323,3 +502,85 @@ export declare namespace validate {
323
502
  | Hex.size.ErrorType
324
503
  | Errors.GlobalErrorType
325
504
  }
505
+
506
+ /** Thrown when an incremental hash state has been consumed or destroyed. */
507
+ export class HasherDestroyedError extends Errors.BaseError {
508
+ override readonly name = 'Hash.HasherDestroyedError'
509
+
510
+ constructor() {
511
+ super('Hasher has been destroyed.')
512
+ }
513
+ }
514
+
515
+ /** Thrown when a digest output buffer is too small. */
516
+ export class InvalidDigestSizeError extends Errors.BaseError {
517
+ override readonly name = 'Hash.InvalidDigestSizeError'
518
+
519
+ constructor(options: InvalidDigestSizeError.Options) {
520
+ const { minimum, size } = options
521
+ super(
522
+ `Digest output is too small. Expected at least ${minimum} bytes. Received ${size} bytes.`,
523
+ )
524
+ }
525
+ }
526
+
527
+ export declare namespace InvalidDigestSizeError {
528
+ type Options = {
529
+ /** Minimum output size. */
530
+ minimum: number
531
+ /** Received output size. */
532
+ size: number
533
+ }
534
+ }
535
+
536
+ function fromState(state: HashState, digestSize: number): Hasher {
537
+ let active = true
538
+
539
+ const assertActive = () => {
540
+ if (!active) throw new HasherDestroyedError()
541
+ }
542
+
543
+ const consume = <returnType>(fn: () => returnType): returnType => {
544
+ assertActive()
545
+ active = false
546
+ try {
547
+ return fn()
548
+ } finally {
549
+ state.destroy()
550
+ }
551
+ }
552
+
553
+ const hasher: Hasher = {
554
+ clone() {
555
+ assertActive()
556
+ return fromState(state.clone(), digestSize)
557
+ },
558
+ destroy() {
559
+ if (!active) return
560
+ active = false
561
+ state.destroy()
562
+ },
563
+ digest(options = {}) {
564
+ const value = new Uint8Array(digestSize)
565
+ consume(() => state.digestInto(value))
566
+ if (options.as === 'Bytes') return value as never
567
+ return Hex.fromBytes(value) as never
568
+ },
569
+ digestInto(output) {
570
+ assertActive()
571
+ if (output.length < digestSize)
572
+ throw new InvalidDigestSizeError({
573
+ minimum: digestSize,
574
+ size: output.length,
575
+ })
576
+ consume(() => state.digestInto(output))
577
+ },
578
+ update(value) {
579
+ assertActive()
580
+ state.update(Bytes.from(value))
581
+ return hasher
582
+ },
583
+ }
584
+
585
+ return hasher
586
+ }
package/src/core/Hex.ts CHANGED
@@ -74,10 +74,7 @@ export function concat(...values: readonly Hex[]): Hex {
74
74
  if (values.length === 1) return values[0]!
75
75
  if (values.length === 2)
76
76
  return `0x${(values[0] as string).slice(2)}${(values[1] as string).slice(2)}` as Hex
77
- const parts = Array.from<string>({ length: values.length })
78
- for (let i = 0; i < values.length; i++)
79
- parts[i] = (values[i] as string).slice(2)
80
- return `0x${parts.join('')}` as Hex
77
+ return `0x${values.reduce((hex, value) => hex + value.replace('0x', ''), '')}`
81
78
  }
82
79
 
83
80
  export declare namespace concat {