ox 1.1.0 → 1.1.2
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/Engine.d.ts +43 -11
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +40 -10
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/internal/engine.d.ts +8 -8
- package/dist/core/internal/engine.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/node/Ed25519.d.ts +33 -0
- package/dist/node/Ed25519.d.ts.map +1 -0
- package/dist/node/Ed25519.js +67 -0
- package/dist/node/Ed25519.js.map +1 -0
- package/dist/node/Engine.d.ts +29 -16
- package/dist/node/Engine.d.ts.map +1 -1
- package/dist/node/Engine.js +38 -14
- package/dist/node/Engine.js.map +1 -1
- package/dist/node/Hash.d.ts +14 -16
- package/dist/node/Hash.d.ts.map +1 -1
- package/dist/node/Hash.js +14 -16
- package/dist/node/Hash.js.map +1 -1
- package/dist/node/Keystore.d.ts +36 -0
- package/dist/node/Keystore.d.ts.map +1 -0
- package/dist/node/Keystore.js +72 -0
- package/dist/node/Keystore.js.map +1 -0
- package/dist/node/Mnemonic.d.ts +35 -0
- package/dist/node/Mnemonic.d.ts.map +1 -0
- package/dist/node/Mnemonic.js +46 -0
- package/dist/node/Mnemonic.js.map +1 -0
- package/dist/node/P256.d.ts +32 -0
- package/dist/node/P256.d.ts.map +1 -0
- package/dist/node/P256.js +34 -0
- package/dist/node/P256.js.map +1 -0
- package/dist/node/X25519.d.ts +31 -0
- package/dist/node/X25519.d.ts.map +1 -0
- package/dist/node/X25519.js +71 -0
- package/dist/node/X25519.js.map +1 -0
- package/dist/node/index.d.ts +95 -8
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +95 -8
- package/dist/node/index.js.map +1 -1
- package/dist/wasm/Ed25519.d.ts +35 -0
- package/dist/wasm/Ed25519.d.ts.map +1 -0
- package/dist/wasm/Ed25519.js +88 -0
- package/dist/wasm/Ed25519.js.map +1 -0
- package/dist/wasm/Engine.d.ts +35 -26
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js +44 -25
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/Hash.d.ts +13 -19
- package/dist/wasm/Hash.d.ts.map +1 -1
- package/dist/wasm/Hash.js +21 -27
- package/dist/wasm/Hash.js.map +1 -1
- package/dist/wasm/Keystore.d.ts +37 -0
- package/dist/wasm/Keystore.d.ts.map +1 -0
- package/dist/wasm/Keystore.js +34 -0
- package/dist/wasm/Keystore.js.map +1 -0
- package/dist/wasm/Mnemonic.d.ts +33 -0
- package/dist/wasm/Mnemonic.d.ts.map +1 -0
- package/dist/wasm/Mnemonic.js +30 -0
- package/dist/wasm/Mnemonic.js.map +1 -0
- package/dist/wasm/X25519.d.ts +33 -0
- package/dist/wasm/X25519.d.ts.map +1 -0
- package/dist/wasm/X25519.js +53 -0
- package/dist/wasm/X25519.js.map +1 -0
- package/dist/wasm/index.d.ts +81 -12
- package/dist/wasm/index.d.ts.map +1 -1
- package/dist/wasm/index.js +81 -12
- package/dist/wasm/index.js.map +1 -1
- package/dist/wasm/internal/blake3.d.ts +11 -0
- package/dist/wasm/internal/blake3.d.ts.map +1 -0
- package/dist/wasm/internal/blake3.js +31 -0
- package/dist/wasm/internal/blake3.js.map +1 -0
- package/dist/wasm/internal/blake3.wasm.d.ts +3 -0
- package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/blake3.wasm.js +10 -0
- package/dist/wasm/internal/blake3.wasm.js.map +1 -0
- package/dist/wasm/internal/crypto25519.d.ts +16 -0
- package/dist/wasm/internal/crypto25519.d.ts.map +1 -0
- package/dist/wasm/internal/crypto25519.js +5 -0
- package/dist/wasm/internal/crypto25519.js.map +1 -0
- package/dist/wasm/internal/crypto25519.wasm.d.ts +3 -0
- package/dist/wasm/internal/crypto25519.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/crypto25519.wasm.js +10 -0
- package/dist/wasm/internal/crypto25519.wasm.js.map +1 -0
- package/dist/wasm/internal/ed25519.d.ts +4 -0
- package/dist/wasm/internal/ed25519.d.ts.map +1 -0
- package/dist/wasm/internal/ed25519.js +21 -0
- package/dist/wasm/internal/ed25519.js.map +1 -0
- package/dist/wasm/internal/hashes.d.ts +12 -0
- package/dist/wasm/internal/hashes.d.ts.map +1 -0
- package/dist/wasm/internal/hashes.js +8 -0
- package/dist/wasm/internal/hashes.js.map +1 -0
- package/dist/wasm/internal/hashes.wasm.d.ts +4 -2
- package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
- package/dist/wasm/internal/hashes.wasm.js +4 -2
- package/dist/wasm/internal/hashes.wasm.js.map +1 -1
- package/dist/wasm/internal/mnemonic.d.ts +4 -0
- package/dist/wasm/internal/mnemonic.d.ts.map +1 -0
- package/dist/wasm/internal/mnemonic.js +45 -0
- package/dist/wasm/internal/mnemonic.js.map +1 -0
- package/dist/wasm/internal/pbkdf2.d.ts +12 -0
- package/dist/wasm/internal/pbkdf2.d.ts.map +1 -0
- package/dist/wasm/internal/pbkdf2.js +51 -0
- package/dist/wasm/internal/pbkdf2.js.map +1 -0
- package/dist/wasm/internal/x25519.d.ts +4 -0
- package/dist/wasm/internal/x25519.d.ts.map +1 -0
- package/dist/wasm/internal/x25519.js +21 -0
- package/dist/wasm/internal/x25519.js.map +1 -0
- package/package.json +46 -1
- package/src/core/Engine.ts +84 -11
- package/src/core/_test/Engine.test-d.ts +56 -0
- package/src/core/_test/Engine.test.ts +95 -0
- package/src/core/_test/Hash.vectors.test.ts +8 -0
- package/src/core/internal/engine.ts +8 -8
- package/src/index.ts +5 -2
- package/src/node/Ed25519.ts +87 -0
- package/src/node/Engine.ts +79 -23
- package/src/node/Hash.ts +22 -27
- package/src/node/Keystore.ts +103 -0
- package/src/node/Mnemonic.ts +68 -0
- package/src/node/P256.ts +48 -0
- package/src/node/X25519.ts +93 -0
- package/src/node/_test/Curves.fuzz.ts +76 -0
- package/src/node/_test/Ed25519.test-d.ts +35 -0
- package/src/node/_test/Ed25519.test.ts +220 -0
- package/src/node/_test/Engine.test-d.ts +28 -0
- package/src/node/_test/Engine.test.ts +62 -22
- package/src/node/_test/Hash.test-d.ts +15 -9
- package/src/node/_test/Hash.test.ts +10 -11
- package/src/node/_test/Keystore.fuzz.ts +58 -0
- package/src/node/_test/Keystore.test-d.ts +43 -0
- package/src/node/_test/Keystore.test.ts +198 -0
- package/src/node/_test/Mnemonic.fuzz.ts +31 -0
- package/src/node/_test/Mnemonic.test-d.ts +21 -0
- package/src/node/_test/Mnemonic.test.ts +98 -0
- package/src/node/_test/P256.test-d.ts +29 -0
- package/src/node/_test/P256.test.ts +102 -0
- package/src/node/_test/X25519.test-d.ts +28 -0
- package/src/node/_test/X25519.test.ts +239 -0
- package/src/node/index.ts +100 -8
- package/src/version.ts +1 -1
- package/src/wasm/Ed25519.ts +114 -0
- package/src/wasm/Engine.ts +76 -31
- package/src/wasm/Hash.bench.ts +2 -2
- package/src/wasm/Hash.ts +29 -50
- package/src/wasm/Keystore.ts +48 -0
- package/src/wasm/Mnemonic.ts +44 -0
- package/src/wasm/X25519.ts +70 -0
- package/src/wasm/_test/Crypto25519.conformance.ts +57 -0
- package/src/wasm/_test/Crypto25519.fuzz.ts +187 -0
- package/src/wasm/_test/Ed25519.test-d.ts +28 -0
- package/src/wasm/_test/Ed25519.test.ts +168 -0
- package/src/wasm/_test/Ed25519.vectors.test.ts +73 -0
- package/src/wasm/_test/Engine.test-d.ts +28 -0
- package/src/wasm/_test/Engine.test.ts +59 -25
- package/src/wasm/_test/Hash.browser.test.ts +11 -7
- package/src/wasm/_test/Hash.fuzz.ts +58 -23
- package/src/wasm/_test/Hash.test-d.ts +15 -10
- package/src/wasm/_test/Hash.test.ts +148 -48
- package/src/wasm/_test/Hash.vectors.test.ts +15 -8
- package/src/wasm/_test/Keystore.browser.test.ts +42 -0
- package/src/wasm/_test/Keystore.fuzz.ts +113 -0
- package/src/wasm/_test/Keystore.test-d.ts +33 -0
- package/src/wasm/_test/Keystore.test.ts +192 -0
- package/src/wasm/_test/Keystore.vectors.test.ts +21 -0
- package/src/wasm/_test/Mnemonic.test-d.ts +17 -0
- package/src/wasm/_test/Mnemonic.test.ts +110 -0
- package/src/wasm/_test/X25519.test-d.ts +21 -0
- package/src/wasm/_test/X25519.test.ts +194 -0
- package/src/wasm/_test/fixtures.ts +4 -0
- package/src/wasm/index.ts +85 -12
- package/src/wasm/internal/blake3.ts +45 -0
- package/src/wasm/internal/blake3.wasm.ts +11 -0
- package/src/wasm/internal/crypto25519.ts +43 -0
- package/src/wasm/internal/crypto25519.wasm.ts +11 -0
- package/src/wasm/internal/ed25519.ts +31 -0
- package/src/wasm/internal/hashes.ts +29 -0
- package/src/wasm/internal/hashes.wasm.ts +5 -2
- package/src/wasm/internal/mnemonic.ts +56 -0
- package/src/wasm/internal/pbkdf2.ts +86 -0
- package/src/wasm/internal/x25519.ts +29 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { fc, test } from '@fast-check/vitest'
|
|
2
|
+
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
4
|
+
import { beforeAll, describe, expect } from 'vp/test'
|
|
5
|
+
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
6
|
+
import * as WasmHash from '../Hash.js'
|
|
7
|
+
import * as WasmKeystore from '../Keystore.js'
|
|
8
|
+
|
|
9
|
+
let hash: WasmHash.engine.ReturnType
|
|
10
|
+
let keystore: WasmKeystore.engine.ReturnType
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
;[hash, keystore] = await Promise.all([
|
|
14
|
+
WasmHash.engine(),
|
|
15
|
+
WasmKeystore.engine(),
|
|
16
|
+
])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const arbitraryPassword = fc.oneof(
|
|
20
|
+
{
|
|
21
|
+
arbitrary: fc
|
|
22
|
+
.constantFrom(0, 1, 32, 63, 64, 65, 128, 129)
|
|
23
|
+
.chain((size) => fc.uint8Array({ maxLength: size, minLength: size })),
|
|
24
|
+
weight: 3,
|
|
25
|
+
},
|
|
26
|
+
{ arbitrary: fc.uint8Array({ maxLength: 200 }), weight: 2 },
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const arbitrarySalt = fc.oneof(
|
|
30
|
+
{
|
|
31
|
+
arbitrary: fc
|
|
32
|
+
.constantFrom(0, 1, 31, 32, 51, 52, 55, 56, 63, 64, 65, 136)
|
|
33
|
+
.chain((size) => fc.uint8Array({ maxLength: size, minLength: size })),
|
|
34
|
+
weight: 3,
|
|
35
|
+
},
|
|
36
|
+
{ arbitrary: fc.uint8Array({ maxLength: 200 }), weight: 2 },
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const arbitraryDkLen = fc.oneof(
|
|
40
|
+
{
|
|
41
|
+
arbitrary: fc.constantFrom(1, 2, 31, 32, 33, 63, 64, 65, 96, 97),
|
|
42
|
+
weight: 3,
|
|
43
|
+
},
|
|
44
|
+
{ arbitrary: fc.integer({ max: 256, min: 1 }), weight: 2 },
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
function asSubview(input: Uint8Array) {
|
|
48
|
+
const backing = new Uint8Array(input.length + 11).fill(0xa5)
|
|
49
|
+
backing.set(input, 7)
|
|
50
|
+
return { backing, view: backing.subarray(7, 7 + input.length) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('pbkdf2Sha256', () => {
|
|
54
|
+
test.prop(
|
|
55
|
+
{
|
|
56
|
+
c: fc.integer({ max: 20, min: 1 }),
|
|
57
|
+
dkLen: arbitraryDkLen,
|
|
58
|
+
password: arbitraryPassword,
|
|
59
|
+
salt: arbitrarySalt,
|
|
60
|
+
},
|
|
61
|
+
{ numRuns },
|
|
62
|
+
)(
|
|
63
|
+
'matches @noble/hashes for arbitrary inputs and output lengths',
|
|
64
|
+
({ c, dkLen, password, salt }) => {
|
|
65
|
+
expect(keystore.pbkdf2Sha256(password, salt, { c, dkLen })).toEqual(
|
|
66
|
+
pbkdf2_noble(sha256, password, salt, { c, dkLen }),
|
|
67
|
+
)
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
test.prop(
|
|
72
|
+
{
|
|
73
|
+
c: fc.integer({ max: 10, min: 1 }),
|
|
74
|
+
dkLen: arbitraryDkLen,
|
|
75
|
+
password: arbitraryPassword,
|
|
76
|
+
salt: arbitrarySalt,
|
|
77
|
+
},
|
|
78
|
+
{ numRuns },
|
|
79
|
+
)(
|
|
80
|
+
'handles subviews without mutating either backing buffer',
|
|
81
|
+
({ c, dkLen, password, salt }) => {
|
|
82
|
+
const password_ = asSubview(password)
|
|
83
|
+
const salt_ = asSubview(salt)
|
|
84
|
+
const passwordSnapshot = password_.backing.slice()
|
|
85
|
+
const saltSnapshot = salt_.backing.slice()
|
|
86
|
+
|
|
87
|
+
expect(
|
|
88
|
+
keystore.pbkdf2Sha256(password_.view, salt_.view, {
|
|
89
|
+
c,
|
|
90
|
+
dkLen,
|
|
91
|
+
}),
|
|
92
|
+
).toEqual(pbkdf2_noble(sha256, password_.view, salt_.view, { c, dkLen }))
|
|
93
|
+
expect(password_.backing).toEqual(passwordSnapshot)
|
|
94
|
+
expect(salt_.backing).toEqual(saltSnapshot)
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
test.prop(
|
|
99
|
+
{
|
|
100
|
+
dkLen: arbitraryDkLen,
|
|
101
|
+
input: fc.uint8Array({ maxLength: 256 }),
|
|
102
|
+
},
|
|
103
|
+
{ numRuns },
|
|
104
|
+
)('shares memory safely with hash calls', ({ dkLen, input }) => {
|
|
105
|
+
const derived = keystore.pbkdf2Sha256(input, input, {
|
|
106
|
+
c: 2,
|
|
107
|
+
dkLen,
|
|
108
|
+
})
|
|
109
|
+
const snapshot = derived.slice()
|
|
110
|
+
expect(hash.sha256(input)).toEqual(sha256(input))
|
|
111
|
+
expect(derived).toEqual(snapshot)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
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 WasmKeystore from '../Keystore.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof WasmKeystore.engine>>
|
|
7
|
+
|
|
8
|
+
test('synchronous PBKDF2 is present', () => {
|
|
9
|
+
expectTypeOf<Slot['pbkdf2Sha256']>().toEqualTypeOf<
|
|
10
|
+
(
|
|
11
|
+
password: Uint8Array,
|
|
12
|
+
salt: Uint8Array,
|
|
13
|
+
options: { c: number; dkLen: number },
|
|
14
|
+
) => Uint8Array
|
|
15
|
+
>()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('unsupported primitives are absent', () => {
|
|
19
|
+
expectTypeOf<Slot>().not.toHaveProperty('aesCtrDecrypt')
|
|
20
|
+
expectTypeOf<Slot>().not.toHaveProperty('aesCtrEncrypt')
|
|
21
|
+
expectTypeOf<Slot>().not.toHaveProperty('pbkdf2Sha256Async')
|
|
22
|
+
expectTypeOf<Slot>().not.toHaveProperty('scrypt')
|
|
23
|
+
expectTypeOf<Slot>().not.toHaveProperty('scryptAsync')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('the result is the raw slot', () => {
|
|
27
|
+
expectTypeOf<{ Keystore: Slot }>().toExtend<Engine.Engine>()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('the WASM namespace exposes the public Keystore API', () => {
|
|
31
|
+
expectTypeOf(WasmKeystore.pbkdf2).toEqualTypeOf(core_Keystore.pbkdf2)
|
|
32
|
+
expectTypeOf<typeof WasmKeystore>().not.toHaveProperty('create')
|
|
33
|
+
})
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
3
|
+
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
4
|
+
import * as Bytes from '../../core/Bytes.js'
|
|
5
|
+
import * as Engine from '../../core/Engine.js'
|
|
6
|
+
import * as Keystore from '../../core/Keystore.js'
|
|
7
|
+
import * as WasmKeystore from '../Keystore.js'
|
|
8
|
+
import * as hashes from '../internal/hashes.js'
|
|
9
|
+
import { wasmBase64 } from '../internal/hashes.wasm.js'
|
|
10
|
+
import * as internal from '../internal/instantiate.js'
|
|
11
|
+
import * as pbkdf2 from '../internal/pbkdf2.js'
|
|
12
|
+
|
|
13
|
+
let engine: WasmKeystore.engine.ReturnType
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
engine = await WasmKeystore.engine()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function input(size: number) {
|
|
20
|
+
return new Uint8Array(size).map((_, index) => (index * 37 + 11) & 0xff)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('engine', () => {
|
|
24
|
+
test('default', async () => {
|
|
25
|
+
expect(Object.keys(await WasmKeystore.engine())).toMatchInlineSnapshot(`
|
|
26
|
+
[
|
|
27
|
+
"pbkdf2Sha256",
|
|
28
|
+
]
|
|
29
|
+
`)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('behavior: shares the compiled hashes instance', async () => {
|
|
33
|
+
const [a, b] = await Promise.all([hashes.load(), hashes.load()])
|
|
34
|
+
expect(a).toBe(b)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('behavior: hands every caller its own engine', async () => {
|
|
38
|
+
const [a, b] = await Promise.all([
|
|
39
|
+
WasmKeystore.engine(),
|
|
40
|
+
WasmKeystore.engine(),
|
|
41
|
+
])
|
|
42
|
+
expect(a).not.toBe(b)
|
|
43
|
+
|
|
44
|
+
a.pbkdf2Sha256 = () => new Uint8Array([9])
|
|
45
|
+
const c = await WasmKeystore.engine()
|
|
46
|
+
expect(c.pbkdf2Sha256(input(8), input(16), { c: 2, dkLen: 33 })).toEqual(
|
|
47
|
+
b.pbkdf2Sha256(input(8), input(16), { c: 2, dkLen: 33 }),
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const cases = [
|
|
53
|
+
{ c: 1, dkLen: 1, password: 0, salt: 0 },
|
|
54
|
+
{ c: 2, dkLen: 31, password: 1, salt: 51 },
|
|
55
|
+
{ c: 3, dkLen: 32, password: 63, salt: 52 },
|
|
56
|
+
{ c: 4, dkLen: 33, password: 64, salt: 55 },
|
|
57
|
+
{ c: 7, dkLen: 64, password: 65, salt: 64 },
|
|
58
|
+
{ c: 2, dkLen: 65, password: 129, salt: 137 },
|
|
59
|
+
] as const
|
|
60
|
+
|
|
61
|
+
describe('pbkdf2Sha256', () => {
|
|
62
|
+
test.each(cases)(
|
|
63
|
+
'matches the default for password=$password salt=$salt c=$c dkLen=$dkLen',
|
|
64
|
+
({ c, dkLen, password: passwordLength, salt: saltLength }) => {
|
|
65
|
+
const password = input(passwordLength)
|
|
66
|
+
const salt = input(saltLength)
|
|
67
|
+
expect(engine.pbkdf2Sha256(password, salt, { c, dkLen })).toEqual(
|
|
68
|
+
pbkdf2_noble(sha256, password, salt, { c, dkLen }),
|
|
69
|
+
)
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
test('behavior: reads non-zero-offset views without mutating their backing buffers', () => {
|
|
74
|
+
const passwordBacking = new Uint8Array(80).fill(0xa5)
|
|
75
|
+
const saltBacking = new Uint8Array(90).fill(0x5a)
|
|
76
|
+
const password = passwordBacking.subarray(7, 72)
|
|
77
|
+
const salt = saltBacking.subarray(11, 63)
|
|
78
|
+
password.set(input(password.length))
|
|
79
|
+
salt.set(input(salt.length))
|
|
80
|
+
const passwordSnapshot = passwordBacking.slice()
|
|
81
|
+
const saltSnapshot = saltBacking.slice()
|
|
82
|
+
|
|
83
|
+
expect(engine.pbkdf2Sha256(password, salt, { c: 3, dkLen: 65 })).toEqual(
|
|
84
|
+
pbkdf2_noble(sha256, password, salt, { c: 3, dkLen: 65 }),
|
|
85
|
+
)
|
|
86
|
+
expect(passwordBacking).toEqual(passwordSnapshot)
|
|
87
|
+
expect(saltBacking).toEqual(saltSnapshot)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('behavior: results are fresh and never alias WASM memory', () => {
|
|
91
|
+
const password = input(65)
|
|
92
|
+
const salt = input(52)
|
|
93
|
+
const first = engine.pbkdf2Sha256(password, salt, {
|
|
94
|
+
c: 2,
|
|
95
|
+
dkLen: 65,
|
|
96
|
+
})
|
|
97
|
+
const snapshot = first.slice()
|
|
98
|
+
const second = engine.pbkdf2Sha256(input(5), input(9), {
|
|
99
|
+
c: 3,
|
|
100
|
+
dkLen: 97,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(first).not.toBe(second)
|
|
104
|
+
expect(first).toEqual(snapshot)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('behavior: clears passwords, salts, outputs, and scratch', async () => {
|
|
108
|
+
const module = await internal.instantiate<hashes.Exports>(wasmBase64)
|
|
109
|
+
const password = input(65)
|
|
110
|
+
const salt = input(52)
|
|
111
|
+
const stack = module.view().slice(0, module.heapBase)
|
|
112
|
+
|
|
113
|
+
expect(
|
|
114
|
+
pbkdf2.pbkdf2Sha256(module, password, salt, { c: 3, dkLen: 65 }),
|
|
115
|
+
).toEqual(pbkdf2_noble(sha256, password, salt, { c: 3, dkLen: 65 }))
|
|
116
|
+
expect(module.view().slice(0, module.heapBase)).toEqual(stack)
|
|
117
|
+
expect(module.view().slice(module.heapBase)).toEqual(
|
|
118
|
+
new Uint8Array(module.view().length - module.heapBase),
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('behavior: clears the complete region after a late trap', async () => {
|
|
123
|
+
const module = await internal.instantiate<hashes.Exports>(wasmBase64)
|
|
124
|
+
const password = input(65)
|
|
125
|
+
const salt = input(52)
|
|
126
|
+
const exports: pbkdf2.Exports = {
|
|
127
|
+
pbkdf2_sha256(...parameters) {
|
|
128
|
+
module.exports.pbkdf2_sha256(...parameters)
|
|
129
|
+
throw new WebAssembly.RuntimeError('forced late trap')
|
|
130
|
+
},
|
|
131
|
+
zero: (ptr, length) => module.exports.zero(ptr, length),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
expect(() =>
|
|
135
|
+
pbkdf2.pbkdf2Sha256({ ...module, exports }, password, salt, {
|
|
136
|
+
c: 3,
|
|
137
|
+
dkLen: 65,
|
|
138
|
+
}),
|
|
139
|
+
).toThrow(WebAssembly.RuntimeError)
|
|
140
|
+
expect(module.view().slice(module.heapBase)).toEqual(
|
|
141
|
+
new Uint8Array(module.view().length - module.heapBase),
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('behavior: rejects a workspace that overflows wasm32 before growing memory', async () => {
|
|
146
|
+
const module = await internal.instantiate<hashes.Exports>(wasmBase64)
|
|
147
|
+
const memory = module.view().length
|
|
148
|
+
|
|
149
|
+
expect(() =>
|
|
150
|
+
pbkdf2.pbkdf2Sha256(module, new Uint8Array(), new Uint8Array(), {
|
|
151
|
+
c: 1,
|
|
152
|
+
dkLen: 0xffff_ffff,
|
|
153
|
+
}),
|
|
154
|
+
).toThrow(internal.MemoryError)
|
|
155
|
+
expect(module.view()).toHaveLength(memory)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test.each([
|
|
159
|
+
{ name: 'c', options: { c: 0, dkLen: 32 } },
|
|
160
|
+
{ name: 'c', options: { c: 1.5, dkLen: 32 } },
|
|
161
|
+
{ name: 'c', options: { c: 0x1_0000_0000, dkLen: 32 } },
|
|
162
|
+
{ name: 'dkLen', options: { c: 1, dkLen: 0 } },
|
|
163
|
+
{ name: 'dkLen', options: { c: 1, dkLen: 1.5 } },
|
|
164
|
+
{ name: 'dkLen', options: { c: 1, dkLen: 0x1_0000_0000 } },
|
|
165
|
+
])('behavior: rejects invalid $name before entering WASM', ({ options }) => {
|
|
166
|
+
expect(() => engine.pbkdf2Sha256(input(8), input(16), options)).toThrow()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('Engine.install', () => {
|
|
171
|
+
test('behavior: ox uses synchronous WASM PBKDF2 once installed', async () => {
|
|
172
|
+
await Engine.install({ Keystore: engine })
|
|
173
|
+
try {
|
|
174
|
+
const salt = input(32)
|
|
175
|
+
const [key] = Keystore.pbkdf2({
|
|
176
|
+
iterations: 1_000,
|
|
177
|
+
password: 'testpassword',
|
|
178
|
+
salt,
|
|
179
|
+
})
|
|
180
|
+
expect(key()).toBe(
|
|
181
|
+
Bytes.toHex(
|
|
182
|
+
pbkdf2_noble(sha256, Bytes.fromString('testpassword'), salt, {
|
|
183
|
+
c: 1_000,
|
|
184
|
+
dkLen: 32,
|
|
185
|
+
}),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
} finally {
|
|
189
|
+
Engine.reset()
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
2
|
+
import * as vectors from '../../../test/vectors/pbkdf2/index.js'
|
|
3
|
+
import * as WasmKeystore from '../Keystore.js'
|
|
4
|
+
|
|
5
|
+
let engine: WasmKeystore.engine.ReturnType
|
|
6
|
+
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
engine = await WasmKeystore.engine()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('pbkdf2Sha256', () => {
|
|
12
|
+
test(`matches ${vectors.vectors.length} RFC 7914 vectors`, () => {
|
|
13
|
+
for (const { iterations, key, password, salt } of vectors.vectors)
|
|
14
|
+
expect(
|
|
15
|
+
engine.pbkdf2Sha256(password, salt, {
|
|
16
|
+
c: iterations,
|
|
17
|
+
dkLen: key.length,
|
|
18
|
+
}),
|
|
19
|
+
).toEqual(key)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as Engine from '../../core/Engine.js'
|
|
3
|
+
import * as core_Mnemonic from '../../core/Mnemonic.js'
|
|
4
|
+
import * as Mnemonic from '../Mnemonic.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof Mnemonic.engine>>
|
|
7
|
+
|
|
8
|
+
test('return type exposes Mnemonic.toSeed', () => {
|
|
9
|
+
expectTypeOf<Slot['toSeed']>().toEqualTypeOf<
|
|
10
|
+
NonNullable<Engine.Mnemonic['toSeed']>
|
|
11
|
+
>()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('the WASM namespace exposes the public Mnemonic API', () => {
|
|
15
|
+
expectTypeOf(Mnemonic.toSeed).toEqualTypeOf(core_Mnemonic.toSeed)
|
|
16
|
+
expectTypeOf<typeof Mnemonic>().not.toHaveProperty('create')
|
|
17
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mnemonicToSeedSync } from '@scure/bip39'
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
import { vectors } from '../../../test/vectors/bip39/index.js'
|
|
4
|
+
import * as Mnemonic from '../Mnemonic.js'
|
|
5
|
+
import * as crypto25519 from '../internal/crypto25519.js'
|
|
6
|
+
import { wasmBase64 } from '../internal/crypto25519.wasm.js'
|
|
7
|
+
import * as internal from '../internal/instantiate.js'
|
|
8
|
+
import * as mnemonic_internal from '../internal/mnemonic.js'
|
|
9
|
+
|
|
10
|
+
describe('engine', () => {
|
|
11
|
+
test('vectors: matches the official English BIP-39 vector', async () => {
|
|
12
|
+
const { toSeed } = await Mnemonic.engine()
|
|
13
|
+
|
|
14
|
+
expect(
|
|
15
|
+
toHex(toSeed(vectors.english.mnemonic, vectors.english.passphrase)),
|
|
16
|
+
).toBe(vectors.english.seed)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('vectors: matches the official Japanese Unicode BIP-39 vector', async () => {
|
|
20
|
+
const { toSeed } = await Mnemonic.engine()
|
|
21
|
+
|
|
22
|
+
expect(
|
|
23
|
+
toHex(toSeed(vectors.japanese.mnemonic, vectors.japanese.passphrase)),
|
|
24
|
+
).toBe(vectors.japanese.seed)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('behavior: matches NFKD and every supported word count', async () => {
|
|
28
|
+
const { toSeed } = await Mnemonic.engine()
|
|
29
|
+
|
|
30
|
+
for (const count of [12, 15, 18, 21, 24]) {
|
|
31
|
+
const composed = Array.from({ length: count }, () => 'café').join(' ')
|
|
32
|
+
const decomposed = Array.from({ length: count }, () => 'cafe\u0301').join(
|
|
33
|
+
' ',
|
|
34
|
+
)
|
|
35
|
+
expect(toSeed(composed, 'pássphrase')).toEqual(
|
|
36
|
+
toSeed(decomposed, 'pa\u0301ssphrase'),
|
|
37
|
+
)
|
|
38
|
+
expect(toSeed(composed, 'pássphrase')).toEqual(
|
|
39
|
+
mnemonicToSeedSync(composed, 'pássphrase'),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('behavior: rejects the same malformed phrase shapes', async () => {
|
|
45
|
+
const { toSeed } = await Mnemonic.engine()
|
|
46
|
+
|
|
47
|
+
for (const count of [0, 1, 11, 13, 23, 25])
|
|
48
|
+
expect(() =>
|
|
49
|
+
toSeed(Array.from({ length: count }, () => 'word').join(' ')),
|
|
50
|
+
).toThrowError('Invalid mnemonic')
|
|
51
|
+
expect(() => toSeed(1 as never)).toThrowError(
|
|
52
|
+
'invalid mnemonic type: number',
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('behavior: outputs remain owned and staging memory is cleared', async () => {
|
|
57
|
+
const { toSeed } = await Mnemonic.engine()
|
|
58
|
+
const seed = toSeed(vectors.english.mnemonic, vectors.japanese.passphrase)
|
|
59
|
+
const snapshot = seed.slice()
|
|
60
|
+
toSeed(Array.from({ length: 24 }, () => 'word').join(' '), 'other')
|
|
61
|
+
expect(seed).toEqual(snapshot)
|
|
62
|
+
|
|
63
|
+
const passwordSize = new TextEncoder().encode(
|
|
64
|
+
vectors.english.mnemonic.normalize('NFKD'),
|
|
65
|
+
).length
|
|
66
|
+
const saltSize = new TextEncoder().encode(
|
|
67
|
+
`mnemonic${vectors.japanese.passphrase}`.normalize('NFKD'),
|
|
68
|
+
).length
|
|
69
|
+
const module = await crypto25519.load()
|
|
70
|
+
expect(
|
|
71
|
+
module
|
|
72
|
+
.view()
|
|
73
|
+
.subarray(
|
|
74
|
+
module.heapBase,
|
|
75
|
+
module.heapBase + passwordSize + saltSize + 64,
|
|
76
|
+
)
|
|
77
|
+
.every((byte) => byte === 0),
|
|
78
|
+
).toBe(true)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('behavior: clears password, salt, and output after a late trap', async () => {
|
|
82
|
+
const module = await internal.instantiate<crypto25519.Exports>(wasmBase64)
|
|
83
|
+
const vector = vectors.japanese
|
|
84
|
+
const exports: crypto25519.Exports = {
|
|
85
|
+
...module.exports,
|
|
86
|
+
mnemonic_to_seed(...parameters) {
|
|
87
|
+
module.exports.mnemonic_to_seed(...parameters)
|
|
88
|
+
throw new WebAssembly.RuntimeError('forced late trap')
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
expect(() =>
|
|
93
|
+
mnemonic_internal.toSeed(
|
|
94
|
+
{ ...module, exports },
|
|
95
|
+
vector.mnemonic,
|
|
96
|
+
vector.passphrase,
|
|
97
|
+
),
|
|
98
|
+
).toThrow(WebAssembly.RuntimeError)
|
|
99
|
+
expect(
|
|
100
|
+
module
|
|
101
|
+
.view()
|
|
102
|
+
.subarray(module.heapBase)
|
|
103
|
+
.every((byte) => byte === 0),
|
|
104
|
+
).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
function toHex(value: Uint8Array): string {
|
|
109
|
+
return Buffer.from(value).toString('hex')
|
|
110
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as Engine from '../../core/Engine.js'
|
|
3
|
+
import * as core_X25519 from '../../core/X25519.js'
|
|
4
|
+
import * as X25519 from '../X25519.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof X25519.engine>>
|
|
7
|
+
|
|
8
|
+
test('return type exposes only implemented X25519 primitives', () => {
|
|
9
|
+
expectTypeOf<Slot['getPublicKey']>().toEqualTypeOf<
|
|
10
|
+
NonNullable<Engine.Ecdh['getPublicKey']>
|
|
11
|
+
>()
|
|
12
|
+
expectTypeOf<Slot['getSharedSecret']>().toEqualTypeOf<
|
|
13
|
+
NonNullable<Engine.Ecdh['getSharedSecret']>
|
|
14
|
+
>()
|
|
15
|
+
expectTypeOf<Slot>().not.toHaveProperty('randomSecretKey')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('the WASM namespace exposes the public X25519 API', () => {
|
|
19
|
+
expectTypeOf(X25519.getPublicKey).toEqualTypeOf(core_X25519.getPublicKey)
|
|
20
|
+
expectTypeOf<typeof X25519>().not.toHaveProperty('create')
|
|
21
|
+
})
|