banner
BGIcon

Overview

After building tools that check whether servers use post-quantum cryptography, I wanted to understand the algorithms themselves at the level of every polynomial and every byte. lattice-crypto-from-scratch implements ML-KEM (FIPS 203, key encapsulation), ML-DSA (FIPS 204, lattice signatures) and SLH-DSA (FIPS 205, hash-based signatures) straight from the standards, in Rust, without a single crypto crate.

Even the hashes are written here: Keccak-f[1600] with SHA3-224/256/384/512 and SHAKE128/256, and all six SHA-2 variants for the pre-hash modes. The library has zero dependencies; serde_json is used only by the tests to read NIST's vector files.

Technologies

  • - Rust (no dependencies)
  • - FIPS 203 — ML-KEM-512 / 768 / 1024
  • - FIPS 204 — ML-DSA-44 / 65 / 87 + HashML-DSA
  • - FIPS 205 — SLH-DSA (SHA-2 and SHAKE)
  • - Keccak / SHA-3 / SHAKE, SHA-2 family
  • - NIST ACVP test vectors
  • - LMS / HSS (RFC 8554, SP 800-208), in progress
The full test run: every NIST ACVP vector file for ML-KEM, ML-DSA and SLH-DSA passes
BG
Icon
  • Year

    2026

  • Type

    Rust Library

  • Role

    Solo Developer

  • Vectors

    2,103 / 2,103 pass

Highlights

Passes every official NIST ACVP vector: ML-KEM key generation and encapsulation/decapsulation including implicit rejection (240), ML-DSA key generation, signing and verification including pre-hash with 12 hash functions and external mu (615), and SLH-DSA key generation, signing and verification (1,248).

Every function names the FIPS algorithm number it implements, so the code can be read side by side with the standard.

A mutation check confirmed the vectors are doing their job: deliberately planted bugs were each caught by a failing vector.

BG
Icon
  • ML-KEM / ML-DSA

    q = 3329 / 8380417

  • Unit tests

    42

Technical Implementation

ML-KEM: the number-theoretic transform over q = 3329, SampleNTT from SHAKE128, centred binomial sampling, ByteEncode and Compress, K-PKE, and the Fujisaki–Okamoto transform with implicit rejection. Decapsulation's final choice between the real key and the rejection key is branch-free, and both FIPS input checks on keys are enforced.

ML-DSA: the NTT over q = 8380417, rejection samplers, SampleInBall, Power2Round, Decompose and the hint functions, bit packing for keys and signatures, and the signing rejection loop, in both deterministic and hedged modes, plus HashML-DSA with twelve pre-hash functions.

SLH-DSA: WOTS+, XMSS and FORS trees inside the hypertree, for both the SHA-2 and SHAKE parameter sets. The slowest part of the test suite by far is signing large hash-based signatures, which made the size and speed trade-off of stateless hash-based signatures very concrete.

Testing: beyond the vectors, unit tests check the NTTs against schoolbook polynomial multiplication, encode/decode round trips, the rounding and hint identities, and every hash against Python's hashlib. Randomness comes from the OS (BCryptGenRandom on Windows, /dev/urandom elsewhere).

BG
Icon
  • Purpose

    Learning, not production

Limits & What's Next

Correct against the vectors is not the same as safe to deploy, and the README says so. Nobody has audited the compiled code for secret-dependent timing beyond the constant-time select in decapsulation, secrets are not zeroized after use, and the arithmetic is written to be read (no Montgomery or Barrett reduction, no SIMD).

Next: stateful hash-based signatures (LMS / HSS, the scheme most relevant to secure boot, which ties into my post-quantum secure boot research), a dudect-style timing harness to measure leaks, Montgomery arithmetic, and benchmarks against a production library.