ox 1.1.2 → 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.
- package/CHANGELOG.md +27 -0
- package/dist/core/Hash.d.ts +36 -0
- package/dist/core/Hash.d.ts.map +1 -1
- package/dist/core/Hash.js +35 -0
- package/dist/core/Hash.js.map +1 -1
- package/dist/node/Hash.d.ts +3 -3
- package/dist/node/Hash.js +3 -3
- package/dist/trusted-setups/Paths.d.ts +1 -0
- package/dist/trusted-setups/Paths.d.ts.map +1 -1
- package/dist/trusted-setups/Paths.js +1 -0
- package/dist/trusted-setups/Paths.js.map +1 -1
- package/dist/trusted-setups/Setups.d.ts +35 -0
- package/dist/trusted-setups/Setups.d.ts.map +1 -0
- package/dist/trusted-setups/Setups.js +29 -0
- package/dist/trusted-setups/Setups.js.map +1 -0
- package/dist/trusted-setups/index.d.ts +20 -0
- package/dist/trusted-setups/index.d.ts.map +1 -1
- package/dist/trusted-setups/index.js +20 -0
- package/dist/trusted-setups/index.js.map +1 -1
- package/dist/trusted-setups/internal/paths.d.ts.map +1 -1
- package/dist/trusted-setups/internal/paths.js +1 -1
- package/dist/trusted-setups/internal/paths.js.map +1 -1
- package/dist/trusted-setups/internal/setups/mainnet.d.ts +7 -0
- package/dist/trusted-setups/internal/setups/mainnet.d.ts.map +1 -0
- package/dist/trusted-setups/internal/setups/mainnet.js +11 -0
- package/dist/trusted-setups/internal/setups/mainnet.js.map +1 -0
- package/dist/wasm/Engine.d.ts +3 -2
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js +5 -5
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/Keystore.d.ts +23 -9
- package/dist/wasm/Keystore.d.ts.map +1 -1
- package/dist/wasm/Keystore.js +30 -13
- package/dist/wasm/Keystore.js.map +1 -1
- package/dist/wasm/Kzg.d.ts +112 -0
- package/dist/wasm/Kzg.d.ts.map +1 -0
- package/dist/wasm/Kzg.js +333 -0
- package/dist/wasm/Kzg.js.map +1 -0
- package/dist/wasm/Secp256k1.d.ts +55 -0
- package/dist/wasm/Secp256k1.d.ts.map +1 -0
- package/dist/wasm/Secp256k1.js +181 -0
- package/dist/wasm/Secp256k1.js.map +1 -0
- package/dist/wasm/index.d.ts +44 -1
- package/dist/wasm/index.d.ts.map +1 -1
- package/dist/wasm/index.js +44 -1
- package/dist/wasm/index.js.map +1 -1
- package/dist/wasm/internal/instantiate.d.ts +12 -0
- package/dist/wasm/internal/instantiate.d.ts.map +1 -1
- package/dist/wasm/internal/instantiate.js +23 -7
- package/dist/wasm/internal/instantiate.js.map +1 -1
- package/dist/wasm/internal/keystore.d.ts +10 -0
- package/dist/wasm/internal/keystore.d.ts.map +1 -0
- package/dist/wasm/internal/keystore.js +10 -0
- package/dist/wasm/internal/keystore.js.map +1 -0
- package/dist/wasm/internal/kzg.d.ts +17 -0
- package/dist/wasm/internal/kzg.d.ts.map +1 -0
- package/dist/wasm/internal/kzg.js +8 -0
- package/dist/wasm/internal/kzg.js.map +1 -0
- package/dist/wasm/internal/kzg.wasm.d.ts +3 -0
- package/dist/wasm/internal/kzg.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/kzg.wasm.js +10 -0
- package/dist/wasm/internal/kzg.wasm.js.map +1 -0
- package/dist/wasm/internal/scrypt.d.ts +16 -0
- package/dist/wasm/internal/scrypt.d.ts.map +1 -0
- package/dist/wasm/internal/scrypt.js +78 -0
- package/dist/wasm/internal/scrypt.js.map +1 -0
- package/dist/wasm/internal/scrypt.wasm.d.ts +7 -0
- package/dist/wasm/internal/scrypt.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/scrypt.wasm.js +14 -0
- package/dist/wasm/internal/scrypt.wasm.js.map +1 -0
- package/dist/wasm/internal/secp256k1.d.ts +16 -0
- package/dist/wasm/internal/secp256k1.d.ts.map +1 -0
- package/dist/wasm/internal/secp256k1.js +20 -0
- package/dist/wasm/internal/secp256k1.js.map +1 -0
- package/dist/wasm/internal/secp256k1.wasm.d.ts +3 -0
- package/dist/wasm/internal/secp256k1.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/secp256k1.wasm.js +10 -0
- package/dist/wasm/internal/secp256k1.wasm.js.map +1 -0
- package/package.json +19 -2
- package/src/core/Hash.ts +59 -0
- package/src/core/_test/Block.test.ts +9 -2
- package/src/core/_test/Hash.test-d.ts +18 -0
- package/src/core/_test/Hash.test.ts +100 -1
- package/src/core/_test/Hash.vectors.test.ts +1 -2
- package/src/core/_test/Transaction.test.ts +11 -2
- package/src/node/Hash.ts +3 -3
- package/src/node/_test/Hash.test-d.ts +1 -0
- package/src/trusted-setups/Paths.ts +1 -0
- package/src/trusted-setups/README.md +39 -0
- package/src/trusted-setups/Setups.ts +46 -0
- package/src/trusted-setups/_test/Paths.test.ts +16 -0
- package/src/trusted-setups/_test/Setups.test-d.ts +7 -0
- package/src/trusted-setups/_test/Setups.test.ts +43 -0
- package/src/trusted-setups/_test/index.test.ts +1 -0
- package/src/trusted-setups/index.ts +21 -0
- package/src/trusted-setups/internal/paths.ts +4 -1
- package/src/trusted-setups/internal/setups/mainnet.source.json +7 -0
- package/src/trusted-setups/internal/setups/mainnet.ts +16 -0
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +8 -6
- package/src/wasm/Keystore.bench.ts +26 -0
- package/src/wasm/Keystore.ts +32 -14
- package/src/wasm/Kzg.bench.ts +63 -0
- package/src/wasm/Kzg.ts +518 -0
- package/src/wasm/Secp256k1.bench.ts +79 -0
- package/src/wasm/Secp256k1.ts +248 -0
- package/src/wasm/_test/Engine.test-d.ts +1 -0
- package/src/wasm/_test/Hash.browser.test.ts +3 -0
- package/src/wasm/_test/Hash.test-d.ts +1 -0
- package/src/wasm/_test/Hash.test.ts +2 -2
- package/src/wasm/_test/Keystore.browser.test.ts +31 -0
- package/src/wasm/_test/Keystore.fuzz.ts +50 -0
- package/src/wasm/_test/Keystore.test-d.ts +10 -1
- package/src/wasm/_test/Keystore.test.ts +289 -4
- package/src/wasm/_test/Kzg.test-d.ts +19 -0
- package/src/wasm/_test/Kzg.test.ts +220 -0
- package/src/wasm/_test/Runtime.test.ts +9 -0
- package/src/wasm/_test/Secp256k1.test-d.ts +35 -0
- package/src/wasm/_test/Secp256k1.test.ts +354 -0
- package/src/wasm/index.ts +46 -1
- package/src/wasm/internal/instantiate.ts +29 -9
- package/src/wasm/internal/keystore.ts +19 -0
- package/src/wasm/internal/kzg.ts +50 -0
- package/src/wasm/internal/kzg.wasm.ts +11 -0
- package/src/wasm/internal/scrypt.ts +131 -0
- package/src/wasm/internal/scrypt.wasm.ts +17 -0
- package/src/wasm/internal/secp256k1.ts +62 -0
- package/src/wasm/internal/secp256k1.wasm.ts +11 -0
- package/wasm/vendor/c-kzg-4844/LICENSE +201 -0
- package/wasm/vendor/c-kzg-4844/blst/LICENSE +201 -0
- package/src/trusted-setups/internal/setups/mainnet.txt +0 -4163
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
2
|
+
import { scrypt as scrypt_noble } from '@noble/hashes/scrypt.js'
|
|
2
3
|
import { sha256 } from '@noble/hashes/sha2.js'
|
|
3
4
|
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
4
5
|
import * as Bytes from '../../core/Bytes.js'
|
|
@@ -6,9 +7,11 @@ import * as Engine from '../../core/Engine.js'
|
|
|
6
7
|
import * as Keystore from '../../core/Keystore.js'
|
|
7
8
|
import * as WasmKeystore from '../Keystore.js'
|
|
8
9
|
import * as hashes from '../internal/hashes.js'
|
|
9
|
-
import { wasmBase64 } from '../internal/hashes.wasm.js'
|
|
10
|
+
import { wasmBase64 as hashesWasmBase64 } from '../internal/hashes.wasm.js'
|
|
10
11
|
import * as internal from '../internal/instantiate.js'
|
|
11
12
|
import * as pbkdf2 from '../internal/pbkdf2.js'
|
|
13
|
+
import * as scrypt from '../internal/scrypt.js'
|
|
14
|
+
import { wasmBase64 as scryptWasmBase64 } from '../internal/scrypt.wasm.js'
|
|
12
15
|
|
|
13
16
|
let engine: WasmKeystore.engine.ReturnType
|
|
14
17
|
|
|
@@ -25,6 +28,7 @@ describe('engine', () => {
|
|
|
25
28
|
expect(Object.keys(await WasmKeystore.engine())).toMatchInlineSnapshot(`
|
|
26
29
|
[
|
|
27
30
|
"pbkdf2Sha256",
|
|
31
|
+
"scrypt",
|
|
28
32
|
]
|
|
29
33
|
`)
|
|
30
34
|
})
|
|
@@ -34,6 +38,11 @@ describe('engine', () => {
|
|
|
34
38
|
expect(a).toBe(b)
|
|
35
39
|
})
|
|
36
40
|
|
|
41
|
+
test('behavior: shares the compiled scrypt instance', async () => {
|
|
42
|
+
const [a, b] = await Promise.all([scrypt.load(), scrypt.load()])
|
|
43
|
+
expect(a).toBe(b)
|
|
44
|
+
})
|
|
45
|
+
|
|
37
46
|
test('behavior: hands every caller its own engine', async () => {
|
|
38
47
|
const [a, b] = await Promise.all([
|
|
39
48
|
WasmKeystore.engine(),
|
|
@@ -105,7 +114,7 @@ describe('pbkdf2Sha256', () => {
|
|
|
105
114
|
})
|
|
106
115
|
|
|
107
116
|
test('behavior: clears passwords, salts, outputs, and scratch', async () => {
|
|
108
|
-
const module = await internal.instantiate<hashes.Exports>(
|
|
117
|
+
const module = await internal.instantiate<hashes.Exports>(hashesWasmBase64)
|
|
109
118
|
const password = input(65)
|
|
110
119
|
const salt = input(52)
|
|
111
120
|
const stack = module.view().slice(0, module.heapBase)
|
|
@@ -120,7 +129,7 @@ describe('pbkdf2Sha256', () => {
|
|
|
120
129
|
})
|
|
121
130
|
|
|
122
131
|
test('behavior: clears the complete region after a late trap', async () => {
|
|
123
|
-
const module = await internal.instantiate<hashes.Exports>(
|
|
132
|
+
const module = await internal.instantiate<hashes.Exports>(hashesWasmBase64)
|
|
124
133
|
const password = input(65)
|
|
125
134
|
const salt = input(52)
|
|
126
135
|
const exports: pbkdf2.Exports = {
|
|
@@ -143,7 +152,7 @@ describe('pbkdf2Sha256', () => {
|
|
|
143
152
|
})
|
|
144
153
|
|
|
145
154
|
test('behavior: rejects a workspace that overflows wasm32 before growing memory', async () => {
|
|
146
|
-
const module = await internal.instantiate<hashes.Exports>(
|
|
155
|
+
const module = await internal.instantiate<hashes.Exports>(hashesWasmBase64)
|
|
147
156
|
const memory = module.view().length
|
|
148
157
|
|
|
149
158
|
expect(() =>
|
|
@@ -167,6 +176,236 @@ describe('pbkdf2Sha256', () => {
|
|
|
167
176
|
})
|
|
168
177
|
})
|
|
169
178
|
|
|
179
|
+
const scryptCases = [
|
|
180
|
+
{ N: 16, dkLen: 64, p: 1, password: 0, r: 1, salt: 0 },
|
|
181
|
+
{ N: 1024, dkLen: 32, p: 1, password: 8, r: 1, salt: 16 },
|
|
182
|
+
{ N: 1024, dkLen: 64, p: 1, password: 65, r: 8, salt: 52 },
|
|
183
|
+
{ N: 1024, dkLen: 33, p: 2, password: 9, r: 4, salt: 17 },
|
|
184
|
+
] as const
|
|
185
|
+
|
|
186
|
+
describe('scrypt', () => {
|
|
187
|
+
test.each(scryptCases)(
|
|
188
|
+
'matches the default for password=$password salt=$salt N=$N r=$r p=$p dkLen=$dkLen',
|
|
189
|
+
({ N, dkLen, p, password: passwordLength, r, salt: saltLength }) => {
|
|
190
|
+
const password = input(passwordLength)
|
|
191
|
+
const salt = input(saltLength)
|
|
192
|
+
const options = { N, dkLen, p, r }
|
|
193
|
+
expect(engine.scrypt(password, salt, options)).toEqual(
|
|
194
|
+
scrypt_noble(password, salt, options),
|
|
195
|
+
)
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
test.each([
|
|
200
|
+
{
|
|
201
|
+
N: 16,
|
|
202
|
+
expected:
|
|
203
|
+
'0x77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',
|
|
204
|
+
p: 1,
|
|
205
|
+
password: '',
|
|
206
|
+
r: 1,
|
|
207
|
+
salt: '',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
N: 1024,
|
|
211
|
+
expected:
|
|
212
|
+
'0xfdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640',
|
|
213
|
+
p: 16,
|
|
214
|
+
password: 'password',
|
|
215
|
+
r: 8,
|
|
216
|
+
salt: 'NaCl',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
N: 16_384,
|
|
220
|
+
expected:
|
|
221
|
+
'0x7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887',
|
|
222
|
+
p: 1,
|
|
223
|
+
password: 'pleaseletmein',
|
|
224
|
+
r: 8,
|
|
225
|
+
salt: 'SodiumChloride',
|
|
226
|
+
},
|
|
227
|
+
])(
|
|
228
|
+
'matches the RFC 7914 vector for N=$N r=$r p=$p',
|
|
229
|
+
({ N, expected, p, password, r, salt }) => {
|
|
230
|
+
expect(
|
|
231
|
+
Bytes.toHex(
|
|
232
|
+
engine.scrypt(Bytes.fromString(password), Bytes.fromString(salt), {
|
|
233
|
+
N,
|
|
234
|
+
dkLen: 64,
|
|
235
|
+
p,
|
|
236
|
+
r,
|
|
237
|
+
}),
|
|
238
|
+
),
|
|
239
|
+
).toBe(expected)
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
test('behavior: reads non-zero-offset views without mutating their backing buffers', () => {
|
|
244
|
+
const passwordBacking = new Uint8Array(80).fill(0xa5)
|
|
245
|
+
const saltBacking = new Uint8Array(90).fill(0x5a)
|
|
246
|
+
const password = passwordBacking.subarray(7, 72)
|
|
247
|
+
const salt = saltBacking.subarray(11, 63)
|
|
248
|
+
password.set(input(password.length))
|
|
249
|
+
salt.set(input(salt.length))
|
|
250
|
+
const passwordSnapshot = passwordBacking.slice()
|
|
251
|
+
const saltSnapshot = saltBacking.slice()
|
|
252
|
+
const options = { N: 1024, dkLen: 65, p: 2, r: 1 }
|
|
253
|
+
|
|
254
|
+
expect(engine.scrypt(password, salt, options)).toEqual(
|
|
255
|
+
scrypt_noble(password, salt, options),
|
|
256
|
+
)
|
|
257
|
+
expect(passwordBacking).toEqual(passwordSnapshot)
|
|
258
|
+
expect(saltBacking).toEqual(saltSnapshot)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('behavior: results are fresh and never alias WASM memory', () => {
|
|
262
|
+
const first = engine.scrypt(input(65), input(52), {
|
|
263
|
+
N: 16,
|
|
264
|
+
dkLen: 65,
|
|
265
|
+
p: 1,
|
|
266
|
+
r: 1,
|
|
267
|
+
})
|
|
268
|
+
const snapshot = first.slice()
|
|
269
|
+
const second = engine.scrypt(input(5), input(9), {
|
|
270
|
+
N: 32,
|
|
271
|
+
dkLen: 97,
|
|
272
|
+
p: 1,
|
|
273
|
+
r: 1,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
expect(first).not.toBe(second)
|
|
277
|
+
expect(first).toEqual(snapshot)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('behavior: clears inputs, outputs, and every workspace', async () => {
|
|
281
|
+
const module = await internal.instantiate<scrypt.Exports>(scryptWasmBase64)
|
|
282
|
+
const password = input(65)
|
|
283
|
+
const salt = input(52)
|
|
284
|
+
const options = { N: 16, dkLen: 65, p: 2, r: 2 }
|
|
285
|
+
const stack = module.view().slice(0, module.heapBase)
|
|
286
|
+
|
|
287
|
+
expect(scrypt.derive(module, password, salt, options)).toEqual(
|
|
288
|
+
scrypt_noble(password, salt, options),
|
|
289
|
+
)
|
|
290
|
+
expect(module.view().slice(0, module.heapBase)).toEqual(stack)
|
|
291
|
+
expect(module.view().slice(module.heapBase)).toEqual(
|
|
292
|
+
new Uint8Array(module.view().length - module.heapBase),
|
|
293
|
+
)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test('behavior: clears the complete region after a late trap', async () => {
|
|
297
|
+
const module = await internal.instantiate<scrypt.Exports>(scryptWasmBase64)
|
|
298
|
+
const password = input(65)
|
|
299
|
+
const salt = input(52)
|
|
300
|
+
const exports: scrypt.Exports = {
|
|
301
|
+
scrypt(...parameters) {
|
|
302
|
+
module.exports.scrypt(...parameters)
|
|
303
|
+
throw new WebAssembly.RuntimeError('forced late trap')
|
|
304
|
+
},
|
|
305
|
+
zero: (ptr, length) => module.exports.zero(ptr, length),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
expect(() =>
|
|
309
|
+
scrypt.derive({ ...module, exports }, password, salt, {
|
|
310
|
+
N: 16,
|
|
311
|
+
dkLen: 65,
|
|
312
|
+
p: 2,
|
|
313
|
+
r: 2,
|
|
314
|
+
}),
|
|
315
|
+
).toThrow(WebAssembly.RuntimeError)
|
|
316
|
+
expect(module.view().slice(module.heapBase)).toEqual(
|
|
317
|
+
new Uint8Array(module.view().length - module.heapBase),
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('behavior: rejects the workspace limit before growing memory', async () => {
|
|
322
|
+
const module = await internal.instantiate<scrypt.Exports>(scryptWasmBase64)
|
|
323
|
+
const memory = module.view().length
|
|
324
|
+
|
|
325
|
+
expect(() =>
|
|
326
|
+
scrypt.derive(module, new Uint8Array(), new Uint8Array(), {
|
|
327
|
+
N: 1_048_576,
|
|
328
|
+
dkLen: 32,
|
|
329
|
+
p: 1,
|
|
330
|
+
r: 16,
|
|
331
|
+
}),
|
|
332
|
+
).toThrow(
|
|
333
|
+
'"maxmem" limit was hit: memUsed(128*r*(N+p+1))=2147487744, maxmem=1073742848',
|
|
334
|
+
)
|
|
335
|
+
expect(module.view()).toHaveLength(memory)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('behavior: rejects the WASM memory limit before growing memory', async () => {
|
|
339
|
+
const module = await internal.instantiate<scrypt.Exports>(scryptWasmBase64)
|
|
340
|
+
const memory = module.view().length
|
|
341
|
+
|
|
342
|
+
expect(() =>
|
|
343
|
+
scrypt.derive(module, new Uint8Array(), new Uint8Array(), {
|
|
344
|
+
N: 2,
|
|
345
|
+
dkLen: 1_074_790_400,
|
|
346
|
+
p: 1,
|
|
347
|
+
r: 1,
|
|
348
|
+
}),
|
|
349
|
+
).toThrow(internal.MemoryError)
|
|
350
|
+
expect(module.view()).toHaveLength(memory)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('behavior: rejects reentrant use of one instance without corrupting the outer call', async () => {
|
|
354
|
+
const module = await internal.instantiate<scrypt.Exports>(scryptWasmBase64)
|
|
355
|
+
const password = input(8)
|
|
356
|
+
const salt = input(16)
|
|
357
|
+
const options = { N: 16, dkLen: 32, p: 1, r: 1 }
|
|
358
|
+
let reentered = false
|
|
359
|
+
const wrapper: internal.Module<scrypt.Exports> = {
|
|
360
|
+
...module,
|
|
361
|
+
exports: {
|
|
362
|
+
scrypt(...parameters) {
|
|
363
|
+
expect(() => scrypt.derive(wrapper, password, salt, options)).toThrow(
|
|
364
|
+
'The scrypt WASM instance is already deriving a key.',
|
|
365
|
+
)
|
|
366
|
+
reentered = true
|
|
367
|
+
module.exports.scrypt(...parameters)
|
|
368
|
+
},
|
|
369
|
+
zero: (ptr, length) => module.exports.zero(ptr, length),
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
expect(scrypt.derive(wrapper, password, salt, options)).toEqual(
|
|
374
|
+
scrypt_noble(password, salt, options),
|
|
375
|
+
)
|
|
376
|
+
expect(reentered).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test.each([
|
|
380
|
+
{ N: 1, dkLen: 32, p: 1, r: 8 },
|
|
381
|
+
{ N: 3, dkLen: 32, p: 1, r: 8 },
|
|
382
|
+
{ N: 1024.5, dkLen: 32, p: 1, r: 8 },
|
|
383
|
+
{ N: 1024, dkLen: 32, p: 1, r: -1 },
|
|
384
|
+
{ N: 1024, dkLen: 32, p: 0, r: 8 },
|
|
385
|
+
{ N: 1024, dkLen: 0, p: 1, r: 8 },
|
|
386
|
+
{ N: 1024, dkLen: 32, p: 1, r: 0 },
|
|
387
|
+
])(
|
|
388
|
+
'behavior: matches the default error for N=$N r=$r p=$p dkLen=$dkLen',
|
|
389
|
+
(options) => {
|
|
390
|
+
const getError = (fn: () => unknown) => {
|
|
391
|
+
try {
|
|
392
|
+
fn()
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return {
|
|
395
|
+
message: (error as Error).message,
|
|
396
|
+
name: (error as Error).constructor.name,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
throw new Error('Expected derivation to throw.')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
expect(
|
|
403
|
+
getError(() => engine.scrypt(input(8), input(16), options)),
|
|
404
|
+
).toEqual(getError(() => scrypt_noble(input(8), input(16), options)))
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
})
|
|
408
|
+
|
|
170
409
|
describe('Engine.install', () => {
|
|
171
410
|
test('behavior: ox uses synchronous WASM PBKDF2 once installed', async () => {
|
|
172
411
|
await Engine.install({ Keystore: engine })
|
|
@@ -189,4 +428,50 @@ describe('Engine.install', () => {
|
|
|
189
428
|
Engine.reset()
|
|
190
429
|
}
|
|
191
430
|
})
|
|
431
|
+
|
|
432
|
+
test('behavior: ox uses synchronous WASM scrypt once installed', async () => {
|
|
433
|
+
const wasm = engine.scrypt
|
|
434
|
+
let calls = 0
|
|
435
|
+
await Engine.install({
|
|
436
|
+
Keystore: {
|
|
437
|
+
...engine,
|
|
438
|
+
scrypt(...parameters) {
|
|
439
|
+
calls++
|
|
440
|
+
return wasm(...parameters)
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
})
|
|
444
|
+
try {
|
|
445
|
+
const salt = input(32)
|
|
446
|
+
const [key] = Keystore.scrypt({
|
|
447
|
+
n: 1024,
|
|
448
|
+
p: 2,
|
|
449
|
+
password: 'testpassword',
|
|
450
|
+
r: 1,
|
|
451
|
+
salt,
|
|
452
|
+
})
|
|
453
|
+
expect(key()).toBe(
|
|
454
|
+
Bytes.toHex(
|
|
455
|
+
scrypt_noble(Bytes.fromString('testpassword'), salt, {
|
|
456
|
+
N: 1024,
|
|
457
|
+
dkLen: 32,
|
|
458
|
+
p: 2,
|
|
459
|
+
r: 1,
|
|
460
|
+
}),
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
expect(calls).toBe(1)
|
|
464
|
+
|
|
465
|
+
await Keystore.scryptAsync({
|
|
466
|
+
n: 1024,
|
|
467
|
+
p: 1,
|
|
468
|
+
password: 'testpassword',
|
|
469
|
+
r: 1,
|
|
470
|
+
salt,
|
|
471
|
+
})
|
|
472
|
+
expect(calls).toBe(1)
|
|
473
|
+
} finally {
|
|
474
|
+
Engine.reset()
|
|
475
|
+
}
|
|
476
|
+
})
|
|
192
477
|
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as CoreKzg from '../../core/Kzg.js'
|
|
3
|
+
import { mainnet as trustedSetup } from '../../trusted-setups/Setups.js'
|
|
4
|
+
import * as Kzg from '../Kzg.js'
|
|
5
|
+
|
|
6
|
+
test('create returns a disposable Kzg.Kzg implementation', async () => {
|
|
7
|
+
const instance = await Kzg.create({ trustedSetup })
|
|
8
|
+
|
|
9
|
+
expectTypeOf(instance).toMatchTypeOf<CoreKzg.Kzg>()
|
|
10
|
+
expectTypeOf<Kzg.create.ReturnType['dispose']>().toEqualTypeOf<() => void>()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('trusted setup accepts packed bytes', () => {
|
|
14
|
+
expectTypeOf<Kzg.TrustedSetup>().toEqualTypeOf<{
|
|
15
|
+
readonly g1_lagrange: Uint8Array | readonly string[]
|
|
16
|
+
readonly g1_monomial: Uint8Array | readonly string[]
|
|
17
|
+
readonly g2_monomial: Uint8Array | readonly string[]
|
|
18
|
+
}>()
|
|
19
|
+
})
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'vp/test'
|
|
2
|
+
import { kzg as reference } from '../../../test/kzg.js'
|
|
3
|
+
import type * as Bytes from '../../core/Bytes.js'
|
|
4
|
+
import type * as CoreKzg from '../../core/Kzg.js'
|
|
5
|
+
import { mainnet as trustedSetup } from '../../trusted-setups/Setups.js'
|
|
6
|
+
import * as Kzg from '../Kzg.js'
|
|
7
|
+
|
|
8
|
+
const blob = Uint8Array.from({ length: 131_072 }, (_, index) =>
|
|
9
|
+
index % 32 === 31 ? index % 251 : 0,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
let instance: Kzg.create.ReturnType
|
|
13
|
+
let commitment: Bytes.Bytes
|
|
14
|
+
let cells: readonly Bytes.Bytes[]
|
|
15
|
+
let proofs: readonly Bytes.Bytes[]
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
instance = await Kzg.create({ trustedSetup })
|
|
19
|
+
commitment = instance.blobToKzgCommitment(blob)
|
|
20
|
+
;({ cells, proofs } = instance.computeCellsAndKzgProofs(blob))
|
|
21
|
+
}, 120_000)
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
instance.dispose()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('create', () => {
|
|
28
|
+
test('returns the injected Kzg.Kzg boundary', () => {
|
|
29
|
+
expect(instance satisfies CoreKzg.Kzg).toMatchInlineSnapshot(`
|
|
30
|
+
{
|
|
31
|
+
"blobToKzgCommitment": [Function],
|
|
32
|
+
"computeCells": [Function],
|
|
33
|
+
"computeCellsAndKzgProofs": [Function],
|
|
34
|
+
"dispose": [Function],
|
|
35
|
+
"recoverCellsAndKzgProofs": [Function],
|
|
36
|
+
"verifyCellKzgProofBatch": [Function],
|
|
37
|
+
}
|
|
38
|
+
`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('compiles concurrently into independently owned instances', async () => {
|
|
42
|
+
const [first, second] = await Promise.all([
|
|
43
|
+
Kzg.create({ trustedSetup }),
|
|
44
|
+
Kzg.create({ trustedSetup }),
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
first.dispose()
|
|
48
|
+
expect(() => first.blobToKzgCommitment(blob)).toThrow(Kzg.DisposedError)
|
|
49
|
+
expect(second.blobToKzgCommitment(blob)).toEqual(commitment)
|
|
50
|
+
second.dispose()
|
|
51
|
+
second.dispose()
|
|
52
|
+
expect(() => second.computeCells(blob)).toThrowErrorMatchingInlineSnapshot(
|
|
53
|
+
`[Kzg.DisposedError: KZG instance has been disposed.]`,
|
|
54
|
+
)
|
|
55
|
+
}, 120_000)
|
|
56
|
+
|
|
57
|
+
test('rejects precomputation beyond the memory bound', async () => {
|
|
58
|
+
await expect(
|
|
59
|
+
Kzg.create({ precompute: 9, trustedSetup }),
|
|
60
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
61
|
+
`[Kzg.InvalidPrecomputeError: \`precompute\` must be an integer from 0 through 8. Got 9.]`,
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('rejects malformed trusted setup points', async () => {
|
|
66
|
+
await expect(
|
|
67
|
+
Kzg.create({
|
|
68
|
+
trustedSetup: {
|
|
69
|
+
...trustedSetup,
|
|
70
|
+
g2_monomial: trustedSetup.g2_monomial.slice(1),
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
74
|
+
`[Kzg.InvalidTrustedSetupError: \`g2_monomial\` must contain exactly 6240 bytes.]`,
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('rejects invalid trusted setup points', async () => {
|
|
79
|
+
const g1Lagrange = trustedSetup.g1_lagrange.slice()
|
|
80
|
+
g1Lagrange.fill(0, 0, 48)
|
|
81
|
+
await expect(
|
|
82
|
+
Kzg.create({
|
|
83
|
+
trustedSetup: {
|
|
84
|
+
...trustedSetup,
|
|
85
|
+
g1_lagrange: g1Lagrange,
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
89
|
+
`[Kzg.InvalidTrustedSetupError: c-kzg rejected the trusted setup.]`,
|
|
90
|
+
)
|
|
91
|
+
}, 120_000)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('blobToKzgCommitment', () => {
|
|
95
|
+
test('conformance: matches the independent reference implementation', () => {
|
|
96
|
+
expect(commitment).toEqual(reference.blobToKzgCommitment(blob))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('rejects a malformed blob', () => {
|
|
100
|
+
expect(() =>
|
|
101
|
+
instance.blobToKzgCommitment(new Uint8Array(31)),
|
|
102
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
103
|
+
`[Kzg.InvalidInputError: \`blob\` must contain exactly 131072 bytes.]`,
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('reuses setup and scratch state', () => {
|
|
108
|
+
for (let i = 0; i < 100; i++)
|
|
109
|
+
expect(instance.blobToKzgCommitment(blob)).toEqual(commitment)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('computeCells', () => {
|
|
114
|
+
test('conformance: matches the independent reference implementation', () => {
|
|
115
|
+
expect(instance.computeCells(blob)).toEqual(reference.computeCells(blob))
|
|
116
|
+
}, 120_000)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('computeCellsAndKzgProofs', () => {
|
|
120
|
+
test('conformance: matches the independent reference implementation', () => {
|
|
121
|
+
const expected = reference.computeCellsAndKzgProofs(blob)
|
|
122
|
+
expect(cells).toEqual(expected.cells)
|
|
123
|
+
expect(proofs).toEqual(expected.proofs)
|
|
124
|
+
}, 120_000)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('recoverCellsAndKzgProofs', () => {
|
|
128
|
+
test('conformance: matches the independent reference implementation', () => {
|
|
129
|
+
const indices = cells.map((_, index) => index).filter((index) => index % 2)
|
|
130
|
+
const partial = cells.filter((_, index) => index % 2)
|
|
131
|
+
const expected = reference.recoverCellsAndKzgProofs(indices, partial)
|
|
132
|
+
|
|
133
|
+
expect(instance.recoverCellsAndKzgProofs(indices, partial)).toEqual(
|
|
134
|
+
expected,
|
|
135
|
+
)
|
|
136
|
+
}, 120_000)
|
|
137
|
+
|
|
138
|
+
test('accepts unordered indices', () => {
|
|
139
|
+
const indices = cells
|
|
140
|
+
.map((_, index) => index)
|
|
141
|
+
.filter((index) => index % 2)
|
|
142
|
+
.reverse()
|
|
143
|
+
const partial = indices.map((index) => cells[index]!)
|
|
144
|
+
|
|
145
|
+
expect(instance.recoverCellsAndKzgProofs(indices, partial)).toEqual({
|
|
146
|
+
cells,
|
|
147
|
+
proofs,
|
|
148
|
+
})
|
|
149
|
+
}, 120_000)
|
|
150
|
+
|
|
151
|
+
test('rejects duplicate indices', () => {
|
|
152
|
+
expect(() =>
|
|
153
|
+
instance.recoverCellsAndKzgProofs(
|
|
154
|
+
Array.from({ length: 64 }, () => 0),
|
|
155
|
+
cells.slice(0, 64),
|
|
156
|
+
),
|
|
157
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
158
|
+
`[Kzg.InvalidInputError: \`cellIndices\` must not contain duplicate indices.]`,
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('verifyCellKzgProofBatch', () => {
|
|
164
|
+
const indices = Array.from({ length: 128 }, (_, index) => index)
|
|
165
|
+
|
|
166
|
+
test('conformance: matches the independent reference implementation', () => {
|
|
167
|
+
const commitments = cells.map(() => commitment)
|
|
168
|
+
expect(
|
|
169
|
+
instance.verifyCellKzgProofBatch(commitments, indices, cells, proofs),
|
|
170
|
+
).toBe(
|
|
171
|
+
reference.verifyCellKzgProofBatch(commitments, indices, cells, proofs),
|
|
172
|
+
)
|
|
173
|
+
}, 120_000)
|
|
174
|
+
|
|
175
|
+
test('returns false for a corrupted cell', () => {
|
|
176
|
+
const corrupted = cells.map((cell, index) => {
|
|
177
|
+
if (index !== 7) return cell
|
|
178
|
+
const value = Uint8Array.from(cell)
|
|
179
|
+
value[0] = value[0]! ^ 1
|
|
180
|
+
return value
|
|
181
|
+
})
|
|
182
|
+
expect(
|
|
183
|
+
instance.verifyCellKzgProofBatch(
|
|
184
|
+
cells.map(() => commitment),
|
|
185
|
+
indices,
|
|
186
|
+
corrupted,
|
|
187
|
+
proofs,
|
|
188
|
+
),
|
|
189
|
+
).toMatchInlineSnapshot('false')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('rejects mismatched input lengths', () => {
|
|
193
|
+
expect(() =>
|
|
194
|
+
instance.verifyCellKzgProofBatch(
|
|
195
|
+
[commitment],
|
|
196
|
+
[],
|
|
197
|
+
[cells[0]!],
|
|
198
|
+
[proofs[0]!],
|
|
199
|
+
),
|
|
200
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
201
|
+
`[Kzg.InvalidInputError: \`commitments\`, \`cellIndices\`, \`cells\`, and \`proofs\` must have the same non-zero length.]`,
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('exports', () => {
|
|
207
|
+
expect(Object.keys(Kzg)).toMatchInlineSnapshot(`
|
|
208
|
+
[
|
|
209
|
+
"create",
|
|
210
|
+
"DisposedError",
|
|
211
|
+
"InvalidInputError",
|
|
212
|
+
"InvalidPrecomputeError",
|
|
213
|
+
"InvalidTrustedSetupError",
|
|
214
|
+
"MemoryError",
|
|
215
|
+
"WasmError",
|
|
216
|
+
"versionedHashVersion",
|
|
217
|
+
"from",
|
|
218
|
+
]
|
|
219
|
+
`)
|
|
220
|
+
})
|
|
@@ -19,3 +19,12 @@ test('memmove compares flat-memory offsets rather than C object pointers', () =>
|
|
|
19
19
|
expect(body).toContain('if ((size_t)d < (size_t)s)')
|
|
20
20
|
expect(body).not.toMatch(/if\s*\(\s*d\s*<\s*s\s*\)/)
|
|
21
21
|
})
|
|
22
|
+
|
|
23
|
+
test('KZG artifact loads only when create runs', () => {
|
|
24
|
+
const source = readFileSync(new URL('../Kzg.ts', import.meta.url), 'utf8')
|
|
25
|
+
|
|
26
|
+
expect(source).not.toMatch(
|
|
27
|
+
/^import(?!\s+type\b)[^\n]*['"]\.\/internal\/kzg\.js['"]/m,
|
|
28
|
+
)
|
|
29
|
+
expect(source).toContain("await import('./internal/kzg.js')")
|
|
30
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as Engine from '../../core/Engine.js'
|
|
3
|
+
import * as core_Secp256k1 from '../../core/Secp256k1.js'
|
|
4
|
+
import * as Secp256k1 from '../Secp256k1.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof Secp256k1.engine>>
|
|
7
|
+
|
|
8
|
+
test('return type exposes only deterministic Secp256k1 primitives', () => {
|
|
9
|
+
expectTypeOf<Slot['getPublicKey']>().toEqualTypeOf<
|
|
10
|
+
NonNullable<Engine.Ecdsa['getPublicKey']>
|
|
11
|
+
>()
|
|
12
|
+
expectTypeOf<Slot['getSharedSecret']>().toEqualTypeOf<
|
|
13
|
+
NonNullable<Engine.Ecdsa['getSharedSecret']>
|
|
14
|
+
>()
|
|
15
|
+
expectTypeOf<Slot['recoverPublicKey']>().toEqualTypeOf<
|
|
16
|
+
NonNullable<Engine.Ecdsa['recoverPublicKey']>
|
|
17
|
+
>()
|
|
18
|
+
expectTypeOf<Slot['sign']>().toEqualTypeOf<
|
|
19
|
+
NonNullable<Engine.Ecdsa['sign']>
|
|
20
|
+
>()
|
|
21
|
+
expectTypeOf<Slot['verify']>().toEqualTypeOf<
|
|
22
|
+
NonNullable<Engine.Ecdsa['verify']>
|
|
23
|
+
>()
|
|
24
|
+
expectTypeOf<Slot>().not.toHaveProperty('randomSecretKey')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('the WASM namespace exposes the public Secp256k1 API', () => {
|
|
28
|
+
expectTypeOf(Secp256k1.getPublicKey).toEqualTypeOf(
|
|
29
|
+
core_Secp256k1.getPublicKey,
|
|
30
|
+
)
|
|
31
|
+
expectTypeOf(Secp256k1.randomPrivateKey).toEqualTypeOf(
|
|
32
|
+
core_Secp256k1.randomPrivateKey,
|
|
33
|
+
)
|
|
34
|
+
expectTypeOf<typeof Secp256k1>().not.toHaveProperty('create')
|
|
35
|
+
})
|