Calix PQC
NIST FIPS 203 · FIPS 204 · security category 3

Cryptography that outlasts the quantum computer.

Calix PQC is a from-the-standard implementation of the two post-quantum algorithms NIST standardised in 2024 — ML-KEM-768 for key exchange and ML-DSA-65 for signatures — checked byte for byte against NIST's own test vectors.

  • ML-KEM-768
  • ML-DSA-65
  • AES-256-GCM
  • HMAC_DRBG SHA-512
  • WebAssembly
52 / 52automated tests pass on every build
212 / 212NIST ACVP cases match through BoringSSL acvptool
2252reference cases shipped on this site
0divisions on secret data in the machine code
Why now

Encrypted today, readable tomorrow.

RSA and elliptic-curve cryptography fall to Shor's algorithm on a large enough quantum computer. Traffic recorded now can be stored and decrypted later. Moving to NIST's post-quantum standards is the only fix, and it has to start before the machine exists.

1

Harvest now, decrypt later

Long-lived secrets — health, finance, state records — are already worth recording. Key exchange is the part that must change first.

2

Standards are final

NIST published FIPS 203 (ML-KEM) and FIPS 204 (ML-DSA) in August 2024. These are the algorithms governments and vendors are now migrating to.

3

Correct is not optional

A subtly wrong implementation is worse than none. Calix PQC is judged against NIST's vectors, independent implementations and seeded faults — see the validation report.

Technology

Two algorithms. One clean library.

Both are implemented in Rust without borrowing a post-quantum crate, so every line that touches a secret can be audited — and both ship behind the same small C interface.

FIPS 203 · key encapsulation

ML-KEM-768

Lets two parties agree on a 32-byte secret over a public channel. Formerly CRYSTALS-Kyber.

Security categoryNIST 3 (≈ AES-192)
Encapsulation key1,184 bytes
Decapsulation key2,400 bytes
Ciphertext1,088 bytes
Shared secret32 bytes
Key generation · encrypt · decrypt≈ 0.5 · 0.8 · 0.6 ms (ARM64)
Hardeningimplicit rejection, FIPS 203 §7.2/§7.3 key checks
FIPS 204 · digital signature

ML-DSA-65

Non-repudiable signatures that stay unforgeable against quantum attackers. Formerly CRYSTALS-Dilithium.

Security categoryNIST 3
Public key1,952 bytes
Secret key4,032 bytes · or a 32-byte seed
Signature3,309 bytes
Keygen · sign 1 KB · verify 1 KB92 · 193 · 68 µs (Apple M3 Pro)
Modepure ML-DSA, hedged signing, context ≤ 255 B
Data format

One envelope, identical on every platform.

Encrypt on iOS, decrypt on Android, in Go or in a browser: the byte layout is fixed. ML-KEM derives the key; AES-256-GCM protects the data and authenticates the header.

Overhead per message: 1,121 bytes. Blobs written by earlier versions (0x04 Kyber round 3, 0x05 ML-KEM + ChaCha20-Poly1305) can still be read; new data is always 0x06.

Security engineering

Built to survive review.

Passing test vectors proves the maths. The rest of the work is what an attacker, or an auditor, would actually probe.

No division on secrets

Hardware division takes value-dependent time — the KyberSlash class of leaks. The compiled WebAssembly release and an unoptimised debug build are disassembled and searched for division instructions on every build (tests/ct/no-div.sh).

Seeded-fault testing

Eight deliberate ML-DSA bugs (MakeHint boundary, SampleInBall sign, ExpandA order, ExpandMask counter, non-canonical hints, Decompose constants and more) were injected one at a time. Every one turned the suite red.

Independent cross-check

ML-DSA keys, signatures, verdicts and the reaction to a single flipped bit are compared byte for byte against the separate RustCrypto ml-dsa crate (300 cases per run, 10,000 on demand); ML-KEM is checked against the independent ml-kem crate.

Hostile-input handling

Encapsulation keys with out-of-range coefficients are rejected (780 official malformed keys), decapsulation keys failing the H(ek) check are refused, tampered ciphertexts fall through implicit rejection, crafted length fields cannot over-read, and the blob header is authenticated.

Fork-safe randomness

Nonces and hedged-signature randomness come from an HMAC_DRBG (SP 800-90A, SHA-512) seeded from the OS, validated on 480 CAVP cases, that reseeds after a process fork and refuses requests past the reseed interval.

Small, owned surface

The lattice code, Keccak and NTT are in-house; the only dependencies are AES-GCM, HMAC/SHA-2, getrandom, subtle and zeroize. Ephemeral keys are wiped from memory. About 3,600 lines of Rust including tests.

Verification

Don't take our word for it.

Every vector, source commit and checksum is public. Download the NIST files from this site and run them against any implementation, ours included.

AlgorithmStandardSuiteResult
ML-KEM-768FIPS 203NIST ACVP · keyGen · encaps · decaps · key checksPASS
ML-KEM-768FIPS 203C2SP/CCTV · modulus · strcmp · unlucky samplingPASS
ML-DSA-65FIPS 204NIST ACVP · keyGen · sigGen · sigVerPASS
AES-256-GCMSP 800-38DNIST CAVP · 750 encrypt/decrypt casesPASS
HMAC_DRBGSP 800-90ANIST CAVP · 480 SHA-512 casesPASS
Platforms

Write once in Rust. Ship everywhere.

One core, one C header, ready-made bindings for the stacks product teams actually use.

iOS · macOSXCFramework, static + dynamic
AndroidJNI · arm64-v8a, armeabi-v7a, x86_64, x86
Flutterdart:ffi wrapper
Gocgo package
WebWebAssembly + TypeScript types
Windowsx64 DLL
Kotlin · Swiftready-to-copy wrappers
C / C++p_pqc.h
// Go — encrypt to a post-quantum public key, then sign
kp, _ := ppqc.GenerateKeyPair()                    // ML-KEM-768
blob, _ := ppqc.Encrypt(kp.PublicKey, []byte("hello"))
plain, _ := ppqc.Decrypt(kp.SecretKey, blob)

sk, _ := ppqc.MLDSAKeyPairFromSeed(seed)            // ML-DSA-65, 32-byte seed
sig, _ := ppqc.MLDSASign(sk.SecretKey, msg, []byte("ctx"))
ok := ppqc.MLDSAVerify(sk.PublicKey, msg, []byte("ctx"), sig)