WebAssembly cryptography library built on the paranoia of Serpent-256 and the elegance of XChaCha20-Poly1305.
Serpent-256 won the AES security vote but lost the competition. The deciding factor was performance on embedded hardware, a constraint that doesn't apply to modern software. 32 rounds. S-boxes in pure Boolean logic with no table lookups. It processes every bit in every block. You use it because you trust the cryptanalysis, not because a committee endorsed it.
XChaCha20-Poly1305 has nothing to hide. An ARX construction with 20 rounds. Add, rotate, XOR with no S-boxes or cache-timing leakage. It needs no hardware acceleration to be fast. Poly1305 adds an unconditional forgery bound. The security proof is readable.
Two ciphers from opposite design philosophies, only agreeing on security properties.
WebAssembly is the correctness layer. Every primitive runs in its own isolated binary with its own linear memory. Execution is deterministic with no JIT speculation. Key material in one module can't interact with another, even in principle. See the security policy.
TypeScript is the ergonomics layer. The Seal and SealStream family are cipher-agnostic. Drop in SerpentCipher or XChaCha20Cipher and they handle nonces, key derivation, and authentication for you. Explicit init() gates give you full control over how and when WASM loads. Strict typing catches misuse before it reaches production.
Zero dependencies. No npm graph to audit. No supply chain attack surface.
Tree-shakeable. Import only what you use. Subpath exports let bundlers exclude everything else.
Side-effect free. Nothing runs on import. init() is explicit and asynchronous.
Audited primitives. Every implementation is verified against its specification. See the audit index.
# use bun
bun i leviathan-crypto
# or npm
npm install leviathan-cryptoNote
Serpent, ChaCha20, ML-KEM, and constantTimeEqual require WebAssembly SIMD support. This has been a baseline feature of all major browsers and runtimes since 2021. SHA-2 and SHA-3 run on any WASM-capable runtime.
Three loading strategies are available. Choose based on your runtime and bundler setup.
import { init } from 'leviathan-crypto'
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
// Embedded: gzip+base64 blobs bundled in the package
await init({ serpent: serpentWasm, sha2: sha2Wasm })
// URL: streaming compilation from a served .wasm file
await init({ serpent: new URL('/assets/wasm/serpent.wasm', import.meta.url) })
// Pre-compiled: pass a WebAssembly.Module directly (edge runtimes, KV cache)
await init({ serpent: compiledModule })Each module ships as its own subpath export. Bundlers with tree-shaking support and "sideEffects": false drop every module you don't import.
// Only serpent.wasm + sha2.wasm end up in your bundle
import { serpentInit } from 'leviathan-crypto/serpent'
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
import { sha2Init } from 'leviathan-crypto/sha2'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
await serpentInit(serpentWasm)
await sha2Init(sha2Wasm)
// ML-KEM requires kyber + sha3
import { kyberInit } from 'leviathan-crypto/kyber'
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
import { sha3Init } from 'leviathan-crypto/sha3'
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
await kyberInit(kyberWasm)
await sha3Init(sha3Wasm)| Subpath | Entry point |
|---|---|
leviathan-crypto |
./dist/index.js |
leviathan-crypto/stream |
./dist/stream/index.js |
leviathan-crypto/serpent |
./dist/serpent/index.js |
leviathan-crypto/serpent/embedded |
./dist/serpent/embedded.js |
leviathan-crypto/chacha20 |
./dist/chacha20/index.js |
leviathan-crypto/chacha20/embedded |
./dist/chacha20/embedded.js |
leviathan-crypto/sha2 |
./dist/sha2/index.js |
leviathan-crypto/sha2/embedded |
./dist/sha2/embedded.js |
leviathan-crypto/sha3 |
./dist/sha3/index.js |
leviathan-crypto/sha3/embedded |
./dist/sha3/embedded.js |
leviathan-crypto/kyber |
./dist/kyber/index.js |
leviathan-crypto/kyber/embedded |
./dist/kyber/embedded.js |
See loader.md for the full WASM loading reference.
One-shot authenticated encryption. Seal handles nonces, key derivation, and authentication. Zero config beyond init().
import { init, Seal, XChaCha20Cipher } from 'leviathan-crypto'
import { chacha20Wasm } from 'leviathan-crypto/chacha20/embedded'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
await init({ chacha20: chacha20Wasm, sha2: sha2Wasm })
const key = XChaCha20Cipher.keygen()
const blob = Seal.encrypt(XChaCha20Cipher, key, plaintext)
const pt = Seal.decrypt(XChaCha20Cipher, key, blob) // throws AuthenticationError on tamperPrefer Serpent-256? Swap the cipher object and everything else stays the same.
import { init, Seal, SerpentCipher } from 'leviathan-crypto'
import { serpentWasm } from 'leviathan-crypto/serpent/embedded'
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
await init({ serpent: serpentWasm, sha2: sha2Wasm })
const key = SerpentCipher.keygen()
const blob = Seal.encrypt(SerpentCipher, key, plaintext)Data too large to buffer in memory? SealStream and OpenStream encrypt and decrypt in chunks without loading the full message.
import { SealStream, OpenStream } from 'leviathan-crypto/stream'
const sealer = new SealStream(XChaCha20Cipher, key, { chunkSize: 65536 })
const preamble = sealer.preamble // send first
const ct0 = sealer.push(chunk0)
const ct1 = sealer.push(chunk1)
const ctLast = sealer.finalize(lastChunk)
const opener = new OpenStream(XChaCha20Cipher, key, preamble)
const pt0 = opener.pull(ct0)
const pt1 = opener.pull(ct1)
const ptLast = opener.finalize(ctLast)Need parallel throughput? SealStreamPool distributes chunks across Web Workers with the same wire format.
import { SealStreamPool } from 'leviathan-crypto/stream'
const pool = await SealStreamPool.create(XChaCha20Cipher, key, { wasm: chacha20Wasm })
const encrypted = await pool.seal(plaintext)
const decrypted = await pool.open(encrypted)
pool.destroy()Want post-quantum security? KyberSuite wraps ML-KEM and a cipher suite into a hybrid construction. It plugs directly into SealStream. The sender encrypts with the public encapsulation key and only the recipient's private decapsulation key can open it.
import { KyberSuite, MlKem768 } from 'leviathan-crypto/kyber'
import { kyberWasm } from 'leviathan-crypto/kyber/embedded'
import { sha3Wasm } from 'leviathan-crypto/sha3/embedded'
await init({ kyber: kyberWasm, sha3: sha3Wasm, chacha20: chacha20Wasm, sha2: sha2Wasm })
const suite = KyberSuite(new MlKem768(), XChaCha20Cipher)
const { encapsulationKey: ek, decapsulationKey: dk } = suite.keygen()
// sender — encrypts with the public key
const sealer = new SealStream(suite, ek)
const preamble = sealer.preamble // 1108 bytes: 20B header + 1088B KEM ciphertext
const ct0 = sealer.push(chunk0)
const ctLast = sealer.finalize(lastChunk)
// recipient — decrypts with the private key
const opener = new OpenStream(suite, dk, preamble)
const pt0 = opener.pull(ct0)
const ptLast = opener.finalize(ctLast)Looking for more examples including hashing, key derivation, Fortuna, and raw primitives? See the examples page.
web [ demo · source · readme ]
A self-contained browser encryption tool in a single HTML file. Encrypt text or files with Serpent-256-CBC and Argon2id key derivation, then share the armored output. No server, no install, no network connection after initial load. The code is written to be read. The Encrypt-then-MAC construction, HMAC input, and Argon2id parameters are all intentional examples worth studying.
chat [ demo · source · readme ]
End-to-end encrypted chat over X25519 key exchange and XChaCha20-Poly1305 message encryption. The relay server is a dumb WebSocket pipe that never sees plaintext. Messages carry sequence numbers so the protocol detects and rejects replayed messages. The demo deconstructs the protocol step by step with visual feedback for injection and replay attacks.
Command-line file encryption tool supporting both Serpent-256 and
XChaCha20-Poly1305 via --cipher. A single keyfile works with both ciphers.
The header byte determines decryption automatically. Chunks distribute across a
worker pool sized to hardwareConcurrency. Each worker owns an isolated WASM
instance with no shared memory. The tool can export it's own interactive
competitions for a variety of shells.
bun add -g lvthn
lvthn keygen --armor -o my.key
cat secret.txt | lvthn encrypt -k my.key --armor > secret.enckyber [ demo · source · readme ]
Post-quantum cryptography demo simulating a complete ML-KEM key encapsulation ceremony between two browser-side clients. A live wire at the top of the page logs every value that crosses the channel; importantly, the shared secret never appears in the wire. After the ceremony completes, both sides independently derive a symmetric key using HKDF-SHA256 and exchange messages encrypted with XChaCha20-Poly1305. Each wire frame is expandable, revealing the raw nonce, ciphertext, Poly1305 tag, and AAD.
| I want to... | |
|---|---|
| Encrypt data | Seal with SerpentCipher or XChaCha20Cipher |
| Encrypt a stream or large file | SealStream to encrypt, OpenStream to decrypt |
| Encrypt in parallel | SealStreamPool distributes chunks across Web Workers |
| Add post-quantum security | KyberSuite wraps MlKem512, MlKem768, or MlKem1024 with any cipher suite |
| Hash data | SHA256, SHA384, SHA512, SHA3_256, SHA3_512, SHAKE256 ... |
| Authenticate a message | HMAC_SHA256, HMAC_SHA384, or HMAC_SHA512 |
| Derive keys | HKDF_SHA256 or HKDF_SHA512 |
| Generate random bytes | Fortuna for forward-secret generation, randomBytes for one-off use |
| Compare secrets safely | constantTimeEqual uses a WASM SIMD path to prevent timing attacks |
| Work with bytes | hexToBytes, bytesToHex, wipe, xor, concat ... |
For raw primitives, low-level cipher access, and ASM internals see the full API reference.
| Architecture | Repository structure, module relationships, build pipeline, and buffer layouts |
| Test Suite | How the test suite works, vector corpus, and gate discipline |
| Security Policy | Security posture and vulnerability disclosure details |
| Lexicon | Glossary of cryptographic terms |
| WASM Primer | WebAssembly primer in the context of this library |
| CDN | Use leviathan-crypto directly from a CDN with no bundler |
| argon2id | Passphrase-based encryption using Argon2id alongside leviathan primitives |
leviathan-crypto is released under the MIT license.
▄▄▄▄▄▄▄▄▄▄
▄████████████████████▄▄
▄██████████████████████ ▀████▄
▄█████████▀▀▀ ▀███████▄▄███████▌
▐████████▀ ▄▄▄▄ ▀████████▀██▀█▌
████████ ███▀▀ ████▀ █▀ █▀
███████▌ ▀██▀ ██
███████ ▀███ ▀██ ▀█▄
▀██████ ▄▄██ ▀▀ ██▄
▀█████▄ ▄██▄ ▄▀▄▀
▀████▄ ▄██▄
▐████ ▐███
▄▄██████████ ▐███ ▄▄
▄██▀▀▀▀▀▀▀▀▀▀ ▄████ ▄██▀
▄▀ ▄▄█████████▄▄ ▀▀▀▀▀ ▄███
▄██████▀▀▀▀▀▀██████▄ ▀▄▄▄▄████▀
████▀ ▄▄▄▄▄▄▄ ▀████▄ ▀█████▀ ▄▄▄▄
█████▄▄█████▀▀▀▀▀▀▄ ▀███▄ ▄███▀
▀██████▀ ▀████▄▄▄████▀
▀█████▀