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
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
import type { Engine } from 'ox'
|
|
2
1
|
import { Hash as NodeHash } from 'ox/node'
|
|
3
2
|
import { expectTypeOf, test } from 'vp/test'
|
|
3
|
+
import type * as CoreEngine from '../../core/Engine.js'
|
|
4
|
+
import * as CoreHash from '../../core/Hash.js'
|
|
4
5
|
|
|
5
|
-
type
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof NodeHash.engine>>
|
|
6
7
|
|
|
7
8
|
test('every implemented primitive is present', () => {
|
|
8
|
-
expectTypeOf<
|
|
9
|
+
expectTypeOf<Slot['hmacSha256']>().toEqualTypeOf<
|
|
9
10
|
(key: Uint8Array, message: Uint8Array) => Uint8Array
|
|
10
11
|
>()
|
|
11
|
-
expectTypeOf<
|
|
12
|
+
expectTypeOf<Slot['ripemd160']>().toEqualTypeOf<
|
|
12
13
|
(input: Uint8Array) => Uint8Array
|
|
13
14
|
>()
|
|
14
|
-
expectTypeOf<
|
|
15
|
+
expectTypeOf<Slot['sha256']>().toEqualTypeOf<
|
|
15
16
|
(input: Uint8Array) => Uint8Array
|
|
16
17
|
>()
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
test('unsupported primitives are absent', () => {
|
|
20
|
-
expectTypeOf<
|
|
21
|
-
expectTypeOf<
|
|
21
|
+
expectTypeOf<Slot>().not.toHaveProperty('blake3')
|
|
22
|
+
expectTypeOf<Slot>().not.toHaveProperty('keccak256')
|
|
22
23
|
})
|
|
23
24
|
|
|
24
|
-
test('the result is
|
|
25
|
-
expectTypeOf<
|
|
25
|
+
test('the result is the raw slot, so `Engine.install` accepts it', () => {
|
|
26
|
+
expectTypeOf<{ Hash: Slot }>().toExtend<CoreEngine.Engine>()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('the Node namespace exposes the public Hash API', () => {
|
|
30
|
+
expectTypeOf(NodeHash.sha256).toEqualTypeOf(CoreHash.sha256)
|
|
31
|
+
expectTypeOf<typeof NodeHash>().not.toHaveProperty('create')
|
|
26
32
|
})
|
|
@@ -2,11 +2,11 @@ import { Hash as CoreHash, Hex } from 'ox'
|
|
|
2
2
|
import { Hash } from 'ox/node'
|
|
3
3
|
import { describe, expect, test } from 'vp/test'
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('engine', () => {
|
|
6
6
|
test('behavior: exposes the supported primitives', async () => {
|
|
7
|
-
const engine = await Hash.
|
|
7
|
+
const engine = await Hash.engine()
|
|
8
8
|
|
|
9
|
-
expect(Object.keys(engine
|
|
9
|
+
expect(Object.keys(engine).sort()).toMatchInlineSnapshot(`
|
|
10
10
|
[
|
|
11
11
|
"hmacSha256",
|
|
12
12
|
"ripemd160",
|
|
@@ -16,7 +16,7 @@ describe('create', () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test('behavior: matches published empty-input vectors', async () => {
|
|
19
|
-
const { hmacSha256, ripemd160, sha256 } =
|
|
19
|
+
const { hmacSha256, ripemd160, sha256 } = await Hash.engine()
|
|
20
20
|
const empty = new Uint8Array()
|
|
21
21
|
|
|
22
22
|
expect(Hex.fromBytes(sha256(empty))).toMatchInlineSnapshot(
|
|
@@ -31,7 +31,7 @@ describe('create', () => {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
test('behavior: agrees with the default across block boundaries', async () => {
|
|
34
|
-
const node =
|
|
34
|
+
const node = await Hash.engine()
|
|
35
35
|
|
|
36
36
|
for (const size of [0, 1, 55, 56, 63, 64, 65, 127, 128, 129]) {
|
|
37
37
|
const input = Uint8Array.from({ length: size }, (_, index) => index % 251)
|
|
@@ -45,7 +45,7 @@ describe('create', () => {
|
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
test('behavior: agrees with the default across HMAC key sizes', async () => {
|
|
48
|
-
const { hmacSha256 } =
|
|
48
|
+
const { hmacSha256 } = await Hash.engine()
|
|
49
49
|
const message = Uint8Array.from({ length: 65 }, (_, index) => index % 251)
|
|
50
50
|
|
|
51
51
|
for (const size of [0, 1, 32, 63, 64, 65, 200]) {
|
|
@@ -57,7 +57,7 @@ describe('create', () => {
|
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
test('behavior: respects typed-array offsets', async () => {
|
|
60
|
-
const node =
|
|
60
|
+
const node = await Hash.engine()
|
|
61
61
|
const input = Uint8Array.from(
|
|
62
62
|
{ length: 80 },
|
|
63
63
|
(_, index) => (index * 17) % 251,
|
|
@@ -77,15 +77,14 @@ describe('create', () => {
|
|
|
77
77
|
})
|
|
78
78
|
|
|
79
79
|
test('behavior: returns a fresh engine', async () => {
|
|
80
|
-
const first = await Hash.
|
|
81
|
-
const second = await Hash.
|
|
80
|
+
const first = await Hash.engine()
|
|
81
|
+
const second = await Hash.engine()
|
|
82
82
|
|
|
83
83
|
expect(first === second).toMatchInlineSnapshot('false')
|
|
84
|
-
expect(first.Hash === second.Hash).toMatchInlineSnapshot('false')
|
|
85
84
|
})
|
|
86
85
|
|
|
87
86
|
test('behavior: returns owned Uint8Array values', async () => {
|
|
88
|
-
const { hmacSha256, ripemd160, sha256 } =
|
|
87
|
+
const { hmacSha256, ripemd160, sha256 } = await Hash.engine()
|
|
89
88
|
const input = Uint8Array.of(1, 2, 3)
|
|
90
89
|
const outputs = [hmacSha256(input, input), ripemd160(input), sha256(input)]
|
|
91
90
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ctr } from '@noble/ciphers/aes.js'
|
|
2
|
+
import { pbkdf2 } from '@noble/hashes/pbkdf2.js'
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
4
|
+
import { fc, test } from '@fast-check/vitest'
|
|
5
|
+
import { expect } from 'vp/test'
|
|
6
|
+
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
7
|
+
import * as Keystore from '../Keystore.js'
|
|
8
|
+
|
|
9
|
+
const subview = (bytes: Uint8Array) => {
|
|
10
|
+
const value = new Uint8Array(bytes.length + 4)
|
|
11
|
+
value.set(bytes, 2)
|
|
12
|
+
return value.subarray(2, bytes.length + 2)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const arbitraryKey = fc
|
|
16
|
+
.oneof(
|
|
17
|
+
fc.uint8Array({ maxLength: 16, minLength: 16 }),
|
|
18
|
+
fc.uint8Array({ maxLength: 24, minLength: 24 }),
|
|
19
|
+
fc.uint8Array({ maxLength: 32, minLength: 32 }),
|
|
20
|
+
)
|
|
21
|
+
.map(subview)
|
|
22
|
+
|
|
23
|
+
test.prop(
|
|
24
|
+
{
|
|
25
|
+
data: fc.uint8Array({ maxLength: 256 }).map(subview),
|
|
26
|
+
iv: fc.uint8Array({ maxLength: 16, minLength: 16 }).map(subview),
|
|
27
|
+
key: arbitraryKey,
|
|
28
|
+
},
|
|
29
|
+
{ numRuns },
|
|
30
|
+
)('Node AES-CTR agrees with the default', async ({ data, iv, key }) => {
|
|
31
|
+
const { aesCtrDecrypt, aesCtrEncrypt } = await Keystore.engine()
|
|
32
|
+
const expected = ctr(key, iv).encrypt(data)
|
|
33
|
+
const encrypted = aesCtrEncrypt(key, iv, data)
|
|
34
|
+
|
|
35
|
+
expect(encrypted).toEqual(expected)
|
|
36
|
+
expect(aesCtrDecrypt(key, iv, encrypted)).toEqual(data)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test.prop(
|
|
40
|
+
{
|
|
41
|
+
c: fc.integer({ max: 32, min: 1 }),
|
|
42
|
+
dkLen: fc.integer({ max: 96, min: 1 }),
|
|
43
|
+
password: fc.uint8Array({ maxLength: 96 }).map(subview),
|
|
44
|
+
salt: fc.uint8Array({ maxLength: 96 }).map(subview),
|
|
45
|
+
},
|
|
46
|
+
{ numRuns },
|
|
47
|
+
)(
|
|
48
|
+
'Node PBKDF2-HMAC-SHA256 agrees with the default',
|
|
49
|
+
async ({ c, dkLen, password, salt }) => {
|
|
50
|
+
const { pbkdf2Sha256, pbkdf2Sha256Async } = await Keystore.engine()
|
|
51
|
+
const expected = pbkdf2(sha256, password, salt, { c, dkLen })
|
|
52
|
+
|
|
53
|
+
expect(pbkdf2Sha256(password, salt, { c, dkLen })).toEqual(expected)
|
|
54
|
+
expect(await pbkdf2Sha256Async(password, salt, { c, dkLen })).toEqual(
|
|
55
|
+
expected,
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Engine } from 'ox'
|
|
2
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
3
|
+
import * as core_Keystore from '../../core/Keystore.js'
|
|
4
|
+
import * as NodeKeystore from '../Keystore.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof NodeKeystore.engine>>
|
|
7
|
+
|
|
8
|
+
test('every implemented primitive is present', () => {
|
|
9
|
+
expectTypeOf<Slot['aesCtrDecrypt']>().toEqualTypeOf<
|
|
10
|
+
(key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array
|
|
11
|
+
>()
|
|
12
|
+
expectTypeOf<Slot['aesCtrEncrypt']>().toEqualTypeOf<
|
|
13
|
+
(key: Uint8Array, iv: Uint8Array, data: Uint8Array) => Uint8Array
|
|
14
|
+
>()
|
|
15
|
+
expectTypeOf<Slot['pbkdf2Sha256']>().toEqualTypeOf<
|
|
16
|
+
(
|
|
17
|
+
password: Uint8Array,
|
|
18
|
+
salt: Uint8Array,
|
|
19
|
+
options: { c: number; dkLen: number },
|
|
20
|
+
) => Uint8Array
|
|
21
|
+
>()
|
|
22
|
+
expectTypeOf<Slot['pbkdf2Sha256Async']>().toEqualTypeOf<
|
|
23
|
+
(
|
|
24
|
+
password: Uint8Array,
|
|
25
|
+
salt: Uint8Array,
|
|
26
|
+
options: { c: number; dkLen: number },
|
|
27
|
+
) => Promise<Uint8Array>
|
|
28
|
+
>()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('unsupported primitives are absent', () => {
|
|
32
|
+
expectTypeOf<Slot>().not.toHaveProperty('scrypt')
|
|
33
|
+
expectTypeOf<Slot>().not.toHaveProperty('scryptAsync')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('the result is the raw slot', () => {
|
|
37
|
+
expectTypeOf<{ Keystore: Slot }>().toExtend<Engine.Engine>()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('the Node namespace exposes the public Keystore API', () => {
|
|
41
|
+
expectTypeOf(NodeKeystore.pbkdf2).toEqualTypeOf(core_Keystore.pbkdf2)
|
|
42
|
+
expectTypeOf<typeof NodeKeystore>().not.toHaveProperty('create')
|
|
43
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { ctr } from '@noble/ciphers/aes.js'
|
|
2
|
+
import { pbkdf2 } from '@noble/hashes/pbkdf2.js'
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
4
|
+
import { Hex } from 'ox'
|
|
5
|
+
import { describe, expect, test } from 'vp/test'
|
|
6
|
+
import * as vectors from '../../../test/vectors/pbkdf2/index.js'
|
|
7
|
+
import * as Keystore from '../Keystore.js'
|
|
8
|
+
|
|
9
|
+
describe('engine', () => {
|
|
10
|
+
test('behavior: matches NIST SP 800-38A AES-CTR vectors', async () => {
|
|
11
|
+
const { aesCtrDecrypt, aesCtrEncrypt } = await Keystore.engine()
|
|
12
|
+
const iv = Hex.toBytes('0xf0f1f2f3f4f5f6f7f8f9fafbfcfdfeff')
|
|
13
|
+
const plaintext = Hex.toBytes('0x6bc1bee22e409f96e93d7e117393172a')
|
|
14
|
+
const vectors = [
|
|
15
|
+
{
|
|
16
|
+
ciphertext: '0x874d6191b620e3261bef6864990db6ce',
|
|
17
|
+
key: '0x2b7e151628aed2a6abf7158809cf4f3c',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
ciphertext: '0x1abc932417521ca24f2b0459fe7e6e0b',
|
|
21
|
+
key: '0x8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
ciphertext: '0x601ec313775789a5b7a7f504bbf3d228',
|
|
25
|
+
key: '0x603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4',
|
|
26
|
+
},
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
expect(
|
|
30
|
+
vectors.map(({ ciphertext, key }) => {
|
|
31
|
+
const encrypted = aesCtrEncrypt(Hex.toBytes(key), iv, plaintext)
|
|
32
|
+
const decrypted = aesCtrDecrypt(
|
|
33
|
+
Hex.toBytes(key),
|
|
34
|
+
iv,
|
|
35
|
+
Hex.toBytes(ciphertext),
|
|
36
|
+
)
|
|
37
|
+
return {
|
|
38
|
+
decrypted: Hex.fromBytes(decrypted),
|
|
39
|
+
encrypted: Hex.fromBytes(encrypted),
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
).toMatchInlineSnapshot(`
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
"decrypted": "0x6bc1bee22e409f96e93d7e117393172a",
|
|
46
|
+
"encrypted": "0x874d6191b620e3261bef6864990db6ce",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"decrypted": "0x6bc1bee22e409f96e93d7e117393172a",
|
|
50
|
+
"encrypted": "0x1abc932417521ca24f2b0459fe7e6e0b",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"decrypted": "0x6bc1bee22e409f96e93d7e117393172a",
|
|
54
|
+
"encrypted": "0x601ec313775789a5b7a7f504bbf3d228",
|
|
55
|
+
},
|
|
56
|
+
]
|
|
57
|
+
`)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('behavior: matches RFC 7914 PBKDF2-HMAC-SHA256 vectors', async () => {
|
|
61
|
+
const { pbkdf2Sha256 } = await Keystore.engine()
|
|
62
|
+
|
|
63
|
+
for (const { iterations, key, password, salt } of vectors.vectors)
|
|
64
|
+
expect(
|
|
65
|
+
pbkdf2Sha256(password, salt, {
|
|
66
|
+
c: iterations,
|
|
67
|
+
dkLen: key.length,
|
|
68
|
+
}),
|
|
69
|
+
).toEqual(key)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('behavior: agrees with the defaults across boundary sizes', async () => {
|
|
73
|
+
const node = await Keystore.engine()
|
|
74
|
+
const iv = Uint8Array.from(
|
|
75
|
+
{ length: 20 },
|
|
76
|
+
(_, index) => (index * 13) % 251,
|
|
77
|
+
).subarray(2, 18)
|
|
78
|
+
|
|
79
|
+
for (const keySize of [16, 24, 32]) {
|
|
80
|
+
const key = Uint8Array.from(
|
|
81
|
+
{ length: keySize + 4 },
|
|
82
|
+
(_, index) => (index * 17) % 251,
|
|
83
|
+
).subarray(2, keySize + 2)
|
|
84
|
+
|
|
85
|
+
for (const size of [0, 1, 15, 16, 17, 31, 32, 33]) {
|
|
86
|
+
const data = Uint8Array.from(
|
|
87
|
+
{ length: size + 6 },
|
|
88
|
+
(_, index) => (index * 29) % 251,
|
|
89
|
+
).subarray(3, size + 3)
|
|
90
|
+
const encrypted = node.aesCtrEncrypt(key, iv, data)
|
|
91
|
+
|
|
92
|
+
expect(encrypted).toEqual(ctr(key, iv).encrypt(data))
|
|
93
|
+
expect(node.aesCtrDecrypt(key, iv, encrypted)).toEqual(data)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const password = Uint8Array.from(
|
|
98
|
+
{ length: 37 },
|
|
99
|
+
(_, index) => (index * 31) % 251,
|
|
100
|
+
).subarray(3, 34)
|
|
101
|
+
const salt = Uint8Array.from(
|
|
102
|
+
{ length: 41 },
|
|
103
|
+
(_, index) => (index * 37) % 251,
|
|
104
|
+
).subarray(5, 37)
|
|
105
|
+
|
|
106
|
+
for (const options of [
|
|
107
|
+
{ c: 1, dkLen: 1 },
|
|
108
|
+
{ c: 2, dkLen: 31 },
|
|
109
|
+
{ c: 3, dkLen: 32 },
|
|
110
|
+
{ c: 4, dkLen: 33 },
|
|
111
|
+
{ c: 16, dkLen: 64 },
|
|
112
|
+
]) {
|
|
113
|
+
const expected = pbkdf2(sha256, password, salt, options)
|
|
114
|
+
expect(node.pbkdf2Sha256(password, salt, options)).toEqual(expected)
|
|
115
|
+
expect(await node.pbkdf2Sha256Async(password, salt, options)).toEqual(
|
|
116
|
+
expected,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('behavior: does not mutate inputs and returns owned arrays', async () => {
|
|
122
|
+
const node = await Keystore.engine()
|
|
123
|
+
const data = Uint8Array.of(1, 2, 3, 4, 5)
|
|
124
|
+
const iv = new Uint8Array(16).fill(2)
|
|
125
|
+
const key = new Uint8Array(16).fill(3)
|
|
126
|
+
const password = Uint8Array.of(4, 5, 6)
|
|
127
|
+
const salt = Uint8Array.of(7, 8, 9)
|
|
128
|
+
const inputs = [data, iv, key, password, salt]
|
|
129
|
+
const before = inputs.map((input) => input.slice())
|
|
130
|
+
|
|
131
|
+
const outputs = [
|
|
132
|
+
node.aesCtrEncrypt(key, iv, data),
|
|
133
|
+
node.aesCtrDecrypt(key, iv, data),
|
|
134
|
+
node.pbkdf2Sha256(password, salt, { c: 1, dkLen: 32 }),
|
|
135
|
+
await node.pbkdf2Sha256Async(password, salt, { c: 1, dkLen: 32 }),
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
expect(inputs).toEqual(before)
|
|
139
|
+
expect(outputs.map((output) => output.constructor === Uint8Array))
|
|
140
|
+
.toMatchInlineSnapshot(`
|
|
141
|
+
[
|
|
142
|
+
true,
|
|
143
|
+
true,
|
|
144
|
+
true,
|
|
145
|
+
true,
|
|
146
|
+
]
|
|
147
|
+
`)
|
|
148
|
+
expect(
|
|
149
|
+
node.aesCtrEncrypt(key, iv, data) === node.aesCtrEncrypt(key, iv, data),
|
|
150
|
+
).toMatchInlineSnapshot('false')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('behavior: rejects malformed AES and PBKDF2 parameters', async () => {
|
|
154
|
+
const node = await Keystore.engine()
|
|
155
|
+
const data = new Uint8Array()
|
|
156
|
+
const iv = new Uint8Array(16)
|
|
157
|
+
const key = new Uint8Array(16)
|
|
158
|
+
|
|
159
|
+
for (const keyLength of [0, 15, 17, 23, 25, 31, 33]) {
|
|
160
|
+
expect(() => ctr(new Uint8Array(keyLength), iv).encrypt(data)).toThrow()
|
|
161
|
+
expect(() =>
|
|
162
|
+
node.aesCtrEncrypt(new Uint8Array(keyLength), iv, data),
|
|
163
|
+
).toThrow()
|
|
164
|
+
expect(() =>
|
|
165
|
+
node.aesCtrDecrypt(new Uint8Array(keyLength), iv, data),
|
|
166
|
+
).toThrow()
|
|
167
|
+
}
|
|
168
|
+
for (const ivLength of [0, 15, 17]) {
|
|
169
|
+
expect(() => ctr(key, new Uint8Array(ivLength)).encrypt(data)).toThrow()
|
|
170
|
+
expect(() =>
|
|
171
|
+
node.aesCtrEncrypt(key, new Uint8Array(ivLength), data),
|
|
172
|
+
).toThrow()
|
|
173
|
+
expect(() =>
|
|
174
|
+
node.aesCtrDecrypt(key, new Uint8Array(ivLength), data),
|
|
175
|
+
).toThrow()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const options of [
|
|
179
|
+
{ c: 0, dkLen: 32 },
|
|
180
|
+
{ c: 1.5, dkLen: 32 },
|
|
181
|
+
{ c: 1, dkLen: 0 },
|
|
182
|
+
{ c: 1, dkLen: 1.5 },
|
|
183
|
+
]) {
|
|
184
|
+
expect(() => pbkdf2(sha256, data, data, options)).toThrow()
|
|
185
|
+
expect(() => node.pbkdf2Sha256(data, data, options)).toThrow()
|
|
186
|
+
await expect(
|
|
187
|
+
node.pbkdf2Sha256Async(data, data, options),
|
|
188
|
+
).rejects.toThrow()
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('behavior: returns a fresh engine', async () => {
|
|
193
|
+
const first = await Keystore.engine()
|
|
194
|
+
const second = await Keystore.engine()
|
|
195
|
+
|
|
196
|
+
expect(first === second).toMatchInlineSnapshot('false')
|
|
197
|
+
})
|
|
198
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mnemonicToSeedSync } from '@scure/bip39'
|
|
2
|
+
import { fc, test } from '@fast-check/vitest'
|
|
3
|
+
import { expect } from 'vp/test'
|
|
4
|
+
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
5
|
+
import * as Mnemonic from '../Mnemonic.js'
|
|
6
|
+
|
|
7
|
+
const arbitraryWord = fc.oneof(
|
|
8
|
+
fc
|
|
9
|
+
.string({ maxLength: 16, minLength: 1 })
|
|
10
|
+
.filter((value) => !value.normalize('NFKD').includes(' ')),
|
|
11
|
+
fc.constantFrom('café', 'cafe\u0301', 'あおぞら', '㍍ガバヴァ'),
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
test.prop(
|
|
15
|
+
{
|
|
16
|
+
mnemonic: fc
|
|
17
|
+
.array(arbitraryWord, { maxLength: 12, minLength: 12 })
|
|
18
|
+
.map((words) => words.join(' ')),
|
|
19
|
+
passphrase: fc.string({ maxLength: 32 }),
|
|
20
|
+
},
|
|
21
|
+
{ numRuns },
|
|
22
|
+
)(
|
|
23
|
+
'Node BIP-39 seed derivation agrees with the default',
|
|
24
|
+
async ({ mnemonic, passphrase }) => {
|
|
25
|
+
const { toSeed } = await Mnemonic.engine()
|
|
26
|
+
|
|
27
|
+
expect(toSeed(mnemonic, passphrase)).toEqual(
|
|
28
|
+
mnemonicToSeedSync(mnemonic, passphrase),
|
|
29
|
+
)
|
|
30
|
+
},
|
|
31
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
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 NodeMnemonic from '../Mnemonic.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof NodeMnemonic.engine>>
|
|
7
|
+
|
|
8
|
+
test('every implemented primitive is present', () => {
|
|
9
|
+
expectTypeOf<Slot['toSeed']>().toEqualTypeOf<
|
|
10
|
+
(mnemonic: string, passphrase?: string) => Uint8Array
|
|
11
|
+
>()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('the result is the raw slot', () => {
|
|
15
|
+
expectTypeOf<{ Mnemonic: Slot }>().toExtend<Engine.Engine>()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('the Node namespace exposes the public Mnemonic API', () => {
|
|
19
|
+
expectTypeOf(NodeMnemonic.toSeed).toEqualTypeOf(core_Mnemonic.toSeed)
|
|
20
|
+
expectTypeOf<typeof NodeMnemonic>().not.toHaveProperty('create')
|
|
21
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mnemonicToSeedSync } from '@scure/bip39'
|
|
2
|
+
import { Hex } from 'ox'
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
import * as bip39 from '../../../test/vectors/bip39/index.js'
|
|
5
|
+
import * as Mnemonic from '../Mnemonic.js'
|
|
6
|
+
|
|
7
|
+
describe('engine', () => {
|
|
8
|
+
test('behavior: matches the BIP-39 English vector', async () => {
|
|
9
|
+
const { toSeed } = await Mnemonic.engine()
|
|
10
|
+
const { mnemonic, passphrase, seed } = bip39.vectors.english
|
|
11
|
+
|
|
12
|
+
expect(Hex.fromBytes(toSeed(mnemonic, passphrase))).toBe(`0x${seed}`)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('behavior: matches the BIP-39 Japanese Unicode vector', async () => {
|
|
16
|
+
const { toSeed } = await Mnemonic.engine()
|
|
17
|
+
const { mnemonic, passphrase, seed } = bip39.vectors.japanese
|
|
18
|
+
|
|
19
|
+
expect(Hex.fromBytes(toSeed(mnemonic, passphrase))).toBe(`0x${seed}`)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('behavior: applies BIP-39 NFKD normalization', async () => {
|
|
23
|
+
const { toSeed } = await Mnemonic.engine()
|
|
24
|
+
const composed = Array.from({ length: 12 }, () => 'café').join(' ')
|
|
25
|
+
const decomposed = Array.from({ length: 12 }, () => 'cafe\u0301').join(' ')
|
|
26
|
+
|
|
27
|
+
expect(toSeed(composed, 'pássphrase')).toEqual(
|
|
28
|
+
toSeed(decomposed, 'pa\u0301ssphrase'),
|
|
29
|
+
)
|
|
30
|
+
expect(toSeed(composed, 'pássphrase')).toEqual(
|
|
31
|
+
mnemonicToSeedSync(composed, 'pássphrase'),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('behavior: accepts every supported phrase length', async () => {
|
|
36
|
+
const { toSeed } = await Mnemonic.engine()
|
|
37
|
+
|
|
38
|
+
expect(
|
|
39
|
+
[12, 15, 18, 21, 24].map(
|
|
40
|
+
(length) =>
|
|
41
|
+
toSeed(Array.from({ length }, () => 'word').join(' ')).length,
|
|
42
|
+
),
|
|
43
|
+
).toMatchInlineSnapshot(`
|
|
44
|
+
[
|
|
45
|
+
64,
|
|
46
|
+
64,
|
|
47
|
+
64,
|
|
48
|
+
64,
|
|
49
|
+
64,
|
|
50
|
+
]
|
|
51
|
+
`)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('behavior: rejects unsupported phrase lengths', async () => {
|
|
55
|
+
const { toSeed } = await Mnemonic.engine()
|
|
56
|
+
|
|
57
|
+
for (const length of [0, 1, 11, 13, 23, 25])
|
|
58
|
+
expect(() =>
|
|
59
|
+
toSeed(Array.from({ length }, () => 'word').join(' ')),
|
|
60
|
+
).toThrowErrorMatchingInlineSnapshot(`[Error: Invalid mnemonic]`)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('behavior: agrees with the default for localized input', async () => {
|
|
64
|
+
const { toSeed } = await Mnemonic.engine()
|
|
65
|
+
const mnemonic = Array.from({ length: 12 }, () => 'あおぞら').join(' ')
|
|
66
|
+
const passphrase = '㍍ガバヴァぱばぐゞちぢ十人十色'
|
|
67
|
+
|
|
68
|
+
expect(toSeed(mnemonic, passphrase)).toEqual(
|
|
69
|
+
mnemonicToSeedSync(mnemonic, passphrase),
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('behavior: rejects non-string mnemonics like the default', async () => {
|
|
74
|
+
const { toSeed } = await Mnemonic.engine()
|
|
75
|
+
|
|
76
|
+
expect(() => toSeed(1 as never)).toThrowErrorMatchingInlineSnapshot(
|
|
77
|
+
`[TypeError: invalid mnemonic type: number]`,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('behavior: returns owned Uint8Array values', async () => {
|
|
82
|
+
const { toSeed } = await Mnemonic.engine()
|
|
83
|
+
const mnemonic = Array.from({ length: 12 }, () => 'word').join(' ')
|
|
84
|
+
const first = toSeed(mnemonic)
|
|
85
|
+
const second = toSeed(mnemonic)
|
|
86
|
+
|
|
87
|
+
expect(first.constructor === Uint8Array).toMatchInlineSnapshot('true')
|
|
88
|
+
expect(first === second).toMatchInlineSnapshot('false')
|
|
89
|
+
expect(first).toEqual(second)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('behavior: returns a fresh engine', async () => {
|
|
93
|
+
const first = await Mnemonic.engine()
|
|
94
|
+
const second = await Mnemonic.engine()
|
|
95
|
+
|
|
96
|
+
expect(first === second).toMatchInlineSnapshot('false')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as Engine from '../../core/Engine.js'
|
|
3
|
+
import * as core_P256 from '../../core/P256.js'
|
|
4
|
+
import * as NodeP256 from '../P256.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof NodeP256.engine>>
|
|
7
|
+
|
|
8
|
+
test('public-key derivation is present', () => {
|
|
9
|
+
expectTypeOf<Slot['getPublicKey']>().toEqualTypeOf<
|
|
10
|
+
(privateKey: Uint8Array) => Uint8Array
|
|
11
|
+
>()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('unsupported primitives are absent', () => {
|
|
15
|
+
expectTypeOf<Slot>().not.toHaveProperty('getSharedSecret')
|
|
16
|
+
expectTypeOf<Slot>().not.toHaveProperty('randomSecretKey')
|
|
17
|
+
expectTypeOf<Slot>().not.toHaveProperty('recoverPublicKey')
|
|
18
|
+
expectTypeOf<Slot>().not.toHaveProperty('sign')
|
|
19
|
+
expectTypeOf<Slot>().not.toHaveProperty('verify')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('the result is the raw slot', () => {
|
|
23
|
+
expectTypeOf<{ P256: Slot }>().toExtend<Engine.Engine>()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('the Node namespace exposes the public P256 API', () => {
|
|
27
|
+
expectTypeOf(NodeP256.getPublicKey).toEqualTypeOf(core_P256.getPublicKey)
|
|
28
|
+
expectTypeOf<typeof NodeP256>().not.toHaveProperty('create')
|
|
29
|
+
})
|