ox 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/core/Hash.d.ts +36 -0
- package/dist/core/Hash.d.ts.map +1 -1
- package/dist/core/Hash.js +35 -0
- package/dist/core/Hash.js.map +1 -1
- package/dist/node/Hash.d.ts +3 -3
- package/dist/node/Hash.js +3 -3
- package/dist/trusted-setups/Paths.d.ts +1 -0
- package/dist/trusted-setups/Paths.d.ts.map +1 -1
- package/dist/trusted-setups/Paths.js +1 -0
- package/dist/trusted-setups/Paths.js.map +1 -1
- package/dist/trusted-setups/Setups.d.ts +35 -0
- package/dist/trusted-setups/Setups.d.ts.map +1 -0
- package/dist/trusted-setups/Setups.js +29 -0
- package/dist/trusted-setups/Setups.js.map +1 -0
- package/dist/trusted-setups/index.d.ts +20 -0
- package/dist/trusted-setups/index.d.ts.map +1 -1
- package/dist/trusted-setups/index.js +20 -0
- package/dist/trusted-setups/index.js.map +1 -1
- package/dist/trusted-setups/internal/paths.d.ts.map +1 -1
- package/dist/trusted-setups/internal/paths.js +1 -1
- package/dist/trusted-setups/internal/paths.js.map +1 -1
- package/dist/trusted-setups/internal/setups/mainnet.d.ts +7 -0
- package/dist/trusted-setups/internal/setups/mainnet.d.ts.map +1 -0
- package/dist/trusted-setups/internal/setups/mainnet.js +11 -0
- package/dist/trusted-setups/internal/setups/mainnet.js.map +1 -0
- package/dist/wasm/Engine.d.ts +3 -2
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js +5 -5
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/Keystore.d.ts +23 -9
- package/dist/wasm/Keystore.d.ts.map +1 -1
- package/dist/wasm/Keystore.js +30 -13
- package/dist/wasm/Keystore.js.map +1 -1
- package/dist/wasm/Kzg.d.ts +112 -0
- package/dist/wasm/Kzg.d.ts.map +1 -0
- package/dist/wasm/Kzg.js +333 -0
- package/dist/wasm/Kzg.js.map +1 -0
- package/dist/wasm/Secp256k1.d.ts +55 -0
- package/dist/wasm/Secp256k1.d.ts.map +1 -0
- package/dist/wasm/Secp256k1.js +181 -0
- package/dist/wasm/Secp256k1.js.map +1 -0
- package/dist/wasm/index.d.ts +44 -1
- package/dist/wasm/index.d.ts.map +1 -1
- package/dist/wasm/index.js +44 -1
- package/dist/wasm/index.js.map +1 -1
- package/dist/wasm/internal/instantiate.d.ts +12 -0
- package/dist/wasm/internal/instantiate.d.ts.map +1 -1
- package/dist/wasm/internal/instantiate.js +23 -7
- package/dist/wasm/internal/instantiate.js.map +1 -1
- package/dist/wasm/internal/keystore.d.ts +10 -0
- package/dist/wasm/internal/keystore.d.ts.map +1 -0
- package/dist/wasm/internal/keystore.js +10 -0
- package/dist/wasm/internal/keystore.js.map +1 -0
- package/dist/wasm/internal/kzg.d.ts +17 -0
- package/dist/wasm/internal/kzg.d.ts.map +1 -0
- package/dist/wasm/internal/kzg.js +8 -0
- package/dist/wasm/internal/kzg.js.map +1 -0
- package/dist/wasm/internal/kzg.wasm.d.ts +3 -0
- package/dist/wasm/internal/kzg.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/kzg.wasm.js +10 -0
- package/dist/wasm/internal/kzg.wasm.js.map +1 -0
- package/dist/wasm/internal/scrypt.d.ts +16 -0
- package/dist/wasm/internal/scrypt.d.ts.map +1 -0
- package/dist/wasm/internal/scrypt.js +78 -0
- package/dist/wasm/internal/scrypt.js.map +1 -0
- package/dist/wasm/internal/scrypt.wasm.d.ts +7 -0
- package/dist/wasm/internal/scrypt.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/scrypt.wasm.js +14 -0
- package/dist/wasm/internal/scrypt.wasm.js.map +1 -0
- package/dist/wasm/internal/secp256k1.d.ts +16 -0
- package/dist/wasm/internal/secp256k1.d.ts.map +1 -0
- package/dist/wasm/internal/secp256k1.js +20 -0
- package/dist/wasm/internal/secp256k1.js.map +1 -0
- package/dist/wasm/internal/secp256k1.wasm.d.ts +3 -0
- package/dist/wasm/internal/secp256k1.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/secp256k1.wasm.js +10 -0
- package/dist/wasm/internal/secp256k1.wasm.js.map +1 -0
- package/package.json +19 -2
- package/src/core/Hash.ts +59 -0
- package/src/core/_test/Block.test.ts +9 -2
- package/src/core/_test/Hash.test-d.ts +18 -0
- package/src/core/_test/Hash.test.ts +100 -1
- package/src/core/_test/Hash.vectors.test.ts +1 -2
- package/src/core/_test/Transaction.test.ts +11 -2
- package/src/node/Hash.ts +3 -3
- package/src/node/_test/Hash.test-d.ts +1 -0
- package/src/trusted-setups/Paths.ts +1 -0
- package/src/trusted-setups/README.md +39 -0
- package/src/trusted-setups/Setups.ts +46 -0
- package/src/trusted-setups/_test/Paths.test.ts +16 -0
- package/src/trusted-setups/_test/Setups.test-d.ts +7 -0
- package/src/trusted-setups/_test/Setups.test.ts +43 -0
- package/src/trusted-setups/_test/index.test.ts +1 -0
- package/src/trusted-setups/index.ts +21 -0
- package/src/trusted-setups/internal/paths.ts +4 -1
- package/src/trusted-setups/internal/setups/mainnet.source.json +7 -0
- package/src/trusted-setups/internal/setups/mainnet.ts +16 -0
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +8 -6
- package/src/wasm/Keystore.bench.ts +26 -0
- package/src/wasm/Keystore.ts +32 -14
- package/src/wasm/Kzg.bench.ts +63 -0
- package/src/wasm/Kzg.ts +518 -0
- package/src/wasm/Secp256k1.bench.ts +79 -0
- package/src/wasm/Secp256k1.ts +248 -0
- package/src/wasm/_test/Engine.test-d.ts +1 -0
- package/src/wasm/_test/Hash.browser.test.ts +3 -0
- package/src/wasm/_test/Hash.test-d.ts +1 -0
- package/src/wasm/_test/Hash.test.ts +2 -2
- package/src/wasm/_test/Keystore.browser.test.ts +31 -0
- package/src/wasm/_test/Keystore.fuzz.ts +50 -0
- package/src/wasm/_test/Keystore.test-d.ts +10 -1
- package/src/wasm/_test/Keystore.test.ts +289 -4
- package/src/wasm/_test/Kzg.test-d.ts +19 -0
- package/src/wasm/_test/Kzg.test.ts +220 -0
- package/src/wasm/_test/Runtime.test.ts +9 -0
- package/src/wasm/_test/Secp256k1.test-d.ts +35 -0
- package/src/wasm/_test/Secp256k1.test.ts +354 -0
- package/src/wasm/index.ts +46 -1
- package/src/wasm/internal/instantiate.ts +29 -9
- package/src/wasm/internal/keystore.ts +19 -0
- package/src/wasm/internal/kzg.ts +50 -0
- package/src/wasm/internal/kzg.wasm.ts +11 -0
- package/src/wasm/internal/scrypt.ts +131 -0
- package/src/wasm/internal/scrypt.wasm.ts +17 -0
- package/src/wasm/internal/secp256k1.ts +62 -0
- package/src/wasm/internal/secp256k1.wasm.ts +11 -0
- package/wasm/vendor/c-kzg-4844/LICENSE +201 -0
- package/wasm/vendor/c-kzg-4844/blst/LICENSE +201 -0
- package/src/trusted-setups/internal/setups/mainnet.txt +0 -4163
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type * as Engine from '../core/Engine.js'
|
|
2
|
+
import * as Errors from '../core/Errors.js'
|
|
3
|
+
import * as internal from './internal/instantiate.js'
|
|
4
|
+
import * as secp256k1 from './internal/secp256k1.js'
|
|
5
|
+
|
|
6
|
+
export * from '../core/Secp256k1.js'
|
|
7
|
+
export { MemoryError } from './internal/instantiate.js'
|
|
8
|
+
|
|
9
|
+
const privateKeySize = 32
|
|
10
|
+
const publicKeySize = 65
|
|
11
|
+
const sharedSecretSize = 33
|
|
12
|
+
const compactSignatureSize = 64
|
|
13
|
+
const recoveredSignatureSize = 65
|
|
14
|
+
const maxRawMessageSize = 8192
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compiles WASM implementations of deterministic
|
|
18
|
+
* [`Secp256k1`](/api/Secp256k1) primitives, without installing them.
|
|
19
|
+
*
|
|
20
|
+
* Random key generation remains on Ox's host-backed default implementation.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts twoslash
|
|
24
|
+
* // @noErrors
|
|
25
|
+
* import { Engine } from 'ox'
|
|
26
|
+
* import { Secp256k1 } from 'ox/wasm'
|
|
27
|
+
*
|
|
28
|
+
* await Engine.install({ Secp256k1: Secp256k1.engine() })
|
|
29
|
+
*
|
|
30
|
+
* Secp256k1.getPublicKey({ privateKey: '0x...' })
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @returns The WASM implementation of the deterministic `Secp256k1` primitives.
|
|
34
|
+
*/
|
|
35
|
+
export async function engine(): Promise<engine.ReturnType> {
|
|
36
|
+
const module = await secp256k1.load()
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
getPublicKey(privateKey) {
|
|
40
|
+
assertLength(privateKey, privateKeySize, 'private key')
|
|
41
|
+
const size = privateKeySize + publicKeySize
|
|
42
|
+
module.reserve(size)
|
|
43
|
+
const privateKeyPtr = module.heapBase
|
|
44
|
+
const publicKeyPtr = privateKeyPtr + privateKeySize
|
|
45
|
+
try {
|
|
46
|
+
module.view().set(privateKey, privateKeyPtr)
|
|
47
|
+
if (
|
|
48
|
+
module.exports.secp256k1_get_public_key(
|
|
49
|
+
privateKeyPtr,
|
|
50
|
+
publicKeyPtr,
|
|
51
|
+
) !== 1
|
|
52
|
+
)
|
|
53
|
+
throw new InvalidInputError({ operation: 'getPublicKey' })
|
|
54
|
+
return module.view().slice(publicKeyPtr, publicKeyPtr + publicKeySize)
|
|
55
|
+
} finally {
|
|
56
|
+
module.exports.zero(privateKeyPtr, size)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
getSharedSecret(privateKey, publicKey) {
|
|
60
|
+
assertLength(privateKey, privateKeySize, 'private key')
|
|
61
|
+
assertPublicKeyLength(publicKey)
|
|
62
|
+
const size = privateKeySize + publicKey.length + sharedSecretSize
|
|
63
|
+
module.reserve(size)
|
|
64
|
+
const privateKeyPtr = module.heapBase
|
|
65
|
+
const publicKeyPtr = privateKeyPtr + privateKeySize
|
|
66
|
+
const sharedSecretPtr = publicKeyPtr + publicKey.length
|
|
67
|
+
try {
|
|
68
|
+
const memory = module.view()
|
|
69
|
+
memory.set(privateKey, privateKeyPtr)
|
|
70
|
+
memory.set(publicKey, publicKeyPtr)
|
|
71
|
+
if (
|
|
72
|
+
module.exports.secp256k1_get_shared_secret(
|
|
73
|
+
privateKeyPtr,
|
|
74
|
+
publicKeyPtr,
|
|
75
|
+
publicKey.length,
|
|
76
|
+
sharedSecretPtr,
|
|
77
|
+
) !== 1
|
|
78
|
+
)
|
|
79
|
+
throw new InvalidInputError({ operation: 'getSharedSecret' })
|
|
80
|
+
return module
|
|
81
|
+
.view()
|
|
82
|
+
.slice(sharedSecretPtr, sharedSecretPtr + sharedSecretSize)
|
|
83
|
+
} finally {
|
|
84
|
+
module.exports.zero(privateKeyPtr, size)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
recoverPublicKey(signature, payload) {
|
|
88
|
+
assertLength(signature, recoveredSignatureSize, 'signature')
|
|
89
|
+
assertRawMessageLength(payload)
|
|
90
|
+
const size = recoveredSignatureSize + payload.length + publicKeySize
|
|
91
|
+
module.reserve(size)
|
|
92
|
+
const signaturePtr = module.heapBase
|
|
93
|
+
const payloadPtr = signaturePtr + recoveredSignatureSize
|
|
94
|
+
const publicKeyPtr = payloadPtr + payload.length
|
|
95
|
+
try {
|
|
96
|
+
const memory = module.view()
|
|
97
|
+
memory.set(signature, signaturePtr)
|
|
98
|
+
memory.set(payload, payloadPtr)
|
|
99
|
+
if (
|
|
100
|
+
module.exports.secp256k1_recover_public_key(
|
|
101
|
+
signaturePtr,
|
|
102
|
+
payloadPtr,
|
|
103
|
+
payload.length,
|
|
104
|
+
publicKeyPtr,
|
|
105
|
+
) !== 1
|
|
106
|
+
)
|
|
107
|
+
throw new InvalidInputError({ operation: 'recoverPublicKey' })
|
|
108
|
+
return module.view().slice(publicKeyPtr, publicKeyPtr + publicKeySize)
|
|
109
|
+
} finally {
|
|
110
|
+
module.exports.zero(signaturePtr, size)
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
sign(payload, privateKey, options) {
|
|
114
|
+
assertLength(privateKey, privateKeySize, 'private key')
|
|
115
|
+
if (!options.prehash) assertRawMessageLength(payload)
|
|
116
|
+
const entropy =
|
|
117
|
+
options.extraEntropy === true
|
|
118
|
+
? crypto.getRandomValues(new Uint8Array(privateKeySize))
|
|
119
|
+
: options.extraEntropy === false
|
|
120
|
+
? undefined
|
|
121
|
+
: options.extraEntropy
|
|
122
|
+
const entropySize = entropy?.length ?? 0
|
|
123
|
+
const size =
|
|
124
|
+
payload.length + privateKeySize + entropySize + recoveredSignatureSize
|
|
125
|
+
module.reserve(size)
|
|
126
|
+
const payloadPtr = module.heapBase
|
|
127
|
+
const privateKeyPtr = payloadPtr + payload.length
|
|
128
|
+
const entropyPtr = entropy ? privateKeyPtr + privateKeySize : 0
|
|
129
|
+
const signaturePtr = privateKeyPtr + privateKeySize + entropySize
|
|
130
|
+
try {
|
|
131
|
+
const memory = module.view()
|
|
132
|
+
memory.set(payload, payloadPtr)
|
|
133
|
+
memory.set(privateKey, privateKeyPtr)
|
|
134
|
+
if (entropy) memory.set(entropy, entropyPtr)
|
|
135
|
+
if (
|
|
136
|
+
module.exports.secp256k1_sign(
|
|
137
|
+
payloadPtr,
|
|
138
|
+
payload.length,
|
|
139
|
+
privateKeyPtr,
|
|
140
|
+
entropyPtr,
|
|
141
|
+
entropySize,
|
|
142
|
+
Number(options.prehash),
|
|
143
|
+
signaturePtr,
|
|
144
|
+
) !== 1
|
|
145
|
+
)
|
|
146
|
+
throw new InvalidInputError({ operation: 'sign' })
|
|
147
|
+
return module
|
|
148
|
+
.view()
|
|
149
|
+
.slice(signaturePtr, signaturePtr + recoveredSignatureSize)
|
|
150
|
+
} finally {
|
|
151
|
+
module.exports.zero(payloadPtr, size)
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
verify(signature, payload, publicKey, options) {
|
|
155
|
+
assertLength(signature, compactSignatureSize, 'signature')
|
|
156
|
+
assertPublicKeyLength(publicKey)
|
|
157
|
+
if (!options.prehash && payload.length > maxRawMessageSize) return false
|
|
158
|
+
const size = compactSignatureSize + payload.length + publicKey.length
|
|
159
|
+
module.reserve(size)
|
|
160
|
+
const signaturePtr = module.heapBase
|
|
161
|
+
const payloadPtr = signaturePtr + compactSignatureSize
|
|
162
|
+
const publicKeyPtr = payloadPtr + payload.length
|
|
163
|
+
try {
|
|
164
|
+
const memory = module.view()
|
|
165
|
+
memory.set(signature, signaturePtr)
|
|
166
|
+
memory.set(payload, payloadPtr)
|
|
167
|
+
memory.set(publicKey, publicKeyPtr)
|
|
168
|
+
return (
|
|
169
|
+
module.exports.secp256k1_verify(
|
|
170
|
+
signaturePtr,
|
|
171
|
+
payloadPtr,
|
|
172
|
+
payload.length,
|
|
173
|
+
publicKeyPtr,
|
|
174
|
+
publicKey.length,
|
|
175
|
+
Number(options.prehash),
|
|
176
|
+
) === 1
|
|
177
|
+
)
|
|
178
|
+
} finally {
|
|
179
|
+
module.exports.zero(signaturePtr, size)
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export declare namespace engine {
|
|
186
|
+
/** Every `Secp256k1` primitive this module implements. */
|
|
187
|
+
type ReturnType = {
|
|
188
|
+
[key in
|
|
189
|
+
| 'getPublicKey'
|
|
190
|
+
| 'getSharedSecret'
|
|
191
|
+
| 'recoverPublicKey'
|
|
192
|
+
| 'sign'
|
|
193
|
+
| 'verify']-?: NonNullable<Engine.Ecdsa[key]>
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
type ErrorType =
|
|
197
|
+
| InvalidInputError
|
|
198
|
+
| internal.MemoryError
|
|
199
|
+
| Errors.GlobalErrorType
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function assertLength(value: Uint8Array, length: number, name: string): void {
|
|
203
|
+
if (value.length !== length)
|
|
204
|
+
throw new RangeError(
|
|
205
|
+
`Secp256k1 ${name} must be ${length} bytes, got ${value.length}`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function assertPublicKeyLength(publicKey: Uint8Array): void {
|
|
210
|
+
if (
|
|
211
|
+
publicKey.length !== sharedSecretSize &&
|
|
212
|
+
publicKey.length !== publicKeySize
|
|
213
|
+
)
|
|
214
|
+
throw new RangeError(
|
|
215
|
+
`Secp256k1 public key must be 33 or 65 bytes, got ${publicKey.length}`,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function assertRawMessageLength(payload: Uint8Array): void {
|
|
220
|
+
if (payload.length > maxRawMessageSize)
|
|
221
|
+
throw new RangeError(
|
|
222
|
+
`Secp256k1 unhashed payload must not exceed ${maxRawMessageSize} bytes, got ${payload.length}`,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Thrown when libsecp256k1 rejects a key or recovered signature.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts twoslash
|
|
231
|
+
* import { Secp256k1 } from 'ox/wasm'
|
|
232
|
+
*
|
|
233
|
+
* try {
|
|
234
|
+
* const engine = await Secp256k1.engine()
|
|
235
|
+
* engine.getPublicKey(new Uint8Array(32))
|
|
236
|
+
* } catch (error) {
|
|
237
|
+
* if (error instanceof Secp256k1.InvalidInputError)
|
|
238
|
+
* console.error(error.message)
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export class InvalidInputError extends Errors.BaseError {
|
|
243
|
+
override readonly name = 'Secp256k1.InvalidInputError'
|
|
244
|
+
|
|
245
|
+
constructor({ operation }: { operation: string }) {
|
|
246
|
+
super(`WASM secp256k1 ${operation} received invalid input.`)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -10,6 +10,7 @@ test('install and engine expose the same precise engine', () => {
|
|
|
10
10
|
expectTypeOf<Installed['Hash']['sha256']>().toEqualTypeOf<
|
|
11
11
|
(input: Uint8Array) => Uint8Array
|
|
12
12
|
>()
|
|
13
|
+
expectTypeOf<Installed['Keystore']>().not.toHaveProperty('scrypt')
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
test('the WASM Engine namespace exposes synchronous core operations', () => {
|
|
@@ -44,6 +44,9 @@ describe('engine', () => {
|
|
|
44
44
|
test('behavior: ox uses the WASM implementation once installed', async () => {
|
|
45
45
|
await Engine.install({ Hash: WasmHash.engine() })
|
|
46
46
|
try {
|
|
47
|
+
expect(Hash.blake3('0xdeadbeef')).toBe(
|
|
48
|
+
'0x53147f3ce49ed4f60dfa5b9654c36ba6103c11f5737df3dabd4cbd296c4161bd',
|
|
49
|
+
)
|
|
47
50
|
expect(Hash.keccak256('0xdeadbeef')).toBe(
|
|
48
51
|
'0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1',
|
|
49
52
|
)
|
|
@@ -28,6 +28,7 @@ test('the result is the raw slot, so `Engine.install` accepts it', () => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
test('the WASM namespace exposes the public Hash API', () => {
|
|
31
|
+
expectTypeOf(WasmHash.blake3).toEqualTypeOf(CoreHash.blake3)
|
|
31
32
|
expectTypeOf(WasmHash.sha256).toEqualTypeOf(CoreHash.sha256)
|
|
32
33
|
expectTypeOf<typeof WasmHash>().not.toHaveProperty('create')
|
|
33
34
|
})
|
|
@@ -292,8 +292,8 @@ describe('Engine.install', () => {
|
|
|
292
292
|
test('behavior: ox uses the WASM implementation once installed', async () => {
|
|
293
293
|
await Engine.install({ Hash: engine })
|
|
294
294
|
try {
|
|
295
|
-
expect(
|
|
296
|
-
|
|
295
|
+
expect(Hash.blake3('0xdeadbeef')).toBe(
|
|
296
|
+
'0x53147f3ce49ed4f60dfa5b9654c36ba6103c11f5737df3dabd4cbd296c4161bd',
|
|
297
297
|
)
|
|
298
298
|
expect(Hash.keccak256('0xdeadbeef')).toBe(
|
|
299
299
|
'0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
2
|
+
import { scrypt as scrypt_noble } from '@noble/hashes/scrypt.js'
|
|
2
3
|
import { sha256 } from '@noble/hashes/sha2.js'
|
|
3
4
|
import { describe, expect, test } from 'vp/test'
|
|
4
5
|
import * as Bytes from '../../core/Bytes.js'
|
|
@@ -15,6 +16,9 @@ describe('engine', () => {
|
|
|
15
16
|
expect(engine.pbkdf2Sha256(password, salt, { c: 3, dkLen: 65 })).toEqual(
|
|
16
17
|
pbkdf2_noble(sha256, password, salt, { c: 3, dkLen: 65 }),
|
|
17
18
|
)
|
|
19
|
+
expect(
|
|
20
|
+
engine.scrypt(password, salt, { N: 1024, dkLen: 65, p: 2, r: 1 }),
|
|
21
|
+
).toEqual(scrypt_noble(password, salt, { N: 1024, dkLen: 65, p: 2, r: 1 }))
|
|
18
22
|
})
|
|
19
23
|
|
|
20
24
|
test('behavior: ox uses synchronous WASM PBKDF2 once installed', async () => {
|
|
@@ -39,4 +43,31 @@ describe('engine', () => {
|
|
|
39
43
|
Engine.reset()
|
|
40
44
|
}
|
|
41
45
|
})
|
|
46
|
+
|
|
47
|
+
test('behavior: ox uses synchronous WASM scrypt once installed', async () => {
|
|
48
|
+
const engine = await WasmKeystore.engine()
|
|
49
|
+
await Engine.install({ Keystore: engine })
|
|
50
|
+
try {
|
|
51
|
+
const salt = new Uint8Array(32).fill(7)
|
|
52
|
+
const [key] = Keystore.scrypt({
|
|
53
|
+
n: 1024,
|
|
54
|
+
p: 2,
|
|
55
|
+
password: 'testpassword',
|
|
56
|
+
r: 1,
|
|
57
|
+
salt,
|
|
58
|
+
})
|
|
59
|
+
expect(key()).toBe(
|
|
60
|
+
Bytes.toHex(
|
|
61
|
+
scrypt_noble(Bytes.fromString('testpassword'), salt, {
|
|
62
|
+
N: 1024,
|
|
63
|
+
dkLen: 32,
|
|
64
|
+
p: 2,
|
|
65
|
+
r: 1,
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
} finally {
|
|
70
|
+
Engine.reset()
|
|
71
|
+
}
|
|
72
|
+
})
|
|
42
73
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fc, test } from '@fast-check/vitest'
|
|
2
2
|
import { pbkdf2 as pbkdf2_noble } from '@noble/hashes/pbkdf2.js'
|
|
3
|
+
import { scrypt as scrypt_noble } from '@noble/hashes/scrypt.js'
|
|
3
4
|
import { sha256 } from '@noble/hashes/sha2.js'
|
|
4
5
|
import { beforeAll, describe, expect } from 'vp/test'
|
|
5
6
|
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
@@ -111,3 +112,52 @@ describe('pbkdf2Sha256', () => {
|
|
|
111
112
|
expect(derived).toEqual(snapshot)
|
|
112
113
|
})
|
|
113
114
|
})
|
|
115
|
+
|
|
116
|
+
describe('scrypt', () => {
|
|
117
|
+
test.prop(
|
|
118
|
+
{
|
|
119
|
+
dkLen: arbitraryDkLen,
|
|
120
|
+
logN: fc.integer({ max: 7, min: 1 }),
|
|
121
|
+
p: fc.integer({ max: 3, min: 1 }),
|
|
122
|
+
password: arbitraryPassword,
|
|
123
|
+
r: fc.integer({ max: 4, min: 1 }),
|
|
124
|
+
salt: arbitrarySalt,
|
|
125
|
+
},
|
|
126
|
+
{ numRuns },
|
|
127
|
+
)(
|
|
128
|
+
'matches @noble/hashes for arbitrary parameters and inputs',
|
|
129
|
+
({ dkLen, logN, p, password, r, salt }) => {
|
|
130
|
+
const options = { N: 2 ** logN, dkLen, p, r }
|
|
131
|
+
expect(keystore.scrypt(password, salt, options)).toEqual(
|
|
132
|
+
scrypt_noble(password, salt, options),
|
|
133
|
+
)
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
test.prop(
|
|
138
|
+
{
|
|
139
|
+
dkLen: arbitraryDkLen,
|
|
140
|
+
logN: fc.integer({ max: 7, min: 1 }),
|
|
141
|
+
p: fc.integer({ max: 3, min: 1 }),
|
|
142
|
+
password: arbitraryPassword,
|
|
143
|
+
r: fc.integer({ max: 4, min: 1 }),
|
|
144
|
+
salt: arbitrarySalt,
|
|
145
|
+
},
|
|
146
|
+
{ numRuns },
|
|
147
|
+
)(
|
|
148
|
+
'handles subviews without mutating either backing buffer',
|
|
149
|
+
({ dkLen, logN, p, password, r, salt }) => {
|
|
150
|
+
const password_ = asSubview(password)
|
|
151
|
+
const salt_ = asSubview(salt)
|
|
152
|
+
const passwordSnapshot = password_.backing.slice()
|
|
153
|
+
const saltSnapshot = salt_.backing.slice()
|
|
154
|
+
const options = { N: 2 ** logN, dkLen, p, r }
|
|
155
|
+
|
|
156
|
+
expect(keystore.scrypt(password_.view, salt_.view, options)).toEqual(
|
|
157
|
+
scrypt_noble(password_.view, salt_.view, options),
|
|
158
|
+
)
|
|
159
|
+
expect(password_.backing).toEqual(passwordSnapshot)
|
|
160
|
+
expect(salt_.backing).toEqual(saltSnapshot)
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
})
|
|
@@ -15,11 +15,20 @@ test('synchronous PBKDF2 is present', () => {
|
|
|
15
15
|
>()
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
+
test('synchronous scrypt is present', () => {
|
|
19
|
+
expectTypeOf<Slot['scrypt']>().toEqualTypeOf<
|
|
20
|
+
(
|
|
21
|
+
password: Uint8Array,
|
|
22
|
+
salt: Uint8Array,
|
|
23
|
+
options: { N: number; dkLen: number; p: number; r: number },
|
|
24
|
+
) => Uint8Array
|
|
25
|
+
>()
|
|
26
|
+
})
|
|
27
|
+
|
|
18
28
|
test('unsupported primitives are absent', () => {
|
|
19
29
|
expectTypeOf<Slot>().not.toHaveProperty('aesCtrDecrypt')
|
|
20
30
|
expectTypeOf<Slot>().not.toHaveProperty('aesCtrEncrypt')
|
|
21
31
|
expectTypeOf<Slot>().not.toHaveProperty('pbkdf2Sha256Async')
|
|
22
|
-
expectTypeOf<Slot>().not.toHaveProperty('scrypt')
|
|
23
32
|
expectTypeOf<Slot>().not.toHaveProperty('scryptAsync')
|
|
24
33
|
})
|
|
25
34
|
|