ox 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/core/Engine.d.ts +43 -11
  3. package/dist/core/Engine.d.ts.map +1 -1
  4. package/dist/core/Engine.js +40 -10
  5. package/dist/core/Engine.js.map +1 -1
  6. package/dist/core/Hash.d.ts +36 -0
  7. package/dist/core/Hash.d.ts.map +1 -1
  8. package/dist/core/Hash.js +35 -0
  9. package/dist/core/Hash.js.map +1 -1
  10. package/dist/core/internal/engine.d.ts +8 -8
  11. package/dist/core/internal/engine.d.ts.map +1 -1
  12. package/dist/index.d.ts +5 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +5 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/node/Ed25519.d.ts +33 -0
  17. package/dist/node/Ed25519.d.ts.map +1 -0
  18. package/dist/node/Ed25519.js +67 -0
  19. package/dist/node/Ed25519.js.map +1 -0
  20. package/dist/node/Engine.d.ts +29 -16
  21. package/dist/node/Engine.d.ts.map +1 -1
  22. package/dist/node/Engine.js +38 -14
  23. package/dist/node/Engine.js.map +1 -1
  24. package/dist/node/Hash.d.ts +17 -19
  25. package/dist/node/Hash.d.ts.map +1 -1
  26. package/dist/node/Hash.js +17 -19
  27. package/dist/node/Hash.js.map +1 -1
  28. package/dist/node/Keystore.d.ts +36 -0
  29. package/dist/node/Keystore.d.ts.map +1 -0
  30. package/dist/node/Keystore.js +72 -0
  31. package/dist/node/Keystore.js.map +1 -0
  32. package/dist/node/Mnemonic.d.ts +35 -0
  33. package/dist/node/Mnemonic.d.ts.map +1 -0
  34. package/dist/node/Mnemonic.js +46 -0
  35. package/dist/node/Mnemonic.js.map +1 -0
  36. package/dist/node/P256.d.ts +32 -0
  37. package/dist/node/P256.d.ts.map +1 -0
  38. package/dist/node/P256.js +34 -0
  39. package/dist/node/P256.js.map +1 -0
  40. package/dist/node/X25519.d.ts +31 -0
  41. package/dist/node/X25519.d.ts.map +1 -0
  42. package/dist/node/X25519.js +71 -0
  43. package/dist/node/X25519.js.map +1 -0
  44. package/dist/node/index.d.ts +95 -8
  45. package/dist/node/index.d.ts.map +1 -1
  46. package/dist/node/index.js +95 -8
  47. package/dist/node/index.js.map +1 -1
  48. package/dist/trusted-setups/Paths.d.ts +1 -0
  49. package/dist/trusted-setups/Paths.d.ts.map +1 -1
  50. package/dist/trusted-setups/Paths.js +1 -0
  51. package/dist/trusted-setups/Paths.js.map +1 -1
  52. package/dist/trusted-setups/Setups.d.ts +35 -0
  53. package/dist/trusted-setups/Setups.d.ts.map +1 -0
  54. package/dist/trusted-setups/Setups.js +29 -0
  55. package/dist/trusted-setups/Setups.js.map +1 -0
  56. package/dist/trusted-setups/index.d.ts +20 -0
  57. package/dist/trusted-setups/index.d.ts.map +1 -1
  58. package/dist/trusted-setups/index.js +20 -0
  59. package/dist/trusted-setups/index.js.map +1 -1
  60. package/dist/trusted-setups/internal/paths.d.ts.map +1 -1
  61. package/dist/trusted-setups/internal/paths.js +1 -1
  62. package/dist/trusted-setups/internal/paths.js.map +1 -1
  63. package/dist/trusted-setups/internal/setups/mainnet.d.ts +7 -0
  64. package/dist/trusted-setups/internal/setups/mainnet.d.ts.map +1 -0
  65. package/dist/trusted-setups/internal/setups/mainnet.js +11 -0
  66. package/dist/trusted-setups/internal/setups/mainnet.js.map +1 -0
  67. package/dist/wasm/Ed25519.d.ts +35 -0
  68. package/dist/wasm/Ed25519.d.ts.map +1 -0
  69. package/dist/wasm/Ed25519.js +88 -0
  70. package/dist/wasm/Ed25519.js.map +1 -0
  71. package/dist/wasm/Engine.d.ts +36 -26
  72. package/dist/wasm/Engine.d.ts.map +1 -1
  73. package/dist/wasm/Engine.js +44 -25
  74. package/dist/wasm/Engine.js.map +1 -1
  75. package/dist/wasm/Hash.d.ts +13 -19
  76. package/dist/wasm/Hash.d.ts.map +1 -1
  77. package/dist/wasm/Hash.js +21 -27
  78. package/dist/wasm/Hash.js.map +1 -1
  79. package/dist/wasm/Keystore.d.ts +51 -0
  80. package/dist/wasm/Keystore.d.ts.map +1 -0
  81. package/dist/wasm/Keystore.js +51 -0
  82. package/dist/wasm/Keystore.js.map +1 -0
  83. package/dist/wasm/Kzg.d.ts +112 -0
  84. package/dist/wasm/Kzg.d.ts.map +1 -0
  85. package/dist/wasm/Kzg.js +333 -0
  86. package/dist/wasm/Kzg.js.map +1 -0
  87. package/dist/wasm/Mnemonic.d.ts +33 -0
  88. package/dist/wasm/Mnemonic.d.ts.map +1 -0
  89. package/dist/wasm/Mnemonic.js +30 -0
  90. package/dist/wasm/Mnemonic.js.map +1 -0
  91. package/dist/wasm/Secp256k1.d.ts +55 -0
  92. package/dist/wasm/Secp256k1.d.ts.map +1 -0
  93. package/dist/wasm/Secp256k1.js +181 -0
  94. package/dist/wasm/Secp256k1.js.map +1 -0
  95. package/dist/wasm/X25519.d.ts +33 -0
  96. package/dist/wasm/X25519.d.ts.map +1 -0
  97. package/dist/wasm/X25519.js +53 -0
  98. package/dist/wasm/X25519.js.map +1 -0
  99. package/dist/wasm/index.d.ts +124 -12
  100. package/dist/wasm/index.d.ts.map +1 -1
  101. package/dist/wasm/index.js +124 -12
  102. package/dist/wasm/index.js.map +1 -1
  103. package/dist/wasm/internal/blake3.d.ts +11 -0
  104. package/dist/wasm/internal/blake3.d.ts.map +1 -0
  105. package/dist/wasm/internal/blake3.js +31 -0
  106. package/dist/wasm/internal/blake3.js.map +1 -0
  107. package/dist/wasm/internal/blake3.wasm.d.ts +3 -0
  108. package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -0
  109. package/dist/wasm/internal/blake3.wasm.js +10 -0
  110. package/dist/wasm/internal/blake3.wasm.js.map +1 -0
  111. package/dist/wasm/internal/crypto25519.d.ts +16 -0
  112. package/dist/wasm/internal/crypto25519.d.ts.map +1 -0
  113. package/dist/wasm/internal/crypto25519.js +5 -0
  114. package/dist/wasm/internal/crypto25519.js.map +1 -0
  115. package/dist/wasm/internal/crypto25519.wasm.d.ts +3 -0
  116. package/dist/wasm/internal/crypto25519.wasm.d.ts.map +1 -0
  117. package/dist/wasm/internal/crypto25519.wasm.js +10 -0
  118. package/dist/wasm/internal/crypto25519.wasm.js.map +1 -0
  119. package/dist/wasm/internal/ed25519.d.ts +4 -0
  120. package/dist/wasm/internal/ed25519.d.ts.map +1 -0
  121. package/dist/wasm/internal/ed25519.js +21 -0
  122. package/dist/wasm/internal/ed25519.js.map +1 -0
  123. package/dist/wasm/internal/hashes.d.ts +12 -0
  124. package/dist/wasm/internal/hashes.d.ts.map +1 -0
  125. package/dist/wasm/internal/hashes.js +8 -0
  126. package/dist/wasm/internal/hashes.js.map +1 -0
  127. package/dist/wasm/internal/hashes.wasm.d.ts +4 -2
  128. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
  129. package/dist/wasm/internal/hashes.wasm.js +4 -2
  130. package/dist/wasm/internal/hashes.wasm.js.map +1 -1
  131. package/dist/wasm/internal/instantiate.d.ts +12 -0
  132. package/dist/wasm/internal/instantiate.d.ts.map +1 -1
  133. package/dist/wasm/internal/instantiate.js +23 -7
  134. package/dist/wasm/internal/instantiate.js.map +1 -1
  135. package/dist/wasm/internal/keystore.d.ts +10 -0
  136. package/dist/wasm/internal/keystore.d.ts.map +1 -0
  137. package/dist/wasm/internal/keystore.js +10 -0
  138. package/dist/wasm/internal/keystore.js.map +1 -0
  139. package/dist/wasm/internal/kzg.d.ts +17 -0
  140. package/dist/wasm/internal/kzg.d.ts.map +1 -0
  141. package/dist/wasm/internal/kzg.js +8 -0
  142. package/dist/wasm/internal/kzg.js.map +1 -0
  143. package/dist/wasm/internal/kzg.wasm.d.ts +3 -0
  144. package/dist/wasm/internal/kzg.wasm.d.ts.map +1 -0
  145. package/dist/wasm/internal/kzg.wasm.js +10 -0
  146. package/dist/wasm/internal/kzg.wasm.js.map +1 -0
  147. package/dist/wasm/internal/mnemonic.d.ts +4 -0
  148. package/dist/wasm/internal/mnemonic.d.ts.map +1 -0
  149. package/dist/wasm/internal/mnemonic.js +45 -0
  150. package/dist/wasm/internal/mnemonic.js.map +1 -0
  151. package/dist/wasm/internal/pbkdf2.d.ts +12 -0
  152. package/dist/wasm/internal/pbkdf2.d.ts.map +1 -0
  153. package/dist/wasm/internal/pbkdf2.js +51 -0
  154. package/dist/wasm/internal/pbkdf2.js.map +1 -0
  155. package/dist/wasm/internal/scrypt.d.ts +16 -0
  156. package/dist/wasm/internal/scrypt.d.ts.map +1 -0
  157. package/dist/wasm/internal/scrypt.js +78 -0
  158. package/dist/wasm/internal/scrypt.js.map +1 -0
  159. package/dist/wasm/internal/scrypt.wasm.d.ts +7 -0
  160. package/dist/wasm/internal/scrypt.wasm.d.ts.map +1 -0
  161. package/dist/wasm/internal/scrypt.wasm.js +14 -0
  162. package/dist/wasm/internal/scrypt.wasm.js.map +1 -0
  163. package/dist/wasm/internal/secp256k1.d.ts +16 -0
  164. package/dist/wasm/internal/secp256k1.d.ts.map +1 -0
  165. package/dist/wasm/internal/secp256k1.js +20 -0
  166. package/dist/wasm/internal/secp256k1.js.map +1 -0
  167. package/dist/wasm/internal/secp256k1.wasm.d.ts +3 -0
  168. package/dist/wasm/internal/secp256k1.wasm.d.ts.map +1 -0
  169. package/dist/wasm/internal/secp256k1.wasm.js +10 -0
  170. package/dist/wasm/internal/secp256k1.wasm.js.map +1 -0
  171. package/dist/wasm/internal/x25519.d.ts +4 -0
  172. package/dist/wasm/internal/x25519.d.ts.map +1 -0
  173. package/dist/wasm/internal/x25519.js +21 -0
  174. package/dist/wasm/internal/x25519.js.map +1 -0
  175. package/package.json +64 -2
  176. package/src/core/Engine.ts +84 -11
  177. package/src/core/Hash.ts +59 -0
  178. package/src/core/_test/Block.test.ts +9 -2
  179. package/src/core/_test/Engine.test-d.ts +56 -0
  180. package/src/core/_test/Engine.test.ts +95 -0
  181. package/src/core/_test/Hash.test-d.ts +18 -0
  182. package/src/core/_test/Hash.test.ts +100 -1
  183. package/src/core/_test/Hash.vectors.test.ts +7 -0
  184. package/src/core/_test/Transaction.test.ts +11 -2
  185. package/src/core/internal/engine.ts +8 -8
  186. package/src/index.ts +5 -2
  187. package/src/node/Ed25519.ts +87 -0
  188. package/src/node/Engine.ts +79 -23
  189. package/src/node/Hash.ts +25 -30
  190. package/src/node/Keystore.ts +103 -0
  191. package/src/node/Mnemonic.ts +68 -0
  192. package/src/node/P256.ts +48 -0
  193. package/src/node/X25519.ts +93 -0
  194. package/src/node/_test/Curves.fuzz.ts +76 -0
  195. package/src/node/_test/Ed25519.test-d.ts +35 -0
  196. package/src/node/_test/Ed25519.test.ts +220 -0
  197. package/src/node/_test/Engine.test-d.ts +28 -0
  198. package/src/node/_test/Engine.test.ts +62 -22
  199. package/src/node/_test/Hash.test-d.ts +16 -9
  200. package/src/node/_test/Hash.test.ts +10 -11
  201. package/src/node/_test/Keystore.fuzz.ts +58 -0
  202. package/src/node/_test/Keystore.test-d.ts +43 -0
  203. package/src/node/_test/Keystore.test.ts +198 -0
  204. package/src/node/_test/Mnemonic.fuzz.ts +31 -0
  205. package/src/node/_test/Mnemonic.test-d.ts +21 -0
  206. package/src/node/_test/Mnemonic.test.ts +98 -0
  207. package/src/node/_test/P256.test-d.ts +29 -0
  208. package/src/node/_test/P256.test.ts +102 -0
  209. package/src/node/_test/X25519.test-d.ts +28 -0
  210. package/src/node/_test/X25519.test.ts +239 -0
  211. package/src/node/index.ts +100 -8
  212. package/src/trusted-setups/Paths.ts +1 -0
  213. package/src/trusted-setups/README.md +39 -0
  214. package/src/trusted-setups/Setups.ts +46 -0
  215. package/src/trusted-setups/_test/Paths.test.ts +16 -0
  216. package/src/trusted-setups/_test/Setups.test-d.ts +7 -0
  217. package/src/trusted-setups/_test/Setups.test.ts +43 -0
  218. package/src/trusted-setups/_test/index.test.ts +1 -0
  219. package/src/trusted-setups/index.ts +21 -0
  220. package/src/trusted-setups/internal/paths.ts +4 -1
  221. package/src/trusted-setups/internal/setups/mainnet.source.json +7 -0
  222. package/src/trusted-setups/internal/setups/mainnet.ts +16 -0
  223. package/src/version.ts +1 -1
  224. package/src/wasm/Ed25519.ts +114 -0
  225. package/src/wasm/Engine.ts +78 -31
  226. package/src/wasm/Hash.bench.ts +2 -2
  227. package/src/wasm/Hash.ts +29 -50
  228. package/src/wasm/Keystore.bench.ts +26 -0
  229. package/src/wasm/Keystore.ts +66 -0
  230. package/src/wasm/Kzg.bench.ts +63 -0
  231. package/src/wasm/Kzg.ts +518 -0
  232. package/src/wasm/Mnemonic.ts +44 -0
  233. package/src/wasm/Secp256k1.bench.ts +79 -0
  234. package/src/wasm/Secp256k1.ts +248 -0
  235. package/src/wasm/X25519.ts +70 -0
  236. package/src/wasm/_test/Crypto25519.conformance.ts +57 -0
  237. package/src/wasm/_test/Crypto25519.fuzz.ts +187 -0
  238. package/src/wasm/_test/Ed25519.test-d.ts +28 -0
  239. package/src/wasm/_test/Ed25519.test.ts +168 -0
  240. package/src/wasm/_test/Ed25519.vectors.test.ts +73 -0
  241. package/src/wasm/_test/Engine.test-d.ts +29 -0
  242. package/src/wasm/_test/Engine.test.ts +59 -25
  243. package/src/wasm/_test/Hash.browser.test.ts +14 -7
  244. package/src/wasm/_test/Hash.fuzz.ts +58 -23
  245. package/src/wasm/_test/Hash.test-d.ts +16 -10
  246. package/src/wasm/_test/Hash.test.ts +148 -48
  247. package/src/wasm/_test/Hash.vectors.test.ts +15 -8
  248. package/src/wasm/_test/Keystore.browser.test.ts +73 -0
  249. package/src/wasm/_test/Keystore.fuzz.ts +163 -0
  250. package/src/wasm/_test/Keystore.test-d.ts +42 -0
  251. package/src/wasm/_test/Keystore.test.ts +477 -0
  252. package/src/wasm/_test/Keystore.vectors.test.ts +21 -0
  253. package/src/wasm/_test/Kzg.test-d.ts +19 -0
  254. package/src/wasm/_test/Kzg.test.ts +220 -0
  255. package/src/wasm/_test/Mnemonic.test-d.ts +17 -0
  256. package/src/wasm/_test/Mnemonic.test.ts +110 -0
  257. package/src/wasm/_test/Runtime.test.ts +9 -0
  258. package/src/wasm/_test/Secp256k1.test-d.ts +35 -0
  259. package/src/wasm/_test/Secp256k1.test.ts +354 -0
  260. package/src/wasm/_test/X25519.test-d.ts +21 -0
  261. package/src/wasm/_test/X25519.test.ts +194 -0
  262. package/src/wasm/_test/fixtures.ts +4 -0
  263. package/src/wasm/index.ts +130 -12
  264. package/src/wasm/internal/blake3.ts +45 -0
  265. package/src/wasm/internal/blake3.wasm.ts +11 -0
  266. package/src/wasm/internal/crypto25519.ts +43 -0
  267. package/src/wasm/internal/crypto25519.wasm.ts +11 -0
  268. package/src/wasm/internal/ed25519.ts +31 -0
  269. package/src/wasm/internal/hashes.ts +29 -0
  270. package/src/wasm/internal/hashes.wasm.ts +5 -2
  271. package/src/wasm/internal/instantiate.ts +29 -9
  272. package/src/wasm/internal/keystore.ts +19 -0
  273. package/src/wasm/internal/kzg.ts +50 -0
  274. package/src/wasm/internal/kzg.wasm.ts +11 -0
  275. package/src/wasm/internal/mnemonic.ts +56 -0
  276. package/src/wasm/internal/pbkdf2.ts +86 -0
  277. package/src/wasm/internal/scrypt.ts +131 -0
  278. package/src/wasm/internal/scrypt.wasm.ts +17 -0
  279. package/src/wasm/internal/secp256k1.ts +62 -0
  280. package/src/wasm/internal/secp256k1.wasm.ts +11 -0
  281. package/src/wasm/internal/x25519.ts +29 -0
  282. package/wasm/vendor/c-kzg-4844/LICENSE +201 -0
  283. package/wasm/vendor/c-kzg-4844/blst/LICENSE +201 -0
  284. package/src/trusted-setups/internal/setups/mainnet.txt +0 -4163
@@ -0,0 +1,220 @@
1
+ import * as crypto from 'node:crypto'
2
+ import * as fs from 'node:fs'
3
+ import { ed25519 } from '@noble/curves/ed25519.js'
4
+ import { Hex } from 'ox'
5
+ import { describe, expect, test } from 'vp/test'
6
+ import * as Ed25519 from '../Ed25519.js'
7
+
8
+ type Zip215Vector = {
9
+ sig_bytes: string
10
+ valid_legacy: boolean
11
+ valid_zip215: boolean
12
+ vk_bytes: string
13
+ }
14
+
15
+ const publicKeyPrefix = Buffer.from('302a300506032b6570032100', 'hex')
16
+ const zip215Text = fs.readFileSync(
17
+ new URL('../../../test/vectors/ed25519/zip215.json', import.meta.url),
18
+ 'utf8',
19
+ )
20
+ const zip215 = JSON.parse(zip215Text) as readonly Zip215Vector[]
21
+
22
+ const vectors = [
23
+ {
24
+ payload: '',
25
+ privateKey:
26
+ '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60',
27
+ },
28
+ {
29
+ payload: '72',
30
+ privateKey:
31
+ '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb',
32
+ },
33
+ ] as const
34
+
35
+ describe('engine', () => {
36
+ test('behavior: exposes only primitives compatible with Ox semantics', async () => {
37
+ const engine = await Ed25519.engine()
38
+
39
+ // Node verification is deliberately absent because it does not implement
40
+ // the ZIP-215 rules used by Ox's default verifier.
41
+ expect(Object.keys(engine).sort()).toMatchInlineSnapshot(`
42
+ [
43
+ "getPublicKey",
44
+ "sign",
45
+ "toMontgomerySecret",
46
+ ]
47
+ `)
48
+ })
49
+
50
+ test('behavior: matches RFC 8032 test vectors', async () => {
51
+ const engine = await Ed25519.engine()
52
+
53
+ expect(
54
+ vectors.map(({ payload, privateKey }) => ({
55
+ publicKey: Hex.fromBytes(engine.getPublicKey(fromHex(privateKey))),
56
+ signature: Hex.fromBytes(
57
+ engine.sign(fromHex(payload), fromHex(privateKey)),
58
+ ),
59
+ })),
60
+ ).toMatchInlineSnapshot(`
61
+ [
62
+ {
63
+ "publicKey": "0xd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
64
+ "signature": "0xe5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
65
+ },
66
+ {
67
+ "publicKey": "0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
68
+ "signature": "0x92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
69
+ },
70
+ ]
71
+ `)
72
+ })
73
+
74
+ test('behavior: omits verification that disagrees with ZIP-215', async () => {
75
+ const engine = await Ed25519.engine()
76
+ const digest = crypto
77
+ .createHash('sha256')
78
+ .update(JSON.stringify(zip215))
79
+ .digest('hex')
80
+ const message = new TextEncoder().encode('Zcash')
81
+ let defaultAccepted = 0
82
+ let nodeAccepted = 0
83
+
84
+ for (const vector of zip215) {
85
+ const publicKey = fromHex(vector.vk_bytes)
86
+ const signature = fromHex(vector.sig_bytes)
87
+ if (ed25519.verify(signature, message, publicKey)) defaultAccepted++
88
+ if (nodeVerify(vector, message)) nodeAccepted++
89
+ }
90
+
91
+ expect({
92
+ defaultAccepted,
93
+ digest,
94
+ legacyAccepted: zip215.filter((vector) => vector.valid_legacy).length,
95
+ nodeDisagrees: nodeAccepted < defaultAccepted,
96
+ vectors: zip215.length,
97
+ }).toMatchInlineSnapshot(`
98
+ {
99
+ "defaultAccepted": 196,
100
+ "digest": "e99ef30c602df87b8d5970dea07f35bcd5993b003e5682ca2c8dc4dab9de4335",
101
+ "legacyAccepted": 3,
102
+ "nodeDisagrees": true,
103
+ "vectors": 196,
104
+ }
105
+ `)
106
+ expect('verify' in engine).toMatchInlineSnapshot('false')
107
+ })
108
+
109
+ test('behavior: matches the default across messages and private keys', async () => {
110
+ const engine = await Ed25519.engine()
111
+
112
+ for (const [index, { privateKey }] of vectors.entries()) {
113
+ const key = fromHex(privateKey)
114
+ for (const size of [0, 1, 31, 32, 63, 64, 65, 255]) {
115
+ const payload = Uint8Array.from(
116
+ { length: size },
117
+ (_, offset) => (offset * 29 + index) % 251,
118
+ )
119
+ expect(engine.sign(payload, key)).toEqual(ed25519.sign(payload, key))
120
+ }
121
+ expect(engine.getPublicKey(key)).toEqual(ed25519.getPublicKey(key))
122
+ expect(engine.toMontgomerySecret(key)).toEqual(
123
+ ed25519.utils.toMontgomerySecret(key),
124
+ )
125
+ }
126
+ })
127
+
128
+ test('behavior: respects typed-array offsets without mutating inputs', async () => {
129
+ const engine = await Ed25519.engine()
130
+ const privateKey = offsetView(fromHex(vectors[0].privateKey))
131
+ const payload = offsetView(
132
+ Uint8Array.from({ length: 65 }, (_, index) => (index * 17) % 251),
133
+ )
134
+ const privateKeyBefore = privateKey.slice()
135
+ const payloadBefore = payload.slice()
136
+
137
+ expect(engine.getPublicKey(privateKey)).toEqual(
138
+ ed25519.getPublicKey(privateKey),
139
+ )
140
+ expect(engine.sign(payload, privateKey)).toEqual(
141
+ ed25519.sign(payload, privateKey),
142
+ )
143
+ expect(engine.toMontgomerySecret(privateKey)).toEqual(
144
+ ed25519.utils.toMontgomerySecret(privateKey),
145
+ )
146
+ expect({ payload, privateKey }).toEqual({
147
+ payload: payloadBefore,
148
+ privateKey: privateKeyBefore,
149
+ })
150
+ })
151
+
152
+ test('behavior: rejects malformed private-key lengths', async () => {
153
+ const engine = await Ed25519.engine()
154
+
155
+ for (const size of [0, 1, 31, 33, 64]) {
156
+ const privateKey = new Uint8Array(size)
157
+ expect(() => engine.getPublicKey(privateKey)).toThrowError(
158
+ `Ed25519 private key must be 32 bytes, got ${size}`,
159
+ )
160
+ expect(() => engine.sign(new Uint8Array(), privateKey)).toThrowError(
161
+ `Ed25519 private key must be 32 bytes, got ${size}`,
162
+ )
163
+ expect(() => engine.toMontgomerySecret(privateKey)).toThrowError(
164
+ `Ed25519 private key must be 32 bytes, got ${size}`,
165
+ )
166
+ }
167
+ })
168
+
169
+ test('behavior: returns owned Uint8Array values', async () => {
170
+ const engine = await Ed25519.engine()
171
+ const privateKey = fromHex(vectors[0].privateKey)
172
+ const outputs = [
173
+ engine.getPublicKey(privateKey),
174
+ engine.sign(new Uint8Array(), privateKey),
175
+ engine.toMontgomerySecret(privateKey),
176
+ ]
177
+
178
+ expect(outputs.map((output) => output.constructor === Uint8Array))
179
+ .toMatchInlineSnapshot(`
180
+ [
181
+ true,
182
+ true,
183
+ true,
184
+ ]
185
+ `)
186
+ expect(
187
+ engine.getPublicKey(privateKey) === engine.getPublicKey(privateKey),
188
+ ).toMatchInlineSnapshot('false')
189
+ })
190
+
191
+ test('behavior: returns a fresh engine', async () => {
192
+ const first = await Ed25519.engine()
193
+ const second = await Ed25519.engine()
194
+
195
+ expect(first === second).toMatchInlineSnapshot('false')
196
+ })
197
+ })
198
+
199
+ function fromHex(value: string): Uint8Array {
200
+ return Hex.toBytes(`0x${value}`)
201
+ }
202
+
203
+ function offsetView(value: Uint8Array): Uint8Array {
204
+ const bytes = new Uint8Array(value.length + 4).fill(0xff)
205
+ bytes.set(value, 2)
206
+ return bytes.subarray(2, -2)
207
+ }
208
+
209
+ function nodeVerify(vector: Zip215Vector, message: Uint8Array): boolean {
210
+ try {
211
+ const publicKey = crypto.createPublicKey({
212
+ format: 'der',
213
+ key: Buffer.concat([publicKeyPrefix, fromHex(vector.vk_bytes)]),
214
+ type: 'spki',
215
+ })
216
+ return crypto.verify(null, message, publicKey, fromHex(vector.sig_bytes))
217
+ } catch {
218
+ return false
219
+ }
220
+ }
@@ -0,0 +1,28 @@
1
+ import { Engine } from 'ox/node'
2
+ import { expectTypeOf, test } from 'vp/test'
3
+ import * as CoreEngine from '../../core/Engine.js'
4
+
5
+ type Installed = Awaited<ReturnType<typeof Engine.install>>
6
+ type Uninstalled = Awaited<ReturnType<typeof Engine.engine>>
7
+
8
+ test('install and engine expose the same precise engine', () => {
9
+ expectTypeOf<Installed>().toEqualTypeOf<Uninstalled>()
10
+ expectTypeOf<Installed['Hash']['sha256']>().toEqualTypeOf<
11
+ (input: Uint8Array) => Uint8Array
12
+ >()
13
+ })
14
+
15
+ test('the Node Engine namespace exposes synchronous core operations', () => {
16
+ expectTypeOf(Engine.get).toEqualTypeOf(CoreEngine.get)
17
+ expectTypeOf(Engine.InvalidSlotValueError).toEqualTypeOf(
18
+ CoreEngine.InvalidSlotValueError,
19
+ )
20
+ expectTypeOf(Engine.reset).toEqualTypeOf(CoreEngine.reset)
21
+ expectTypeOf(Engine.set).toEqualTypeOf(CoreEngine.set)
22
+ expectTypeOf(Engine.with).toEqualTypeOf(CoreEngine.with)
23
+ })
24
+
25
+ test('the replaced aliases are absent', () => {
26
+ expectTypeOf<typeof Engine>().not.toHaveProperty('create')
27
+ expectTypeOf<typeof Engine>().not.toHaveProperty('load')
28
+ })
@@ -1,22 +1,27 @@
1
- import { Engine as CoreEngine, Hash } from 'ox'
2
- import { Engine } from 'ox/node'
1
+ import { Engine as CoreEngine } from 'ox'
2
+ import { Engine, Hash } from 'ox/node'
3
3
  import { describe, expect, test } from 'vp/test'
4
4
 
5
- describe('create', () => {
5
+ describe('engine', () => {
6
6
  test('behavior: does not install', async () => {
7
- const engine = await Engine.create()
7
+ const engine = await Engine.engine()
8
8
 
9
9
  expect(Object.keys(engine)).toMatchInlineSnapshot(`
10
10
  [
11
+ "Ed25519",
11
12
  "Hash",
13
+ "Keystore",
14
+ "Mnemonic",
15
+ "P256",
16
+ "X25519",
12
17
  ]
13
18
  `)
14
- expect(CoreEngine.get()).toMatchInlineSnapshot('{}')
19
+ expect(Engine.get()).toMatchInlineSnapshot('{}')
15
20
  })
16
21
 
17
22
  test('behavior: installs for the duration of a call', async () => {
18
- const node = await Engine.create()
19
- const digest = CoreEngine.with(node, () => Hash.sha256('0xdeadbeef'))
23
+ const node = await Engine.engine()
24
+ const digest = Engine.with(node, () => Hash.sha256('0xdeadbeef'))
20
25
 
21
26
  expect(digest).toMatchInlineSnapshot(
22
27
  `"0x5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"`,
@@ -25,23 +30,53 @@ describe('create', () => {
25
30
  })
26
31
  })
27
32
 
28
- describe('load', () => {
33
+ describe('install', () => {
29
34
  test('behavior: installs and returns what it installed', async () => {
30
- const engine = await Engine.load()
35
+ const engine = await Engine.install()
31
36
 
32
- expect(Object.keys(CoreEngine.get())).toEqual(Object.keys(engine))
33
- expect(Object.keys(CoreEngine.get().Hash ?? {}).sort())
34
- .toMatchInlineSnapshot(`
35
- [
36
- "hmacSha256",
37
- "ripemd160",
38
- "sha256",
39
- ]
40
- `)
37
+ expect(Engine.get()).toEqual(CoreEngine.get())
38
+ expect(Object.keys(Engine.get())).toEqual(Object.keys(engine))
39
+ expect(
40
+ Object.fromEntries(
41
+ Object.entries(Engine.get()).map(([slot, value]) => [
42
+ slot,
43
+ Object.keys(value ?? {}).sort(),
44
+ ]),
45
+ ),
46
+ ).toMatchInlineSnapshot(`
47
+ {
48
+ "Ed25519": [
49
+ "getPublicKey",
50
+ "sign",
51
+ "toMontgomerySecret",
52
+ ],
53
+ "Hash": [
54
+ "hmacSha256",
55
+ "ripemd160",
56
+ "sha256",
57
+ ],
58
+ "Keystore": [
59
+ "aesCtrDecrypt",
60
+ "aesCtrEncrypt",
61
+ "pbkdf2Sha256",
62
+ "pbkdf2Sha256Async",
63
+ ],
64
+ "Mnemonic": [
65
+ "toSeed",
66
+ ],
67
+ "P256": [
68
+ "getPublicKey",
69
+ ],
70
+ "X25519": [
71
+ "getPublicKey",
72
+ "getSharedSecret",
73
+ ],
74
+ }
75
+ `)
41
76
  })
42
77
 
43
78
  test('behavior: leaves unsupported primitives on the default', async () => {
44
- await Engine.load()
79
+ await Engine.install()
45
80
 
46
81
  expect(Hash.keccak256('0xdeadbeef')).toMatchInlineSnapshot(
47
82
  `"0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1"`,
@@ -49,13 +84,18 @@ describe('load', () => {
49
84
  })
50
85
 
51
86
  test('behavior: merges with later engines', async () => {
52
- await Engine.load()
53
- CoreEngine.set({ Mnemonic: { toSeed: () => new Uint8Array(64) } })
87
+ await Engine.install()
88
+ Engine.set({ Bls: { randomSecretKey: () => new Uint8Array(32) } })
54
89
 
55
- expect(Object.keys(CoreEngine.get()).sort()).toMatchInlineSnapshot(`
90
+ expect(Object.keys(Engine.get()).sort()).toMatchInlineSnapshot(`
56
91
  [
92
+ "Bls",
93
+ "Ed25519",
57
94
  "Hash",
95
+ "Keystore",
58
96
  "Mnemonic",
97
+ "P256",
98
+ "X25519",
59
99
  ]
60
100
  `)
61
101
  })
@@ -1,26 +1,33 @@
1
- import type { Engine } from 'ox'
2
1
  import { Hash as NodeHash } from 'ox/node'
3
2
  import { expectTypeOf, test } from 'vp/test'
3
+ import type * as CoreEngine from '../../core/Engine.js'
4
+ import * as CoreHash from '../../core/Hash.js'
4
5
 
5
- type Created = Awaited<ReturnType<typeof NodeHash.create>>
6
+ type Slot = Awaited<ReturnType<typeof NodeHash.engine>>
6
7
 
7
8
  test('every implemented primitive is present', () => {
8
- expectTypeOf<Created['Hash']['hmacSha256']>().toEqualTypeOf<
9
+ expectTypeOf<Slot['hmacSha256']>().toEqualTypeOf<
9
10
  (key: Uint8Array, message: Uint8Array) => Uint8Array
10
11
  >()
11
- expectTypeOf<Created['Hash']['ripemd160']>().toEqualTypeOf<
12
+ expectTypeOf<Slot['ripemd160']>().toEqualTypeOf<
12
13
  (input: Uint8Array) => Uint8Array
13
14
  >()
14
- expectTypeOf<Created['Hash']['sha256']>().toEqualTypeOf<
15
+ expectTypeOf<Slot['sha256']>().toEqualTypeOf<
15
16
  (input: Uint8Array) => Uint8Array
16
17
  >()
17
18
  })
18
19
 
19
20
  test('unsupported primitives are absent', () => {
20
- expectTypeOf<Created['Hash']>().not.toHaveProperty('blake3')
21
- expectTypeOf<Created['Hash']>().not.toHaveProperty('keccak256')
21
+ expectTypeOf<Slot>().not.toHaveProperty('blake3')
22
+ expectTypeOf<Slot>().not.toHaveProperty('keccak256')
22
23
  })
23
24
 
24
- test('the result is still an engine', () => {
25
- expectTypeOf<Created>().toExtend<Engine.Engine>()
25
+ test('the result is the raw slot, so `Engine.install` accepts it', () => {
26
+ expectTypeOf<{ Hash: Slot }>().toExtend<CoreEngine.Engine>()
27
+ })
28
+
29
+ test('the Node namespace exposes the public Hash API', () => {
30
+ expectTypeOf(NodeHash.blake3).toEqualTypeOf(CoreHash.blake3)
31
+ expectTypeOf(NodeHash.sha256).toEqualTypeOf(CoreHash.sha256)
32
+ expectTypeOf<typeof NodeHash>().not.toHaveProperty('create')
26
33
  })
@@ -2,11 +2,11 @@ import { Hash as CoreHash, Hex } from 'ox'
2
2
  import { Hash } from 'ox/node'
3
3
  import { describe, expect, test } from 'vp/test'
4
4
 
5
- describe('create', () => {
5
+ describe('engine', () => {
6
6
  test('behavior: exposes the supported primitives', async () => {
7
- const engine = await Hash.create()
7
+ const engine = await Hash.engine()
8
8
 
9
- expect(Object.keys(engine.Hash).sort()).toMatchInlineSnapshot(`
9
+ expect(Object.keys(engine).sort()).toMatchInlineSnapshot(`
10
10
  [
11
11
  "hmacSha256",
12
12
  "ripemd160",
@@ -16,7 +16,7 @@ describe('create', () => {
16
16
  })
17
17
 
18
18
  test('behavior: matches published empty-input vectors', async () => {
19
- const { hmacSha256, ripemd160, sha256 } = (await Hash.create()).Hash
19
+ const { hmacSha256, ripemd160, sha256 } = await Hash.engine()
20
20
  const empty = new Uint8Array()
21
21
 
22
22
  expect(Hex.fromBytes(sha256(empty))).toMatchInlineSnapshot(
@@ -31,7 +31,7 @@ describe('create', () => {
31
31
  })
32
32
 
33
33
  test('behavior: agrees with the default across block boundaries', async () => {
34
- const node = (await Hash.create()).Hash
34
+ const node = await Hash.engine()
35
35
 
36
36
  for (const size of [0, 1, 55, 56, 63, 64, 65, 127, 128, 129]) {
37
37
  const input = Uint8Array.from({ length: size }, (_, index) => index % 251)
@@ -45,7 +45,7 @@ describe('create', () => {
45
45
  })
46
46
 
47
47
  test('behavior: agrees with the default across HMAC key sizes', async () => {
48
- const { hmacSha256 } = (await Hash.create()).Hash
48
+ const { hmacSha256 } = await Hash.engine()
49
49
  const message = Uint8Array.from({ length: 65 }, (_, index) => index % 251)
50
50
 
51
51
  for (const size of [0, 1, 32, 63, 64, 65, 200]) {
@@ -57,7 +57,7 @@ describe('create', () => {
57
57
  })
58
58
 
59
59
  test('behavior: respects typed-array offsets', async () => {
60
- const node = (await Hash.create()).Hash
60
+ const node = await Hash.engine()
61
61
  const input = Uint8Array.from(
62
62
  { length: 80 },
63
63
  (_, index) => (index * 17) % 251,
@@ -77,15 +77,14 @@ describe('create', () => {
77
77
  })
78
78
 
79
79
  test('behavior: returns a fresh engine', async () => {
80
- const first = await Hash.create()
81
- const second = await Hash.create()
80
+ const first = await Hash.engine()
81
+ const second = await Hash.engine()
82
82
 
83
83
  expect(first === second).toMatchInlineSnapshot('false')
84
- expect(first.Hash === second.Hash).toMatchInlineSnapshot('false')
85
84
  })
86
85
 
87
86
  test('behavior: returns owned Uint8Array values', async () => {
88
- const { hmacSha256, ripemd160, sha256 } = (await Hash.create()).Hash
87
+ const { hmacSha256, ripemd160, sha256 } = await Hash.engine()
89
88
  const input = Uint8Array.of(1, 2, 3)
90
89
  const outputs = [hmacSha256(input, input), ripemd160(input), sha256(input)]
91
90
 
@@ -0,0 +1,58 @@
1
+ import { ctr } from '@noble/ciphers/aes.js'
2
+ import { pbkdf2 } from '@noble/hashes/pbkdf2.js'
3
+ import { sha256 } from '@noble/hashes/sha2.js'
4
+ import { fc, test } from '@fast-check/vitest'
5
+ import { expect } from 'vp/test'
6
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
7
+ import * as Keystore from '../Keystore.js'
8
+
9
+ const subview = (bytes: Uint8Array) => {
10
+ const value = new Uint8Array(bytes.length + 4)
11
+ value.set(bytes, 2)
12
+ return value.subarray(2, bytes.length + 2)
13
+ }
14
+
15
+ const arbitraryKey = fc
16
+ .oneof(
17
+ fc.uint8Array({ maxLength: 16, minLength: 16 }),
18
+ fc.uint8Array({ maxLength: 24, minLength: 24 }),
19
+ fc.uint8Array({ maxLength: 32, minLength: 32 }),
20
+ )
21
+ .map(subview)
22
+
23
+ test.prop(
24
+ {
25
+ data: fc.uint8Array({ maxLength: 256 }).map(subview),
26
+ iv: fc.uint8Array({ maxLength: 16, minLength: 16 }).map(subview),
27
+ key: arbitraryKey,
28
+ },
29
+ { numRuns },
30
+ )('Node AES-CTR agrees with the default', async ({ data, iv, key }) => {
31
+ const { aesCtrDecrypt, aesCtrEncrypt } = await Keystore.engine()
32
+ const expected = ctr(key, iv).encrypt(data)
33
+ const encrypted = aesCtrEncrypt(key, iv, data)
34
+
35
+ expect(encrypted).toEqual(expected)
36
+ expect(aesCtrDecrypt(key, iv, encrypted)).toEqual(data)
37
+ })
38
+
39
+ test.prop(
40
+ {
41
+ c: fc.integer({ max: 32, min: 1 }),
42
+ dkLen: fc.integer({ max: 96, min: 1 }),
43
+ password: fc.uint8Array({ maxLength: 96 }).map(subview),
44
+ salt: fc.uint8Array({ maxLength: 96 }).map(subview),
45
+ },
46
+ { numRuns },
47
+ )(
48
+ 'Node PBKDF2-HMAC-SHA256 agrees with the default',
49
+ async ({ c, dkLen, password, salt }) => {
50
+ const { pbkdf2Sha256, pbkdf2Sha256Async } = await Keystore.engine()
51
+ const expected = pbkdf2(sha256, password, salt, { c, dkLen })
52
+
53
+ expect(pbkdf2Sha256(password, salt, { c, dkLen })).toEqual(expected)
54
+ expect(await pbkdf2Sha256Async(password, salt, { c, dkLen })).toEqual(
55
+ expected,
56
+ )
57
+ },
58
+ )
@@ -0,0 +1,43 @@
1
+ import type { Engine } from 'ox'
2
+ import { expectTypeOf, test } from 'vp/test'
3
+ import * as core_Keystore from '../../core/Keystore.js'
4
+ import * as NodeKeystore from '../Keystore.js'
5
+
6
+ type Slot = Awaited<ReturnType<typeof NodeKeystore.engine>>
7
+
8
+ test('every implemented primitive is present', () => {
9
+ expectTypeOf<Slot['aesCtrDecrypt']>().toEqualTypeOf<
10
+ (key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array
11
+ >()
12
+ expectTypeOf<Slot['aesCtrEncrypt']>().toEqualTypeOf<
13
+ (key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array
14
+ >()
15
+ expectTypeOf<Slot['pbkdf2Sha256']>().toEqualTypeOf<
16
+ (
17
+ password: Uint8Array,
18
+ salt: Uint8Array,
19
+ options: { c: number; dkLen: number },
20
+ ) => Uint8Array
21
+ >()
22
+ expectTypeOf<Slot['pbkdf2Sha256Async']>().toEqualTypeOf<
23
+ (
24
+ password: Uint8Array,
25
+ salt: Uint8Array,
26
+ options: { c: number; dkLen: number },
27
+ ) => Promise<Uint8Array>
28
+ >()
29
+ })
30
+
31
+ test('unsupported primitives are absent', () => {
32
+ expectTypeOf<Slot>().not.toHaveProperty('scrypt')
33
+ expectTypeOf<Slot>().not.toHaveProperty('scryptAsync')
34
+ })
35
+
36
+ test('the result is the raw slot', () => {
37
+ expectTypeOf<{ Keystore: Slot }>().toExtend<Engine.Engine>()
38
+ })
39
+
40
+ test('the Node namespace exposes the public Keystore API', () => {
41
+ expectTypeOf(NodeKeystore.pbkdf2).toEqualTypeOf(core_Keystore.pbkdf2)
42
+ expectTypeOf<typeof NodeKeystore>().not.toHaveProperty('create')
43
+ })