architecture - xero/leviathan-crypto GitHub Wiki
Overview of Leviathan Crypto's architecture design, comprising six independent WASM modules unified by a misuse-resistant TypeScript API, which delivers both Serpent's paranoia and ChaCha's elegance as a zero-dependency, tree-shakable, post-quantum library.
leviathan-crypto is a post-quantum WASM cryptography library with zero dependencies, tree-shakable, and side-effect free.
JS is the problem, SIMD WASM is the solution. JavaScript engines offer no formal constant-time guarantees. JIT compilers optimize based on runtime patterns, which leak secrets through cache access and instruction timing. By contrast, WebAssembly executes outside the JIT entirely, running compiled bytecode with linear memory you control. No speculative optimization, no value-dependent branches between source and execution.
WebAssembly is the correctness layer. All algorithm logic lives in WASM. Six AssemblyScript modules (serpent, chacha20, sha2, sha3, kyber, and ct) compile independently to WASM with SIMD where it pays off. Each module is its own instance with its own linear memory. Within a module, stateful primitives share the instance, and a runtime exclusivity model keeps them from interfering with each other.
TypeScript is the ergonomics layer. The strongly-typed public API covers Seal, SealStream, SealStreamPool, Fortuna, HKDF, SkippedKeyStore, and others. The design is misuse-resistant by default. Authentication is verify-then-decrypt; key material wipes on dispose; validation runs before any crypto path; one-shot AEADs lock on first call. TypeScript never implements cryptographic algorithms. It orchestrates the WASM layer and enforces best practice through API shape, not convention.
Serpent-256: maximum paranoia. 32 rounds of S-boxes in pure Boolean logic with no table lookups. An ouroboros devouring every bit, in every block, through every round.
XChaCha20-Poly1305: precise elegance. 20 rounds of add-rotate-XOR, choreography without S-boxes or cache-timing leakage. A dance closing with Poly1305's unconditional forgery bound.
Two ciphers, one interface. Both share the CipherSuite shape and slot into Seal, SealStream, and SealStreamPool interchangeably. Post-quantum extends the same model, KyberSuite wraps MlKem512, MlKem768, or MlKem1024 around either cipher, and the SPQR ratchet builds forward-secret sessions on top.
Primitives. WASM algorithms with their TypeScript wrapper classes.
| Module | Algorithms | TypeScript API |
|---|---|---|
serpent |
Serpent-256 block cipher: ECB, CTR, CBC |
Serpent (cipher class for mode operations) |
chacha20 |
ChaCha20, Poly1305, ChaCha20-Poly1305, XChaCha20-Poly1305 |
ChaCha20, Poly1305, XChaCha20Poly1305
|
sha2 |
SHA-256, SHA-384, SHA-512, HMAC variants, HKDF variants |
SHA256, SHA384, SHA512, HMAC_SHA256, HMAC_SHA384, HMAC_SHA512, HKDF_SHA256, HKDF_SHA512
|
sha3 |
SHA3-224/256/384/512, SHAKE128, SHAKE256 (XOFs) |
SHA3_256, SHA3_512, SHAKE256
|
kyber |
MlKem512, MlKem768, MlKem1024 |
MlKem512, MlKem768, MlKem1024
|
ct |
Constant-time comparison primitives | constantTimeEqual |
Cipher Suites. Composition of WASM modules into complete cipher packages.
| Suite | Composition | Use case |
|---|---|---|
SerpentCipher |
serpent + sha2 (CBC+HMAC-SHA256) |
Authenticated encryption via STREAM |
XChaCha20Cipher |
chacha20 (XChaCha20-Poly1305 AEAD) |
Streaming authenticated encryption |
KyberSuite |
kyber + (any cipher) |
Post-quantum key encapsulation |
High-Level Constructs. Pure TypeScript abstractions over cipher suites.
| API | Dependencies | Purpose |
|---|---|---|
Seal / SealStream / SealStreamPool
|
Any CipherSuite | One-shot, streaming, and parallel encryption |
ratchetInit, KDFChain, kemRatchetEncap/kemRatchetDecap
|
sha2; kyber + sha3 for KEM |
Forward-secret session ratcheting (SPQR) |
Fortuna |
Cipher PRF + HashFn | Cryptographically-secure RNG |
Utilities. Pure TypeScript helpers, no init() dependency.
| Utility | Purpose |
|---|---|
hexToBytes, bytesToHex
|
Hex/byte conversions |
wipe |
Secure memory zeroing |
xor, concat
|
Byte operations |
randomBytes |
One-off random byte generation |
constantTimeEqual |
Timing-attack resistant comparison (WASM-backed) |
Source lives under src/, split between AssemblyScript primitives in src/asm/ and the TypeScript API in src/ts/. Tests are in test/. Build, codegen, and tooling scripts go in scripts/. CI/CD configuration sits in .github/. The repository root holds project documentation, package metadata, and tool configs. Each subsection below shows the relevant tree and notes the conventions that apply across files in that tier.
.github/ holds GitHub-specific repository configuration: workflow definitions, the CI image build context, and platform metadata. Workflows split along functional lines.
Merge gate. build.yml, lint.yml, e2e.yml, test-suite.yml. test-suite.yml orchestrates the per-domain unit runners (unit-*.yml) plus verify-vectors.yml for parallel execution and per-domain failure isolation.
Test vectors. verify-vectors.yml validates the corpus against SHA256SUMS.
Release flow. Manual release.yml bumps the version and creates the tag; the resulting v* tag push triggers publish.yml, which runs the npm publish with provenance attestations. npm-remove.yml is the manual deprecate/unpublish escape hatch.
Wiki. wiki.yml syncs docs/ to the GitHub Wiki on every merge to main.
CI image. ci-image.yml rebuilds the test-runner container from ci.Dockerfile whenever the Dockerfile changes.
.github/
βββ ci.Dockerfile
βββ workflows/
βββ build.yml
βββ ci-image.yml
βββ e2e.yml
βββ lint.yml
βββ npm-remove.yml
βββ publish.yml
βββ release.yml
βββ test-suite.yml
βββ unit-chacha20.yml
βββ unit-core.yml
βββ unit-hashing.yml
βββ unit-kyber.yml
βββ unit-montecarlo-cbc.yml
βββ unit-montecarlo-ecb.yml
βββ unit-nessie.yml
βββ unit-ratchet.yml
βββ unit-serpent.yml
βββ unit-stream.yml
βββ verify-vectors.yml
βββ wiki.yml
scripts/ holds the build, codegen, and tooling scripts that produce dist/ and the test-vector corpus. Three categories.
Build orchestration. build-asm.js drives AssemblyScript compilation across the six modules. embed-wasm.ts produces the gzip+base64 blob for each .wasm. embed-workers.ts bundles each pool worker into a self-contained IIFE via esbuild. copy-docs.ts ships the consumer doc subset into dist/. See Build Pipeline for the full sequence.
Codegen. generate_simd.ts produces src/asm/serpent/serpent_simd.ts from a template by translating S-box gate logic into v128 ops; the generator and its output are both committed and the output is never edited by hand. gen-seal-vectors.ts, gen-sealstream-vectors.ts, gen-fortuna-vectors.ts, and gen-ratchet-vectors.ts produce known-answer-test vectors for their respective primitives.
Tooling. gen-changelog.ts generates CHANGELOG entries. lint-asm.js lints the AssemblyScript sources. pin-actions.ts pins every GitHub Action reference to a SHA, run via bun pin after workflow changes.
scripts/
βββ build-asm.js
βββ copy-docs.ts
βββ embed-wasm.ts
βββ embed-workers.ts
βββ gen-changelog.ts
βββ gen-fortuna-vectors.ts
βββ gen-ratchet-vectors.ts
βββ gen-seal-vectors.ts
βββ gen-sealstream-vectors.ts
βββ generate_simd.ts
βββ lint-asm.js
βββ pin-actions.ts
src/asm/ holds the AssemblyScript sources for each WASM binary. Every subdirectory compiles to its own .wasm with fully independent linear memory and no cross-module imports.
Per-module conventions. Every module exposes an index.ts as the asc entry point; it re-exports the public surface that becomes the WASM exports. Every module except ct/ has a buffers.ts that defines the static memory layout and the offset getters that all other files in that module import. The ct/ module is intentionally minimal: a single index.ts whose layout is implicit in its single 64 KB page.
src/asm/
βββ chacha20/
β βββ index.ts
β βββ chacha20.ts β block function (RFC 8439)
β βββ chacha20_simd_4x.ts β SIMD 4-wide inter-block keystream
β βββ poly1305.ts β one-time MAC
β βββ wipe.ts β module-wide buffer zeroizer
β βββ buffers.ts
βββ ct/
β βββ index.ts β v128 XOR-accumulate constant-time compare
βββ kyber/
β βββ index.ts
β βββ ntt.ts β scalar NTT/invNTT + zetas table
β βββ ntt_simd.ts β v128 NTT butterflies, fqmul_8x, barrett_reduce_8x
β βββ reduce.ts β Montgomery/Barrett reduction, fqmul
β βββ poly.ts β polynomial serialization, compression, basemul
β βββ poly_simd.ts β SIMD poly add/sub/reduce/ntt wrappers
β βββ polyvec.ts β k-wide polyvec operations
β βββ cbd.ts β centered binomial distribution (Ξ·=2, Ξ·=3)
β βββ sampling.ts β uniform rejection sampling
β βββ verify.ts β constant-time compare and conditional move
β βββ params.ts β Q, QINV, MONT, Barrett/compression constants
β βββ buffers.ts
βββ serpent/
β βββ index.ts
β βββ serpent.ts β block function + key schedule
β βββ serpent_unrolled.ts β unrolled S-boxes and round functions
β βββ serpent_simd.ts β SIMD bitsliced block operations
β βββ cbc.ts β CBC mode
β βββ cbc_simd.ts β SIMD CBC decrypt
β βββ ctr.ts β CTR mode
β βββ ctr_simd.ts β SIMD CTR 4-wide inter-block
β βββ buffers.ts
βββ sha2/
β βββ index.ts
β βββ sha256.ts
β βββ sha512.ts β shared by SHA-512 and SHA-384
β βββ hmac.ts β HMAC-SHA256
β βββ hmac512.ts β HMAC-SHA512 and HMAC-SHA384
β βββ buffers.ts
βββ sha3/
βββ index.ts
βββ keccak.ts β Keccak-f[1600] permutation, sponge absorb/squeeze
βββ buffers.ts
src/ts/ is the public API layer. Each subdirectory is a published npm subpath; top-level files cover cross-cutting concerns and standalone utilities.
Subpath conventions. Every cipher and hash module has an index.ts barrel, a types.ts for TypeScript-only declarations, and an embedded.ts that re-exports its gzip+base64 WASM blob from src/ts/embedded/. The keccak/ alias subpath omits types.ts and re-exports sha3's instead. The ratchet/ and stream/ modules have no embedded.ts because they compose other modules and ship no WASM of their own.
Cipher modules (serpent/, chacha20/) add a cipher-suite.ts (the CipherSuite implementation for STREAM), a pool-worker.ts (Web Worker source for SealStreamPool), a generator.ts (Fortuna Generator), and a shared-ops.ts (serpent) or ops.ts (chacha20) holding pure primitive functions shared between the cipher-suite and the pool worker.
Hash modules (sha2/, sha3/) add a hash.ts (the stateless Fortuna HashFn).
Build artifacts. ct-wasm.ts and the embedded/ directory hold auto-generated outputs that only exist after bun run build. Both are gitignored. ct-wasm.ts is the inline raw byte array of the ct WASM module. embedded/ holds gzip+base64 blobs of each WASM binary (from scripts/embed-wasm.ts) and IIFE source strings for each pool worker (from scripts/embed-workers.ts).
src/ts/
βββ chacha20/
β βββ cipher-suite.ts
β βββ embedded.ts
β βββ generator.ts
β βββ index.ts
β βββ ops.ts
β βββ pool-worker.ts
β βββ types.ts
βββ ct-wasm.ts β gitignored build artifact: raw ct WASM bytes
βββ embedded/ β gitignored build artifacts
β βββ chacha20-pool-worker.ts β ChaCha20 pool-worker IIFE source string
β βββ chacha20.ts β chacha20.wasm gzip+base64 blob
β βββ kyber.ts β kyber.wasm gzip+base64 blob
β βββ serpent-pool-worker.ts β Serpent pool-worker IIFE source string
β βββ serpent.ts β serpent.wasm gzip+base64 blob
β βββ sha2.ts β sha2.wasm gzip+base64 blob
β βββ sha3.ts β sha3.wasm gzip+base64 blob
βββ errors.ts β AuthenticationError
βββ fortuna.ts β Fortuna CSPRNG (composes pluggable Generator + HashFn)
βββ index.ts β root barrel + dispatching init()
βββ init.ts β initModule(), module cache, isInitialized
βββ keccak/ β alias subpath; same WASM and instance slot as sha3
β βββ embedded.ts
β βββ index.ts
βββ kyber/
β βββ embedded.ts
β βββ indcpa.ts β IND-CPA encrypt/decrypt + matrix generation
β βββ index.ts
β βββ kem.ts β Fujisaki-Okamoto transform (keygen, encaps, decaps)
β βββ params.ts β MLKEM512, MLKEM768, MLKEM1024 parameter sets
β βββ suite.ts β KyberSuite (hybrid KEM+AEAD CipherSuite factory)
β βββ types.ts
β βββ validate.ts β key validation (FIPS 203 Β§7.2, Β§7.3)
βββ loader.ts β loadWasm()/compileWasm() WasmSource dispatch
βββ ratchet/
β βββ index.ts
β βββ kdf-chain.ts β KDFChain (per-message KDF chain, DR Β§5.2)
β βββ ratchet-keypair.ts β RatchetKeypair (single-use ek/dk wrapper)
β βββ root-kdf.ts β ratchetInit, kemRatchetEncap, kemRatchetDecap (DR Β§7.2)
β βββ skipped-key-store.ts β SkippedKeyStore (MKSKIPPED cache, DR Β§3.2/Β§3.5)
β βββ types.ts
βββ serpent/
β βββ cipher-suite.ts
β βββ embedded.ts
β βββ generator.ts
β βββ index.ts
β βββ pool-worker.ts
β βββ serpent-cbc.ts β SerpentCbc (broken out to avoid circular import)
β βββ shared-ops.ts
β βββ types.ts
βββ sha2/
β βββ embedded.ts
β βββ hash.ts
β βββ hkdf.ts β HKDF_SHA256, HKDF_SHA512 (pure TS over HMAC)
β βββ index.ts
β βββ types.ts
βββ sha3/
β βββ embedded.ts
β βββ hash.ts
β βββ index.ts
β βββ types.ts
βββ stream/
β βββ constants.ts β HEADER_SIZE, CHUNK_MIN/MAX, TAG_DATA/FINAL, FLAG_FRAMED
β βββ header.ts β wire format header encode/decode, counter nonce
β βββ index.ts
β βββ open-stream.ts β OpenStream (cipher-agnostic streaming decryption)
β βββ seal-stream-pool.ts β SealStreamPool (worker-based parallel batch)
β βββ seal-stream.ts β SealStream (cipher-agnostic streaming encryption)
β βββ seal.ts β Seal (static one-shot AEAD)
β βββ types.ts
βββ types.ts β shared interfaces: Hash, KeyedHash, Blockcipher, Streamcipher, AEAD, Generator, HashFn
βββ utils.ts β encoding, wipe, randomBytes, constantTimeEqual, CT_MAX_BYTES, hasSIMD
βββ wasm-source.ts β WasmSource union type
test/ holds three independent categories of files, used by separate workflows.
Unit tests (unit/) are Vitest suites that compile to a JS target for fast local iteration. The directory mirrors src/ts/ structure with one folder per module, plus a handful of top-level .test.ts files for cross-cutting concerns (init, errors, utils, fortuna). CI splits these by domain via unit-*.yml for parallel execution.
End-to-end tests (e2e/) are Playwright suites that exercise the actual WASM artifacts across V8, SpiderMonkey, and JavaScriptCore. They run after the full build, including pool-worker bundling.
Test vectors (vectors/) is the immutable known-answer-test corpus. Files are read-only reference data. Some come from authoritative specifications (FIPS, RFCs, ACVP, NIST CAVP); others are self generated as regression vectors by scripts/gen-*-vectors.ts. CI validates KAT file integrity against SHA256SUMS.
See test-suite.md for full testing methodology, vector corpus inventory with provenance, and gate discipline.
test/
βββ e2e/ β Playwright suites against built WASM in V8, SpiderMonkey, JSC
βββ unit/
β βββ chacha20/
β βββ ct/
β βββ errors.test.ts
β βββ fortuna/
β βββ fortuna.test.ts
β βββ helpers.ts
β βββ init/
β βββ init.test.ts
β βββ kyber/
β βββ loader/
β βββ ratchet/
β βββ serpent/
β βββ sha2/
β βββ sha3/
β βββ stream/
β βββ utils.test.ts
βββ vectors/ β KAT corpus; integrity verified against SHA256SUMS
The repository root holds project documentation, package metadata, and tool configuration. Build artifacts that only exist after bun run build are listed at the end.
Documentation. README.md is the entry point. SECURITY.md covers the vulnerability disclosure policy. AGENTS.md is the agent contract that governs how AI agents work in the repo. CHANGELOG tracks release history and LICENSE is MIT. The docs/ directory holds the full API reference, audits, benchmarks, and architecture notes (this file lives there).
Package metadata. package.json declares the npm manifest, subpath exports, and scripts. package-lock.json and bun.lock are the lockfiles for npm and bun respectively; both ship checked in so either tool can install reproducibly.
Tool configs. asconfig.json configures AssemblyScript compilation. eslint.config.ts is the active linter, run via bun fix. playwright.config.ts and vitest.config.ts configure the e2e and unit test runners. tsconfig.json is the base TypeScript config; tsconfig.test.json and tsconfig.e2e.json extend it for the test targets. tslint.json is a TSLint config (older format).
Build artifacts (gitignored; only exist after bun run build). build/ holds the raw .wasm outputs from AssemblyScript compilation. dist/ is the published npm package contents (compiled JS, declarations, copied WASM, embedded blobs, doc subset).
.
βββ build/ β gitignored: .wasm outputs from AS compilation
βββ dist/ β gitignored: published npm package contents
βββ docs/ β API reference, audits, benchmarks (this file lives here)
βββ README.md
βββ SECURITY.md
βββ AGENTS.md
βββ CHANGELOG
βββ LICENSE
βββ package.json
βββ package-lock.json
βββ bun.lock
βββ asconfig.json
βββ eslint.config.ts
βββ playwright.config.ts
βββ tsconfig.json
βββ tsconfig.e2e.json
βββ tsconfig.test.json
βββ tslint.json
βββ vitest.config.ts
The TypeScript layer never implements cryptographic algorithms. It manages the boundary between JavaScript and WebAssembly by writing inputs into WASM linear memory, calling exported functions, and reading back outputs. All algorithm logic resides within AssemblyScript.
Higher-level classes like Seal, SealStream, and SealStreamPool are pure TypeScript, but they compose WASM-backed primitives (Serpent-CBC, HMAC-SHA256, ChaCha20-Poly1305, and HKDF-SHA256) rather than implementing new cryptographic logic. TypeScript orchestrates, while WASM computes. Pool workers instantiate their own WASM modules and directly call primitives, bypassing the main-thread module cache.
Each primitive family compiles to its own .wasm binary with fully independent linear memory and buffer layouts. No shared state, no cross-module interference. Five of the six modules load through init(). The sixth, ct, sits outside the public Module union and the init() gate; it occupies a single 64 KB memory page and lazy-loads on the first call to constantTimeEqual. The ct module backs the public constantTimeEqual and CT_MAX_BYTES exports from the root barrel; neither requires an init() call.
| Module | Binary | Primitives |
|---|---|---|
serpent |
serpent.wasm |
Serpent-256 block cipher: ECB, CTR mode, CBC mode |
chacha20 |
chacha20.wasm |
ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD, XChaCha20-Poly1305 AEAD |
sha2 |
sha2.wasm |
SHA-256, SHA-384, SHA-512, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 |
sha3 |
sha3.wasm |
SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 |
kyber |
kyber.wasm |
ML-KEM polynomial arithmetic: SIMD NTT/invNTT (v128 butterflies with scalar tail), basemul, Montgomery/Barrett, CBD, compress, CT verify/cmov |
ct |
ct.wasm |
SIMD constant-time byte comparison. Backs constantTimeEqual and CT_MAX_BYTES, lazy-loaded outside init(). Single 64 KB page. |
Size. Consumers who only use Serpent don't load the SHA-3 binary.
Isolation. Key material in serpent.wasm memory cannot bleed into sha3.wasm memory even in theory.
Each module's buffer layout starts at offset 0 and is defined in its own buffers.ts. Buffer layouts are fully independent across modules.
serpent.wasm implements Serpent-256, a 128-bit block cipher. It handles key scheduling, block encryption and decryption, and both CTR and CBC streaming modes with SIMD variants for inter-block parallelism. See: Serpent-256 WASM Module Reference
The TypeScript module wraps this with SerpentCipher, a CipherSuite that combines Serpent-CBC with HMAC-SHA256 and HKDF key derivation for the STREAM construction. Primitive operations (HMAC, CBC, PKCS7 padding) live in serpent/shared-ops.ts and are reused by both the main thread and pool workers, guaranteeing byte-identical output and consistent Vaudenay 2002 padding normalization. Requires serpent and sha2 to be initialized.
chacha20.wasm implements the full ChaCha20-Poly1305 AEAD family per RFC 8439 and draft-irtf-cfrg-xchacha. It includes ChaCha20 stream cipher, Poly1305 one-time MAC, the AEAD construction, HChaCha20 for nonce extension, and SIMD 4-wide inter-block parallelism. See: ChaCha20/Poly1305 WASM Reference
The TypeScript module exports XChaCha20Cipher, a CipherSuite implementation for STREAM using XChaCha20-Poly1305 with HKDF key derivation. Pool workers load internally via SealStreamPool at runtime and don't appear in the package exports map.
sha2.wasm implements SHA-256 and SHA-512 per FIPS 180-4, plus SHA-384 (which reuses SHA-512's buffer and compress function with different IVs and truncation). It also provides HMAC per RFC 2104 for all three variants. HKDF-SHA256 and HKDF-SHA512 (RFC 5869) are pure TypeScript compositions over HMAC with no new WASM logic. See: SHA-2 WASM Reference
sha3.wasm implements the Keccak-f[1600] permutation per FIPS 202. All SHA3 variants (SHA3-224, SHA3-256, SHA3-384, SHA3-512) and XOF variants (SHAKE128, SHAKE256) share a single permutation, differing only in rate, domain separation byte, and output length. SHAKE supports unbounded multi-squeeze output. See: SHA-3 WASM Reference
kyber.wasm implements ML-KEM polynomial arithmetic per FIPS 203. It includes Montgomery and Barrett reduction, 7-layer NTT and inverse NTT with SIMD butterflies, basemul in Z_q[X]/(XΒ²-ΞΆ), centered binomial distribution sampling (Ξ·=2 and Ξ·=3), compression and decompression across all five bit-width paths, rejection sampling for matrix generation, and constant-time byte comparison and conditional move. Requires WebAssembly SIMD (v128 instructions). Uses 3 memory pages (192 KB) with 10 polynomial slots, 8 polynomial vector slots, and dedicated buffers for keys and ciphertexts. See: Kyber WASM Reference
The TypeScript module exports MlKem512, MlKem768, and MlKem1024βKEM classes implementing the Fujisaki-Okamoto transform. All three require both kyber and sha3 to be initialized; the sha3 module provides the Keccak sponge for matrix generation (SHAKE128), noise sampling (SHAKE256), and finalization (SHA3-256 for H, SHA3-512 for G).
ct.wasm implements constant-time byte array equality with a single SIMD-only primitive. The module exports compare(aOff, bOff, len), which reads both arrays directly from caller-specified offsets in linear memory and returns 1 if all bytes match, 0 otherwise. Comparison is zero-copy: no internal staging buffers, no buffer slots, no wipeBuffers export. The implementation is structurally branch-free. A v128.xor/v128.or accumulator processes 16-byte blocks, a scalar tail handles any remainder, and the final zero-test is an arithmetic shift, not a conditional. Requires WebAssembly SIMD (v128 instructions); if the runtime lacks SIMD or compilation fails, the first call throws a branded error. See: Constant-Time WASM Reference
The TypeScript module exports constantTimeEqual and CT_MAX_BYTES from the root barrel. The wrapper instantiates the WASM synchronously on first call and caches it for subsequent calls. It writes both arrays into linear memory, calls compare, and zeroes both regions in a finally block before returning. CT_MAX_BYTES is 32 KB per side; the 64 KB page holds two equal-length inputs.
WASM instantiation is async. init() is the initialization gate, call it once before using any cryptographic class. The cost is explicit and the developer controls when it is paid.
type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3' | 'keccak' | 'kyber'
type WasmSource =
| string // gzip+base64 embedded blob
| URL // fetch + compileStreaming
| ArrayBuffer // compile from raw bytes
| Uint8Array // compile from raw bytes
| WebAssembly.Module // pre-compiled (edge runtimes)
| Response // instantiateStreaming
| Promise<Response> // deferred fetch
async function init(
sources: Partial<Record<Module, WasmSource>>,
): Promise<void>The loading strategy is inferred from the source type, so there is no need for a mode string. Each module also exports its own init function, such as serpentInit(source), chacha20Init(source), sha2Init(source), sha3Init(source), keccakInit(source), and kyberInit(source), enabling tree-shakeable imports.
Note
keccak is an alias for sha3. Both names are accepted by init(), initModule(), getInstance(), and isInitialized(). They share the same WASM binary and the same instance slot. The alias exists so Kyber/ML-KEM consumers can write init({ keccak: keccakWasm }) using the semantically correct name for the underlying sponge primitive.
Each module provides a /embedded subpath that exports the gzip+base64 blob as a ready-to-use WasmSource:
import { init } from 'leviathan-crypto'
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
await init({ serpent: serpentWasm, sha2: sha2Wasm })Idempotent initialization. Calling init() on an already initialized module is a no-op. It is safe to call init() from multiple locations within the codebase.
Module-scope cache. Each WebAssembly.Instance is cached at module scope after initial instantiation. All subsequent class constructions use the cached instance with no recompilation.
Error before initialization. Invoking any cryptographic class before calling init() throws a clear error prompting the developer to call init({ <module>: ... }) first.
No implicit initialization. Classes never call init() automatically on first use. Explicit initialization is preferable to hidden costs.
Thread safety. The main thread uses a single WASM instance per module. SealStreamPool provides worker-based parallelism. Each pool worker is spawned from an IIFE bundled at build time and instantiates its own WASM modules with isolated linear memory, bypassing the main-thread cache entirely. For other primitives, create one instance per Worker if Workers are used.
| Module | Classes |
|---|---|
serpent + sha2
|
SerpentCipher |
serpent |
Serpent, SerpentCtr, SerpentCbc
|
chacha20 |
ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher
|
sha2 |
SHA256, SHA384, SHA512, HMAC_SHA256, HMAC_SHA384, HMAC_SHA512, HKDF_SHA256, HKDF_SHA512
|
sha3 |
SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256
|
kyber + sha3
|
MlKem512, MlKem768, MlKem1024
|
kyber + sha3 + inner cipher |
KyberSuite (hybrid KEM+AEAD factory) |
sha2 |
ratchetInit, KDFChain, SkippedKeyStore
|
kyber + sha3 + sha2
|
kemRatchetEncap, kemRatchetDecap, RatchetKeypair
|
stream |
Seal, SealStream, OpenStream, SealStreamPool
|
serpent + sha2
|
Fortuna with SerpentGenerator + SHA256Hash
|
serpent + sha3
|
Fortuna with SerpentGenerator + SHA3_256Hash
|
chacha20 + sha2
|
Fortuna with ChaCha20Generator + SHA256Hash
|
chacha20 + sha3
|
Fortuna with ChaCha20Generator + SHA3_256Hash
|
Note
Class Names match conventional cryptographic notation.
- HMAC names use underscore separator (
HMAC_SHA256) matching RFC convention. - SHA-3 names use underscore separator (
SHA3_256) for readability. - Ratchet exports are KDF primitives from Signal's Sparse Post-Quantum Ratchet spec; session state, message ordering, and header format remain application concerns.
-
Fortunarequiresawait Fortuna.create({ generator, hash })rather thannew Fortuna(). Required modules depend on the generator and hash you pass. See fortuna.md for valid combinations. -
SealStream,OpenStream, andSealStreamPoolare cipher-agnostic; you select the cipher by passingXChaCha20CipherorSerpentCipherat construction.
All WASM-backed classes follow the same pattern:
import { init, Seal, SerpentCipher, SHA3_256 } from 'leviathan-crypto'
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
await init({ serpent: serpentWasm, sha2: sha2Wasm, sha3: sha3Wasm })
const key = SerpentCipher.keygen()
const blob = Seal.encrypt(SerpentCipher, key, plaintext)
const hasher = new SHA3_256()
const digest = hasher.hash(message)Pure TypeScript utilities ship alongside the WASM-backed primitives:
| Category | Exports |
|---|---|
| Encoding |
hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64
|
| Security |
constantTimeEqual, CT_MAX_BYTES, wipe, xor
|
| Helpers |
concat, randomBytes, hasSIMD
|
| Types |
Hash, KeyedHash, Blockcipher, Streamcipher, AEAD
|
-
npm run build:asm: AssemblyScript compiler readssrc/asm/*/index.ts, emitsbuild/*.wasm -
npm run build:embed:scripts/embed-wasm.tsreads each.wasm, gzip compresses, base64 encodes, writes tosrc/ts/embedded/*.tsand per-modulesrc/ts/*/embedded.ts -
npm run build:embed-workers:scripts/embed-workers.tsbundles each pool worker into a self-contained IIFE via esbuild and writes the source tosrc/ts/embedded/<cipher>-pool-worker.tsas a string export -
npm run build:ts: TypeScript compiler emitsdist/ -
cp build/*.wasm dist/: WASM binaries copied for URL-based consumers - At runtime (subpath):
serpentInit(serpentWasm)βinitModule()βloadWasm(source)β decode gzip+base64 βWebAssembly.instantiateβ cache ininit.ts - At runtime (root):
init({ serpent: serpentWasm, sha2: sha2Wasm })β dispatches to each module's init function viaPromise.allβ same path as step 6 per module
src/ts/embedded/ is gitignored; these files are build artifacts. The WASM blobs (<module>.ts) derive from the AssemblyScript source in src/asm/. The pool-worker bundles (<cipher>-pool-worker.ts) derive from the worker source in src/ts/<cipher>/pool-worker.ts, bundled as a self-contained IIFE by scripts/embed-workers.ts.
Each WASM module is fully independent. No cross-module imports exist.
Serpent (src/asm/serpent/)
buffers.ts
<- serpent.ts (offsets for key, block, subkey, work, CBC IV)
<- serpent_unrolled.ts (block offsets, subkey, work)
<- serpent_simd.ts (SIMD bitsliced block operations)
<- cbc.ts (IV, block, chunk offsets)
<- cbc_simd.ts (SIMD CBC decrypt)
<- ctr.ts (nonce, counter, block, chunk offsets)
<- ctr_simd.ts (SIMD CTR 4-wide inter-block)
serpent.ts
<- serpent_unrolled.ts (S-boxes sb0-sb7, si0-si7, lk, kl, keyXor)
serpent_unrolled.ts
<- cbc.ts (encryptBlock_unrolled, decryptBlock_unrolled)
<- ctr.ts (encryptBlock_unrolled)
serpent_simd.ts
<- cbc_simd.ts (SIMD block operations)
<- ctr_simd.ts (SIMD block operations)
index.ts
re-exports: buffers + serpent + serpent_unrolled + serpent_simd + cbc + cbc_simd + ctr + ctr_simd
ChaCha (src/asm/chacha20/)
buffers.ts
<- chacha20.ts (key, nonce, counter, block, state, poly key, xchacha offsets)
<- chacha20_simd_4x.ts (SIMD work buffer, chunk offsets)
<- poly1305.ts (poly key, msg, buf, tag, h, r, rs, s offsets)
<- wipe.ts (all buffer offsets, zeroes everything)
index.ts
re-exports: buffers + chacha20 + chacha20_simd_4x + poly1305 + wipe
SHA-2 (src/asm/sha2/)
buffers.ts
<- sha256.ts (H, block, W, out, input, partial, total offsets)
<- sha512.ts (H, block, W, out, input, partial, total offsets)
<- hmac.ts (SHA-256 input, out, ipad, opad, inner offsets)
<- hmac512.ts (SHA-512 input, out, ipad, opad, inner offsets)
sha256.ts
<- hmac.ts (sha256Init, sha256Update, sha256Final)
sha512.ts
<- hmac512.ts (sha512Init, sha384Init, sha512Update, sha512Final, sha384Final)
index.ts
re-exports: buffers + sha256 + sha512 + hmac + hmac512
defines: wipeBuffers() inline
SHA-3 (src/asm/sha3/)
buffers.ts
<- keccak.ts (state, rate, absorbed, dsbyte, input, out offsets)
index.ts
re-exports: buffers + keccak
Kyber (src/asm/kyber/)
params.ts
<- reduce.ts (Q, QINV, BARRETT_V, BARRETT_SHIFT)
<- poly.ts (Q, POLY_BYTES, HALF_Q, compression constants)
<- polyvec.ts (Q, POLY_BYTES, compression constants)
<- sampling.ts (Q)
buffers.ts
<- polyvec.ts (POLY_ACC_OFFSET)
reduce.ts
<- ntt.ts (fqmul, barrett_reduce)
<- ntt_simd.ts (fqmul, barrett_reduce β scalar tail)
<- poly.ts (montgomery_reduce, barrett_reduce, fqmul)
ntt.ts
<- ntt_simd.ts (getZetasOffset β zetas table pointer)
<- poly.ts (ntt, invntt, basemul, getZeta)
ntt_simd.ts
<- poly_simd.ts (ntt_simd, invntt_simd, barrett_reduce_8x)
poly.ts
<- polyvec.ts (poly_tobytes, poly_frombytes, poly_basemul_montgomery)
poly_simd.ts
<- polyvec.ts (poly_add_simd, poly_reduce_simd, poly_ntt_simd, poly_invntt_simd)
cbd.ts
<- poly.ts (cbd2, cbd3)
index.ts
re-exports: buffers + ntt (scalar aliases) + ntt_simd (as ntt/invntt) +
reduce + poly (scalar serialization/compression/basemul) +
poly_simd (as poly_add/sub/reduce/ntt/invntt) +
polyvec + sampling + verify
Each module's init function (serpentInit, chacha20Init, sha2Init, sha3Init, kyberInit) calls initModule() from init.ts, passing a WasmSource. initModule() delegates to loadWasm(source) in loader.ts. The loader infers the loading strategy from the source type, with no mode string and no knowledge of module names or embedded file paths.
Pool workers (serpent/pool-worker.ts, chacha20/pool-worker.ts) instantiate their own WASM modules from pre-compiled WebAssembly.Module objects passed via postMessage. They do not use initModule() or the main-thread cache. Workers are spawned from blob URLs constructed in cipher-suite.ts over an IIFE source string built at lib build time (src/ts/embedded/<cipher>-pool-worker.ts). The pool-worker.ts file itself is the source the bundler reads, not the runtime spawn entry.
Each TS wrapper class maps to one WASM module and specific exported functions. Tier 2 composition classes are pure TypeScript; they call Tier 1 classes rather than WASM functions directly.
serpent/index.ts β asm/serpent/ (Tier 1: direct WASM callers)
| TS Class | WASM functions called |
|---|---|
Serpent |
loadKey, encryptBlock, decryptBlock, wipeBuffers + buffer getters |
SerpentCtr |
loadKey, resetCounter, setCounter, encryptChunk, encryptChunk_simd, wipeBuffers + buffer getters |
SerpentCbc |
loadKey, cbcEncryptChunk, cbcDecryptChunk, cbcDecryptChunk_simd, wipeBuffers + buffer getters |
SerpentGenerator |
loadKey, encryptBlock, wipeBuffers + buffer getters |
chacha20/index.ts β asm/chacha20/ (Tier 1: direct WASM callers)
| TS Class | WASM functions called |
|---|---|
ChaCha20 |
chachaLoadKey, chachaSetCounter, chachaEncryptChunk, chachaEncryptChunk_simd, wipeBuffers + buffer getters |
Poly1305 |
polyInit, polyUpdate, polyFinal, wipeBuffers + buffer getters |
ChaCha20Poly1305 |
chachaLoadKey, chachaSetCounter, chachaGenPolyKey, chachaEncryptChunk, polyInit, polyUpdate, polyFinal, wipeBuffers + buffer getters (via ops.ts) |
XChaCha20Poly1305 |
All of ChaCha20Poly1305 + hchacha20 + xchacha buffer getters (via ops.ts) |
ChaCha20Generator |
chachaLoadKey, chachaSetCounter, chachaEncryptChunk_simd, wipeBuffers + buffer getters |
sha2/index.ts β asm/sha2/ (Tier 1: direct WASM callers)
| TS Class | WASM functions called |
|---|---|
SHA256 |
sha256Init, sha256Update, sha256Final, wipeBuffers + buffer getters |
SHA512 |
sha512Init, sha512Update, sha512Final, wipeBuffers + buffer getters |
SHA384 |
sha384Init, sha512Update, sha384Final, wipeBuffers + buffer getters |
HMAC_SHA256 |
hmac256Init, hmac256Update, hmac256Final, sha256Init, sha256Update, sha256Final, wipeBuffers + buffer getters |
HMAC_SHA512 |
hmac512Init, hmac512Update, hmac512Final, sha512Init, sha512Update, sha512Final, wipeBuffers + buffer getters |
HMAC_SHA384 |
hmac384Init, hmac384Update, hmac384Final, sha384Init, sha512Update, sha384Final, wipeBuffers + buffer getters |
SHA256Hash |
sha256Init, sha256Update, sha256Final, wipeBuffers + buffer getters |
sha3/index.ts β asm/sha3/ (Tier 1: direct WASM callers)
| TS Class | WASM functions called |
|---|---|
SHA3_224 |
sha3_224Init, keccakAbsorb, sha3_224Final, wipeBuffers + buffer getters |
SHA3_256 |
sha3_256Init, keccakAbsorb, sha3_256Final, wipeBuffers + buffer getters |
SHA3_384 |
sha3_384Init, keccakAbsorb, sha3_384Final, wipeBuffers + buffer getters |
SHA3_512 |
sha3_512Init, keccakAbsorb, sha3_512Final, wipeBuffers + buffer getters |
SHAKE128 |
shake128Init, keccakAbsorb, shakePad, shakeSqueezeBlock, wipeBuffers + buffer getters |
SHAKE256 |
shake256Init, keccakAbsorb, shakePad, shakeSqueezeBlock, wipeBuffers + buffer getters |
SHA3_256Hash |
sha3_256Init, keccakAbsorb, sha3_256Final, wipeBuffers + buffer getters |
kyber/index.ts + kyber/kem.ts + kyber/indcpa.ts β asm/kyber/ (Tier 1)
| TS Class | WASM functions called |
|---|---|
MlKem512, MlKem768, MlKem1024
|
polyvec_ntt, polyvec_invntt, polyvec_basemul_acc_montgomery, polyvec_add, polyvec_reduce, polyvec_tobytes, polyvec_frombytes, polyvec_compress, polyvec_decompress, poly_ntt, poly_invntt, poly_tomont, poly_add, poly_sub, poly_reduce, poly_basemul_montgomery, poly_frommsg, poly_tomsg, poly_compress, poly_decompress, poly_getnoise, rej_uniform, ct_verify, ct_cmov, wipeBuffers + buffer getters |
All MlKem classes also call sha3 WASM via indcpa.ts: sha3_256Init, sha3_512Init, shake128Init, shake256Init, keccakAbsorb, sha3_256Final, sha3_512Final, shakeFinal, shakePad, shakeSqueezeBlock.
Tier 2: pure TS composition
| TS Class / Object | Composes |
|---|---|
SerpentCipher |
SerpentCbc + HMAC_SHA256 + HKDF_SHA256
|
XChaCha20Cipher |
ChaCha20Poly1305 (via ops.ts) + HKDF_SHA256
|
Seal |
SealStream + OpenStream (degenerate single-chunk case) |
SealStream |
CipherSuite (generic β caller provides cipher) |
OpenStream |
CipherSuite (generic β caller provides cipher) |
SealStreamPool |
CipherSuite + compileWasm() + Web Workers |
HKDF_SHA256 |
HMAC_SHA256 (extract + expand per RFC 5869) |
HKDF_SHA512 |
HMAC_SHA512 (extract + expand per RFC 5869) |
Fortuna |
Generator + HashFn (any compatible pair: SerpentGenerator/ChaCha20Generator Γ SHA256Hash/SHA3_256Hash) |
| Relationship | Notes |
|---|---|
SerpentCipher β serpent + sha2
|
Tier 2 composition: Serpent-CBC + HMAC-SHA256 + HKDF-SHA256. |
XChaCha20Cipher β chacha20 + sha2
|
HKDF-SHA256 for key derivation + HChaCha20 + ChaCha20-Poly1305 for per-chunk AEAD. |
KyberSuite β kyber + sha3 + inner cipher |
KEM encaps/decaps + HKDF with kemCt binding + inner CipherSuite. |
SealStream, OpenStream β depends on cipher |
Cipher-agnostic. Module requirements are determined by the CipherSuite passed at construction. |
SealStreamPool β depends on cipher |
Same module requirements as the cipher, plus WasmSource in pool opts for worker compilation. |
Fortuna β cipher module + hash module |
Uses Fortuna.create({ generator, hash }) static factory instead of new. Required modules depend on which generator and hash you pass. See fortuna.md. |
MlKem512, MlKem768, MlKem1024 β kyber + sha3
|
Kyber module handles polynomial arithmetic; sha3 provides SHAKE128/256, SHA3-256/512 for G/H/J/matrix gen. |
HKDF_SHA256, HKDF_SHA512 β sha2
|
Pure TS composition β extract and expand steps per RFC 5869. |
| All other classes | Each depends on exactly one WASM module. |
The root barrel defines and exports the dispatching init() function. It is the only file that imports all four module-scoped init functions.
| Source | Exports |
|---|---|
| (barrel itself) |
init (dispatching function β calls per-module init functions via Promise.all) |
init.ts |
Module, WasmSource, isInitialized
|
errors.ts |
AuthenticationError |
serpent/index.ts |
Serpent, SerpentCtr, SerpentCbc, SerpentCipher, _serpentReady
|
chacha20/index.ts |
ChaCha20, Poly1305, ChaCha20Poly1305, XChaCha20Poly1305, XChaCha20Cipher, _chachaReady
|
sha2/index.ts |
SHA256, SHA512, SHA384, HMAC_SHA256, HMAC_SHA512, HMAC_SHA384, HKDF_SHA256, HKDF_SHA512, _sha2Ready
|
sha3/index.ts |
SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, _sha3Ready
|
keccak/index.ts |
keccakInit + re-exports all sha3 classes (alias subpath) |
kyber/index.ts |
kyberInit, KyberSuite, MlKem512, MlKem768, MlKem1024, KyberKeyPair, KyberEncapsulation, KyberParams, MLKEM512, MLKEM768, MLKEM1024
|
stream/index.ts |
Seal, SealStream, OpenStream, SealStreamPool, CipherSuite, DerivedKeys, SealStreamOpts, PoolOpts, FLAG_FRAMED, TAG_DATA, TAG_FINAL, HEADER_SIZE, CHUNK_MIN, CHUNK_MAX
|
fortuna.ts |
Fortuna |
types.ts |
Hash, KeyedHash, Blockcipher, Streamcipher, AEAD, Generator, HashFn
|
utils.ts |
hexToBytes, bytesToHex, utf8ToBytes, bytesToUtf8, base64ToBytes, bytesToBase64, constantTimeEqual, CT_MAX_BYTES, wipe, xor, concat, randomBytes, hasSIMD
|
Each subpath export also exports its own module-specific init function for tree-shakeable loading: serpentInit(source), chacha20Init(source), sha2Init(source), sha3Init(source), keccakInit(source).
Subpath exports:
{
"exports": {
".": "./dist/index.js",
"./stream": "./dist/stream/index.js",
"./serpent": "./dist/serpent/index.js",
"./serpent/embedded": "./dist/serpent/embedded.js",
"./chacha20": "./dist/chacha20/index.js",
"./chacha20/embedded": "./dist/chacha20/embedded.js",
"./sha2": "./dist/sha2/index.js",
"./sha2/embedded": "./dist/sha2/embedded.js",
"./sha3": "./dist/sha3/index.js",
"./sha3/embedded": "./dist/sha3/embedded.js",
"./keccak": "./dist/keccak/index.js",
"./keccak/embedded": "./dist/keccak/embedded.js",
"./kyber": "./dist/kyber/index.js",
"./kyber/embedded": "./dist/kyber/embedded.js",
"./ratchet": "./dist/ratchet/index.js"
}
}Note
Pool worker source files (dist/serpent/pool-worker.js, dist/chacha20/pool-worker.js) ship in the package but are not in the exports map. They are the build inputs from which scripts/embed-workers.ts produces the IIFE source strings embedded in dist/<cipher>/cipher-suite.js at lib build time. Workers are spawned from those embedded strings via classic blob URLs. Consumers do not import the pool-worker.js files directly, and bundlers do not need to chunk them. Strict-CSP consumers (worker-src 'self', no blob:) can supply their own URL-based factory by spread-overriding createPoolWorker on the cipher object; see ciphersuite.md.
The root . export re-exports everything. Subpath exports allow bundlers to tree-shake at the module level; a consumer importing only ./sha3 does not include the Serpent wrapper classes or their embedded WASM binaries in their bundle.
The /embedded subpaths provide gzip+base64 WASM blobs for zero-config usage. Consumers using URL-based or pre-compiled loading can skip the /embedded imports entirely, keeping them out of the bundle.
Tree-shaking: "sideEffects": false is set in package.json. Bundlers that support tree-shaking (webpack, Rollup, esbuild) can eliminate unused modules and their embedded WASM binaries from the final bundle.
Published. The npm package includes:
-
dist/: compiled JS, TypeScript declarations, WASM binaries, pool worker source files (build inputs, not runtime spawn entries; see the NOTE above), and a subset of consumer-facing API docs for offline use. -
CLAUDE.md: agent-facing project context. -
SECURITY.md: vulnerability disclosure policy.
Not published. src/, test/, build/, scripts/, .github/, editor configs.
All offsets start at 0 per module. Independent linear memory. No offsets are shared or coordinated across modules.
Source: src/asm/serpent/buffers.ts
| Offset | Size | Name |
|---|---|---|
| 0 | 32 |
KEY_BUFFER β key input (padded to 32 bytes for all key sizes) |
| 32 | 16 |
BLOCK_PT_BUFFER β single block plaintext |
| 48 | 16 |
BLOCK_CT_BUFFER β single block ciphertext |
| 64 | 16 |
NONCE_BUFFER β CTR mode nonce |
| 80 | 16 |
COUNTER_BUFFER β 128-bit little-endian counter |
| 96 | 528 |
SUBKEY_BUFFER β key schedule output (33 rounds Γ 4 Γ 4 bytes) |
| 624 | 65552 |
CHUNK_PT_BUFFER β streaming plaintext (CTR/CBC); +16 from 65536 to fit PKCS7 max overhead |
| 66176 | 65552 |
CHUNK_CT_BUFFER β streaming ciphertext (CTR/CBC) |
| 131728 | 20 |
WORK_BUFFER β 5 Γ i32 scratch registers (key schedule + S-box/LT rounds) |
| 131748 | 16 |
CBC_IV_BUFFER β CBC initialization vector / chaining value |
| 131856 | β | END |
wipeBuffers() zeroes all 10 buffers (key, block pt/ct, nonce, counter, subkeys, work, chunk pt/ct, CBC IV).
Source: src/asm/chacha20/buffers.ts
| Offset | Size | Name |
|---|---|---|
| 0 | 32 |
KEY_BUFFER β ChaCha20 256-bit key |
| 32 | 12 |
CHACHA_NONCE_BUFFER β 96-bit nonce (3 Γ u32, LE) |
| 44 | 4 |
CHACHA_CTR_BUFFER β u32 block counter |
| 48 | 64 |
CHACHA_BLOCK_BUFFER β 64-byte keystream block output |
| 112 | 64 |
CHACHA_STATE_BUFFER β 16 Γ u32 initial state |
| 176 | 65536 |
CHUNK_PT_BUFFER β streaming plaintext |
| 65712 | 65536 |
CHUNK_CT_BUFFER β streaming ciphertext |
| 131248 | 32 |
POLY_KEY_BUFFER β one-time key rβs |
| 131280 | 64 |
POLY_MSG_BUFFER β message staging (β€ 64 bytes per polyUpdate) |
| 131344 | 16 |
POLY_BUF_BUFFER β partial block accumulator |
| 131360 | 4 |
POLY_BUF_LEN_BUFFER β u32 bytes in partial block |
| 131364 | 16 |
POLY_TAG_BUFFER β 16-byte output MAC tag |
| 131380 | 40 |
POLY_H_BUFFER β accumulator h: 5 Γ u64 |
| 131420 | 40 |
POLY_R_BUFFER β clamped r: 5 Γ u64 |
| 131460 | 32 |
POLY_RS_BUFFER β precomputed 5Γr[1..4]: 4 Γ u64 |
| 131492 | 16 |
POLY_S_BUFFER β s pad: 4 Γ u32 |
| 131508 | 24 |
XCHACHA_NONCE_BUFFER β full 24-byte XChaCha20 nonce |
| 131532 | 32 |
XCHACHA_SUBKEY_BUFFER β HChaCha20 output (key material) |
| 131564 | 4 | (padding for 16-byte SIMD alignment) |
| 131568 | 256 |
CHACHA_SIMD_WORK_BUFFER β 4-wide inter-block keystream (4 Γ 64 bytes) |
| 131824 | β | END |
wipeBuffers() zeroes all 15 buffer regions (key, chacha nonce/ctr/block/state, chunk pt/ct, poly key/msg/buf/tag/h/r/rs/s, xchacha nonce/subkey, SIMD work).
Source: src/asm/sha2/buffers.ts
| Offset | Size | Name |
|---|---|---|
| 0 | 32 |
SHA256_H β SHA-256 hash state H0..H7 (8 Γ u32) |
| 32 | 64 |
SHA256_BLOCK β SHA-256 block accumulator |
| 96 | 256 |
SHA256_W β SHA-256 message schedule W[0..63] (64 Γ u32) |
| 352 | 32 |
SHA256_OUT β SHA-256 digest output |
| 384 | 64 |
SHA256_INPUT β SHA-256 user input staging (one block) |
| 448 | 4 |
SHA256_PARTIAL β u32 partial block length |
| 452 | 8 |
SHA256_TOTAL β u64 total bytes hashed |
| 460 | 64 |
HMAC256_IPAD β HMAC-SHA256 K' XOR ipad |
| 524 | 64 |
HMAC256_OPAD β HMAC-SHA256 K' XOR opad |
| 588 | 32 |
HMAC256_INNER β HMAC-SHA256 inner hash |
| 620 | 64 |
SHA512_H β SHA-512 hash state H0..H7 (8 Γ u64) |
| 684 | 128 |
SHA512_BLOCK β SHA-512 block accumulator |
| 812 | 640 |
SHA512_W β SHA-512 message schedule W[0..79] (80 Γ u64) |
| 1452 | 64 |
SHA512_OUT β SHA-512 digest output (SHA-384 uses first 48 bytes) |
| 1516 | 128 |
SHA512_INPUT β SHA-512 user input staging (one block) |
| 1644 | 4 |
SHA512_PARTIAL β u32 partial block length |
| 1648 | 8 |
SHA512_TOTAL β u64 total bytes hashed |
| 1656 | 128 |
HMAC512_IPAD β HMAC-SHA512 K' XOR ipad (128-byte block size) |
| 1784 | 128 |
HMAC512_OPAD β HMAC-SHA512 K' XOR opad |
| 1912 | 64 |
HMAC512_INNER β HMAC-SHA512 inner hash |
| 1976 | β | END |
wipeBuffers() zeroes all 20 buffer regions (SHA-256 state/block/W/out/input/partial/total, HMAC-256 ipad/opad/inner, SHA-512 state/block/W/out/input/partial/total, HMAC-512 ipad/opad/inner).
Source: src/asm/sha3/buffers.ts
| Offset | Size | Name |
|---|---|---|
| 0 | 200 |
KECCAK_STATE: 25 Γ u64 Keccak-f[1600] lane matrix (5Γ5, row-major x+5y) |
| 200 | 4 |
KECCAK_RATE: u32 rate in bytes (variant-specific: 72β168) |
| 204 | 4 |
KECCAK_ABSORBED: u32 bytes absorbed into current block |
| 208 | 1 |
KECCAK_DSBYTE: u8 domain separation byte (0x06 for SHA-3, 0x1f for SHAKE) |
| 209 | 168 |
KECCAK_INPUT: input staging buffer (max rate = SHAKE128 at 168 bytes) |
| 377 | 168 |
KECCAK_OUT: output buffer (one SHAKE128 squeeze block) |
| 545 | β | END |
wipeBuffers() zeroes all 6 buffer regions (state, rate, absorbed, dsbyte, input, output).
Source: src/asm/kyber/
| Region | Offset | Size | Purpose |
|---|---|---|---|
| AS data segment | 0 | 4096 | Zetas table (128 Γ i16, bit-reversed Montgomery domain) |
| Poly slots | 4096 | 5120 | 10 Γ 512B scratch polynomials (256 Γ i16 each) |
| Polyvec slots | 9216 | 16384 | 8 Γ 2048B scratch polyvecs (k=4 max: 4 Γ 512B) |
| SEED buffer | 25600 | 32 | Seed Ο/Ο |
| MSG buffer | 25632 | 32 | Message / shared secret |
| PK buffer | 25664 | 1568 | Encapsulation key (max k=4) |
| SK buffer | 27232 | 1536 | IND-CPA secret key (max k=4) |
| CT buffer | 28768 | 1568 | Ciphertext (max k=4) |
| CT_PRIME buffer | 30336 | 1568 | Decaps re-encrypt comparison (max k=4) |
| XOF/PRF buffer | 31904 | 1024 | SHAKE squeeze output for rej_uniform / CBD |
| Poly accumulator | 32928 | 512 | Internal scratch for polyvec_basemul_acc |
Total mutable: 29344 bytes (4096β33440). End = 33440 < 192 KB.
wipeBuffers() zeroes all mutable regions (poly slots, polyvec slots, SEED, MSG, PK, SK, CT, CT_PRIME, XOF/PRF, accumulator). The zetas data segment is read-only and is not wiped.
For the full testing methodology and vector corpus, see: test-suite.md
Each primitive family has a gate test: the simplest authoritative vector for that primitive. The gate must pass before any other tests in that family are written or run. Gate tests are annotated with a // GATE comment.
-
init()with eachWasmSourcetype loads and caches the module correctly - Idempotency: second
init()call for same module is a no-op - Error before init: clear error thrown for each class before its module is loaded
- Partial init: loading
{ serpent: ... }does not makesha3classes available
leviathan-crypto must produce byte-identical output to the authoritative specification for every known test vector. Cross-checks against the leviathan TypeScript reference and external tools (OpenSSL, Python hashlib, Node.js crypto) provide additional verification layers.
The vector corpus in test/vectors/ act as a source of immutable known-answer-test truth. KAT files are reference data from authoritative specifications (FIPS, RFCs, ACVP, NIST CAVP, NESSIE) or are self generated as regression vectors by scripts/gen-*-vectors.ts. CI validates integrity against SHA256SUMS. See test-suite.md for the full corpus inventory, provenance, and gate discipline.
-
SerpentCbcis unauthenticated: useSealwithSerpentCipherfor authenticated Serpent encryption, or pairSerpentCbcwithHMAC_SHA256(Encrypt-then-MAC) if direct CBC access is required. -
Single-threaded WASM per instance: one WASM instance per binary per thread.
SealStreamPoolprovides Worker-based parallelism for both cipher families; other primitives remain single-threaded. - Max input per WASM call: CTR accepts at most 65536 bytes per call; CBC accepts at most 65552 bytes (65536 + 16 bytes PKCS7 maximum overhead). Wrappers handle splitting automatically for larger inputs.
- WASM side-channel posture: WebAssembly implementations offer the best available side-channel resistance (branchless, table-free), but lack hardware-level constant-time guarantees. For applications where timing side channels are a primary threat, a native cryptographic library with verified constant-time guarantees will be more appropriate than any WASM-based implementation.
| Document | Description |
|---|---|
| index | Project Documentation index |
| lexicon | Glossary of cryptographic terms |
| test-suite | testing methodology, vector corpus, and gate discipline |
| init |
init() API, WasmSource, and idempotent behavior |
| loader | internal WASM binary loading strategies |
| wasm | WebAssembly primer: modules, instances, memory, and the init gate |
| types | public TypeScript interfaces and CipherSuite
|
| utils | encoding helpers, constantTimeEqual, wipe, randomBytes
|
| authenticated encryption |
Seal, SealStream, OpenStream: cipher-agnostic AEAD APIs using a CipherSuite such as SerpentCipher or XChaCha20Cipher
|
| serpent | Serpent-256 TypeScript API, SerpentCipher |
| chacha20 | ChaCha20/Poly1305 TypeScript API, XChaCha20Cipher |
| sha2 | SHA-2 hashes, HMAC, and HKDF TypeScript API |
| sha3 | SHA-3 hashes and SHAKE XOFs TypeScript API |
| fortuna | Fortuna CSPRNG with forward secrecy and entropy pooling |
| argon2id | Argon2id password hashing and key derivation |