ox 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/core/Base64.d.ts +10 -21
  3. package/dist/core/Base64.d.ts.map +1 -1
  4. package/dist/core/Base64.js +8 -127
  5. package/dist/core/Base64.js.map +1 -1
  6. package/dist/core/BinaryStateTree.d.ts.map +1 -1
  7. package/dist/core/BinaryStateTree.js +2 -2
  8. package/dist/core/BinaryStateTree.js.map +1 -1
  9. package/dist/core/Bls.d.ts.map +1 -1
  10. package/dist/core/Bls.js +19 -44
  11. package/dist/core/Bls.js.map +1 -1
  12. package/dist/core/Ed25519.d.ts.map +1 -1
  13. package/dist/core/Ed25519.js +7 -6
  14. package/dist/core/Ed25519.js.map +1 -1
  15. package/dist/core/Engine.d.ts +124 -0
  16. package/dist/core/Engine.d.ts.map +1 -0
  17. package/dist/core/Engine.js +180 -0
  18. package/dist/core/Engine.js.map +1 -0
  19. package/dist/core/Hash.d.ts +4 -4
  20. package/dist/core/Hash.d.ts.map +1 -1
  21. package/dist/core/Hash.js +9 -12
  22. package/dist/core/Hash.js.map +1 -1
  23. package/dist/core/Keystore.d.ts.map +1 -1
  24. package/dist/core/Keystore.js +15 -10
  25. package/dist/core/Keystore.js.map +1 -1
  26. package/dist/core/Mnemonic.d.ts.map +1 -1
  27. package/dist/core/Mnemonic.js +3 -2
  28. package/dist/core/Mnemonic.js.map +1 -1
  29. package/dist/core/P256.d.ts.map +1 -1
  30. package/dist/core/P256.js +8 -9
  31. package/dist/core/P256.js.map +1 -1
  32. package/dist/core/Secp256k1.d.ts.map +1 -1
  33. package/dist/core/Secp256k1.js +8 -9
  34. package/dist/core/Secp256k1.js.map +1 -1
  35. package/dist/core/WebCryptoP256.d.ts.map +1 -1
  36. package/dist/core/WebCryptoP256.js +2 -2
  37. package/dist/core/WebCryptoP256.js.map +1 -1
  38. package/dist/core/X25519.d.ts.map +1 -1
  39. package/dist/core/X25519.js +4 -3
  40. package/dist/core/X25519.js.map +1 -1
  41. package/dist/core/internal/bls.d.ts +12 -0
  42. package/dist/core/internal/bls.d.ts.map +1 -0
  43. package/dist/core/internal/bls.js +79 -0
  44. package/dist/core/internal/bls.js.map +1 -0
  45. package/dist/core/internal/codec/base64.d.ts +84 -0
  46. package/dist/core/internal/codec/base64.d.ts.map +1 -0
  47. package/dist/core/internal/codec/base64.js +177 -0
  48. package/dist/core/internal/codec/base64.js.map +1 -0
  49. package/dist/core/internal/codec/hex.d.ts +49 -4
  50. package/dist/core/internal/codec/hex.d.ts.map +1 -1
  51. package/dist/core/internal/codec/hex.js +109 -35
  52. package/dist/core/internal/codec/hex.js.map +1 -1
  53. package/dist/core/internal/curves.d.ts +17 -0
  54. package/dist/core/internal/curves.d.ts.map +1 -0
  55. package/dist/core/internal/curves.js +17 -0
  56. package/dist/core/internal/curves.js.map +1 -0
  57. package/dist/core/internal/ed25519.d.ts +14 -0
  58. package/dist/core/internal/ed25519.d.ts.map +1 -0
  59. package/dist/core/internal/ed25519.js +29 -0
  60. package/dist/core/internal/ed25519.js.map +1 -0
  61. package/dist/core/internal/engine.d.ts +278 -0
  62. package/dist/core/internal/engine.d.ts.map +1 -0
  63. package/dist/core/internal/engine.js +131 -0
  64. package/dist/core/internal/engine.js.map +1 -0
  65. package/dist/core/internal/hash.d.ts +12 -0
  66. package/dist/core/internal/hash.d.ts.map +1 -0
  67. package/dist/core/internal/hash.js +30 -0
  68. package/dist/core/internal/hash.js.map +1 -0
  69. package/dist/core/internal/keystore.d.ts +14 -0
  70. package/dist/core/internal/keystore.d.ts.map +1 -0
  71. package/dist/core/internal/keystore.js +32 -0
  72. package/dist/core/internal/keystore.js.map +1 -0
  73. package/dist/core/internal/mnemonic.d.ts +4 -0
  74. package/dist/core/internal/mnemonic.d.ts.map +1 -0
  75. package/dist/core/internal/mnemonic.js +14 -0
  76. package/dist/core/internal/mnemonic.js.map +1 -0
  77. package/dist/core/internal/p256.d.ts +14 -0
  78. package/dist/core/internal/p256.d.ts.map +1 -0
  79. package/dist/core/internal/p256.js +36 -0
  80. package/dist/core/internal/p256.js.map +1 -0
  81. package/dist/core/internal/secp256k1.d.ts +14 -0
  82. package/dist/core/internal/secp256k1.d.ts.map +1 -0
  83. package/dist/core/internal/secp256k1.js +36 -0
  84. package/dist/core/internal/secp256k1.js.map +1 -0
  85. package/dist/core/internal/x25519.d.ts +8 -0
  86. package/dist/core/internal/x25519.d.ts.map +1 -0
  87. package/dist/core/internal/x25519.js +20 -0
  88. package/dist/core/internal/x25519.js.map +1 -0
  89. package/dist/index.d.ts +35 -0
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.docs.d.ts +2 -0
  92. package/dist/index.docs.d.ts.map +1 -1
  93. package/dist/index.docs.js +2 -0
  94. package/dist/index.docs.js.map +1 -1
  95. package/dist/index.js +35 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/node/Engine.d.ts +55 -0
  98. package/dist/node/Engine.d.ts.map +1 -0
  99. package/dist/node/Engine.js +51 -0
  100. package/dist/node/Engine.js.map +1 -0
  101. package/dist/node/Hash.d.ts +39 -0
  102. package/dist/node/Hash.d.ts.map +1 -0
  103. package/dist/node/Hash.js +37 -0
  104. package/dist/node/Hash.js.map +1 -0
  105. package/dist/node/index.d.ts +39 -0
  106. package/dist/node/index.d.ts.map +1 -0
  107. package/dist/node/index.js +39 -0
  108. package/dist/node/index.js.map +1 -0
  109. package/dist/tempo/VirtualMaster.d.ts +2 -0
  110. package/dist/tempo/VirtualMaster.d.ts.map +1 -1
  111. package/dist/tempo/VirtualMaster.js +19 -5
  112. package/dist/tempo/VirtualMaster.js.map +1 -1
  113. package/dist/tempo/ZoneId.d.ts +35 -17
  114. package/dist/tempo/ZoneId.d.ts.map +1 -1
  115. package/dist/tempo/ZoneId.js +41 -16
  116. package/dist/tempo/ZoneId.js.map +1 -1
  117. package/dist/tempo/index.d.ts +5 -6
  118. package/dist/tempo/index.d.ts.map +1 -1
  119. package/dist/tempo/index.js +5 -6
  120. package/dist/tempo/index.js.map +1 -1
  121. package/dist/tempo/internal/mine.wasm.d.ts +2 -4
  122. package/dist/tempo/internal/mine.wasm.d.ts.map +1 -1
  123. package/dist/tempo/internal/mine.wasm.js +7 -12
  124. package/dist/tempo/internal/mine.wasm.js.map +1 -1
  125. package/dist/tempo/internal/virtualMasterPool.js +4 -1
  126. package/dist/tempo/internal/virtualMasterPool.js.map +1 -1
  127. package/dist/wasm/Engine.d.ts +62 -0
  128. package/dist/wasm/Engine.d.ts.map +1 -0
  129. package/dist/wasm/Engine.js +57 -0
  130. package/dist/wasm/Engine.js.map +1 -0
  131. package/dist/wasm/Hash.d.ts +49 -0
  132. package/dist/wasm/Hash.d.ts.map +1 -0
  133. package/dist/wasm/Hash.js +70 -0
  134. package/dist/wasm/Hash.js.map +1 -0
  135. package/dist/wasm/index.d.ts +46 -0
  136. package/dist/wasm/index.d.ts.map +1 -0
  137. package/dist/wasm/index.js +46 -0
  138. package/dist/wasm/index.js.map +1 -0
  139. package/dist/wasm/internal/hash.d.ts +9 -0
  140. package/dist/wasm/internal/hash.d.ts.map +1 -0
  141. package/dist/wasm/internal/hash.js +26 -0
  142. package/dist/wasm/internal/hash.js.map +1 -0
  143. package/dist/wasm/internal/hashes.wasm.d.ts +5 -0
  144. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -0
  145. package/dist/wasm/internal/hashes.wasm.js +12 -0
  146. package/dist/wasm/internal/hashes.wasm.js.map +1 -0
  147. package/dist/wasm/internal/instantiate.d.ts +56 -0
  148. package/dist/wasm/internal/instantiate.d.ts.map +1 -0
  149. package/dist/wasm/internal/instantiate.js +92 -0
  150. package/dist/wasm/internal/instantiate.js.map +1 -0
  151. package/dist/zod/TypedData.d.ts +12 -12
  152. package/package.json +36 -1
  153. package/src/core/Base64.ts +19 -166
  154. package/src/core/BinaryStateTree.ts +2 -3
  155. package/src/core/Bls.ts +27 -77
  156. package/src/core/Ed25519.ts +7 -7
  157. package/src/core/Engine.ts +235 -0
  158. package/src/core/Hash.ts +9 -12
  159. package/src/core/Keystore.ts +17 -17
  160. package/src/core/Mnemonic.ts +3 -6
  161. package/src/core/P256.ts +15 -24
  162. package/src/core/Rlp_tx.bench.ts +2 -4
  163. package/src/core/Secp256k1.ts +9 -14
  164. package/src/core/TxEnvelopeEip1559.bench.ts +0 -5
  165. package/src/core/WebCryptoP256.ts +2 -2
  166. package/src/core/X25519.ts +4 -3
  167. package/src/core/_test/AbiParameters.adversarial.fuzz.ts +1 -2
  168. package/src/core/_test/AbiParameters.fuzz.ts +1 -2
  169. package/src/core/_test/Base.fuzz.ts +1 -2
  170. package/src/core/_test/Cbor.fuzz.ts +1 -2
  171. package/src/core/_test/Engine.fuzz.ts +293 -0
  172. package/src/core/_test/Engine.test-d.ts +84 -0
  173. package/src/core/_test/Engine.test.ts +523 -0
  174. package/src/core/_test/Hash.vectors.test.ts +56 -0
  175. package/src/core/_test/Hex.fuzz.ts +1 -2
  176. package/src/core/_test/Rlp.fuzz.ts +1 -2
  177. package/src/core/_test/Signature.fuzz.ts +1 -2
  178. package/src/core/_test/curves.test.ts +7 -0
  179. package/src/core/_test/index.test.ts +1 -0
  180. package/src/core/internal/bls.ts +121 -0
  181. package/src/core/internal/codec/_test/base64.conformance.ts +156 -0
  182. package/src/core/internal/codec/_test/hex.conformance.ts +204 -0
  183. package/src/core/internal/codec/_test/hex.fuzz.ts +234 -0
  184. package/src/core/internal/codec/_test/hex.test.ts +124 -0
  185. package/src/core/internal/codec/base64.ts +222 -0
  186. package/src/core/internal/codec/hex.ts +115 -34
  187. package/src/core/internal/curves.ts +18 -0
  188. package/src/core/internal/ed25519.ts +64 -0
  189. package/src/core/internal/engine.ts +410 -0
  190. package/src/core/internal/hash.ts +48 -0
  191. package/src/core/internal/keystore.ts +107 -0
  192. package/src/core/internal/mnemonic.ts +18 -0
  193. package/src/core/internal/p256.ts +91 -0
  194. package/src/core/internal/secp256k1.ts +91 -0
  195. package/src/core/internal/x25519.ts +40 -0
  196. package/src/index.docs.ts +2 -0
  197. package/src/index.ts +36 -0
  198. package/src/node/Engine.ts +69 -0
  199. package/src/node/Hash.ts +56 -0
  200. package/src/node/_test/Engine.test.ts +62 -0
  201. package/src/node/_test/Hash.test-d.ts +26 -0
  202. package/src/node/_test/Hash.test.ts +102 -0
  203. package/src/node/index.ts +40 -0
  204. package/src/tempo/VirtualMaster.test.ts +75 -0
  205. package/src/tempo/VirtualMaster.ts +28 -6
  206. package/src/tempo/ZoneId.test-d.ts +16 -0
  207. package/src/tempo/ZoneId.test.ts +48 -13
  208. package/src/tempo/ZoneId.ts +55 -19
  209. package/src/tempo/index.ts +5 -6
  210. package/src/tempo/internal/mine.wasm.ts +7 -13
  211. package/src/tempo/internal/virtualMasterPool.ts +4 -1
  212. package/src/tsconfig.json +2 -1
  213. package/src/version.ts +1 -1
  214. package/src/wasm/Engine.ts +76 -0
  215. package/src/wasm/Hash.bench.ts +60 -0
  216. package/src/wasm/Hash.ts +109 -0
  217. package/src/wasm/_test/Engine.test.ts +62 -0
  218. package/src/wasm/_test/Hash.browser.test.ts +50 -0
  219. package/src/wasm/_test/Hash.fuzz.ts +188 -0
  220. package/src/wasm/_test/Hash.test-d.ts +28 -0
  221. package/src/wasm/_test/Hash.test.ts +211 -0
  222. package/src/wasm/_test/Hash.vectors.test.ts +117 -0
  223. package/src/wasm/_test/Runtime.test.ts +21 -0
  224. package/src/wasm/_test/runtime.wasm.ts +11 -0
  225. package/src/wasm/index.ts +47 -0
  226. package/src/wasm/internal/hash.ts +50 -0
  227. package/src/wasm/internal/hashes.wasm.ts +14 -0
  228. package/src/wasm/internal/instantiate.ts +135 -0
  229. package/src/zod/tempo/_test/ZoneId.test.ts +2 -2
  230. package/src/tempo/internal/mine.c +0 -182
@@ -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
+ })