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,4 +1,5 @@
|
|
|
1
1
|
import { fc, test } from '@fast-check/vitest'
|
|
2
|
+
import { blake3 } from '@noble/hashes/blake3.js'
|
|
2
3
|
import { hmac } from '@noble/hashes/hmac.js'
|
|
3
4
|
import { ripemd160 } from '@noble/hashes/legacy.js'
|
|
4
5
|
import { sha256 } from '@noble/hashes/sha2.js'
|
|
@@ -7,10 +8,10 @@ import { Hash as WasmHash } from 'ox/wasm'
|
|
|
7
8
|
import { beforeAll, describe, expect } from 'vp/test'
|
|
8
9
|
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
9
10
|
|
|
10
|
-
let engine: WasmHash.
|
|
11
|
+
let engine: WasmHash.engine.ReturnType
|
|
11
12
|
|
|
12
13
|
beforeAll(async () => {
|
|
13
|
-
engine = await WasmHash.
|
|
14
|
+
engine = await WasmHash.engine()
|
|
14
15
|
})
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -21,14 +22,33 @@ beforeAll(async () => {
|
|
|
21
22
|
*
|
|
22
23
|
* The distribution is weighted toward the sizes where padding and block-boundary
|
|
23
24
|
* bugs hide -- empty input, exact multiples of the keccak256 rate (136) and the
|
|
24
|
-
* SHA-2 block (64), and the 56-byte threshold at which
|
|
25
|
-
* longer fits in the final block.
|
|
25
|
+
* SHA-2 block (64), BLAKE3 chunk (1024), and the 56-byte threshold at which
|
|
26
|
+
* the SHA-2 length counter no longer fits in the final block.
|
|
26
27
|
*/
|
|
27
28
|
const arbitraryInput = fc.oneof(
|
|
28
29
|
{ arbitrary: fc.uint8Array({ maxLength: 300, minLength: 0 }), weight: 6 },
|
|
29
30
|
{
|
|
30
31
|
arbitrary: fc
|
|
31
|
-
.constantFrom(
|
|
32
|
+
.constantFrom(
|
|
33
|
+
0,
|
|
34
|
+
1,
|
|
35
|
+
55,
|
|
36
|
+
56,
|
|
37
|
+
63,
|
|
38
|
+
64,
|
|
39
|
+
65,
|
|
40
|
+
111,
|
|
41
|
+
112,
|
|
42
|
+
135,
|
|
43
|
+
136,
|
|
44
|
+
137,
|
|
45
|
+
272,
|
|
46
|
+
1023,
|
|
47
|
+
1024,
|
|
48
|
+
1025,
|
|
49
|
+
2048,
|
|
50
|
+
2049,
|
|
51
|
+
)
|
|
32
52
|
.chain((size) => fc.uint8Array({ maxLength: size, minLength: size })),
|
|
33
53
|
weight: 3,
|
|
34
54
|
},
|
|
@@ -54,55 +74,63 @@ const arbitraryKey = fc.oneof(
|
|
|
54
74
|
)
|
|
55
75
|
|
|
56
76
|
const primitives = {
|
|
77
|
+
blake3: {
|
|
78
|
+
reference: (input: Uint8Array) => blake3(input),
|
|
79
|
+
wasm: (input: Uint8Array) => engine.blake3(input),
|
|
80
|
+
},
|
|
57
81
|
hmacSha256: {
|
|
58
82
|
reference: (input: Uint8Array, key: Uint8Array) => hmac(sha256, key, input),
|
|
59
|
-
wasm: (input: Uint8Array, key: Uint8Array) =>
|
|
60
|
-
engine.Hash.hmacSha256(key, input),
|
|
83
|
+
wasm: (input: Uint8Array, key: Uint8Array) => engine.hmacSha256(key, input),
|
|
61
84
|
},
|
|
62
85
|
keccak256: {
|
|
63
86
|
reference: (input: Uint8Array) => keccak_256(input),
|
|
64
|
-
wasm: (input: Uint8Array) => engine.
|
|
87
|
+
wasm: (input: Uint8Array) => engine.keccak256(input),
|
|
65
88
|
},
|
|
66
89
|
ripemd160: {
|
|
67
90
|
reference: (input: Uint8Array) => ripemd160(input),
|
|
68
|
-
wasm: (input: Uint8Array) => engine.
|
|
91
|
+
wasm: (input: Uint8Array) => engine.ripemd160(input),
|
|
69
92
|
},
|
|
70
93
|
sha256: {
|
|
71
94
|
reference: (input: Uint8Array) => sha256(input),
|
|
72
|
-
wasm: (input: Uint8Array) => engine.
|
|
95
|
+
wasm: (input: Uint8Array) => engine.sha256(input),
|
|
73
96
|
},
|
|
74
97
|
} as const
|
|
75
98
|
|
|
76
99
|
type Name = keyof typeof primitives
|
|
77
100
|
|
|
78
101
|
describe('Hash', () => {
|
|
102
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
103
|
+
'blake3 ≡ @noble/hashes',
|
|
104
|
+
({ input }) => {
|
|
105
|
+
expect(engine.blake3(input)).toEqual(blake3(input))
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
79
109
|
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
80
110
|
'keccak256 ≡ @noble/hashes',
|
|
81
111
|
({ input }) => {
|
|
82
|
-
expect(engine.
|
|
112
|
+
expect(engine.keccak256(input)).toEqual(keccak_256(input))
|
|
83
113
|
},
|
|
84
114
|
)
|
|
85
115
|
|
|
86
116
|
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
87
117
|
'sha256 ≡ @noble/hashes',
|
|
88
118
|
({ input }) => {
|
|
89
|
-
expect(engine.
|
|
119
|
+
expect(engine.sha256(input)).toEqual(sha256(input))
|
|
90
120
|
},
|
|
91
121
|
)
|
|
92
122
|
|
|
93
123
|
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
94
124
|
'ripemd160 ≡ @noble/hashes',
|
|
95
125
|
({ input }) => {
|
|
96
|
-
expect(engine.
|
|
126
|
+
expect(engine.ripemd160(input)).toEqual(ripemd160(input))
|
|
97
127
|
},
|
|
98
128
|
)
|
|
99
129
|
|
|
100
130
|
test.prop({ input: arbitraryInput, key: arbitraryKey }, { numRuns })(
|
|
101
131
|
'hmacSha256 ≡ @noble/hashes',
|
|
102
132
|
({ input, key }) => {
|
|
103
|
-
expect(engine.
|
|
104
|
-
hmac(sha256, key, input),
|
|
105
|
-
)
|
|
133
|
+
expect(engine.hmacSha256(key, input)).toEqual(hmac(sha256, key, input))
|
|
106
134
|
},
|
|
107
135
|
)
|
|
108
136
|
})
|
|
@@ -115,6 +143,7 @@ describe('memory', () => {
|
|
|
115
143
|
input: arbitraryInput,
|
|
116
144
|
key: arbitraryKey,
|
|
117
145
|
name: fc.constantFrom<Name>(
|
|
146
|
+
'blake3',
|
|
118
147
|
'hmacSha256',
|
|
119
148
|
'keccak256',
|
|
120
149
|
'ripemd160',
|
|
@@ -156,7 +185,7 @@ describe('memory', () => {
|
|
|
156
185
|
// as zeroes rather than throwing.
|
|
157
186
|
for (const pages of sizes) {
|
|
158
187
|
const input = new Uint8Array(pages * 65_536).fill(pages & 0xff)
|
|
159
|
-
expect(engine.
|
|
188
|
+
expect(engine.keccak256(input), `${pages} pages`).toEqual(
|
|
160
189
|
keccak_256(input),
|
|
161
190
|
)
|
|
162
191
|
}
|
|
@@ -167,10 +196,15 @@ describe('memory', () => {
|
|
|
167
196
|
({ input }) => {
|
|
168
197
|
// A digest returned as a view over linear memory would be silently
|
|
169
198
|
// rewritten by the next call. Hold one across a larger call and check it.
|
|
170
|
-
const digest = engine.
|
|
199
|
+
const digest = engine.keccak256(input)
|
|
171
200
|
const snapshot = digest.slice()
|
|
172
|
-
engine.
|
|
201
|
+
engine.keccak256(new Uint8Array(input.length + 1024).fill(0xaa))
|
|
173
202
|
expect(digest).toEqual(snapshot)
|
|
203
|
+
|
|
204
|
+
const blake3Digest = engine.blake3(input)
|
|
205
|
+
const blake3Snapshot = blake3Digest.slice()
|
|
206
|
+
engine.blake3(new Uint8Array(input.length + 1024).fill(0xaa))
|
|
207
|
+
expect(blake3Digest).toEqual(blake3Snapshot)
|
|
174
208
|
},
|
|
175
209
|
)
|
|
176
210
|
|
|
@@ -178,10 +212,11 @@ describe('memory', () => {
|
|
|
178
212
|
'inputs are not mutated',
|
|
179
213
|
({ input }) => {
|
|
180
214
|
const snapshot = input.slice()
|
|
181
|
-
engine.
|
|
182
|
-
engine.
|
|
183
|
-
engine.
|
|
184
|
-
engine.
|
|
215
|
+
engine.blake3(input)
|
|
216
|
+
engine.keccak256(input)
|
|
217
|
+
engine.sha256(input)
|
|
218
|
+
engine.ripemd160(input)
|
|
219
|
+
engine.hmacSha256(input, input)
|
|
185
220
|
expect(input).toEqual(snapshot)
|
|
186
221
|
},
|
|
187
222
|
)
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
import type { Engine } from 'ox'
|
|
2
1
|
import { Hash as WasmHash } from 'ox/wasm'
|
|
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 WasmHash.engine>>
|
|
6
7
|
|
|
7
8
|
test('every primitive is present, so callers need no assertion', () => {
|
|
8
|
-
expectTypeOf<
|
|
9
|
+
expectTypeOf<Slot['blake3']>().toEqualTypeOf<
|
|
9
10
|
(input: Uint8Array) => Uint8Array
|
|
10
11
|
>()
|
|
11
|
-
expectTypeOf<
|
|
12
|
+
expectTypeOf<Slot['keccak256']>().toEqualTypeOf<
|
|
12
13
|
(input: Uint8Array) => Uint8Array
|
|
13
14
|
>()
|
|
14
|
-
expectTypeOf<
|
|
15
|
+
expectTypeOf<Slot['sha256']>().toEqualTypeOf<
|
|
15
16
|
(input: Uint8Array) => Uint8Array
|
|
16
17
|
>()
|
|
17
|
-
expectTypeOf<
|
|
18
|
+
expectTypeOf<Slot['ripemd160']>().toEqualTypeOf<
|
|
19
|
+
(input: Uint8Array) => Uint8Array
|
|
20
|
+
>()
|
|
21
|
+
expectTypeOf<Slot['hmacSha256']>().toEqualTypeOf<
|
|
18
22
|
(key: Uint8Array, message: Uint8Array) => Uint8Array
|
|
19
23
|
>()
|
|
20
24
|
})
|
|
21
25
|
|
|
22
|
-
test('
|
|
23
|
-
expectTypeOf<
|
|
26
|
+
test('the result is the raw slot, so `Engine.install` accepts it', () => {
|
|
27
|
+
expectTypeOf<{ Hash: Slot }>().toExtend<CoreEngine.Engine>()
|
|
24
28
|
})
|
|
25
29
|
|
|
26
|
-
test('the
|
|
27
|
-
expectTypeOf
|
|
30
|
+
test('the WASM namespace exposes the public Hash API', () => {
|
|
31
|
+
expectTypeOf(WasmHash.sha256).toEqualTypeOf(CoreHash.sha256)
|
|
32
|
+
expectTypeOf<typeof WasmHash>().not.toHaveProperty('create')
|
|
28
33
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { blake3 } from '@noble/hashes/blake3.js'
|
|
1
2
|
import { hmac } from '@noble/hashes/hmac.js'
|
|
2
3
|
import { ripemd160 } from '@noble/hashes/legacy.js'
|
|
3
4
|
import { sha256 } from '@noble/hashes/sha2.js'
|
|
@@ -5,25 +6,23 @@ import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
|
5
6
|
import { Bytes, Engine, Hash } from 'ox'
|
|
6
7
|
import { Hash as WasmHash } from 'ox/wasm'
|
|
7
8
|
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
9
|
+
import { wasmBase64 as blake3WasmBase64 } from '../internal/blake3.wasm.js'
|
|
10
|
+
import * as blake3_internal from '../internal/blake3.js'
|
|
8
11
|
import { hmacSha256ScratchSize, wasmBase64 } from '../internal/hashes.wasm.js'
|
|
9
12
|
import * as hash from '../internal/hash.js'
|
|
10
13
|
import * as internal from '../internal/instantiate.js'
|
|
11
14
|
|
|
12
|
-
let engine: WasmHash.
|
|
15
|
+
let engine: WasmHash.engine.ReturnType
|
|
13
16
|
|
|
14
17
|
beforeAll(async () => {
|
|
15
|
-
engine = await WasmHash.
|
|
18
|
+
engine = await WasmHash.engine()
|
|
16
19
|
})
|
|
17
20
|
|
|
18
|
-
describe('
|
|
21
|
+
describe('engine', () => {
|
|
19
22
|
test('default', async () => {
|
|
20
|
-
expect(Object.keys(await WasmHash.
|
|
21
|
-
[
|
|
22
|
-
"Hash",
|
|
23
|
-
]
|
|
24
|
-
`)
|
|
25
|
-
expect(Object.keys((await WasmHash.create()).Hash)).toMatchInlineSnapshot(`
|
|
23
|
+
expect(Object.keys(await WasmHash.engine())).toMatchInlineSnapshot(`
|
|
26
24
|
[
|
|
25
|
+
"blake3",
|
|
27
26
|
"hmacSha256",
|
|
28
27
|
"keccak256",
|
|
29
28
|
"ripemd160",
|
|
@@ -33,28 +32,26 @@ describe('create', () => {
|
|
|
33
32
|
})
|
|
34
33
|
|
|
35
34
|
test('behavior: hands every caller its own engine', async () => {
|
|
36
|
-
const [a, b] = await Promise.all([WasmHash.
|
|
35
|
+
const [a, b] = await Promise.all([WasmHash.engine(), WasmHash.engine()])
|
|
37
36
|
|
|
38
|
-
// Deliberately not the same object.
|
|
39
|
-
// customisation
|
|
40
|
-
// `Engine.load` -- installs.
|
|
37
|
+
// Deliberately not the same object. Sharing the slot object would let one
|
|
38
|
+
// caller's customisation leak into a later `engine` or `Engine.install`.
|
|
41
39
|
expect(a).not.toBe(b)
|
|
42
|
-
expect(a.Hash).not.toBe(b.Hash)
|
|
43
40
|
|
|
44
|
-
a.
|
|
45
|
-
const c = await WasmHash.
|
|
46
|
-
expect(c.
|
|
47
|
-
b.
|
|
41
|
+
a.keccak256 = () => new Uint8Array(32).fill(9)
|
|
42
|
+
const c = await WasmHash.engine()
|
|
43
|
+
expect(c.keccak256(Bytes.from('0xdeadbeef'))).toEqual(
|
|
44
|
+
b.keccak256(Bytes.from('0xdeadbeef')),
|
|
48
45
|
)
|
|
49
46
|
})
|
|
50
47
|
})
|
|
51
48
|
|
|
52
49
|
// Sizes chosen around the block boundaries where padding bugs live: keccak256's
|
|
53
50
|
// rate is 136 bytes, sha256 and ripemd160 use 64-byte blocks and need a second
|
|
54
|
-
// block once the remainder reaches 56.
|
|
51
|
+
// block once the remainder reaches 56, and BLAKE3 uses 1024-byte chunks.
|
|
55
52
|
const sizes = [
|
|
56
53
|
0, 1, 2, 31, 32, 55, 56, 63, 64, 65, 71, 72, 111, 135, 136, 137, 199, 200,
|
|
57
|
-
271, 272, 1000,
|
|
54
|
+
271, 272, 1000, 1023, 1024, 1025, 2048, 2049,
|
|
58
55
|
]
|
|
59
56
|
|
|
60
57
|
function input(size: number) {
|
|
@@ -62,16 +59,20 @@ function input(size: number) {
|
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
describe('Hash', () => {
|
|
62
|
+
test.each(sizes)('blake3 matches the default at %i bytes', (size) => {
|
|
63
|
+
expect(engine.blake3(input(size))).toEqual(blake3(input(size)))
|
|
64
|
+
})
|
|
65
|
+
|
|
65
66
|
test.each(sizes)('keccak256 matches the default at %i bytes', (size) => {
|
|
66
|
-
expect(engine.
|
|
67
|
+
expect(engine.keccak256(input(size))).toEqual(keccak_256(input(size)))
|
|
67
68
|
})
|
|
68
69
|
|
|
69
70
|
test.each(sizes)('sha256 matches the default at %i bytes', (size) => {
|
|
70
|
-
expect(engine.
|
|
71
|
+
expect(engine.sha256(input(size))).toEqual(sha256(input(size)))
|
|
71
72
|
})
|
|
72
73
|
|
|
73
74
|
test.each(sizes)('ripemd160 matches the default at %i bytes', (size) => {
|
|
74
|
-
expect(engine.
|
|
75
|
+
expect(engine.ripemd160(input(size))).toEqual(ripemd160(input(size)))
|
|
75
76
|
})
|
|
76
77
|
|
|
77
78
|
test.each([0, 1, 32, 63, 64, 65, 200])(
|
|
@@ -79,33 +80,117 @@ describe('Hash', () => {
|
|
|
79
80
|
(size) => {
|
|
80
81
|
const key = new Uint8Array(size).fill(0xa5)
|
|
81
82
|
const message = input(100)
|
|
82
|
-
expect(engine.
|
|
83
|
+
expect(engine.hmacSha256(key, message)).toEqual(
|
|
83
84
|
hmac(sha256, key, message),
|
|
84
85
|
)
|
|
85
86
|
},
|
|
86
87
|
)
|
|
87
88
|
|
|
88
89
|
test('behavior: known vectors', () => {
|
|
89
|
-
expect(Bytes.toHex(engine.
|
|
90
|
+
expect(Bytes.toHex(engine.blake3(new Uint8Array(0)))).toBe(
|
|
91
|
+
'0xaf1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262',
|
|
92
|
+
)
|
|
93
|
+
expect(Bytes.toHex(engine.keccak256(new Uint8Array(0)))).toBe(
|
|
90
94
|
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
|
|
91
95
|
)
|
|
92
|
-
expect(Bytes.toHex(engine.
|
|
96
|
+
expect(Bytes.toHex(engine.sha256(new Uint8Array(0)))).toBe(
|
|
93
97
|
'0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
|
94
98
|
)
|
|
95
|
-
expect(Bytes.toHex(engine.
|
|
99
|
+
expect(Bytes.toHex(engine.ripemd160(new Uint8Array(0)))).toBe(
|
|
96
100
|
'0x9c1185a5c5e9fc54612808977ee8f548b2258d31',
|
|
97
101
|
)
|
|
98
102
|
})
|
|
99
103
|
|
|
100
104
|
test('behavior: grows memory past 64 MiB, then still hashes small inputs', () => {
|
|
101
105
|
// The input itself is exactly 64 MiB, so the heap base and digest force
|
|
102
|
-
// memory past that boundary. Growing detaches the previous `ArrayBuffer`;
|
|
103
|
-
// retained view would silently read zero bytes in the assertion below.
|
|
106
|
+
// memory past that boundary. Growing detaches the previous `ArrayBuffer`;
|
|
107
|
+
// a retained view would silently read zero bytes in the assertion below.
|
|
104
108
|
const large = new Uint8Array(64 * 1024 * 1024).fill(7)
|
|
105
|
-
expect(engine.
|
|
109
|
+
expect(engine.sha256(large)).toEqual(sha256(large))
|
|
110
|
+
expect(engine.blake3(large)).toEqual(blake3(large))
|
|
106
111
|
|
|
107
112
|
const small = input(32)
|
|
108
|
-
expect(engine.
|
|
113
|
+
expect(engine.sha256(small)).toEqual(sha256(small))
|
|
114
|
+
expect(engine.blake3(small)).toEqual(blake3(small))
|
|
115
|
+
}, 60_000)
|
|
116
|
+
|
|
117
|
+
test('behavior: accepts Uint8Array subviews without mutation', () => {
|
|
118
|
+
const backing = input(1027)
|
|
119
|
+
const subview = backing.subarray(1, 1026)
|
|
120
|
+
const snapshot = backing.slice()
|
|
121
|
+
expect(engine.blake3(subview)).toEqual(blake3(subview))
|
|
122
|
+
expect(backing).toEqual(snapshot)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('behavior: blake3 clears its complete memory region', async () => {
|
|
126
|
+
const module =
|
|
127
|
+
await internal.instantiate<blake3_internal.Exports>(blake3WasmBase64)
|
|
128
|
+
const message = input(1025)
|
|
129
|
+
const end = module.heapBase + message.length + 32
|
|
130
|
+
|
|
131
|
+
expect(blake3_internal.hash(module, message)).toEqual(blake3(message))
|
|
132
|
+
expect(module.view().slice(module.heapBase, end)).toEqual(
|
|
133
|
+
new Uint8Array(end - module.heapBase),
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('behavior: blake3 clears its complete region after a late trap', async () => {
|
|
138
|
+
const module =
|
|
139
|
+
await internal.instantiate<blake3_internal.Exports>(blake3WasmBase64)
|
|
140
|
+
const message = input(1025)
|
|
141
|
+
const end = module.heapBase + message.length + 32
|
|
142
|
+
const exports = {
|
|
143
|
+
blake3_hash(inputPtr: number, length: number, outPtr: number) {
|
|
144
|
+
module.exports.blake3_hash(inputPtr, length, outPtr)
|
|
145
|
+
throw new WebAssembly.RuntimeError('forced late trap')
|
|
146
|
+
},
|
|
147
|
+
zero: (ptr: number, length: number) => module.exports.zero(ptr, length),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
expect(() =>
|
|
151
|
+
blake3_internal.hash({ ...module, exports }, message),
|
|
152
|
+
).toThrowError('forced late trap')
|
|
153
|
+
expect(module.view().slice(module.heapBase, end)).toEqual(
|
|
154
|
+
new Uint8Array(end - module.heapBase),
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('behavior: rejects wasm32 pointer overflow before reserving', () => {
|
|
159
|
+
let reserved = false
|
|
160
|
+
const module: internal.Module<blake3_internal.Exports> = {
|
|
161
|
+
exports: {
|
|
162
|
+
blake3_hash() {},
|
|
163
|
+
zero() {},
|
|
164
|
+
},
|
|
165
|
+
heapBase: 0xffff_fff0,
|
|
166
|
+
reserve() {
|
|
167
|
+
reserved = true
|
|
168
|
+
},
|
|
169
|
+
view: () => new Uint8Array(0),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
expect(() => blake3_internal.hash(module, input(1))).toThrow(
|
|
173
|
+
internal.MemoryError,
|
|
174
|
+
)
|
|
175
|
+
expect(reserved).toBe(false)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('behavior: accepts a workspace ending exactly at the wasm32 boundary', () => {
|
|
179
|
+
const marker = new Error('reserve reached')
|
|
180
|
+
const module: internal.Module<blake3_internal.Exports> = {
|
|
181
|
+
exports: {
|
|
182
|
+
blake3_hash() {},
|
|
183
|
+
zero() {},
|
|
184
|
+
},
|
|
185
|
+
heapBase: 0x1_0000_0000 - 33,
|
|
186
|
+
reserve(bytes) {
|
|
187
|
+
expect(bytes).toBe(33)
|
|
188
|
+
throw marker
|
|
189
|
+
},
|
|
190
|
+
view: () => new Uint8Array(0),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
expect(() => blake3_internal.hash(module, input(1))).toThrow(marker)
|
|
109
194
|
})
|
|
110
195
|
|
|
111
196
|
test('behavior: hmacSha256 clears its complete memory region', async () => {
|
|
@@ -182,30 +267,45 @@ describe('Hash', () => {
|
|
|
182
267
|
test('behavior: consecutive calls do not corrupt each other', () => {
|
|
183
268
|
const a = input(200)
|
|
184
269
|
const b = input(7)
|
|
185
|
-
expect(engine.
|
|
186
|
-
expect(engine.
|
|
187
|
-
expect(engine.
|
|
270
|
+
expect(engine.keccak256(a)).toEqual(keccak_256(a))
|
|
271
|
+
expect(engine.blake3(a)).toEqual(blake3(a))
|
|
272
|
+
expect(engine.blake3(b)).toEqual(blake3(b))
|
|
273
|
+
expect(engine.blake3(a)).toEqual(blake3(a))
|
|
274
|
+
expect(engine.keccak256(b)).toEqual(keccak_256(b))
|
|
275
|
+
expect(engine.keccak256(a)).toEqual(keccak_256(a))
|
|
188
276
|
})
|
|
189
277
|
|
|
190
278
|
test('behavior: results do not alias WASM memory', () => {
|
|
191
|
-
const first = engine.
|
|
279
|
+
const first = engine.keccak256(input(32))
|
|
192
280
|
const snapshot = first.slice()
|
|
193
|
-
engine.
|
|
281
|
+
engine.keccak256(input(64))
|
|
194
282
|
expect(first).toEqual(snapshot)
|
|
283
|
+
|
|
284
|
+
const blake3First = engine.blake3(input(32))
|
|
285
|
+
const blake3Snapshot = blake3First.slice()
|
|
286
|
+
engine.blake3(input(64))
|
|
287
|
+
expect(blake3First).toEqual(blake3Snapshot)
|
|
195
288
|
})
|
|
196
289
|
})
|
|
197
290
|
|
|
198
|
-
describe('Engine.
|
|
199
|
-
test('behavior: ox uses the WASM implementation once installed', () => {
|
|
200
|
-
Engine.
|
|
201
|
-
|
|
202
|
-
'
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
'
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
'
|
|
209
|
-
|
|
291
|
+
describe('Engine.install', () => {
|
|
292
|
+
test('behavior: ox uses the WASM implementation once installed', async () => {
|
|
293
|
+
await Engine.install({ Hash: engine })
|
|
294
|
+
try {
|
|
295
|
+
expect(Engine.get().Hash?.blake3?.(Bytes.from('0xdeadbeef'))).toEqual(
|
|
296
|
+
blake3(Bytes.from('0xdeadbeef')),
|
|
297
|
+
)
|
|
298
|
+
expect(Hash.keccak256('0xdeadbeef')).toBe(
|
|
299
|
+
'0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1',
|
|
300
|
+
)
|
|
301
|
+
expect(Hash.sha256('0xdeadbeef')).toBe(
|
|
302
|
+
'0x5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953',
|
|
303
|
+
)
|
|
304
|
+
expect(Hash.ripemd160('0xdeadbeef')).toBe(
|
|
305
|
+
'0x226821c2f5423e11fe9af68bd285c249db2e4b5a',
|
|
306
|
+
)
|
|
307
|
+
} finally {
|
|
308
|
+
Engine.reset()
|
|
309
|
+
}
|
|
210
310
|
})
|
|
211
311
|
})
|
|
@@ -13,16 +13,23 @@ import { instantiate } from '../internal/instantiate.js'
|
|
|
13
13
|
* See `test/vectors/hashes/README.md`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
let engine: WasmHash.
|
|
16
|
+
let engine: WasmHash.engine.ReturnType
|
|
17
17
|
|
|
18
18
|
beforeAll(async () => {
|
|
19
|
-
engine = await WasmHash.
|
|
19
|
+
engine = await WasmHash.engine()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('blake3', () => {
|
|
23
|
+
test(`matches ${vectors.blake3.length} official BLAKE3 vectors`, () => {
|
|
24
|
+
for (const { digest, message } of vectors.blake3)
|
|
25
|
+
expect(engine.blake3(message)).toEqual(digest)
|
|
26
|
+
})
|
|
20
27
|
})
|
|
21
28
|
|
|
22
29
|
describe('sha256', () => {
|
|
23
30
|
test(`matches ${vectors.sha256.length} NIST CAVP vectors`, () => {
|
|
24
31
|
for (const { digest, message } of vectors.sha256)
|
|
25
|
-
expect(engine.
|
|
32
|
+
expect(engine.sha256(message)).toEqual(digest)
|
|
26
33
|
})
|
|
27
34
|
})
|
|
28
35
|
|
|
@@ -31,26 +38,26 @@ describe('hmacSha256', () => {
|
|
|
31
38
|
// Two of these use 131-byte keys, which is the only coverage of the
|
|
32
39
|
// longer-than-a-block path where the key is hashed before padding.
|
|
33
40
|
for (const { digest, key, message } of vectors.hmacSha256)
|
|
34
|
-
expect(engine.
|
|
41
|
+
expect(engine.hmacSha256(key, message)).toEqual(digest)
|
|
35
42
|
})
|
|
36
43
|
})
|
|
37
44
|
|
|
38
45
|
describe('ripemd160', () => {
|
|
39
46
|
test(`matches ${vectors.ripemd160.length} reference vectors`, () => {
|
|
40
47
|
for (const { digest, message } of vectors.ripemd160)
|
|
41
|
-
expect(engine.
|
|
48
|
+
expect(engine.ripemd160(message)).toEqual(digest)
|
|
42
49
|
})
|
|
43
50
|
|
|
44
51
|
test('matches the million-`a` reference vector', () => {
|
|
45
52
|
const { digest, message } = vectors.ripemd160MillionA
|
|
46
|
-
expect(engine.
|
|
53
|
+
expect(engine.ripemd160(message)).toEqual(digest)
|
|
47
54
|
})
|
|
48
55
|
})
|
|
49
56
|
|
|
50
57
|
describe('keccak256', () => {
|
|
51
58
|
test(`matches ${vectors.keccak256.length} OpenSSL vectors`, () => {
|
|
52
59
|
for (const { digest, message } of vectors.keccak256)
|
|
53
|
-
expect(engine.
|
|
60
|
+
expect(engine.keccak256(message)).toEqual(digest)
|
|
54
61
|
})
|
|
55
62
|
})
|
|
56
63
|
|
|
@@ -60,7 +67,7 @@ describe('keccak_f1600', () => {
|
|
|
60
67
|
/** Writes 25 lanes as little-endian 64-bit words, permutes, and reads back. */
|
|
61
68
|
async function permute(lanes: readonly bigint[]) {
|
|
62
69
|
// A separate instance, because this export is not part of the engine and so
|
|
63
|
-
// is deliberately not reachable through `WasmHash.
|
|
70
|
+
// is deliberately not reachable through `WasmHash.engine`.
|
|
64
71
|
const module = await instantiate<Exports>(wasmBase64)
|
|
65
72
|
module.reserve(200)
|
|
66
73
|
const view = module.view()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
3
|
+
import { 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
|
+
|
|
9
|
+
describe('engine', () => {
|
|
10
|
+
test('behavior: derives arbitrary-length keys in a real browser', async () => {
|
|
11
|
+
const engine = await WasmKeystore.engine()
|
|
12
|
+
const password = Bytes.fromString('testpassword')
|
|
13
|
+
const salt = new Uint8Array(52).fill(0xa5)
|
|
14
|
+
|
|
15
|
+
expect(engine.pbkdf2Sha256(password, salt, { c: 3, dkLen: 65 })).toEqual(
|
|
16
|
+
pbkdf2_noble(sha256, password, salt, { c: 3, dkLen: 65 }),
|
|
17
|
+
)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('behavior: ox uses synchronous WASM PBKDF2 once installed', async () => {
|
|
21
|
+
const engine = await WasmKeystore.engine()
|
|
22
|
+
await Engine.install({ Keystore: engine })
|
|
23
|
+
try {
|
|
24
|
+
const salt = new Uint8Array(32).fill(7)
|
|
25
|
+
const [key] = Keystore.pbkdf2({
|
|
26
|
+
iterations: 1_000,
|
|
27
|
+
password: 'testpassword',
|
|
28
|
+
salt,
|
|
29
|
+
})
|
|
30
|
+
expect(key()).toBe(
|
|
31
|
+
Bytes.toHex(
|
|
32
|
+
pbkdf2_noble(sha256, Bytes.fromString('testpassword'), salt, {
|
|
33
|
+
c: 1_000,
|
|
34
|
+
dkLen: 32,
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
} finally {
|
|
39
|
+
Engine.reset()
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
})
|