Skip to content

Releases: xero/leviathan-crypto

v3.0.1

Choose a tag to compare

@github-actions github-actions released this 04 Jun 04:54
  • updates devDependencies and adds overrides pinning for brace-expansion and postcss clearing two moderate transitive advisories
  • typescript6 no longer auto-injects @types/node ambient globals:
    • fortuna: reads Node entropy sources (process.*) via globalThis with a minimal local type (keeping the lib free of any dependencies)
    • tsconfig.test.json: adds "types": ["node"]
  • ci: re-pin actions/checkout to v6.0.3 across all workflows
  • verify-vectors: fixes rust warnings and sign_sth adds ed25519 and ml-dsa-44 secret-key parity checks

2 commits since v3.0.0 · v3.0.0...v3.0.1

Type Commit Meta
𛲜 chore(release): v3.0.1 320637c
𛲜 chore(deps): bump dev toolchain to latest stable, patch transitive advisories 9073207
legend
  • Breaking Changes. breaking change
  • Features. feat
  • 🕱 Bug Fixes. fix
  • Refactors. refactor
  • Build & CI. build
  • 𛲜 Chores. chore
  • 🖹 Documentation. docs
  • 🗹 Tests. test
  • Other. other

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 30 May 06:24

Warning

This release contains breaking changes. Review carefully before upgrading.

Added

  • AES-256-GCM-SIV cipher suite. AESGCMSIVCipher (RFC 8452, AES-GCM-SIV) joins SerpentCipher and XChaCha20Cipher as a first-class peer at formatEnum 0x04, with a 32-byte key, a 16-byte tag, and nonce-misuse resistance. It carries the same HKDF-SHA-256 explicit-commitment construction as the XChaCha20 suite and works with Seal, SealStream, OpenStream, SealStreamPool, and MlKemSuite. The standalone AESGCMSIV primitive supports both AES-128 and AES-256. See the AES guide and the AESGCMSIVCipher reference.
  • MlKemSuite AES variants. MlKemSuite(MlKem512/768/1024, AESGCMSIVCipher) produces formatEnum 0x14, 0x24, and 0x34 with preambles of 820, 1140, and 1620 bytes, matching the XChaCha20 KEM variants. See the MlKemSuite reference.
  • AESGenerator. AES-256 counter-mode PRF for the Fortuna pluggable Generator slot, restoring the Practical Cryptography §9.4 spec primitive. Pair it with SHA256Hash or SHA3_256Hash. See Fortuna.
  • AES pool worker. AESGCMSIVCipher.createPoolWorker() spawns classic workers from a bundled IIFE, matching the XChaCha20Cipher and SerpentCipher pipeline.
  • Sign, SignStream, VerifyStream. Single-shot envelope signing and its streaming counterparts, structurally parallel to Seal. SignStream.finalize is byte-identical to Sign.sign for the same (suite, sk, msg, ctx), so producers and consumers choose independently between in-memory and incremental. See the signing guide.
  • SignatureSuite abstraction. SignatureSuite, StreamableSignatureSuite, and PrehashAlgorithm are the template for every signature scheme, parallel to CipherSuite. See the SignatureSuite reference.
  • SigningError. Discriminator-first error class mirroring AuthenticationError, with twelve stable discriminators spanning the suite, envelope, and stream layers.
  • ML-DSA (FIPS 204). MlDsa44, MlDsa65, and MlDsa87 cover pure ML-DSA (keygen, keygenDerand, hedged sign, signDeterministic, signDerand, verify) and HashML-DSA, whose signHash family exposes all twelve approved pre-hash functions from FIPS 204 §5.4.1. A prehashed-input surface (signHashPrehashed, signHashPrehashedDeterministic, signHashPrehashedDerand, verifyHashPrehashed) accepts a precomputed digest instead of rehashing; every shipped prehash suite throws SigningError('sig-malformed-input') on a wrong-length digest. Subpaths leviathan-crypto/mldsa and leviathan-crypto/mldsa/embedded. See the ML-DSA guide.
  • Six ML-DSA suite consts. MlDsa44Suite, MlDsa65Suite, MlDsa87Suite (catalog bytes 0x03-0x05) cover pure ML-DSA; MlDsa44PreHashSuite, MlDsa65PreHashSuite, MlDsa87PreHashSuite (0x13-0x15) cover HashML-DSA, defaulting to SHA3-256 for 44 and 65 and SHA3-512 for 87. See the SignatureSuite catalog.
  • SLH-DSA (FIPS 205). SlhDsa128f, SlhDsa192f, and SlhDsa256f (SHAKE-family fast parameter sets, NIST categories 1, 3, and 5) cover pure SLH-DSA and HashSLH-DSA per FIPS 205 §10.2.2, including the signHash and signHashPrehashed families. The category-1 SHA2-256 / SHAKE128 restriction is enforced at the public surface. Subpaths leviathan-crypto/slhdsa and leviathan-crypto/slhdsa/embedded. See the SLH-DSA guide.
  • Six SLH-DSA suite consts. SlhDsa128fSuite, SlhDsa192fSuite, SlhDsa256fSuite (catalog bytes 0x06-0x08) cover pure SLH-DSA; SlhDsa128fPreHashSuite, SlhDsa192fPreHashSuite, SlhDsa256fPreHashSuite (0x16-0x18) cover HashSLH-DSA, with SHAKE128 for 128f and SHAKE256 for 192f and 256f. See the SignatureSuite catalog.
  • Three PQ-only hybrid suite consts. MlDsa44SlhDsa128fSuite, MlDsa65SlhDsa192fSuite, and MlDsa87SlhDsa256fSuite (catalog bytes 0x30-0x32) bind ML-DSA and SLH-DSA in one composite (pk = pk_mldsa || pk_slhdsa, sig = sig_mldsa || sig_slhdsa, ML-DSA first). Both sub-verifies always run. These are prehash-only; streaming through SignStream / VerifyStream is mandatory. See the PQ-only hybrid encoding.
  • Ed25519 (RFC 8032). The first classical signature primitive. Ed25519 covers pure Ed25519 (§5.1.6) and Ed25519ph (§5.1.7) with the dom2 prehash binding. signPrehashed takes an explicit ctx argument; pass new Uint8Array(0) for no context binding. Subpath leviathan-crypto/ed25519. See the Ed25519 guide.
  • X25519 (RFC 7748). The first classical key-agreement primitive. X25519 exposes keygen, keygenDerand, and dh, with §5 clamping handled internally so a round-tripped secretKey preserves byte equality. Subpath leviathan-crypto/x25519. See the X25519 guide.
  • KeyAgreementError. New error class. X25519.dh throws it on an all-zero shared secret (a small-order peer pk), distinguishing a degenerate exchange from a caller-contract TypeError or RangeError.
  • Ed25519Suite and Ed25519PreHashSuite. Format bytes 0x01 and 0x11. Ed25519Suite is SignatureSuite only and rejects non-empty user_ctx with SigningError('sig-ctx-unsupported'); Ed25519PreHashSuite is StreamableSignatureSuite, pins SHA-512, and binds dom2 context inside the curve WASM. See the SignatureSuite catalog.
  • ECDSA over NIST P-256 (FIPS 186-5). The second classical signature primitive. EcdsaP256 consumes a 32-byte SHA-256 digest and supports both RFC 6979 §3.2 deterministic and draft-irtf-cfrg-det-sigs-with-noise-05 §4 hedged K derivation. The signer normalises s to low-S per RFC 6979 §3.5 and the verifier rejects high-S. Subpaths leviathan-crypto/ecdsa and leviathan-crypto/ecdsa/embedded. See the ECDSA-P256 guide.
  • EcdsaP256Suite. StreamableSignatureSuite at format byte 0x02, pinned to SHA-256 prehash. It rejects non-empty user_ctx (FIPS 186-5 has no ctx parameter; context-bound ECDSA lives in the classical+PQ hybrids) and is hedged by default. Consumers needing byte-deterministic signatures drop to EcdsaP256 directly with an all-zero rnd. See the SignatureSuite catalog.
  • ecdsaSignatureToDer / ecdsaSignatureFromDer. Strict-DER codec for X.509, JWS, and TLS interop per RFC 3279 §2.2.3. The decoder rejects non-minimal lengths, excess leading zeroes, negative INTEGERs, wrong tags, and trailing bytes via SigningError('sig-malformed-input'). See the ECDSA-P256 DER utility.
  • pointDecompress, EcdsaP256.keygenUncompressed, encodeEcPrivateKey / decodeEcPrivateKey. Three ECDSA exports for the composite-sigs §4 wire encodings: the SEC 1 §2.3.3 and §2.3.4 point codec and the RFC 5915 §3 DER ECPrivateKey codec for P-256. See the ECDSA-P256 guide.
  • Four classical+PQ composite hybrid suites. MlDsa44Ed25519Suite, MlDsa65Ed25519Suite, MlDsa44EcdsaP256Suite, and MlDsa65EcdsaP256Suite (format bytes 0x20-0x23) per draft-ietf-lamps-pq-composite-sigs. Each pre-hashes the message, builds the composite-sigs §3.2 M' construction with the CompositeAlgorithmSignatures2025 prefix and a per-suite label, and signs with both components in parallel (PQ-first, sig = sig_mldsa || sig_trad). Composite private keys carry the 32-byte ML-DSA seed only. See the classical+PQ hybrid encoding.
  • BLAKE3 hash family. blake3.wasm, the tenth WASM binary, joins SHA-2 and SHA-3 with all three modes: default hash (§2.5), keyed_hash (§2.6), and derive_key (§2.7). Seven classes ship: BLAKE3, BLAKE3KeyedHash, and BLAKE3DeriveKey (one-shot); BLAKE3Stream, BLAKE3KeyedHashStream, and BLAKE3DeriveKeyStream (streaming); and BLAKE3OutputReader, the XOF reader from finalizeXof(). BLAKE3Hash is a stateless 32-byte HashFn for the Fortuna accumulator slot, peer to SHA256Hash and SHA3_256Hash. Subpaths leviathan-crypto/blake3 and leviathan-crypto/blake3/embedded. See the BLAKE3 guide.
  • SP 800-185 cSHAKE and KMAC. Six classes on the existing sha3 module: CSHAKE128, CSHAKE256, KMAC128, KMAC256, KMACXOF128, and KMACXOF256. The fixed-output KMAC variants ship a static verify(tag, key, msg, customization) that throws AuthenticationError on mismatch. cSHAKE rejects empty customization and KMAC rejects empty keys. KMACXOF has no static verify by design; squeeze the expected bytes and use constantTimeEqual. See the KMAC guide.
  • Streaming hash classes. SHA3_256Stream, SHA3_512Stream, SHAKE128Stream, and SHAKE256Stream add an incremental update / finalize lifecycle to the sha3 module, required by SignStream for prehash suites. See the SHA-3 guide.
  • merkle module. A...
Read more

v2.1.0 - spqr ratchet primitives

Choose a tag to compare

@github-actions github-actions released this 01 May 17:48

v2.1.0 - post-quantum ratchet primitives

Warning

10 breaking API changes with this release!

See Breaking Changes below and review CHANGELOG for full migration notes.

Adds the four KDF constructions from Signal's Double Ratchet spec §5 and §7.2 (Sparse Post-Quantum Ratchet), plus two tier-1 session utilities that make those primitives misuse-resistant. Pure TypeScript over existing WASM; no new binaries.

Ratchet primitives

ratchetInit(sk, context?) — KDF_SCKA_INIT. Expands a 32-byte shared secret (e.g., a PQXDH output SK) into an initial root key, send chain key, and receive chain key. Both parties call this with the same sk to synchronize their starting state.

KDFChain — KDF_SCKA_CK. Stateful symmetric chain. Each step() call derives a message key and advances the internal chain key via HKDF-SHA-256 with a big-endian uint64 counter in the info bytes. stepWithCounter() returns { key, counter } atomically, removing the two-step step() + .n read pattern. The old chain key is wiped before replacement. Calling step() after dispose() throws.

kemRatchetEncap(kem, rk, peerEk, context?) — KDF_SCKA_RK, encapsulator side. Atomically calls kem.encapsulate(peerEk), mixes the shared secret into the root key via HKDF, wipes the shared secret, and returns { nextRootKey, sendChainKey, recvChainKey, kemCt }. The kemCt field must travel in-band; the peer cannot advance without it. Both sendChainKey and recvChainKey are used: they simultaneously seed the two directions of a bilateral exchange from a single KEM encapsulation. The HKDF info string now binds peerEk, kemCt, and context with u32be length prefixes — see F-010 below.

kemRatchetDecap(kem, rk, dk, kemCt, ownEk, context?) — KDF_SCKA_RK, decapsulator side. Mirrors encap: calls kem.decapsulate, mixes, wipes, returns keys. Chain key slots are swapped relative to the encap result so alice.sendChainKey === bob.recvChainKey. The ownEk parameter is the local party's ML-KEM encapsulation key — the same public key the encap side targeted as peerEk — so both sides bind the identical (peerEk, kemCt, context) tuple into the HKDF info string.

SkippedKeyStore — tier-1 MKSKIPPED management (DR spec §3.2/§3.5). Encapsulates the three delivery paths (in-order, skip-ahead, past lookup), ceiling-enforced eviction with wipe, and the advanceToBoundary(chain, pn) method used at epoch transitions. resolve(chain, counter) now returns a ResolveHandle with commit() / rollback() for transactional auth — see the breaking change note below. Scoped to one chain and one epoch — the application maintains one store per sender per epoch.

RatchetKeypair — tier-1 ek/dk lifecycle. Calls kem.keygen() on construction, enforces single-use via a consumed flag, and wipes the dk in a try/finally block inside decap() so the key is always zeroed regardless of whether decap succeeds or throws. decap() threads the stored ek through kemRatchetDecap as ownEk automatically, so callers using this helper are unaffected by the kemRatchetDecap signature change. After decap, generate a new RatchetKeypair and broadcast the new ek to all senders for the next ratchet step.

RatchetMessageHeader (type) — canonical shape for what goes in a message header when using these primitives: { epoch, counter, pn?, kemCt? }. pn and kemCt are present only on the first message of a new epoch.

Session state, header encryption, and transport are application concerns and are intentionally out of scope.


Breaking changes

Consumers of 2.0.1 will need to address these before upgrading to 2.1.0:

kemRatchetDecap adds a required ownEk parameter (F-010). The new signature is kemRatchetDecap(kem, rk, dk, kemCt, ownEk, context?). ownEk is the local party's encapsulation key so both sides bind the identical transcript into the HKDF info string. RatchetKeypair.decap threads its stored ek through automatically — callers using the high-level helper are unaffected. Direct kemRatchetDecap users must pass ownEk explicitly. Ratchet test vectors regenerated: existing test/vectors/ratchet_kat.ts outputs are obsolete because the HKDF info string changed. The KEM inputs (ACVP tcId 77 dk / kemCt) are unchanged — only the HKDF output hex fields.

OpenStream.seek(index) rejects backward seeks (F-012). index < this.counter now throws RangeError with 'forward-only' in the message. Backward seeks would re-use an already-consumed per-chunk counter nonce against a new ciphertext, permitting plaintext replay against a stale opener. Seek to the current counter is still a no-op. Construct a fresh OpenStream from the same preamble to restart.

ML-KEM auto-validates keys on use (FIPS 203 §7.2 / §7.3). MlKemBase.encapsulate(ek), encapsulateDerand(ek, m), and decapsulate(dk, c) now invoke the FIPS 203 §7.2 / §7.3 validity checks before any cryptographic work. Invalid keys throw RangeError with messages leviathan-crypto: encapsulation key failed FIPS 203 §7.2 validity check or leviathan-crypto: decapsulation key failed FIPS 203 §7.3 validity check. The §7.2 check is a direct coefficient-range scan (polyvec_modulus_check) — not the naïve round-trip check, which is bit-exact identity on any length-valid ek and therefore inert. Public checkEncapsulationKey / checkDecapsulationKey probes remain available for callers that want to inspect a key without triggering an exception.

Strict single-use on ChaCha20Poly1305.encrypt / XChaCha20Poly1305.encrypt. Any throw from encrypt() — including RangeError on key or nonce length — now terminates the instance. A retry always raises the single-use guard, never a fresh length error. Always allocate a new AEAD per message.

SealStream / OpenStream add a 'failed' terminal state. Any throw from push(), pull(), or finalize() wipes the derived keys and transitions the stream to 'failed'. Subsequent operations (including OpenStream.seek) throw with 'failed' in the message. dispose() on a failed instance is a no-op.

SkippedKeyStore.resolve returns ResolveHandle, not a raw key. Migration: replace const key = store.resolve(chain, n); try { ... } finally { wipe(key); } with const handle = store.resolve(chain, n); try { useKey(handle.key); handle.commit(); } catch (err) { handle.rollback(); throw err; }. See the design-decisions section above for the rationale.

SkippedKeyStore splits ceiling into maxCacheSize + maxSkipPerResolve. New defaults: maxCacheSize = 100 (memory bound), maxSkipPerResolve = 50 (per-message HKDF work bound). Legacy { ceiling: N } still accepted and sets both budgets to N. advanceToBoundary also enforces maxSkipPerResolve. Closes the F-007 skip-ahead CPU amplification (~500× asymmetric HKDF work previously possible via high-counter headers) and the accompanying O(n²) eviction (eviction is now O(1) via Map insertion order).

hexToBytes throws on non-hex characters. Previously parseInt('0g', 16) returned 0 (not NaN) because it stops at the first invalid character, silently producing wrong bytes. The leading regex now rejects any input containing characters outside [0-9a-fA-F]. Odd-length input already threw — that's preserved.

base64ToBytes throws on invalid input. Previously returned undefined on illegal charsets or rem=1 length. New behaviour matches the throw-don't-return-null convention elsewhere in the library. Return type is now Uint8Array, not Uint8Array | undefined. Callers doing if (bytes) { ... } should drop the check and let throws propagate.

Security hardening

Exclusivity guards closed (F-001 / R-011 / R-012 / R-025). TASK-A landed a runtime exclusivity registry preventing construction of a second stateful instance while the first is live. TASK-A2 closed three remaining sub-cases: post-dispose method calls on SHAKE128/256, ChaCha20, SerpentCtr, SerpentCbc now throw instead of silently clobbering whatever owns the WASM module; kyber operations now hold the sha3 and kyber exclusivity guards (previously a live SHAKE128 plus any kyber op would silently re-initialise the sha3 sponge mid-stream); MlKemBase.dispose() no longer wipes sha3 buffers (it never owned them).

FIPS 203 §7.2 validator is now functional (TASK-A2 + TASK-A3). The initial TASK-A2 wiring used a ByteDecode₁₂ → ByteEncode₁₂ round-trip check that is bit-exact identity on any 12-bit input, making the modulus check effectively a no-op. TASK-A3 replaced the round-trip with a direct coefficient-range scan in WASM (polyvec_modulus_check in src/asm/kyber/polyvec.ts). §7.3 inherits the fix automatically via the recursive checkEncapsulationKey call on the embedded ek — a dk whose embedded ek has been modulus-tampered is now rejected even when the embedded H has been correctly recomputed.

Fortuna wipe-before-reassign across all state transitions (F-002). pseudoRandomData, reseed, addRandomEvent, and the pool-reset branch inside get() zero the prior Uint8Array before dropping its reference. Fortuna.stop() is now a complete teardown: wipes genKey, genCnt, all 32 pool-hash chain values, and disposes the inner Serpent and SHA256 instances so their exclusivity tokens are released.

Stream / cipher-suite wipe-on-throw (F-003). SerpentCipher.sealChunk / openChunk wrap their scratch allocations in try/finally — intermediate IV, tag input, and expected-tag buffers are zeroed regardless of whether the function returns or throws. The auth-failure explicit-wipe path remains in place as defense in depth. Subsequently refactored in TASK-H to go through shared-ops pure functions, removing the per-chunk new HMAC_SHA256() / new SerpentCbc() construction.

aeadDecrypt auth-failure wipes keystream residuals (R-013, R-027). The 64-byte CHACHA_BLOCK_OFFSET (keystream block) is zeroed alongside CHUNK_CT on auth failure. TASK-B2 extends the wipe to POLY_KEY_OFFSET (32-byte Poly1305 one-time subkey cop...

Read more

⚠ v2.0.1 — Breaking Changes

Choose a tag to compare

@github-actions github-actions released this 10 Apr 23:05

v2.0.1

2 commits since v2.0.0 · v2.0.0...v2.0.1

Warning

This release contains breaking changes to the stream pools wire-format and backend to fix a critical bug in serpent stream pools

Type Commit Meta
𛲜 chore(release): v2.0.1 41a8011
fix(serpent)!: expand CBC chunk buffer to fit PKCS7-padded 65536-byte blocks 228ea23
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

v2.0.0 ⚠

Choose a tag to compare

@github-actions github-actions released this 10 Apr 04:12

Warning

This release contains breaking changes. Review carefully before upgrading.

KYBER ML-KEM (Post-Quantum KEM: FIPS 203)

Adds MlKem512, MlKem768, and MlKem1024 classes implementing the full Fujisaki-Okamoto KEM transform as a new kyber.wasm WASM module. Key details:

  • SIMD NTT/invNTT, Montgomery/Barrett reduction, CBD sampling, division-free compression
  • Constant-time decapsulation via dedicated ct_verify/ct_cmov in WASM (never touches JS)
  • Requires sha3 for Keccak sponge ops (SHAKE128/256, SHA3-256/512)
  • Validated against 240 NIST ACVP vectors (75 keygen, 75 encap, 30 decap w/ implicit rejection, 60 key validity)
  • New leviathan-crypto/kyber and leviathan-crypto/kyber/embedded subpath exports
  • New KyberSuite factory for hybrid KEM+AEAD workflows
  • 'keccak' module name added as an alias for 'sha3' (same binary, same instance slot)

Stream API refactor (⚠ breaking)

  • SerpentSeal and XChaCha20Seal removed; replaced by the unified Seal static class
  • New API: Seal.encrypt(CipherSuite, key, plaintext) / Seal.decrypt(...)
  • SealStream preamble field renamed from header → preamble
  • SealStream/OpenStream/SealStreamPool barrel re-exported under stream/index.ts
  • Read more here

New WASM modules

  • kyber.wasm: ML-KEM polynomial arithmetic (6th WASM binary, joining serpent/chacha20/sha2/sha3/ct)
  • ct.wasm: internal SIMD constant-time compare utility

CICD / test suite reorganization

  • Workflows reorganized and split by cipher family: unit-serpent, unit-chacha20, unit-stream, unit-kyber, unit-core (replacing unit-fast, unit-pools, unit-utils)
  • New kyber_audit.md covering NTT correctness, IND-CCA2 decapsulation, NIST KAT coverage

5 commits since v1.4.0 · v1.4.0...v2.0.0

Type Commit Meta
𛲜 chore(release): v2.0.0 c461bd3
🖹 docs: v2 api 3673786
feat(v2): cipher-agnostic AEAD, WASM SIMD constantTimeEqual, polymorphic init, kyber 256ff27
🕱 fix: strip test seams from public API, harden pool open() parsing, wipe worker buffers d59659c
feat: XChaCha20StreamPool 07d3625
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

v1.4.0 ⚠ XChaCha20Seal

Choose a tag to compare

@github-actions github-actions released this 01 Apr 21:40

Warning

⚠ Breaking Changes

SerpentStreamSealer wire format: explicit isLast byte.

Each sealed chunk now prepends a cleartext isLast(1) byte. Wire format: isLast(1) || IV(16) || ciphertext || HMAC(32). Previously non-final chunks required two HKDF derivations and two HMAC verifications to distinguish from final chunks — a per-chunk timing differential that leaked stream position to observers with oracle access. Existing SerpentStream ciphertexts are unreadable by the new opener.

SerpentSeal / SerpentStreamSealer: AAD support changes wire format.

Optional aad?: Uint8Array parameter added to encrypt/decrypt/seal/final/open/feed. AAD is folded into the HMAC input with a 4-byte length prefix for domain separation. Existing ciphertexts are not compatible with v1.4.0, even with empty AAD, due to the length prefix changing the HMAC input construction. Groups cleanly with the isLast break — one migration, one version bump.

hexToBytes: odd-length input now throws.

Previously right-padded silently: 'abc''abc0'[0xab, 0xc0]. Now throws RangeError('hexToBytes: odd-length string'). Silent corruption in a crypto context is unacceptable.

base64url: encoding drops padding per RFC 4648 §5.

bytesToBase64(bytes, true) previously emitted %3d for = padding. Now omits padding entirely per spec. Decode path still accepts %3d indefinitely for backward compatibility with existing encoded data.

Fortuna.get() always returns Uint8Array.

Return type changed from Uint8Array | undefined to Uint8Array. create() now guarantees the instance is seeded before resolving. The undefined path was unreachable in any runtime that supports WASM SIMD.

Tip

New Stuff

XChaCha20Seal

the new recommended ChaCha20 AEAD. Binds key at construction, generates a fresh random 24-byte nonce per encrypt() call. No nonce management needed. Wire format: nonce(24) || ciphertext || tag(16). Supports optional AAD. This is what most users should reach for.

XChaCha20StreamSealer / XChaCha20StreamOpener

chunked streaming authenticated encryption for large payloads. Per-chunk random nonces, position-bound AAD preventing reorder/splice/truncation, framed mode for network transports. API mirrors SerpentStreamSealer/Opener. 32-byte key.

AAD on SerpentSeal and SerpentStreamSealer.

Both Serpent authenticated encryption paths now support associated data, bringing them to parity with the ChaCha20 side. Bind metadata (filenames, user IDs, version headers) cryptographically without manual HMAC composition.

concat() is now variadic.

concat(a, b)concat(...arrays). Single allocation for N arrays instead of N-1 intermediate allocations. Backward compatible.

Gzip-compressed embedded WASM.

198KB → ~33KB total embedded footprint. Serpent alone: 167KB → 20KB. Fully internal to the loader — init('embedded') works identically.

Note

Fixes & Cleanup

decodeWasm() DecompressionStream guard.

Throws a descriptive error when DecompressionStream is unavailable instead of a bare ReferenceError. Directs callers to streaming or manual init mode.

embed-wasm.ts deterministic gzip output.

Zeroes the gzip mtime header for reproducible builds across identical WASM input.

XChaCha20Poly1305 docs no longer claims to implement the AEAD interface.

The class is the RFC-faithful stateless primitive, its method signature never matched the AEAD interface. Docs and types corrected. XChaCha20Seal is now the recommended AEAD entry point.

Consolidated internal base64ToBytes copies.

Three divergent private copies in the loader and pool modules replaced with a single import from utils.


503 unit tests · 183 e2e tests · 686 total

4 commits since v1.3.1 · v1.3.1...v1.4.0

Type Commit Meta
𛲜 chore(release): v1.4.0 1839f7a
🖹 docs: release prep 663c5b9
feat: xchacha20seal & xchacha20stream 5413e98
docs dc48a1d
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

v1.3.1

Choose a tag to compare

@github-actions github-actions released this 31 Mar 04:22

v1.3.1

Note

normalizes the chacha.wasm name to chacha20.wasm to match other primitives with no consumer api changes.

3 commits since v1.3.0 · v1.3.0...v1.3.1

Type Commit Meta
𛲜 chore(release): v1.3.1 1b94a33
docs 4dbdb92
🕱 fix(nomenclature): normalize to chacha20 wasm cbf0af6
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

⚠ v1.3.0 - SSSA

Choose a tag to compare

@github-actions github-actions released this 29 Mar 04:59

v1.3.0 - Streamlining the Serpent Streaming API (Breaking Changes)

Warning

This release contains breaking changes. Review carefully before upgrading.

Breaking Changes

  • Removed: SerpentStreamEncoder and SerpentStreamDecoder: deleted with no grace period

Migration

Old New
new SerpentStreamEncoder(key, cs) new SerpentStreamSealer(key, cs, { framed: true })
encoder.encode(pt) sealer.seal(pt)
encoder.encodeFinal(pt) sealer.final(pt)
new SerpentStreamDecoder(key, hdr) new SerpentStreamOpener(key, hdr, { framed: true })
decoder.feed(bytes) opener.feed(bytes) (same signature)

Added

  • SerpentStreamSealer: opts?: { framed?: boolean } constructor parameter. When framed: true,
    seal() and final() prepend u32be(sealedLen) to each output chunk.
  • SerpentStreamOpener: opts?: { framed?: boolean } constructor parameter and feed() method.
    feed(bytes: Uint8Array): Uint8Array[] accumulates incoming bytes, parses u32be length
    prefixes, and dispatches complete frames to open() internally.

Wire format compatibility

Ciphertext produced by SerpentStreamEncoder is byte-identical to
SerpentStreamSealer({ framed: true }) output. No re-encryption required
when upgrading.

No cryptographic changes

No algorithm, key format, or protocol changes in this release. All existing
ciphertext produced by any prior version remains valid and decryptable.

Tests

456 unit · 183 e2e (61 tests × Chromium, Firefox, WebKit): all passing

4 commits since v1.2.0 · v1.2.0...v1.3.0

Type Commit Meta
𛲜 chore(release): v1.3.0 7342788
docs de70e4b
BREAKING: serpert stream cipher api opts (#5) 24997bf
🖹 docs: simd c2cacd7
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

v1.2.0 WASM SIMD

Choose a tag to compare

@github-actions github-actions released this 28 Mar 01:16

WebAssembly SIMD acceleration for Serpent-256 and ChaCha20.

Inter-block 4-wide parallelism delivering 2–3× single-thread throughput gains across V8, SpiderMonkey, and JSC.

11 commits since v1.1.0 · v1.1.0...v1.2.0

Type Commit Meta
𛲜 chore(release): v1.2.0 01a11c7
𛲜 chore: release prep 59545fe
🖹 docs: simd 4470260
🖹 docs: simd fe537de
feat(chacha): simd parallelism (#4) cdd52cf
feat(serpent): WASM SMID (#3) ade3182
🖹 docs(linx): local and wiki 5092567
🖹 docs(security): expand scope, posture, and disclosure policy 74d3db9
🖹 docs(security): add audit citations 7327b67
🖹 docs(security): add audit citations 998d581
🖹 docs(security): add audit citations b83a848
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 27 Mar 05:16

v1.1.0

9 commits since v1.0.0 · v1.0.0...v1.1.0

Type Commit Meta
𛲜 chore(release): v1.1.0 e3519fa
𛲜 chore(devDeps): bump versions 6e95fdb
🖹 docs(cicd): wiki gen 92a0bdb
🕱 fix(hmac|hkdf): zero all intermediate key material ad094c3
𛲜 chore(deps): bump the npm_and_yarn group across 1 directory with 1 update aba83ad
🖹 docs: cross refs 2a73b69
🕱 fix(cicd): script tweaks 5fe2dd3
🖹 docs: algo correctness audits 0b22fc1
🕱 fix(cicd): publish workflow a7650c2
legend
  • Breaking Changesbreaking change
  • Featuresfeat
  • 🕱 Bug Fixesfix
  • Refactorsrefactor
  • Build & CIbuild
  • 𛲜 Choreschore
  • 🖹 Documentationdocs
  • 🗹 Teststest
  • Otherother