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
@@ -0,0 +1,523 @@
1
+ import { secp256k1 } from '@noble/curves/secp256k1.js'
2
+ import {
3
+ Address,
4
+ BinaryStateTree,
5
+ Bls,
6
+ Bytes,
7
+ Ed25519,
8
+ Engine,
9
+ Hash,
10
+ Hex,
11
+ Keystore,
12
+ Mnemonic,
13
+ P256,
14
+ Secp256k1,
15
+ X25519,
16
+ } from 'ox'
17
+ import { describe, expect, test, vi } from 'vp/test'
18
+ import {
19
+ identity as identityEngine,
20
+ sentinel as sentinelEngine,
21
+ } from '../../../test/engines.js'
22
+
23
+ const privateKey =
24
+ '0x0000000000000000000000000000000000000000000000000000000000000001'
25
+ const otherPrivateKey =
26
+ '0x0000000000000000000000000000000000000000000000000000000000000002'
27
+ const payload = '0xdeadbeef'
28
+ const mnemonic = 'test test test test test test test test test test test junk'
29
+
30
+ // Inserting a leaf then merkelizing is the only path that reaches blake3.
31
+ function merkelize() {
32
+ const tree = BinaryStateTree.create()
33
+ BinaryStateTree.insert(
34
+ tree,
35
+ new Uint8Array(32).fill(1),
36
+ new Uint8Array(32).fill(2),
37
+ )
38
+ return BinaryStateTree.merkelize(tree)
39
+ }
40
+
41
+ describe('set', () => {
42
+ test('behavior: empty engine is a no-op', () => {
43
+ const before = Hash.keccak256(payload)
44
+ Engine.set({})
45
+ expect(Hash.keccak256(payload)).toEqual(before)
46
+ })
47
+
48
+ test('behavior: empty slot falls back to the default', () => {
49
+ const before = Hash.keccak256(payload)
50
+ Engine.set({ Hash: {} })
51
+ expect(Hash.keccak256(payload)).toEqual(before)
52
+ })
53
+
54
+ test('behavior: identity engine changes nothing', () => {
55
+ const expected = {
56
+ blake3: merkelize(),
57
+ blsPublicKey: Bls.getPublicKey({ privateKey }),
58
+ blsSignature: Bls.sign({ payload, privateKey }),
59
+ ed25519PublicKey: Ed25519.getPublicKey({ privateKey }),
60
+ hmac256: Hash.hmac256('0xbeef', payload),
61
+ keccak256: Hash.keccak256(payload),
62
+ p256Signature: P256.sign({ payload, privateKey }),
63
+ ripemd160: Hash.ripemd160(payload),
64
+ secp256k1PublicKey: Secp256k1.getPublicKey({ privateKey }),
65
+ secp256k1Signature: Secp256k1.sign({ payload, privateKey }),
66
+ seed: Mnemonic.toSeed(mnemonic),
67
+ sha256: Hash.sha256(payload),
68
+ x25519SharedSecret: X25519.getSharedSecret({
69
+ privateKey,
70
+ publicKey: X25519.getPublicKey({ privateKey }),
71
+ }),
72
+ }
73
+
74
+ Engine.set(identityEngine)
75
+
76
+ expect({
77
+ blake3: merkelize(),
78
+ blsPublicKey: Bls.getPublicKey({ privateKey }),
79
+ blsSignature: Bls.sign({ payload, privateKey }),
80
+ ed25519PublicKey: Ed25519.getPublicKey({ privateKey }),
81
+ hmac256: Hash.hmac256('0xbeef', payload),
82
+ keccak256: Hash.keccak256(payload),
83
+ p256Signature: P256.sign({ payload, privateKey }),
84
+ ripemd160: Hash.ripemd160(payload),
85
+ secp256k1PublicKey: Secp256k1.getPublicKey({ privateKey }),
86
+ secp256k1Signature: Secp256k1.sign({ payload, privateKey }),
87
+ seed: Mnemonic.toSeed(mnemonic),
88
+ sha256: Hash.sha256(payload),
89
+ x25519SharedSecret: X25519.getSharedSecret({
90
+ privateKey,
91
+ publicKey: X25519.getPublicKey({ privateKey }),
92
+ }),
93
+ }).toEqual(expected)
94
+ })
95
+
96
+ test('behavior: identity engine round-trips a keystore', () => {
97
+ Engine.set(identityEngine)
98
+ const [key, options] = Keystore.pbkdf2({ iterations: 1000, password: 'pw' })
99
+ const encrypted = Keystore.encrypt(Bytes.from('0xdeadbeef'), key, options)
100
+ expect(Keystore.decrypt(encrypted, key, { as: 'Hex' })).toEqual(
101
+ '0xdeadbeef',
102
+ )
103
+ })
104
+
105
+ test('behavior: merges across calls', () => {
106
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
107
+ Engine.set({ Hash: { sha256: () => new Uint8Array(32).fill(2) } })
108
+ expect(Hash.keccak256(payload, { as: 'Bytes' })[0]).toBe(1)
109
+ expect(Hash.sha256(payload, { as: 'Bytes' })[0]).toBe(2)
110
+ })
111
+
112
+ test('behavior: replaces a previously set function', () => {
113
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
114
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(3) } })
115
+ expect(Hash.keccak256(payload, { as: 'Bytes' })[0]).toBe(3)
116
+ })
117
+
118
+ test('behavior: an undefined slot is removed', () => {
119
+ const before = Hash.keccak256(payload)
120
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
121
+ Engine.set({ Hash: undefined })
122
+ expect(Engine.get()).toEqual({})
123
+ expect(Hash.keccak256(payload)).toEqual(before)
124
+ })
125
+
126
+ test('behavior: invalidates caches derived from a swapped primitive', () => {
127
+ const address = '0xa0cf798816d4b9b9866b5330eea46a18382f251e'
128
+ const checksummed = Address.checksum(address)
129
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32) } })
130
+ expect(Address.checksum(address)).not.toEqual(checksummed)
131
+ })
132
+
133
+ test('behavior: does not affect the `noble` escape hatches', () => {
134
+ Engine.set({
135
+ Secp256k1: { sign: () => new Uint8Array(65).fill(9) },
136
+ })
137
+ expect(
138
+ Secp256k1.noble.sign(Bytes.from(payload), Bytes.from(privateKey), {
139
+ format: 'recovered',
140
+ lowS: true,
141
+ }),
142
+ ).not.toEqual(new Uint8Array(65).fill(9))
143
+ })
144
+
145
+ test('behavior: async slots resolve independently of sync slots', async () => {
146
+ const scrypt = vi.fn(() => new Uint8Array(32).fill(1))
147
+ Engine.set({ Keystore: { scrypt } })
148
+
149
+ Keystore.scrypt({ n: 1024, password: 'pw', r: 1, p: 1 })
150
+ expect(scrypt).toHaveBeenCalledTimes(1)
151
+
152
+ await Keystore.scryptAsync({ n: 1024, password: 'pw', r: 1, p: 1 })
153
+ expect(scrypt).toHaveBeenCalledTimes(1)
154
+ })
155
+
156
+ test('error: unknown slot', () => {
157
+ expect(() =>
158
+ Engine.set({ Keccak: {} } as never),
159
+ ).toThrowErrorMatchingInlineSnapshot(
160
+ `
161
+ [Engine.UnknownSlotError: \`Keccak\` is not a valid engine slot.
162
+
163
+ Valid slots: Bls, Ed25519, Hash, Keystore, Mnemonic, P256, Secp256k1, X25519]
164
+ `,
165
+ )
166
+ })
167
+
168
+ test('error: unknown primitive', () => {
169
+ expect(() =>
170
+ Engine.set({ Hash: { keccak_256: () => new Uint8Array(32) } } as never),
171
+ ).toThrowErrorMatchingInlineSnapshot(`
172
+ [Engine.UnknownPrimitiveError: \`keccak_256\` is not a valid primitive for the \`Hash\` slot.
173
+
174
+ Valid primitives: blake3, hmacSha256, keccak256, ripemd160, sha256]
175
+ `)
176
+ })
177
+
178
+ test('behavior: a rejected engine is not partially installed', () => {
179
+ // The whole point of rejecting the name: without this, the override is
180
+ // stored, no resolver ever reads it, and the caller is told nothing.
181
+ expect(() =>
182
+ Engine.set({
183
+ Hash: { keccak256: () => new Uint8Array(32) },
184
+ Secp256k1: { signn: () => new Uint8Array(65) },
185
+ } as never),
186
+ ).toThrow()
187
+ expect(Engine.get()).toEqual({})
188
+ // Still the real digest, not the 32 zero bytes the rejected engine returns.
189
+ expect(Hash.keccak256('0xdeadbeef')).toMatchInlineSnapshot(
190
+ `"0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1"`,
191
+ )
192
+ })
193
+ })
194
+
195
+ describe('get', () => {
196
+ test('default', () => {
197
+ expect(Engine.get()).toEqual({})
198
+ })
199
+
200
+ test('behavior: returns only overrides', () => {
201
+ const keccak256 = () => new Uint8Array(32)
202
+ Engine.set({ Hash: { keccak256 } })
203
+ expect(Engine.get()).toEqual({ Hash: { keccak256 } })
204
+ })
205
+
206
+ test('behavior: returns a copy', () => {
207
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
208
+ const engine = Engine.get()
209
+ delete engine.Hash
210
+ expect(Hash.keccak256(payload, { as: 'Bytes' })[0]).toBe(1)
211
+ })
212
+ })
213
+
214
+ describe('reset', () => {
215
+ test('behavior: resets every slot', () => {
216
+ const before = Hash.keccak256(payload)
217
+ Engine.set({
218
+ Hash: { keccak256: () => new Uint8Array(32) },
219
+ Secp256k1: { sign: () => new Uint8Array(65) },
220
+ })
221
+ Engine.reset()
222
+ expect(Engine.get()).toEqual({})
223
+ expect(Hash.keccak256(payload)).toEqual(before)
224
+ })
225
+
226
+ test('behavior: resets a single slot', () => {
227
+ const sign = () => new Uint8Array(65)
228
+ Engine.set({
229
+ Hash: { keccak256: () => new Uint8Array(32) },
230
+ Secp256k1: { sign },
231
+ })
232
+ Engine.reset('Hash')
233
+ expect(Engine.get()).toEqual({ Secp256k1: { sign } })
234
+ })
235
+
236
+ test('error: unknown slot', () => {
237
+ const keccak256 = () => new Uint8Array(32)
238
+ Engine.set({ Hash: { keccak256 } })
239
+
240
+ expect(() => Engine.reset('Hsah' as never))
241
+ .toThrowErrorMatchingInlineSnapshot(`
242
+ [Engine.UnknownSlotError: \`Hsah\` is not a valid engine slot.
243
+
244
+ Valid slots: Bls, Ed25519, Hash, Keystore, Mnemonic, P256, Secp256k1, X25519]
245
+ `)
246
+
247
+ // The override the caller meant to remove is still installed, which is the
248
+ // reason a silent no-op here is worse than an error.
249
+ expect(Engine.get()).toEqual({ Hash: { keccak256 } })
250
+ })
251
+ })
252
+
253
+ describe('with', () => {
254
+ test('default', () => {
255
+ const hash = Engine.with(
256
+ { Hash: { keccak256: () => new Uint8Array(32) } },
257
+ () => Hash.keccak256(payload),
258
+ )
259
+ expect(hash).toEqual(Hex.fromBytes(new Uint8Array(32)))
260
+ expect(Engine.get()).toEqual({})
261
+ })
262
+
263
+ test('behavior: restores on throw', () => {
264
+ expect(() =>
265
+ Engine.with({ Hash: { keccak256: () => new Uint8Array(32) } }, () => {
266
+ throw new Error('oh no')
267
+ }),
268
+ ).toThrow('oh no')
269
+ expect(Engine.get()).toEqual({})
270
+ })
271
+
272
+ test('behavior: restores the previous engine, not the default', () => {
273
+ const keccak256 = () => new Uint8Array(32).fill(1)
274
+ Engine.set({ Hash: { keccak256 } })
275
+ Engine.with({ Hash: { keccak256: () => new Uint8Array(32).fill(2) } }, () =>
276
+ Hash.keccak256(payload),
277
+ )
278
+ expect(Hash.keccak256(payload, { as: 'Bytes' })[0]).toBe(1)
279
+ })
280
+
281
+ test('behavior: nests', () => {
282
+ const outer = Engine.with(
283
+ { Hash: { keccak256: () => new Uint8Array(32).fill(1) } },
284
+ () =>
285
+ Engine.with(
286
+ { Hash: { keccak256: () => new Uint8Array(32).fill(2) } },
287
+ () => Hash.keccak256(payload, { as: 'Bytes' })[0],
288
+ ),
289
+ )
290
+ expect(outer).toBe(2)
291
+ expect(Engine.get()).toEqual({})
292
+ })
293
+
294
+ test('error: async function', () => {
295
+ expect(() =>
296
+ Engine.with({}, async () => {}),
297
+ ).toThrowErrorMatchingInlineSnapshot(
298
+ `
299
+ [Engine.AsyncScopeError: \`Engine.with\` cannot be used with an asynchronous function.
300
+
301
+ The engine is process-wide, so a scoped override cannot be held across an \`await\`.
302
+ Use \`Engine.set\` instead.]
303
+ `,
304
+ )
305
+ expect(Engine.get()).toEqual({})
306
+ })
307
+ })
308
+
309
+ describe('interception', () => {
310
+ const cases = [
311
+ [
312
+ 'Hash.keccak256',
313
+ { Hash: { keccak256: () => new Uint8Array(32).fill(1) } },
314
+ () => Hash.keccak256(payload, { as: 'Bytes' }),
315
+ ],
316
+ [
317
+ 'Hash.sha256',
318
+ { Hash: { sha256: () => new Uint8Array(32).fill(1) } },
319
+ () => Hash.sha256(payload, { as: 'Bytes' }),
320
+ ],
321
+ [
322
+ 'Hash.ripemd160',
323
+ { Hash: { ripemd160: () => new Uint8Array(32).fill(1) } },
324
+ () => Hash.ripemd160(payload, { as: 'Bytes' }),
325
+ ],
326
+ [
327
+ 'Hash.hmac256',
328
+ { Hash: { hmacSha256: () => new Uint8Array(32).fill(1) } },
329
+ () => Hash.hmac256('0xbeef', payload, { as: 'Bytes' }),
330
+ ],
331
+ [
332
+ 'BinaryStateTree.merkelize',
333
+ { Hash: { blake3: () => new Uint8Array(32).fill(1) } },
334
+ () => merkelize(),
335
+ ],
336
+ [
337
+ 'Secp256k1.getPublicKey',
338
+ {
339
+ Secp256k1: {
340
+ getPublicKey: () =>
341
+ secp256k1.getPublicKey(Bytes.from(otherPrivateKey), false),
342
+ },
343
+ },
344
+ () => Secp256k1.getPublicKey({ privateKey }),
345
+ ],
346
+ [
347
+ 'Secp256k1.getSharedSecret',
348
+ { Secp256k1: { getSharedSecret: () => new Uint8Array(32).fill(1) } },
349
+ () =>
350
+ Secp256k1.getSharedSecret({
351
+ as: 'Bytes',
352
+ privateKey,
353
+ publicKey: Secp256k1.getPublicKey({ privateKey }),
354
+ }),
355
+ ],
356
+ [
357
+ 'Secp256k1.randomPrivateKey',
358
+ { Secp256k1: { randomSecretKey: () => new Uint8Array(32).fill(1) } },
359
+ () => Secp256k1.randomPrivateKey({ as: 'Bytes' }),
360
+ ],
361
+ [
362
+ 'Secp256k1.sign',
363
+ { Secp256k1: { sign: () => new Uint8Array(65).fill(1) } },
364
+ () => Secp256k1.sign({ payload, privateKey }),
365
+ ],
366
+ [
367
+ 'Secp256k1.verify',
368
+ { Secp256k1: { verify: () => false } },
369
+ () =>
370
+ Secp256k1.verify({
371
+ payload,
372
+ publicKey: Secp256k1.getPublicKey({ privateKey }),
373
+ signature: Secp256k1.sign({ payload, privateKey }),
374
+ }),
375
+ ],
376
+ [
377
+ 'Secp256k1.recoverPublicKey',
378
+ {
379
+ Secp256k1: {
380
+ recoverPublicKey: () =>
381
+ secp256k1.getPublicKey(Bytes.from(otherPrivateKey), false),
382
+ },
383
+ },
384
+ () =>
385
+ Secp256k1.recoverPublicKey({
386
+ payload,
387
+ signature: Secp256k1.sign({ payload, privateKey }),
388
+ }),
389
+ ],
390
+ [
391
+ 'P256.sign',
392
+ { P256: { sign: () => new Uint8Array(65).fill(1) } },
393
+ () => P256.sign({ payload, privateKey }),
394
+ ],
395
+ [
396
+ 'P256.verify',
397
+ { P256: { verify: () => false } },
398
+ () =>
399
+ P256.verify({
400
+ payload,
401
+ publicKey: P256.getPublicKey({ privateKey }),
402
+ signature: P256.sign({ payload, privateKey }),
403
+ }),
404
+ ],
405
+ [
406
+ 'Ed25519.sign',
407
+ { Ed25519: { sign: () => new Uint8Array(64).fill(1) } },
408
+ () => Ed25519.sign({ payload, privateKey }),
409
+ ],
410
+ [
411
+ 'Ed25519.verify',
412
+ { Ed25519: { verify: () => false } },
413
+ () =>
414
+ Ed25519.verify({
415
+ payload,
416
+ publicKey: Ed25519.getPublicKey({ privateKey }),
417
+ signature: Ed25519.sign({ payload, privateKey }),
418
+ }),
419
+ ],
420
+ [
421
+ 'X25519.getSharedSecret',
422
+ { X25519: { getSharedSecret: () => new Uint8Array(32).fill(1) } },
423
+ () =>
424
+ X25519.getSharedSecret({
425
+ as: 'Bytes',
426
+ privateKey,
427
+ publicKey: X25519.getPublicKey({ privateKey }),
428
+ }),
429
+ ],
430
+ [
431
+ 'Mnemonic.toSeed',
432
+ { Mnemonic: { toSeed: () => new Uint8Array(64).fill(1) } },
433
+ () => Mnemonic.toSeed(mnemonic),
434
+ ],
435
+ ] as const satisfies readonly (readonly [
436
+ string,
437
+ Engine.Engine,
438
+ () => unknown,
439
+ ])[]
440
+
441
+ test.each(cases)('%s routes through the engine', (_name, engine, invoke) => {
442
+ const before = invoke()
443
+ Engine.set(engine)
444
+ expect(invoke()).not.toEqual(before)
445
+ })
446
+ })
447
+
448
+ describe('Bls', () => {
449
+ test('behavior: identity engine verifies its own signature', () => {
450
+ Engine.set(identityEngine)
451
+ const publicKey = Bls.getPublicKey({ privateKey })
452
+ const signature = Bls.sign({ payload, privateKey })
453
+ expect(Bls.verify({ payload, publicKey, signature })).toBe(true)
454
+ })
455
+
456
+ test('behavior: aggregate is routed through the engine', () => {
457
+ const publicKeys = [
458
+ Bls.getPublicKey({ privateKey }),
459
+ Bls.getPublicKey({ privateKey: otherPrivateKey }),
460
+ ]
461
+ const before = Bls.aggregate(publicKeys)
462
+ Engine.set(sentinelEngine)
463
+ expect(Bls.aggregate(publicKeys)).not.toEqual(before)
464
+ })
465
+
466
+ test('behavior: getPublicKey is routed through the engine', () => {
467
+ const before = Bls.getPublicKey({ privateKey })
468
+ Engine.set(sentinelEngine)
469
+ expect(Bls.getPublicKey({ privateKey })).not.toEqual(before)
470
+ })
471
+
472
+ test('behavior: sign is routed through the engine', () => {
473
+ const before = Bls.sign({ payload, privateKey })
474
+ Engine.set(sentinelEngine)
475
+ expect(Bls.sign({ payload, privateKey })).not.toEqual(before)
476
+ })
477
+
478
+ test('behavior: verify is routed through the engine', () => {
479
+ const publicKey = Bls.getPublicKey({ privateKey })
480
+ const signature = Bls.sign({ payload, privateKey })
481
+ expect(Bls.verify({ payload, publicKey, signature })).toBe(true)
482
+ Engine.set(sentinelEngine)
483
+ expect(Bls.verify({ payload, publicKey, signature })).toBe(false)
484
+ })
485
+
486
+ test('behavior: long-key:short-sig routes through the engine', () => {
487
+ Engine.set(identityEngine)
488
+ const size = 'long-key:short-sig'
489
+ const publicKey = Bls.getPublicKey({ privateKey, size })
490
+ const signature = Bls.sign({ payload, privateKey, size })
491
+ expect(Bls.verify({ payload, publicKey, signature })).toBe(true)
492
+ })
493
+ })
494
+
495
+ describe('merge semantics', () => {
496
+ test('behavior: a primitive cleared to undefined stops being reported', () => {
497
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
498
+ expect(Object.keys(Engine.get().Hash ?? {})).toMatchInlineSnapshot(`
499
+ [
500
+ "keccak256",
501
+ ]
502
+ `)
503
+
504
+ Engine.set({ Hash: { keccak256: undefined } })
505
+ // Calls already fell through to the default; the point is that `get` no
506
+ // longer claims an override that is not there.
507
+ expect(Object.keys(Engine.get().Hash ?? {})).toMatchInlineSnapshot('[]')
508
+ expect(Hash.keccak256(payload)).toEqual(
509
+ Hash.keccak256(payload, { as: 'Hex' }),
510
+ )
511
+ })
512
+
513
+ test('behavior: the returned engine cannot mutate the registry', () => {
514
+ Engine.set({ Hash: { keccak256: () => new Uint8Array(32).fill(1) } })
515
+ const snapshot = Engine.get()
516
+
517
+ snapshot.Hash!.keccak256 = () => new Uint8Array(32).fill(2)
518
+
519
+ // Writing through the snapshot would change what ox calls without going
520
+ // through `set`, so without clearing the caches derived from it.
521
+ expect(Hash.keccak256(payload, { as: 'Bytes' })[0]).toBe(1)
522
+ })
523
+ })
@@ -0,0 +1,56 @@
1
+ import { Hash } from 'ox'
2
+ import { describe, expect, test } from 'vp/test'
3
+ import * as vectors from '../../../test/vectors/hashes/index.js'
4
+
5
+ /**
6
+ * Published vectors against ox's default (`@noble/hashes`) implementations.
7
+ *
8
+ * Every other hash test in ox is differential against `@noble/hashes`, which can
9
+ * only prove two implementations agree. These come from the specifications and
10
+ * the algorithms' designers, so they check the default path on its own terms.
11
+ * See `test/vectors/hashes/README.md`.
12
+ */
13
+
14
+ describe('sha256', () => {
15
+ test(`matches ${vectors.sha256.length} NIST CAVP vectors`, () => {
16
+ for (const { digest, message } of vectors.sha256)
17
+ expect(Hash.sha256(message, { as: 'Bytes' })).toEqual(digest)
18
+ })
19
+ })
20
+
21
+ describe('hmac256', () => {
22
+ test(`matches ${vectors.hmacSha256.length} RFC 4231 vectors`, () => {
23
+ for (const { digest, key, message } of vectors.hmacSha256)
24
+ expect(Hash.hmac256(key, message, { as: 'Bytes' })).toEqual(digest)
25
+ })
26
+ })
27
+
28
+ describe('ripemd160', () => {
29
+ test(`matches ${vectors.ripemd160.length} reference vectors`, () => {
30
+ for (const { digest, message } of vectors.ripemd160)
31
+ expect(Hash.ripemd160(message, { as: 'Bytes' })).toEqual(digest)
32
+ })
33
+
34
+ test('matches the million-`a` reference vector', () => {
35
+ const { digest, message } = vectors.ripemd160MillionA
36
+ expect(Hash.ripemd160(message, { as: 'Bytes' })).toEqual(digest)
37
+ })
38
+ })
39
+
40
+ describe('keccak256', () => {
41
+ test(`matches ${vectors.keccak256.length} OpenSSL vectors`, () => {
42
+ for (const { digest, message } of vectors.keccak256)
43
+ expect(Hash.keccak256(message, { as: 'Bytes' })).toEqual(digest)
44
+ })
45
+
46
+ test('is Keccak, not FIPS-202 SHA3-256', () => {
47
+ // The two differ only by a domain-separation byte, so a padding mistake
48
+ // produces a plausible-looking digest. This pins the distinction.
49
+ expect(Hash.keccak256('0x')).toBe(
50
+ '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
51
+ )
52
+ expect(Hash.keccak256('0x')).not.toBe(
53
+ '0xa7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a',
54
+ )
55
+ })
56
+ })
@@ -6,8 +6,7 @@ import {
6
6
  arbitraryBytes,
7
7
  arbitraryHex,
8
8
  } from '../../../test/fuzz/arbitraries/bytes.js'
9
-
10
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
9
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
11
10
 
12
11
  describe('Hex round-trip', () => {
13
12
  test.prop({ bytes: arbitraryBytes() }, { numRuns })(
@@ -7,8 +7,7 @@ import {
7
7
  arbitraryRecursiveRlpHex,
8
8
  type RecursiveArray,
9
9
  } from '../../../test/fuzz/arbitraries/rlp.js'
10
-
11
- const numRuns = Number(process.env.FC_NUM_RUNS) || 100
10
+ import { numRuns } from '../../../test/fuzz/numRuns.js'
12
11
 
13
12
  describe('Rlp round-trip', () => {
14
13
  test.prop({ tree: arbitraryRecursiveRlpHex() }, { numRuns })(
@@ -1,8 +1,7 @@
1
1
  import { fc, test } from '@fast-check/vitest'
2
2
  import { Hex, Signature } 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
  * Arbitrary secp256k1-style signature components. ox enforces:
@@ -0,0 +1,7 @@
1
+ import { p256 } from '@noble/curves/nist.js'
2
+ import { expect, test } from 'vp/test'
3
+ import { p256N } from '../internal/curves.js'
4
+
5
+ test('p256N matches @noble/curves', () => {
6
+ expect(p256N).toEqual(p256.Point.CURVE().n)
7
+ })
@@ -36,6 +36,7 @@ test('exports', () => {
36
36
  "ContractAddress",
37
37
  "CoseKey",
38
38
  "Ed25519",
39
+ "Engine",
39
40
  "Ens",
40
41
  "Errors",
41
42
  "Fee",