ox 1.0.5 → 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 (217) hide show
  1. package/CHANGELOG.md +50 -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/internal/mine.wasm.d.ts +2 -4
  114. package/dist/tempo/internal/mine.wasm.d.ts.map +1 -1
  115. package/dist/tempo/internal/mine.wasm.js +7 -12
  116. package/dist/tempo/internal/mine.wasm.js.map +1 -1
  117. package/dist/tempo/internal/virtualMasterPool.js +4 -1
  118. package/dist/tempo/internal/virtualMasterPool.js.map +1 -1
  119. package/dist/wasm/Engine.d.ts +62 -0
  120. package/dist/wasm/Engine.d.ts.map +1 -0
  121. package/dist/wasm/Engine.js +57 -0
  122. package/dist/wasm/Engine.js.map +1 -0
  123. package/dist/wasm/Hash.d.ts +49 -0
  124. package/dist/wasm/Hash.d.ts.map +1 -0
  125. package/dist/wasm/Hash.js +70 -0
  126. package/dist/wasm/Hash.js.map +1 -0
  127. package/dist/wasm/index.d.ts +46 -0
  128. package/dist/wasm/index.d.ts.map +1 -0
  129. package/dist/wasm/index.js +46 -0
  130. package/dist/wasm/index.js.map +1 -0
  131. package/dist/wasm/internal/hash.d.ts +9 -0
  132. package/dist/wasm/internal/hash.d.ts.map +1 -0
  133. package/dist/wasm/internal/hash.js +26 -0
  134. package/dist/wasm/internal/hash.js.map +1 -0
  135. package/dist/wasm/internal/hashes.wasm.d.ts +5 -0
  136. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -0
  137. package/dist/wasm/internal/hashes.wasm.js +12 -0
  138. package/dist/wasm/internal/hashes.wasm.js.map +1 -0
  139. package/dist/wasm/internal/instantiate.d.ts +56 -0
  140. package/dist/wasm/internal/instantiate.d.ts.map +1 -0
  141. package/dist/wasm/internal/instantiate.js +92 -0
  142. package/dist/wasm/internal/instantiate.js.map +1 -0
  143. package/dist/zod/TypedData.d.ts +12 -12
  144. package/package.json +36 -1
  145. package/src/core/Base64.ts +19 -166
  146. package/src/core/BinaryStateTree.ts +2 -3
  147. package/src/core/Bls.ts +27 -77
  148. package/src/core/Ed25519.ts +7 -7
  149. package/src/core/Engine.ts +235 -0
  150. package/src/core/Hash.ts +9 -12
  151. package/src/core/Keystore.ts +17 -17
  152. package/src/core/Mnemonic.ts +3 -6
  153. package/src/core/P256.ts +15 -24
  154. package/src/core/Rlp_tx.bench.ts +2 -4
  155. package/src/core/Secp256k1.ts +9 -14
  156. package/src/core/TxEnvelopeEip1559.bench.ts +0 -5
  157. package/src/core/WebCryptoP256.ts +2 -2
  158. package/src/core/X25519.ts +4 -3
  159. package/src/core/_test/AbiParameters.adversarial.fuzz.ts +1 -2
  160. package/src/core/_test/AbiParameters.fuzz.ts +1 -2
  161. package/src/core/_test/Base.fuzz.ts +1 -2
  162. package/src/core/_test/Cbor.fuzz.ts +1 -2
  163. package/src/core/_test/Engine.fuzz.ts +293 -0
  164. package/src/core/_test/Engine.test-d.ts +84 -0
  165. package/src/core/_test/Engine.test.ts +523 -0
  166. package/src/core/_test/Hash.vectors.test.ts +56 -0
  167. package/src/core/_test/Hex.fuzz.ts +1 -2
  168. package/src/core/_test/Rlp.fuzz.ts +1 -2
  169. package/src/core/_test/Signature.fuzz.ts +1 -2
  170. package/src/core/_test/curves.test.ts +7 -0
  171. package/src/core/_test/index.test.ts +1 -0
  172. package/src/core/internal/bls.ts +121 -0
  173. package/src/core/internal/codec/_test/base64.conformance.ts +156 -0
  174. package/src/core/internal/codec/_test/hex.conformance.ts +204 -0
  175. package/src/core/internal/codec/_test/hex.fuzz.ts +234 -0
  176. package/src/core/internal/codec/_test/hex.test.ts +124 -0
  177. package/src/core/internal/codec/base64.ts +222 -0
  178. package/src/core/internal/codec/hex.ts +115 -34
  179. package/src/core/internal/curves.ts +18 -0
  180. package/src/core/internal/ed25519.ts +64 -0
  181. package/src/core/internal/engine.ts +410 -0
  182. package/src/core/internal/hash.ts +48 -0
  183. package/src/core/internal/keystore.ts +107 -0
  184. package/src/core/internal/mnemonic.ts +18 -0
  185. package/src/core/internal/p256.ts +91 -0
  186. package/src/core/internal/secp256k1.ts +91 -0
  187. package/src/core/internal/x25519.ts +40 -0
  188. package/src/index.docs.ts +2 -0
  189. package/src/index.ts +36 -0
  190. package/src/node/Engine.ts +69 -0
  191. package/src/node/Hash.ts +56 -0
  192. package/src/node/_test/Engine.test.ts +62 -0
  193. package/src/node/_test/Hash.test-d.ts +26 -0
  194. package/src/node/_test/Hash.test.ts +102 -0
  195. package/src/node/index.ts +40 -0
  196. package/src/tempo/VirtualMaster.test.ts +75 -0
  197. package/src/tempo/VirtualMaster.ts +28 -6
  198. package/src/tempo/internal/mine.wasm.ts +7 -13
  199. package/src/tempo/internal/virtualMasterPool.ts +4 -1
  200. package/src/tsconfig.json +2 -1
  201. package/src/version.ts +1 -1
  202. package/src/wasm/Engine.ts +76 -0
  203. package/src/wasm/Hash.bench.ts +60 -0
  204. package/src/wasm/Hash.ts +109 -0
  205. package/src/wasm/_test/Engine.test.ts +62 -0
  206. package/src/wasm/_test/Hash.browser.test.ts +50 -0
  207. package/src/wasm/_test/Hash.fuzz.ts +188 -0
  208. package/src/wasm/_test/Hash.test-d.ts +28 -0
  209. package/src/wasm/_test/Hash.test.ts +211 -0
  210. package/src/wasm/_test/Hash.vectors.test.ts +117 -0
  211. package/src/wasm/_test/Runtime.test.ts +21 -0
  212. package/src/wasm/_test/runtime.wasm.ts +11 -0
  213. package/src/wasm/index.ts +47 -0
  214. package/src/wasm/internal/hash.ts +50 -0
  215. package/src/wasm/internal/hashes.wasm.ts +14 -0
  216. package/src/wasm/internal/instantiate.ts +135 -0
  217. package/src/tempo/internal/mine.c +0 -182
package/src/core/P256.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  normalizePublicKey,
9
9
  normalizeSignature,
10
10
  } from './internal/cryptoIo.js'
11
+ import * as engine from './internal/p256.js'
11
12
  import * as Entropy from './internal/entropy.js'
12
13
  import {
13
14
  fromRecoveredBytes,
@@ -85,7 +86,7 @@ export function getPublicKey<as extends 'Hex' | 'Bytes' | 'Object' = 'Object'>(
85
86
  options: getPublicKey.Options<as>,
86
87
  ): getPublicKey.ReturnType<as> {
87
88
  const { as = 'Object', privateKey } = options
88
- const bytes = noble_p256.getPublicKey(Bytes.from(privateKey), false)
89
+ const bytes = engine.getPublicKey(Bytes.from(privateKey))
89
90
  const publicKey = PublicKey.fromBytes(bytes)
90
91
  return formatPublicKey(publicKey, as) as never
91
92
  }
@@ -134,10 +135,9 @@ export function getSharedSecret<as extends 'Hex' | 'Bytes' = 'Hex'>(
134
135
  options: getSharedSecret.Options<as>,
135
136
  ): getSharedSecret.ReturnType<as> {
136
137
  const { as = 'Hex', privateKey, publicKey } = options
137
- const sharedSecret = noble_p256.getSharedSecret(
138
+ const sharedSecret = engine.getSharedSecret(
138
139
  Bytes.from(privateKey),
139
140
  PublicKey.toBytes(normalizePublicKey(publicKey)),
140
- true, // compressed
141
141
  )
142
142
  if (as === 'Hex') return Hex.fromBytes(sharedSecret) as never
143
143
  return sharedSecret as never
@@ -190,7 +190,7 @@ export function randomPrivateKey<as extends 'Hex' | 'Bytes' = 'Hex'>(
190
190
  options: randomPrivateKey.Options<as> = {},
191
191
  ): randomPrivateKey.ReturnType<as> {
192
192
  const { as = 'Hex' } = options
193
- const bytes = noble_p256.utils.randomSecretKey()
193
+ const bytes = engine.randomSecretKey()
194
194
  if (as === 'Hex') return Hex.fromBytes(bytes) as never
195
195
  return bytes as never
196
196
  }
@@ -238,11 +238,8 @@ export function recoverPublicKey<
238
238
  >(options: recoverPublicKey.Options<as>): recoverPublicKey.ReturnType<as> {
239
239
  const { as = 'Object', payload, signature } = options
240
240
  const sigBytes = toRecoveredBytes(normalizeSignature<true>(signature))
241
- const point = noble_p256.Signature.fromBytes(
242
- sigBytes,
243
- 'recovered',
244
- ).recoverPublicKey(Bytes.from(payload))
245
- const publicKey = PublicKey.fromBytes(point.toBytes(false))
241
+ const bytes = engine.recoverPublicKey(sigBytes, Bytes.from(payload))
242
+ const publicKey = PublicKey.fromBytes(bytes)
246
243
  return formatPublicKey(publicKey, as) as never
247
244
  }
248
245
 
@@ -302,19 +299,13 @@ export function sign<as extends 'Hex' | 'Bytes' | 'Object' = 'Object'>(
302
299
  payload,
303
300
  privateKey,
304
301
  } = options
305
- const sigBytes = noble_p256.sign(
306
- Bytes.from(payload),
307
- Bytes.from(privateKey),
308
- {
309
- extraEntropy:
310
- typeof extraEntropy === 'boolean'
311
- ? extraEntropy
312
- : Bytes.from(extraEntropy),
313
- lowS: true,
314
- prehash: hash === true,
315
- format: 'recovered',
316
- },
317
- )
302
+ const sigBytes = engine.sign(Bytes.from(payload), Bytes.from(privateKey), {
303
+ extraEntropy:
304
+ typeof extraEntropy === 'boolean'
305
+ ? extraEntropy
306
+ : Bytes.from(extraEntropy),
307
+ prehash: hash === true,
308
+ })
318
309
  const signature = fromRecoveredBytes(sigBytes)
319
310
  return formatSignature(signature, as) as never
320
311
  }
@@ -381,11 +372,11 @@ export declare namespace sign {
381
372
  */
382
373
  export function verify(options: verify.Options): boolean {
383
374
  const { hash, payload, publicKey, signature } = options
384
- return noble_p256.verify(
375
+ return engine.verify(
385
376
  toCompactBytes(normalizeSignature(signature)),
386
377
  Bytes.from(payload),
387
378
  PublicKey.toBytes(normalizePublicKey(publicKey)),
388
- { lowS: true, prehash: hash === true },
379
+ { prehash: hash === true },
389
380
  )
390
381
  }
391
382
 
@@ -1,7 +1,5 @@
1
- // Track B / transactions: bench coverage for the RLP encode/decode hot
2
- // paths exercised by every TxEnvelope codec. The encoding thread owns
3
- // codec-only bench naming under `src/core/Rlp.bench.ts`; this file scopes
4
- // to transaction-shaped inputs to avoid collision.
1
+ // Scoped to transaction-shaped inputs, the RLP paths every TxEnvelope codec
2
+ // exercises. `src/core/Rlp.bench.ts` covers RLP on its own terms.
5
3
 
6
4
  import { bench, describe } from 'vp/test'
7
5
  import * as Hex from './Hex.js'
@@ -9,6 +9,7 @@ import {
9
9
  normalizePublicKey,
10
10
  normalizeSignature,
11
11
  } from './internal/cryptoIo.js'
12
+ import * as engine from './internal/secp256k1.js'
12
13
  import * as Entropy from './internal/entropy.js'
13
14
  import {
14
15
  fromRecoveredBytes,
@@ -89,7 +90,7 @@ export function getPublicKey<as extends 'Hex' | 'Bytes' | 'Object' = 'Object'>(
89
90
  options: getPublicKey.Options<as>,
90
91
  ): getPublicKey.ReturnType<as> {
91
92
  const { as = 'Object', privateKey } = options
92
- const bytes = secp256k1.getPublicKey(Bytes.from(privateKey), false)
93
+ const bytes = engine.getPublicKey(Bytes.from(privateKey))
93
94
  const publicKey = PublicKey.fromBytes(bytes)
94
95
  return formatPublicKey(publicKey, as) as never
95
96
  }
@@ -142,10 +143,9 @@ export function getSharedSecret<as extends 'Hex' | 'Bytes' = 'Hex'>(
142
143
  options: getSharedSecret.Options<as>,
143
144
  ): getSharedSecret.ReturnType<as> {
144
145
  const { as = 'Hex', privateKey, publicKey } = options
145
- const sharedSecret = secp256k1.getSharedSecret(
146
+ const sharedSecret = engine.getSharedSecret(
146
147
  Bytes.from(privateKey),
147
148
  PublicKey.toBytes(normalizePublicKey(publicKey)),
148
- true, // compressed
149
149
  )
150
150
  if (as === 'Hex') return Hex.fromBytes(sharedSecret) as never
151
151
  return sharedSecret as never
@@ -199,7 +199,7 @@ export function randomPrivateKey<as extends 'Hex' | 'Bytes' = 'Hex'>(
199
199
  options: randomPrivateKey.Options<as> = {},
200
200
  ): randomPrivateKey.ReturnType<as> {
201
201
  const { as = 'Hex' } = options
202
- const bytes = secp256k1.utils.randomSecretKey()
202
+ const bytes = engine.randomSecretKey()
203
203
  if (as === 'Hex') return Hex.fromBytes(bytes) as never
204
204
  return bytes as never
205
205
  }
@@ -296,11 +296,8 @@ export function recoverPublicKey<
296
296
  >(options: recoverPublicKey.Options<as>): recoverPublicKey.ReturnType<as> {
297
297
  const { as = 'Object', payload, signature } = options
298
298
  const sigBytes = toRecoveredBytes(normalizeSignature<true>(signature))
299
- const point = secp256k1.Signature.fromBytes(
300
- sigBytes,
301
- 'recovered',
302
- ).recoverPublicKey(Bytes.from(payload))
303
- const publicKey = PublicKey.fromBytes(point.toBytes(false))
299
+ const bytes = engine.recoverPublicKey(sigBytes, Bytes.from(payload))
300
+ const publicKey = PublicKey.fromBytes(bytes)
304
301
  return formatPublicKey(publicKey, as) as never
305
302
  }
306
303
 
@@ -360,14 +357,12 @@ export function sign<as extends 'Hex' | 'Bytes' | 'Object' = 'Object'>(
360
357
  payload,
361
358
  privateKey,
362
359
  } = options
363
- const sigBytes = secp256k1.sign(Bytes.from(payload), Bytes.from(privateKey), {
360
+ const sigBytes = engine.sign(Bytes.from(payload), Bytes.from(privateKey), {
364
361
  extraEntropy:
365
362
  typeof extraEntropy === 'boolean'
366
363
  ? extraEntropy
367
364
  : Bytes.from(extraEntropy),
368
- lowS: true,
369
365
  prehash: hash === true,
370
- format: 'recovered',
371
366
  })
372
367
  const signature = fromRecoveredBytes(sigBytes)
373
368
  return formatSignature(signature, as) as never
@@ -462,11 +457,11 @@ export function verify(options: verify.Options): boolean {
462
457
  recoverAddress({ payload, signature: options.signature }),
463
458
  )
464
459
  const sig = normalizeSignature(options.signature)
465
- return secp256k1.verify(
460
+ return engine.verify(
466
461
  toCompactBytes(sig),
467
462
  Bytes.from(payload),
468
463
  PublicKey.toBytes(normalizePublicKey(options.publicKey)),
469
- { lowS: true, prehash: hash === true },
464
+ { prehash: hash === true },
470
465
  )
471
466
  }
472
467
 
@@ -1,8 +1,3 @@
1
- // Track B / transactions: bench coverage for the EIP-1559 envelope
2
- // serialize / deserialize / hash hot paths. Targets per
3
- // `tasks/perf-api-audit-transactions.md` Phase 1: ≥ 2× faster envelope
4
- // serialize and deserialize at the same input.
5
-
6
1
  import { bench, describe } from 'vp/test'
7
2
  import * as TxEnvelopeEip1559 from './TxEnvelopeEip1559.js'
8
3
 
@@ -1,4 +1,3 @@
1
- import { p256 } from '@noble/curves/nist.js'
2
1
  import * as Bytes from './Bytes.js'
3
2
  import * as Errors from './Errors.js'
4
3
  import * as Hex from './Hex.js'
@@ -7,12 +6,13 @@ import {
7
6
  normalizePublicKey,
8
7
  normalizeSignature,
9
8
  } from './internal/cryptoIo.js'
9
+ import { p256N } from './internal/curves.js'
10
10
  import type { Compute } from './internal/types.js'
11
11
  import * as PublicKey from './PublicKey.js'
12
12
  import type * as Signature from './Signature.js'
13
13
 
14
14
  /** secp256r1 / P-256 curve order. */
15
- const N = p256.Point.CURVE().n
15
+ const N = p256N
16
16
 
17
17
  /**
18
18
  * Generates an ECDSA P256 key pair that includes:
@@ -2,6 +2,7 @@ import { x25519 } from '@noble/curves/ed25519.js'
2
2
  import * as Bytes from './Bytes.js'
3
3
  import type * as Errors from './Errors.js'
4
4
  import * as Hex from './Hex.js'
5
+ import * as engine from './internal/x25519.js'
5
6
 
6
7
  /** Re-export of noble/curves X25519 utilities. */
7
8
  export const noble = x25519
@@ -77,7 +78,7 @@ export function getPublicKey<as extends 'Hex' | 'Bytes' = 'Hex'>(
77
78
  ): getPublicKey.ReturnType<as> {
78
79
  const { as = 'Hex', privateKey } = options
79
80
  const privateKeyBytes = Bytes.from(privateKey)
80
- const publicKeyBytes = x25519.getPublicKey(privateKeyBytes)
81
+ const publicKeyBytes = engine.getPublicKey(privateKeyBytes)
81
82
  if (as === 'Hex') return Hex.fromBytes(publicKeyBytes) as never
82
83
  return publicKeyBytes as never
83
84
  }
@@ -130,7 +131,7 @@ export function getSharedSecret<as extends 'Hex' | 'Bytes' = 'Hex'>(
130
131
  const { as = 'Hex', privateKey, publicKey } = options
131
132
  const privateKeyBytes = Bytes.from(privateKey)
132
133
  const publicKeyBytes = Bytes.from(publicKey)
133
- const sharedSecretBytes = x25519.getSharedSecret(
134
+ const sharedSecretBytes = engine.getSharedSecret(
134
135
  privateKeyBytes,
135
136
  publicKeyBytes,
136
137
  )
@@ -182,7 +183,7 @@ export function randomPrivateKey<as extends 'Hex' | 'Bytes' = 'Hex'>(
182
183
  options: randomPrivateKey.Options<as> = {},
183
184
  ): randomPrivateKey.ReturnType<as> {
184
185
  const { as = 'Hex' } = options
185
- const bytes = x25519.utils.randomSecretKey()
186
+ const bytes = engine.randomSecretKey()
186
187
  if (as === 'Hex') return Hex.fromBytes(bytes) as never
187
188
  return bytes as never
188
189
  }
@@ -3,8 +3,7 @@ import { AbiParameters, Bytes, Hex } from 'ox'
3
3
  import { describe, expect } from 'vp/test'
4
4
 
5
5
  import { arbitraryAbiCase } from '../../../test/fuzz/arbitraries/abi.js'
6
-
7
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
6
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
8
7
 
9
8
  /**
10
9
  * Whitelist of error names ox is allowed to throw on adversarial-but-
@@ -3,8 +3,7 @@ import { AbiParameters } from 'ox'
3
3
  import { describe, expect } from 'vp/test'
4
4
 
5
5
  import { arbitraryAbiCase } from '../../../test/fuzz/arbitraries/abi.js'
6
-
7
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
6
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
8
7
 
9
8
  describe('AbiParameters round-trip', () => {
10
9
  test.prop(
@@ -3,8 +3,7 @@ import { Base32, Base58, Base64 } from 'ox'
3
3
  import { describe, expect } from 'vp/test'
4
4
 
5
5
  import { arbitraryBytes } from '../../../test/fuzz/arbitraries/bytes.js'
6
-
7
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
6
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
8
7
 
9
8
  describe('Base32 round-trip', () => {
10
9
  test.prop({ bytes: arbitraryBytes() }, { numRuns })(
@@ -1,8 +1,7 @@
1
1
  import { fc, test } from '@fast-check/vitest'
2
2
  import { Cbor } from 'ox'
3
3
  import { describe, expect } from 'vp/test'
4
-
5
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
4
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
6
5
 
7
6
  /**
8
7
  * Recursive CBOR-encodable value. ox's Cbor module supports:
@@ -0,0 +1,293 @@
1
+ import { fc, test } from '@fast-check/vitest'
2
+ import {
3
+ Bls,
4
+ Ed25519,
5
+ Engine,
6
+ Hash,
7
+ Keystore,
8
+ Mnemonic,
9
+ P256,
10
+ Secp256k1,
11
+ X25519,
12
+ } from 'ox'
13
+ import { afterEach, describe, expect } from 'vp/test'
14
+ import { identity, sentinel } from '../../../test/engines.js'
15
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
16
+
17
+ const arbitraryBytes = fc.uint8Array({ maxLength: 256, minLength: 0 })
18
+ /**
19
+ * A 32-byte scalar that is a valid private key on every curve ox uses.
20
+ *
21
+ * Built compositionally rather than by filtering, so no candidate is ever
22
+ * rejected: a leading byte in `[0x01, 0x72]` is clear of zero and below every
23
+ * order in play. BLS12-381 sets the ceiling, not the 256-bit curves -- its
24
+ * scalar field is `0x73eda753…`, so anything above `0x72` reduces rather than
25
+ * being used as given, which would quietly narrow what the fuzz explores.
26
+ */
27
+ const arbitraryPrivateKey = fc
28
+ .tuple(
29
+ fc.integer({ max: 0x72, min: 0x01 }),
30
+ fc.uint8Array({ maxLength: 31, minLength: 31 }),
31
+ )
32
+ .map(([first, rest]) => Uint8Array.of(first, ...rest))
33
+
34
+ afterEach(() => {
35
+ Engine.reset()
36
+ })
37
+
38
+ /**
39
+ * Each entry runs one routed call site. `run` must be deterministic given its
40
+ * inputs, so that the same call before and after installing an engine can be
41
+ * compared directly.
42
+ */
43
+ const callSites = [
44
+ {
45
+ name: 'Hash.keccak256',
46
+ run: (bytes: Uint8Array) => Hash.keccak256(bytes),
47
+ },
48
+ {
49
+ name: 'Hash.sha256',
50
+ run: (bytes: Uint8Array) => Hash.sha256(bytes),
51
+ },
52
+ {
53
+ name: 'Hash.ripemd160',
54
+ run: (bytes: Uint8Array) => Hash.ripemd160(bytes),
55
+ },
56
+ {
57
+ name: 'Hash.hmac256',
58
+ run: (bytes: Uint8Array) => Hash.hmac256(new Uint8Array(32).fill(7), bytes),
59
+ },
60
+ ] as const
61
+
62
+ describe('identity engine', () => {
63
+ test.prop({ bytes: arbitraryBytes }, { numRuns })(
64
+ 'leaves every hash call site unchanged',
65
+ ({ bytes }) => {
66
+ const before = callSites.map((site) => site.run(bytes))
67
+ Engine.set(identity)
68
+ expect(callSites.map((site) => site.run(bytes))).toEqual(before)
69
+ },
70
+ )
71
+
72
+ test.prop(
73
+ { payload: arbitraryBytes, privateKey: arbitraryPrivateKey },
74
+ {
75
+ numRuns,
76
+ },
77
+ )('leaves secp256k1 unchanged', ({ payload, privateKey }) => {
78
+ const before = {
79
+ publicKey: Secp256k1.getPublicKey({ privateKey }),
80
+ signature: Secp256k1.sign({ payload, privateKey }),
81
+ }
82
+ const recovered = Secp256k1.recoverPublicKey({
83
+ payload,
84
+ signature: before.signature,
85
+ })
86
+
87
+ Engine.set(identity)
88
+
89
+ expect(Secp256k1.getPublicKey({ privateKey })).toEqual(before.publicKey)
90
+ expect(Secp256k1.sign({ payload, privateKey })).toEqual(before.signature)
91
+ expect(
92
+ Secp256k1.recoverPublicKey({ payload, signature: before.signature }),
93
+ ).toEqual(recovered)
94
+ expect(
95
+ Secp256k1.verify({
96
+ payload,
97
+ publicKey: before.publicKey,
98
+ signature: before.signature,
99
+ }),
100
+ ).toBe(true)
101
+ })
102
+
103
+ test.prop(
104
+ { payload: arbitraryBytes, privateKey: arbitraryPrivateKey },
105
+ {
106
+ numRuns,
107
+ },
108
+ )('leaves P256 unchanged', ({ payload, privateKey }) => {
109
+ const publicKey = P256.getPublicKey({ privateKey })
110
+ const signature = P256.sign({ payload, privateKey })
111
+
112
+ Engine.set(identity)
113
+
114
+ expect(P256.getPublicKey({ privateKey })).toEqual(publicKey)
115
+ expect(P256.sign({ payload, privateKey })).toEqual(signature)
116
+ expect(P256.verify({ payload, publicKey, signature })).toBe(true)
117
+ })
118
+
119
+ test.prop(
120
+ { payload: arbitraryBytes, privateKey: arbitraryPrivateKey },
121
+ { numRuns },
122
+ )('leaves Ed25519 and X25519 unchanged', ({ payload, privateKey }) => {
123
+ const before = {
124
+ ed25519PublicKey: Ed25519.getPublicKey({ privateKey }),
125
+ signature: Ed25519.sign({ payload, privateKey }),
126
+ x25519PublicKey: X25519.getPublicKey({ privateKey }),
127
+ }
128
+ const sharedSecret = X25519.getSharedSecret({
129
+ privateKey,
130
+ publicKey: before.x25519PublicKey,
131
+ })
132
+
133
+ Engine.set(identity)
134
+
135
+ expect(Ed25519.getPublicKey({ privateKey })).toEqual(
136
+ before.ed25519PublicKey,
137
+ )
138
+ expect(Ed25519.sign({ payload, privateKey })).toEqual(before.signature)
139
+ expect(X25519.getPublicKey({ privateKey })).toEqual(before.x25519PublicKey)
140
+ expect(
141
+ X25519.getSharedSecret({
142
+ privateKey,
143
+ publicKey: before.x25519PublicKey,
144
+ }),
145
+ ).toEqual(sharedSecret)
146
+ })
147
+
148
+ test.prop({ password: fc.string({ maxLength: 64 }) }, { numRuns: 10 })(
149
+ 'leaves Keystore key derivation unchanged',
150
+ ({ password }) => {
151
+ // The smallest parameters ox accepts -- these run per { numRuns } case, and
152
+ // scrypt's real defaults take seconds. `iv` is pinned because it defaults
153
+ // to random, and the returned key is a thunk, so compare derived material
154
+ // rather than the returned tuple.
155
+ const options = {
156
+ iv: '0x6087dab2f9fdbbfaddc31a909735c1e6',
157
+ password,
158
+ salt: '0xae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd',
159
+ } as const
160
+ const before = {
161
+ pbkdf2: Keystore.pbkdf2({ ...options, iterations: 1000 })[0](),
162
+ scrypt: Keystore.scrypt({ ...options, n: 1024, p: 1, r: 1 })[0](),
163
+ }
164
+
165
+ Engine.set(identity)
166
+
167
+ expect(Keystore.pbkdf2({ ...options, iterations: 1000 })[0]()).toEqual(
168
+ before.pbkdf2,
169
+ )
170
+ expect(Keystore.scrypt({ ...options, n: 1024, p: 1, r: 1 })[0]()).toEqual(
171
+ before.scrypt,
172
+ )
173
+ },
174
+ )
175
+
176
+ test.prop(
177
+ { payload: arbitraryBytes, privateKey: arbitraryPrivateKey },
178
+ { numRuns: Math.min(numRuns, 10) },
179
+ )('leaves Bls unchanged', ({ payload, privateKey }) => {
180
+ const before = {
181
+ publicKey: Bls.getPublicKey({ privateKey }),
182
+ signature: Bls.sign({ payload, privateKey }),
183
+ }
184
+
185
+ Engine.set(identity)
186
+
187
+ expect(Bls.getPublicKey({ privateKey })).toEqual(before.publicKey)
188
+ expect(Bls.sign({ payload, privateKey })).toEqual(before.signature)
189
+ expect(
190
+ Bls.verify({
191
+ payload,
192
+ publicKey: before.publicKey,
193
+ signature: before.signature,
194
+ }),
195
+ ).toBe(true)
196
+ })
197
+
198
+ test.prop({ passphrase: fc.string({ maxLength: 32 }) }, { numRuns })(
199
+ 'leaves Mnemonic.toSeed unchanged',
200
+ ({ passphrase }) => {
201
+ const mnemonic =
202
+ 'test test test test test test test test test test test junk'
203
+ const before = Mnemonic.toSeed(mnemonic, { passphrase })
204
+ Engine.set(identity)
205
+ expect(Mnemonic.toSeed(mnemonic, { passphrase })).toEqual(before)
206
+ },
207
+ )
208
+ })
209
+
210
+ describe('sentinel engine', () => {
211
+ test.prop({ bytes: arbitraryBytes }, { numRuns })(
212
+ 'is consulted by every hash call site',
213
+ ({ bytes }) => {
214
+ // The inverse of the identity property: if a result survives a wrong
215
+ // implementation, the call site never reached the engine at all.
216
+ const before = callSites.map((site) => site.run(bytes))
217
+ Engine.set(sentinel)
218
+ const after = callSites.map((site) => site.run(bytes))
219
+ for (const [index, site] of callSites.entries())
220
+ expect(after[index], site.name).not.toEqual(before[index])
221
+ },
222
+ )
223
+ })
224
+
225
+ describe('with', () => {
226
+ test.prop(
227
+ { bytes: arbitraryBytes, depth: fc.integer({ max: 6, min: 1 }) },
228
+ { numRuns },
229
+ )('restores the engine at every nesting depth', ({ bytes, depth }) => {
230
+ const expected = Hash.keccak256(bytes)
231
+
232
+ function nest(level: number): void {
233
+ if (level === 0) return
234
+ Engine.with(
235
+ { Hash: { keccak256: () => new Uint8Array(32).fill(level) } },
236
+ () => {
237
+ expect(Hash.keccak256(bytes, { as: 'Bytes' })[0]).toBe(level)
238
+ nest(level - 1)
239
+ // Unwinding must restore *this* level, not the default.
240
+ expect(Hash.keccak256(bytes, { as: 'Bytes' })[0]).toBe(level)
241
+ },
242
+ )
243
+ }
244
+
245
+ nest(depth)
246
+ expect(Engine.get()).toEqual({})
247
+ expect(Hash.keccak256(bytes)).toEqual(expected)
248
+ })
249
+
250
+ test.prop({ bytes: arbitraryBytes }, { numRuns })(
251
+ 'restores the engine when the callback throws',
252
+ ({ bytes }) => {
253
+ const expected = Hash.keccak256(bytes)
254
+ expect(() =>
255
+ Engine.with({ Hash: { keccak256: () => new Uint8Array(32) } }, () => {
256
+ throw new Error('boom')
257
+ }),
258
+ ).toThrow('boom')
259
+ expect(Hash.keccak256(bytes)).toEqual(expected)
260
+ },
261
+ )
262
+ })
263
+
264
+ describe('merge', () => {
265
+ const slotNames = ['Hash', 'Secp256k1', 'P256', 'Ed25519', 'X25519'] as const
266
+
267
+ test.prop(
268
+ {
269
+ slots: fc.array(fc.constantFrom(...slotNames), {
270
+ maxLength: 8,
271
+ minLength: 1,
272
+ }),
273
+ },
274
+ { numRuns },
275
+ )('accumulates slots across calls in any order', ({ slots }) => {
276
+ for (const slot of slots) Engine.set({ [slot]: {} })
277
+ expect(Object.keys(Engine.get()).sort()).toEqual([...new Set(slots)].sort())
278
+ })
279
+
280
+ test.prop(
281
+ {
282
+ slots: fc.array(fc.constantFrom(...slotNames), {
283
+ maxLength: 8,
284
+ minLength: 1,
285
+ }),
286
+ },
287
+ { numRuns },
288
+ )('reset clears whatever was accumulated', ({ slots }) => {
289
+ for (const slot of slots) Engine.set({ [slot]: {} })
290
+ Engine.reset()
291
+ expect(Engine.get()).toEqual({})
292
+ })
293
+ })
@@ -0,0 +1,84 @@
1
+ import { Engine } from 'ox'
2
+ import { describe, expectTypeOf, test } from 'vp/test'
3
+ import type { primitives } from '../internal/engine.js'
4
+
5
+ /**
6
+ * Primitives on a slot contract that the runtime name table omits.
7
+ *
8
+ * `set` rejects any name absent from that table, so anything showing up here
9
+ * would be a valid override refused at runtime.
10
+ */
11
+ type Unlisted = {
12
+ // `-?` because the slots are optional, which would otherwise leave
13
+ // `undefined` in the union and mask a genuinely missing name.
14
+ [key in keyof Engine.Engine]-?: Exclude<
15
+ keyof NonNullable<Engine.Engine[key]>,
16
+ (typeof primitives)[key][number]
17
+ >
18
+ }[keyof Engine.Engine]
19
+
20
+ describe('set', () => {
21
+ test('accepts a partial engine', () => {
22
+ expectTypeOf(Engine.set).parameter(0).toEqualTypeOf<Engine.Engine>()
23
+ })
24
+
25
+ test('rejects an unknown slot', () => {
26
+ // @ts-expect-error
27
+ Engine.set({ Keccak: {} })
28
+ })
29
+
30
+ test('rejects a misspelled function', () => {
31
+ // @ts-expect-error
32
+ Engine.set({ Hash: { keccak_256: (input: Uint8Array) => input } })
33
+ })
34
+
35
+ test('rejects a mistyped function', () => {
36
+ // @ts-expect-error
37
+ Engine.set({ Hash: { keccak256: (input: string) => input } })
38
+ })
39
+
40
+ test('the runtime name table lists every primitive it must accept', () => {
41
+ expectTypeOf<Unlisted>().toEqualTypeOf<never>()
42
+ })
43
+ })
44
+
45
+ describe('get', () => {
46
+ test('default', () => {
47
+ expectTypeOf(Engine.get()).toEqualTypeOf<Engine.Engine>()
48
+ })
49
+ })
50
+
51
+ describe('reset', () => {
52
+ test('accepts a slot name', () => {
53
+ expectTypeOf(Engine.reset)
54
+ .parameter(0)
55
+ .toEqualTypeOf<keyof Engine.Engine | undefined>()
56
+ })
57
+ })
58
+
59
+ describe('with', () => {
60
+ test('preserves the return type', () => {
61
+ expectTypeOf(Engine.with({}, () => 1 as const)).toEqualTypeOf<1>()
62
+ })
63
+ })
64
+
65
+ describe('Engine', () => {
66
+ test('the Secp256k1 and P256 slots share the Ecdsa contract', () => {
67
+ expectTypeOf<Engine.Engine['Secp256k1']>().toEqualTypeOf<
68
+ Engine.Ecdsa | undefined
69
+ >()
70
+ expectTypeOf<Engine.Engine['P256']>().toEqualTypeOf<
71
+ Engine.Ecdsa | undefined
72
+ >()
73
+ })
74
+
75
+ test('every slot is optional', () => {
76
+ expectTypeOf<Engine.Engine>().toEqualTypeOf<Partial<Engine.Engine>>()
77
+ })
78
+
79
+ test('contracts speak raw bytes, not branded ox types', () => {
80
+ expectTypeOf<NonNullable<Engine.Hash['keccak256']>>().toEqualTypeOf<
81
+ (input: Uint8Array) => Uint8Array
82
+ >()
83
+ })
84
+ })