ox 1.0.4 → 1.1.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 +62 -0
- package/dist/core/Base64.d.ts +10 -21
- package/dist/core/Base64.d.ts.map +1 -1
- package/dist/core/Base64.js +8 -127
- package/dist/core/Base64.js.map +1 -1
- package/dist/core/BinaryStateTree.d.ts.map +1 -1
- package/dist/core/BinaryStateTree.js +2 -2
- package/dist/core/BinaryStateTree.js.map +1 -1
- package/dist/core/Bls.d.ts.map +1 -1
- package/dist/core/Bls.js +19 -44
- package/dist/core/Bls.js.map +1 -1
- package/dist/core/Ed25519.d.ts.map +1 -1
- package/dist/core/Ed25519.js +7 -6
- package/dist/core/Ed25519.js.map +1 -1
- package/dist/core/Engine.d.ts +124 -0
- package/dist/core/Engine.d.ts.map +1 -0
- package/dist/core/Engine.js +180 -0
- package/dist/core/Engine.js.map +1 -0
- package/dist/core/Hash.d.ts +4 -4
- package/dist/core/Hash.d.ts.map +1 -1
- package/dist/core/Hash.js +9 -12
- package/dist/core/Hash.js.map +1 -1
- package/dist/core/Keystore.d.ts.map +1 -1
- package/dist/core/Keystore.js +15 -10
- package/dist/core/Keystore.js.map +1 -1
- package/dist/core/Mnemonic.d.ts.map +1 -1
- package/dist/core/Mnemonic.js +3 -2
- package/dist/core/Mnemonic.js.map +1 -1
- package/dist/core/P256.d.ts.map +1 -1
- package/dist/core/P256.js +8 -9
- package/dist/core/P256.js.map +1 -1
- package/dist/core/Secp256k1.d.ts.map +1 -1
- package/dist/core/Secp256k1.js +8 -9
- package/dist/core/Secp256k1.js.map +1 -1
- package/dist/core/WebCryptoP256.d.ts.map +1 -1
- package/dist/core/WebCryptoP256.js +2 -2
- package/dist/core/WebCryptoP256.js.map +1 -1
- package/dist/core/X25519.d.ts.map +1 -1
- package/dist/core/X25519.js +4 -3
- package/dist/core/X25519.js.map +1 -1
- package/dist/core/internal/bls.d.ts +12 -0
- package/dist/core/internal/bls.d.ts.map +1 -0
- package/dist/core/internal/bls.js +79 -0
- package/dist/core/internal/bls.js.map +1 -0
- package/dist/core/internal/codec/base64.d.ts +84 -0
- package/dist/core/internal/codec/base64.d.ts.map +1 -0
- package/dist/core/internal/codec/base64.js +177 -0
- package/dist/core/internal/codec/base64.js.map +1 -0
- package/dist/core/internal/codec/hex.d.ts +49 -4
- package/dist/core/internal/codec/hex.d.ts.map +1 -1
- package/dist/core/internal/codec/hex.js +109 -35
- package/dist/core/internal/codec/hex.js.map +1 -1
- package/dist/core/internal/curves.d.ts +17 -0
- package/dist/core/internal/curves.d.ts.map +1 -0
- package/dist/core/internal/curves.js +17 -0
- package/dist/core/internal/curves.js.map +1 -0
- package/dist/core/internal/ed25519.d.ts +14 -0
- package/dist/core/internal/ed25519.d.ts.map +1 -0
- package/dist/core/internal/ed25519.js +29 -0
- package/dist/core/internal/ed25519.js.map +1 -0
- package/dist/core/internal/engine.d.ts +278 -0
- package/dist/core/internal/engine.d.ts.map +1 -0
- package/dist/core/internal/engine.js +131 -0
- package/dist/core/internal/engine.js.map +1 -0
- package/dist/core/internal/hash.d.ts +12 -0
- package/dist/core/internal/hash.d.ts.map +1 -0
- package/dist/core/internal/hash.js +30 -0
- package/dist/core/internal/hash.js.map +1 -0
- package/dist/core/internal/keystore.d.ts +14 -0
- package/dist/core/internal/keystore.d.ts.map +1 -0
- package/dist/core/internal/keystore.js +32 -0
- package/dist/core/internal/keystore.js.map +1 -0
- package/dist/core/internal/mnemonic.d.ts +4 -0
- package/dist/core/internal/mnemonic.d.ts.map +1 -0
- package/dist/core/internal/mnemonic.js +14 -0
- package/dist/core/internal/mnemonic.js.map +1 -0
- package/dist/core/internal/p256.d.ts +14 -0
- package/dist/core/internal/p256.d.ts.map +1 -0
- package/dist/core/internal/p256.js +36 -0
- package/dist/core/internal/p256.js.map +1 -0
- package/dist/core/internal/secp256k1.d.ts +14 -0
- package/dist/core/internal/secp256k1.d.ts.map +1 -0
- package/dist/core/internal/secp256k1.js +36 -0
- package/dist/core/internal/secp256k1.js.map +1 -0
- package/dist/core/internal/x25519.d.ts +8 -0
- package/dist/core/internal/x25519.d.ts.map +1 -0
- package/dist/core/internal/x25519.js +20 -0
- package/dist/core/internal/x25519.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.docs.d.ts +2 -0
- package/dist/index.docs.d.ts.map +1 -1
- package/dist/index.docs.js +2 -0
- package/dist/index.docs.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/node/Engine.d.ts +55 -0
- package/dist/node/Engine.d.ts.map +1 -0
- package/dist/node/Engine.js +51 -0
- package/dist/node/Engine.js.map +1 -0
- package/dist/node/Hash.d.ts +39 -0
- package/dist/node/Hash.d.ts.map +1 -0
- package/dist/node/Hash.js +37 -0
- package/dist/node/Hash.js.map +1 -0
- package/dist/node/index.d.ts +39 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +39 -0
- package/dist/node/index.js.map +1 -0
- package/dist/tempo/VirtualMaster.d.ts +2 -0
- package/dist/tempo/VirtualMaster.d.ts.map +1 -1
- package/dist/tempo/VirtualMaster.js +19 -5
- package/dist/tempo/VirtualMaster.js.map +1 -1
- package/dist/tempo/ZoneId.d.ts +35 -17
- package/dist/tempo/ZoneId.d.ts.map +1 -1
- package/dist/tempo/ZoneId.js +41 -16
- package/dist/tempo/ZoneId.js.map +1 -1
- package/dist/tempo/index.d.ts +5 -6
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +5 -6
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/mine.wasm.d.ts +2 -4
- package/dist/tempo/internal/mine.wasm.d.ts.map +1 -1
- package/dist/tempo/internal/mine.wasm.js +7 -12
- package/dist/tempo/internal/mine.wasm.js.map +1 -1
- package/dist/tempo/internal/virtualMasterPool.js +4 -1
- package/dist/tempo/internal/virtualMasterPool.js.map +1 -1
- package/dist/wasm/Engine.d.ts +62 -0
- package/dist/wasm/Engine.d.ts.map +1 -0
- package/dist/wasm/Engine.js +57 -0
- package/dist/wasm/Engine.js.map +1 -0
- package/dist/wasm/Hash.d.ts +49 -0
- package/dist/wasm/Hash.d.ts.map +1 -0
- package/dist/wasm/Hash.js +70 -0
- package/dist/wasm/Hash.js.map +1 -0
- package/dist/wasm/index.d.ts +46 -0
- package/dist/wasm/index.d.ts.map +1 -0
- package/dist/wasm/index.js +46 -0
- package/dist/wasm/index.js.map +1 -0
- package/dist/wasm/internal/hash.d.ts +9 -0
- package/dist/wasm/internal/hash.d.ts.map +1 -0
- package/dist/wasm/internal/hash.js +26 -0
- package/dist/wasm/internal/hash.js.map +1 -0
- package/dist/wasm/internal/hashes.wasm.d.ts +5 -0
- package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/hashes.wasm.js +12 -0
- package/dist/wasm/internal/hashes.wasm.js.map +1 -0
- package/dist/wasm/internal/instantiate.d.ts +56 -0
- package/dist/wasm/internal/instantiate.d.ts.map +1 -0
- package/dist/wasm/internal/instantiate.js +92 -0
- package/dist/wasm/internal/instantiate.js.map +1 -0
- package/dist/zod/TypedData.d.ts +12 -12
- package/package.json +36 -1
- package/src/core/Base64.ts +19 -166
- package/src/core/BinaryStateTree.ts +2 -3
- package/src/core/Bls.ts +27 -77
- package/src/core/Ed25519.ts +7 -7
- package/src/core/Engine.ts +235 -0
- package/src/core/Hash.ts +9 -12
- package/src/core/Keystore.ts +17 -17
- package/src/core/Mnemonic.ts +3 -6
- package/src/core/P256.ts +15 -24
- package/src/core/Rlp_tx.bench.ts +2 -4
- package/src/core/Secp256k1.ts +9 -14
- package/src/core/TxEnvelopeEip1559.bench.ts +0 -5
- package/src/core/WebCryptoP256.ts +2 -2
- package/src/core/X25519.ts +4 -3
- package/src/core/_test/AbiParameters.adversarial.fuzz.ts +1 -2
- package/src/core/_test/AbiParameters.fuzz.ts +1 -2
- package/src/core/_test/Base.fuzz.ts +1 -2
- package/src/core/_test/Cbor.fuzz.ts +1 -2
- package/src/core/_test/Engine.fuzz.ts +293 -0
- package/src/core/_test/Engine.test-d.ts +84 -0
- package/src/core/_test/Engine.test.ts +523 -0
- package/src/core/_test/Hash.vectors.test.ts +56 -0
- package/src/core/_test/Hex.fuzz.ts +1 -2
- package/src/core/_test/Rlp.fuzz.ts +1 -2
- package/src/core/_test/Signature.fuzz.ts +1 -2
- package/src/core/_test/curves.test.ts +7 -0
- package/src/core/_test/index.test.ts +1 -0
- package/src/core/internal/bls.ts +121 -0
- package/src/core/internal/codec/_test/base64.conformance.ts +156 -0
- package/src/core/internal/codec/_test/hex.conformance.ts +204 -0
- package/src/core/internal/codec/_test/hex.fuzz.ts +234 -0
- package/src/core/internal/codec/_test/hex.test.ts +124 -0
- package/src/core/internal/codec/base64.ts +222 -0
- package/src/core/internal/codec/hex.ts +115 -34
- package/src/core/internal/curves.ts +18 -0
- package/src/core/internal/ed25519.ts +64 -0
- package/src/core/internal/engine.ts +410 -0
- package/src/core/internal/hash.ts +48 -0
- package/src/core/internal/keystore.ts +107 -0
- package/src/core/internal/mnemonic.ts +18 -0
- package/src/core/internal/p256.ts +91 -0
- package/src/core/internal/secp256k1.ts +91 -0
- package/src/core/internal/x25519.ts +40 -0
- package/src/index.docs.ts +2 -0
- package/src/index.ts +36 -0
- package/src/node/Engine.ts +69 -0
- package/src/node/Hash.ts +56 -0
- package/src/node/_test/Engine.test.ts +62 -0
- package/src/node/_test/Hash.test-d.ts +26 -0
- package/src/node/_test/Hash.test.ts +102 -0
- package/src/node/index.ts +40 -0
- package/src/tempo/VirtualMaster.test.ts +75 -0
- package/src/tempo/VirtualMaster.ts +28 -6
- package/src/tempo/ZoneId.test-d.ts +16 -0
- package/src/tempo/ZoneId.test.ts +48 -13
- package/src/tempo/ZoneId.ts +55 -19
- package/src/tempo/index.ts +5 -6
- package/src/tempo/internal/mine.wasm.ts +7 -13
- package/src/tempo/internal/virtualMasterPool.ts +4 -1
- package/src/tsconfig.json +2 -1
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +76 -0
- package/src/wasm/Hash.bench.ts +60 -0
- package/src/wasm/Hash.ts +109 -0
- package/src/wasm/_test/Engine.test.ts +62 -0
- package/src/wasm/_test/Hash.browser.test.ts +50 -0
- package/src/wasm/_test/Hash.fuzz.ts +188 -0
- package/src/wasm/_test/Hash.test-d.ts +28 -0
- package/src/wasm/_test/Hash.test.ts +211 -0
- package/src/wasm/_test/Hash.vectors.test.ts +117 -0
- package/src/wasm/_test/Runtime.test.ts +21 -0
- package/src/wasm/_test/runtime.wasm.ts +11 -0
- package/src/wasm/index.ts +47 -0
- package/src/wasm/internal/hash.ts +50 -0
- package/src/wasm/internal/hashes.wasm.ts +14 -0
- package/src/wasm/internal/instantiate.ts +135 -0
- package/src/zod/tempo/_test/ZoneId.test.ts +2 -2
- package/src/tempo/internal/mine.c +0 -182
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as CoreEngine from '../core/Engine.js'
|
|
2
|
+
import type * as Errors from '../core/Errors.js'
|
|
3
|
+
import * as Hash from './Hash.js'
|
|
4
|
+
import type * as internal from './internal/instantiate.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compiles every implementation this entrypoint provides and installs it.
|
|
8
|
+
*
|
|
9
|
+
* WASM must be compiled asynchronously, so this is where the `await` lives.
|
|
10
|
+
* Everything afterwards, including the hashing itself, is synchronous.
|
|
11
|
+
*
|
|
12
|
+
* Call it once, during startup, before any crypto call. ox resolves the engine
|
|
13
|
+
* at call time, so values computed beforehand used whatever was installed then.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts twoslash
|
|
17
|
+
* // @noErrors
|
|
18
|
+
* import { Hash } from 'ox'
|
|
19
|
+
* import { Engine } from 'ox/wasm'
|
|
20
|
+
*
|
|
21
|
+
* await Engine.load()
|
|
22
|
+
*
|
|
23
|
+
* Hash.keccak256('0xdeadbeef')
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @returns The engine that was installed.
|
|
27
|
+
*/
|
|
28
|
+
export async function load(): Promise<load.ReturnType> {
|
|
29
|
+
const engine = await create()
|
|
30
|
+
CoreEngine.set(engine)
|
|
31
|
+
return engine
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare namespace load {
|
|
35
|
+
type ReturnType = create.ReturnType
|
|
36
|
+
|
|
37
|
+
type ErrorType =
|
|
38
|
+
| create.ErrorType
|
|
39
|
+
| CoreEngine.set.ErrorType
|
|
40
|
+
| Errors.GlobalErrorType
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compiles every implementation this entrypoint provides, without installing
|
|
45
|
+
* it.
|
|
46
|
+
*
|
|
47
|
+
* Reach for this where an engine has to exist as a value: measuring one
|
|
48
|
+
* implementation against another, or installing through
|
|
49
|
+
* [`Engine.with`](/api/Engine/with) for the duration of a call. Combining
|
|
50
|
+
* engines does not need it, because
|
|
51
|
+
* [`Engine.set`](/api/Engine/set) merges -- `await load()` followed by
|
|
52
|
+
* `Engine.set({ Secp256k1 })` leaves both in place.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts twoslash
|
|
56
|
+
* // @noErrors
|
|
57
|
+
* import { Engine, Hash } from 'ox'
|
|
58
|
+
* import { Engine as Wasm } from 'ox/wasm'
|
|
59
|
+
*
|
|
60
|
+
* const wasm = await Wasm.create()
|
|
61
|
+
*
|
|
62
|
+
* Engine.with(wasm, () => Hash.keccak256('0xdeadbeef'))
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @returns An engine, ready to install.
|
|
66
|
+
*/
|
|
67
|
+
export async function create(): Promise<create.ReturnType> {
|
|
68
|
+
return { ...(await Hash.create()) }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare namespace create {
|
|
72
|
+
/** Every slot this entrypoint supplies, each with its primitives present. */
|
|
73
|
+
type ReturnType = Hash.create.ReturnType
|
|
74
|
+
|
|
75
|
+
type ErrorType = internal.MemoryError | Errors.GlobalErrorType
|
|
76
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { hmac } from '@noble/hashes/hmac.js'
|
|
2
|
+
import { ripemd160 as noble_ripemd160 } from '@noble/hashes/legacy.js'
|
|
3
|
+
import { sha256 as noble_sha256 } from '@noble/hashes/sha2.js'
|
|
4
|
+
import { keccak_256 as noble_keccak256 } from '@noble/hashes/sha3.js'
|
|
5
|
+
import { bench, describe } from 'vp/test'
|
|
6
|
+
import { create as createWasm } from './Hash.js'
|
|
7
|
+
|
|
8
|
+
// Sizes expose fixed boundary overhead and sustained throughput. Crossovers
|
|
9
|
+
// vary by primitive, runtime, and processor.
|
|
10
|
+
//
|
|
11
|
+
// Run `pnpm bench:engines` for the full engine comparison. Vitest cannot
|
|
12
|
+
// run Rust, and this portable benchmark must remain browser-compatible.
|
|
13
|
+
const sizes = [32, 64, 256, 1024, 4096, 65_536, 1_048_576] as const
|
|
14
|
+
|
|
15
|
+
const wasm = (await createWasm()).Hash
|
|
16
|
+
const key = Uint8Array.from({ length: 32 }, (_, index) => index % 97)
|
|
17
|
+
|
|
18
|
+
for (const size of sizes) {
|
|
19
|
+
const bytes = Uint8Array.from({ length: size }, (_, index) => index % 251)
|
|
20
|
+
|
|
21
|
+
describe(`keccak256 (${size} bytes input)`, () => {
|
|
22
|
+
bench('ox', () => {
|
|
23
|
+
noble_keccak256(bytes)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
bench('ox/wasm', () => {
|
|
27
|
+
wasm.keccak256(bytes)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe(`sha256 (${size} bytes input)`, () => {
|
|
32
|
+
bench('ox', () => {
|
|
33
|
+
noble_sha256(bytes)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
bench('ox/wasm', () => {
|
|
37
|
+
wasm.sha256(bytes)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe(`ripemd160 (${size} bytes input)`, () => {
|
|
42
|
+
bench('ox', () => {
|
|
43
|
+
noble_ripemd160(bytes)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
bench('ox/wasm', () => {
|
|
47
|
+
wasm.ripemd160(bytes)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe(`hmacSha256 (${size} bytes input)`, () => {
|
|
52
|
+
bench('ox', () => {
|
|
53
|
+
hmac(noble_sha256, key, bytes)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
bench('ox/wasm', () => {
|
|
57
|
+
wasm.hmacSha256(key, bytes)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
package/src/wasm/Hash.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type * as Engine from '../core/Engine.js'
|
|
2
|
+
import type * as Errors from '../core/Errors.js'
|
|
3
|
+
import * as hash from './internal/hash.js'
|
|
4
|
+
import { wasmBase64 } from './internal/hashes.wasm.js'
|
|
5
|
+
import * as internal from './internal/instantiate.js'
|
|
6
|
+
|
|
7
|
+
export { MemoryError } from './internal/instantiate.js'
|
|
8
|
+
|
|
9
|
+
type Exports = hash.Exports & {
|
|
10
|
+
keccak256(input: number, length: number, out: number): void
|
|
11
|
+
ripemd160(input: number, length: number, out: number): void
|
|
12
|
+
sha256(input: number, length: number, out: number): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Digest sizes, in bytes. */
|
|
16
|
+
const digestSize = { keccak256: 32, ripemd160: 20, sha256: 32 }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compilation is memoized; the engine object deliberately is not.
|
|
20
|
+
*
|
|
21
|
+
* Handing every caller the same object means one of them customising a slot
|
|
22
|
+
* for composition silently changes what a later `create` -- or `Engine.load`
|
|
23
|
+
* -- installs.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const instantiate = /*#__PURE__*/ internal.memoize(() =>
|
|
28
|
+
internal.instantiate<Exports>(wasmBase64),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compiles the WASM implementation of the [`Hash`](/api/Hash) primitives, without
|
|
33
|
+
* installing it.
|
|
34
|
+
*
|
|
35
|
+
* Most callers want {@link ox#Engine.load} instead, which compiles every
|
|
36
|
+
* implementation this entrypoint provides and installs them in one call. Reach
|
|
37
|
+
* for this to take the `Hash` slot on its own, or to hold the implementation
|
|
38
|
+
* without touching the installed engine.
|
|
39
|
+
*
|
|
40
|
+
* :::note
|
|
41
|
+
* Performance varies by primitive, input size, runtime, and processor. Run
|
|
42
|
+
* `pnpm bench:engines` to compare Ox's default, Node, WASM, and Alloy
|
|
43
|
+
* implementations on your target machine.
|
|
44
|
+
* :::
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts twoslash
|
|
48
|
+
* // @noErrors
|
|
49
|
+
* import { Engine, Hash } from 'ox'
|
|
50
|
+
* import * as WasmHash from 'ox/wasm/Hash'
|
|
51
|
+
*
|
|
52
|
+
* Engine.set(await WasmHash.create())
|
|
53
|
+
*
|
|
54
|
+
* Hash.keccak256('0xdeadbeef')
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @returns An engine supplying the `Hash` slot.
|
|
58
|
+
*/
|
|
59
|
+
export async function create(): Promise<create.ReturnType> {
|
|
60
|
+
{
|
|
61
|
+
const module = await instantiate()
|
|
62
|
+
|
|
63
|
+
// Copies `input` in, runs `hash`, and copies the digest back out.
|
|
64
|
+
function call(
|
|
65
|
+
hash: 'keccak256' | 'ripemd160' | 'sha256',
|
|
66
|
+
input: Uint8Array,
|
|
67
|
+
): Uint8Array {
|
|
68
|
+
const out = digestSize[hash]
|
|
69
|
+
module.reserve(input.length + out)
|
|
70
|
+
const inputPtr = module.heapBase
|
|
71
|
+
const outPtr = inputPtr + input.length
|
|
72
|
+
module.view().set(input, inputPtr)
|
|
73
|
+
module.exports[hash](inputPtr, input.length, outPtr)
|
|
74
|
+
// `slice`, not `subarray`: the result must not alias WASM memory, which the
|
|
75
|
+
// next call overwrites and a `grow` detaches entirely.
|
|
76
|
+
return module.view().slice(outPtr, outPtr + out)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
Hash: {
|
|
81
|
+
hmacSha256: (key, message) => hash.hmacSha256(module, key, message),
|
|
82
|
+
keccak256: (input) => call('keccak256', input),
|
|
83
|
+
ripemd160: (input) => call('ripemd160', input),
|
|
84
|
+
sha256: (input) => call('sha256', input),
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export declare namespace create {
|
|
91
|
+
/**
|
|
92
|
+
* The `Hash` slot, carrying every primitive this module implements.
|
|
93
|
+
*
|
|
94
|
+
* {@link ox#Engine.Engine} is optional all the way down so that an engine can
|
|
95
|
+
* fill in as little as it likes. This one always fills the same four, and
|
|
96
|
+
* saying so is what spares callers a non-null assertion per primitive.
|
|
97
|
+
*/
|
|
98
|
+
type ReturnType = {
|
|
99
|
+
Hash: {
|
|
100
|
+
[key in
|
|
101
|
+
| 'hmacSha256'
|
|
102
|
+
| 'keccak256'
|
|
103
|
+
| 'ripemd160'
|
|
104
|
+
| 'sha256']-?: NonNullable<Engine.Hash[key]>
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type ErrorType = internal.MemoryError | Errors.GlobalErrorType
|
|
109
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Engine as CoreEngine, Hash } from 'ox'
|
|
2
|
+
import { Engine } from 'ox/wasm'
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
|
|
5
|
+
describe('create', () => {
|
|
6
|
+
test('behavior: does not install', async () => {
|
|
7
|
+
const engine = await Engine.create()
|
|
8
|
+
expect(Object.keys(engine)).toMatchInlineSnapshot(`
|
|
9
|
+
[
|
|
10
|
+
"Hash",
|
|
11
|
+
]
|
|
12
|
+
`)
|
|
13
|
+
// The whole point of `create` over `load`: benches, differential tests and
|
|
14
|
+
// composition all need the implementation without touching global state.
|
|
15
|
+
expect(CoreEngine.get()).toMatchInlineSnapshot('{}')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('behavior: installs for the duration of a call', async () => {
|
|
19
|
+
// `with` is the case that genuinely needs a value: there is nothing
|
|
20
|
+
// installed for `set` to merge into.
|
|
21
|
+
const wasm = await Engine.create()
|
|
22
|
+
const inside = CoreEngine.with(wasm, () => Hash.keccak256('0xdeadbeef'))
|
|
23
|
+
expect(inside).toEqual(Hash.keccak256('0xdeadbeef'))
|
|
24
|
+
expect(CoreEngine.get()).toMatchInlineSnapshot('{}')
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('load', () => {
|
|
29
|
+
test('behavior: merges with a later set, rather than being replaced', async () => {
|
|
30
|
+
await Engine.load()
|
|
31
|
+
CoreEngine.set({ Mnemonic: { toSeed: () => new Uint8Array(64) } })
|
|
32
|
+
expect(Object.keys(CoreEngine.get()).sort()).toMatchInlineSnapshot(`
|
|
33
|
+
[
|
|
34
|
+
"Hash",
|
|
35
|
+
"Mnemonic",
|
|
36
|
+
]
|
|
37
|
+
`)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('behavior: installs, and returns what it installed', async () => {
|
|
41
|
+
const before = Hash.keccak256('0xdeadbeef')
|
|
42
|
+
|
|
43
|
+
const engine = await Engine.load()
|
|
44
|
+
|
|
45
|
+
expect(Object.keys(CoreEngine.get())).toEqual(Object.keys(engine))
|
|
46
|
+
// Installing changes which implementation runs, never the answer.
|
|
47
|
+
expect(Hash.keccak256('0xdeadbeef')).toEqual(before)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('behavior: routes every slot it reports', async () => {
|
|
51
|
+
await Engine.load()
|
|
52
|
+
expect(Object.keys(CoreEngine.get().Hash ?? {}).sort())
|
|
53
|
+
.toMatchInlineSnapshot(`
|
|
54
|
+
[
|
|
55
|
+
"hmacSha256",
|
|
56
|
+
"keccak256",
|
|
57
|
+
"ripemd160",
|
|
58
|
+
"sha256",
|
|
59
|
+
]
|
|
60
|
+
`)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
2
|
+
import { describe, expect, test, vi } from 'vp/test'
|
|
3
|
+
import * as Engine from '../../core/Engine.js'
|
|
4
|
+
import * as Hash from '../../core/Hash.js'
|
|
5
|
+
import * as WasmHash from '../Hash.js'
|
|
6
|
+
|
|
7
|
+
describe('create', () => {
|
|
8
|
+
test('behavior: instantiates asynchronously', async () => {
|
|
9
|
+
// Chrome refuses to compile modules above a few kilobytes synchronously on
|
|
10
|
+
// the main thread, so `new WebAssembly.Module` must never be reached. A spy
|
|
11
|
+
// is the only way to assert that, since the artifact happens to be small
|
|
12
|
+
// enough today that a synchronous compile would succeed.
|
|
13
|
+
const Module = vi.spyOn(globalThis.WebAssembly, 'Module')
|
|
14
|
+
const Instance = vi.spyOn(globalThis.WebAssembly, 'Instance')
|
|
15
|
+
try {
|
|
16
|
+
const engine = await WasmHash.create()
|
|
17
|
+
expect(Module).not.toHaveBeenCalled()
|
|
18
|
+
expect(Instance).not.toHaveBeenCalled()
|
|
19
|
+
|
|
20
|
+
const input = new Uint8Array(32).fill(1)
|
|
21
|
+
expect(engine.Hash.keccak256(input)).toEqual(keccak_256(input))
|
|
22
|
+
} finally {
|
|
23
|
+
Module.mockRestore()
|
|
24
|
+
Instance.mockRestore()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('behavior: grows memory in a real browser, then hashes a small input', async () => {
|
|
29
|
+
const engine = await WasmHash.create()
|
|
30
|
+
|
|
31
|
+
const large = new Uint8Array(3 * 1024 * 1024).fill(7)
|
|
32
|
+
expect(engine.Hash.keccak256(large)).toEqual(keccak_256(large))
|
|
33
|
+
|
|
34
|
+
// `memory.grow` detached the previous `ArrayBuffer`; a retained view would
|
|
35
|
+
// now read zero bytes.
|
|
36
|
+
const small = new Uint8Array(32).fill(1)
|
|
37
|
+
expect(engine.Hash.keccak256(small)).toEqual(keccak_256(small))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('behavior: ox uses the WASM implementation once installed', async () => {
|
|
41
|
+
Engine.set(await WasmHash.create())
|
|
42
|
+
try {
|
|
43
|
+
expect(Hash.keccak256('0xdeadbeef')).toBe(
|
|
44
|
+
'0xd4fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1',
|
|
45
|
+
)
|
|
46
|
+
} finally {
|
|
47
|
+
Engine.reset()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { fc, test } from '@fast-check/vitest'
|
|
2
|
+
import { hmac } from '@noble/hashes/hmac.js'
|
|
3
|
+
import { ripemd160 } from '@noble/hashes/legacy.js'
|
|
4
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
5
|
+
import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
6
|
+
import { Hash as WasmHash } from 'ox/wasm'
|
|
7
|
+
import { beforeAll, describe, expect } from 'vp/test'
|
|
8
|
+
import { numRuns } from '../../../test/fuzz/numRuns.js'
|
|
9
|
+
|
|
10
|
+
let engine: WasmHash.create.ReturnType
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
engine = await WasmHash.create()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `@noble/hashes` is the oracle: it is audited and it is what ox ships by
|
|
18
|
+
* default, so any divergence here is a bug in the WASM implementation. Published
|
|
19
|
+
* vectors live in `Hash.vectors.test.ts` and cover the case where both
|
|
20
|
+
* implementations could be wrong together.
|
|
21
|
+
*
|
|
22
|
+
* The distribution is weighted toward the sizes where padding and block-boundary
|
|
23
|
+
* 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 the length counter no
|
|
25
|
+
* longer fits in the final block.
|
|
26
|
+
*/
|
|
27
|
+
const arbitraryInput = fc.oneof(
|
|
28
|
+
{ arbitrary: fc.uint8Array({ maxLength: 300, minLength: 0 }), weight: 6 },
|
|
29
|
+
{
|
|
30
|
+
arbitrary: fc
|
|
31
|
+
.constantFrom(0, 1, 55, 56, 63, 64, 65, 111, 112, 135, 136, 137, 272)
|
|
32
|
+
.chain((size) => fc.uint8Array({ maxLength: size, minLength: size })),
|
|
33
|
+
weight: 3,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
arbitrary: fc.uint8Array({ maxLength: 4096, minLength: 1024 }),
|
|
37
|
+
weight: 1,
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* HMAC key lengths straddling the 64-byte block. At or under it the key is
|
|
43
|
+
* zero-padded; over it, it is hashed first -- a different code path that the
|
|
44
|
+
* message-shaped arbitrary above would only reach by chance.
|
|
45
|
+
*/
|
|
46
|
+
const arbitraryKey = fc.oneof(
|
|
47
|
+
{
|
|
48
|
+
arbitrary: fc
|
|
49
|
+
.constantFrom(0, 1, 32, 63, 64, 65, 96, 128, 129)
|
|
50
|
+
.chain((size) => fc.uint8Array({ maxLength: size, minLength: size })),
|
|
51
|
+
weight: 3,
|
|
52
|
+
},
|
|
53
|
+
{ arbitrary: fc.uint8Array({ maxLength: 200, minLength: 0 }), weight: 1 },
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const primitives = {
|
|
57
|
+
hmacSha256: {
|
|
58
|
+
reference: (input: Uint8Array, key: Uint8Array) => hmac(sha256, key, input),
|
|
59
|
+
wasm: (input: Uint8Array, key: Uint8Array) =>
|
|
60
|
+
engine.Hash.hmacSha256(key, input),
|
|
61
|
+
},
|
|
62
|
+
keccak256: {
|
|
63
|
+
reference: (input: Uint8Array) => keccak_256(input),
|
|
64
|
+
wasm: (input: Uint8Array) => engine.Hash.keccak256(input),
|
|
65
|
+
},
|
|
66
|
+
ripemd160: {
|
|
67
|
+
reference: (input: Uint8Array) => ripemd160(input),
|
|
68
|
+
wasm: (input: Uint8Array) => engine.Hash.ripemd160(input),
|
|
69
|
+
},
|
|
70
|
+
sha256: {
|
|
71
|
+
reference: (input: Uint8Array) => sha256(input),
|
|
72
|
+
wasm: (input: Uint8Array) => engine.Hash.sha256(input),
|
|
73
|
+
},
|
|
74
|
+
} as const
|
|
75
|
+
|
|
76
|
+
type Name = keyof typeof primitives
|
|
77
|
+
|
|
78
|
+
describe('Hash', () => {
|
|
79
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
80
|
+
'keccak256 ≡ @noble/hashes',
|
|
81
|
+
({ input }) => {
|
|
82
|
+
expect(engine.Hash.keccak256(input)).toEqual(keccak_256(input))
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
87
|
+
'sha256 ≡ @noble/hashes',
|
|
88
|
+
({ input }) => {
|
|
89
|
+
expect(engine.Hash.sha256(input)).toEqual(sha256(input))
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
94
|
+
'ripemd160 ≡ @noble/hashes',
|
|
95
|
+
({ input }) => {
|
|
96
|
+
expect(engine.Hash.ripemd160(input)).toEqual(ripemd160(input))
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
test.prop({ input: arbitraryInput, key: arbitraryKey }, { numRuns })(
|
|
101
|
+
'hmacSha256 ≡ @noble/hashes',
|
|
102
|
+
({ input, key }) => {
|
|
103
|
+
expect(engine.Hash.hmacSha256(key, input)).toEqual(
|
|
104
|
+
hmac(sha256, key, input),
|
|
105
|
+
)
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('memory', () => {
|
|
111
|
+
test.prop(
|
|
112
|
+
{
|
|
113
|
+
operations: fc.array(
|
|
114
|
+
fc.record({
|
|
115
|
+
input: arbitraryInput,
|
|
116
|
+
key: arbitraryKey,
|
|
117
|
+
name: fc.constantFrom<Name>(
|
|
118
|
+
'hmacSha256',
|
|
119
|
+
'keccak256',
|
|
120
|
+
'ripemd160',
|
|
121
|
+
'sha256',
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
{ maxLength: 24, minLength: 2 },
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
{ numRuns },
|
|
128
|
+
)('an arbitrary sequence of calls agrees at every step', ({ operations }) => {
|
|
129
|
+
// Every primitive shares one region of WASM linear memory, reusing it call
|
|
130
|
+
// after call. A mis-sized reservation, an off-by-one write, or a digest read
|
|
131
|
+
// from the wrong offset can leave earlier bytes behind and still produce a
|
|
132
|
+
// correct answer in isolation -- it only shows up when calls of differing
|
|
133
|
+
// sizes and kinds are interleaved. Comparing every step of a random sequence
|
|
134
|
+
// is what catches that; a fixed a/b/a pattern does not.
|
|
135
|
+
for (const { input, key, name } of operations) {
|
|
136
|
+
const primitive = primitives[name]
|
|
137
|
+
expect(
|
|
138
|
+
primitive.wasm(input, key),
|
|
139
|
+
`${name} with a ${input.length} byte input`,
|
|
140
|
+
).toEqual(primitive.reference(input, key))
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test.prop(
|
|
145
|
+
{
|
|
146
|
+
sizes: fc.array(fc.integer({ max: 20, min: 0 }), {
|
|
147
|
+
maxLength: 6,
|
|
148
|
+
minLength: 2,
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
{ numRuns: Math.min(numRuns, 20) },
|
|
152
|
+
)('repeated growth keeps results correct', ({ sizes }) => {
|
|
153
|
+
// Each size is a page count. Going up grows linear memory and detaches the
|
|
154
|
+
// previous `ArrayBuffer`; coming back down must not, since WASM memory never
|
|
155
|
+
// shrinks. Either way the view has to be re-derived, and a stale one reads
|
|
156
|
+
// as zeroes rather than throwing.
|
|
157
|
+
for (const pages of sizes) {
|
|
158
|
+
const input = new Uint8Array(pages * 65_536).fill(pages & 0xff)
|
|
159
|
+
expect(engine.Hash.keccak256(input), `${pages} pages`).toEqual(
|
|
160
|
+
keccak_256(input),
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
166
|
+
'digests never alias WASM memory',
|
|
167
|
+
({ input }) => {
|
|
168
|
+
// A digest returned as a view over linear memory would be silently
|
|
169
|
+
// rewritten by the next call. Hold one across a larger call and check it.
|
|
170
|
+
const digest = engine.Hash.keccak256(input)
|
|
171
|
+
const snapshot = digest.slice()
|
|
172
|
+
engine.Hash.keccak256(new Uint8Array(input.length + 1024).fill(0xaa))
|
|
173
|
+
expect(digest).toEqual(snapshot)
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
test.prop({ input: arbitraryInput }, { numRuns })(
|
|
178
|
+
'inputs are not mutated',
|
|
179
|
+
({ input }) => {
|
|
180
|
+
const snapshot = input.slice()
|
|
181
|
+
engine.Hash.keccak256(input)
|
|
182
|
+
engine.Hash.sha256(input)
|
|
183
|
+
engine.Hash.ripemd160(input)
|
|
184
|
+
engine.Hash.hmacSha256(input, input)
|
|
185
|
+
expect(input).toEqual(snapshot)
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Engine } from 'ox'
|
|
2
|
+
import { Hash as WasmHash } from 'ox/wasm'
|
|
3
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
4
|
+
|
|
5
|
+
type Created = Awaited<ReturnType<typeof WasmHash.create>>
|
|
6
|
+
|
|
7
|
+
test('every primitive is present, so callers need no assertion', () => {
|
|
8
|
+
expectTypeOf<Created['Hash']['keccak256']>().toEqualTypeOf<
|
|
9
|
+
(input: Uint8Array) => Uint8Array
|
|
10
|
+
>()
|
|
11
|
+
expectTypeOf<Created['Hash']['sha256']>().toEqualTypeOf<
|
|
12
|
+
(input: Uint8Array) => Uint8Array
|
|
13
|
+
>()
|
|
14
|
+
expectTypeOf<Created['Hash']['ripemd160']>().toEqualTypeOf<
|
|
15
|
+
(input: Uint8Array) => Uint8Array
|
|
16
|
+
>()
|
|
17
|
+
expectTypeOf<Created['Hash']['hmacSha256']>().toEqualTypeOf<
|
|
18
|
+
(key: Uint8Array, message: Uint8Array) => Uint8Array
|
|
19
|
+
>()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('blake3 is absent, because the WASM module does not implement it', () => {
|
|
23
|
+
expectTypeOf<Created['Hash']>().not.toHaveProperty('blake3')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('the result is still an engine, so `Engine.set` accepts it', () => {
|
|
27
|
+
expectTypeOf<Created>().toExtend<Engine.Engine>()
|
|
28
|
+
})
|