quantex covers the NIST post-quantum standards ML-KEM, ML-DSA, and SLH-DSA, plus the X-Wing hybrid KEM. Behind them are upstream C reference implementations (mlkem-native, mldsa-native, slhdsa-c, and Monocypher for X25519), wrapped in a pure, phantom-typed API.
| Scheme | Standard | Parameter sets |
|---|---|---|
| ML-KEM | FIPS 203 | 512, 768, 1024 |
| ML-DSA | FIPS 204 | 44, 65, 87 |
| SLH-DSA | FIPS 205 | all 12 sets |
| X-Wing | CFRG draft | X25519 + ML-KEM-768 |
- Phantom-typed
PublicKey a,SecretKey a,Ciphertext a,Signature a. Mixing schemes does not compile. - Pure
Either QuantexErrorAPI. No exceptions in pure code. - Deterministic seed-based core, with
IOwrappers over a minimal platform entropy shim (no third-party entropy dependency). - Secret keys, seeds, and shared secrets live in zeroizing, page-locked storage with constant-time equality and no
Show. - Every primitive is a vendored C reference implementation, pinned by commit.
cabal install quantexOr add quantex ^>=0.1 to your package's build-depends. The library pulls only
base, bytestring, and deepseq, and is tested with GHC 9.10, 9.12, and 9.14.
Download quantex-<version>.tar.gz from the
releases page and install the
tarball directly:
cabal install quantex-<version>.tar.gzPoint a cabal.project at the repository:
source-repository-package
type: git
location: https://github.com/d3z-the-dev/quantex
subdir: quantexThen add quantex to your build-depends.
Key encapsulation. Any scheme is one type application away:
{-# LANGUAGE TypeApplications #-}
import Quantex.KEM (decaps, encaps, keypair, sharedSecretEq)
import Quantex.KEM.MLKEM (MLKEM768)
-- or: import Quantex.KEM.XWing (XWing)
main :: IO ()
main = do
(public, secret) <- keypair @MLKEM768
(encapsulated, shared) <- encaps public
print (sharedSecretEq shared (decaps secret encapsulated))Signing, hedged by default per FIPS 204/205:
{-# LANGUAGE OverloadedStrings, TypeApplications #-}
import Quantex.Sign (contextEmpty, keypair, sign, verify)
import Quantex.Sign.MLDSA (MLDSA65)
main :: IO ()
main = do
(public, secret) <- keypair @MLDSA65
signed <- sign secret contextEmpty "attack at dawn"
print (verify public contextEmpty "attack at dawn" signed) -- Right ()| Operation | KEM | Signatures |
|---|---|---|
| Key generation | keypair, keypairFrom |
keypair, keypairFrom |
| Core | encaps / decaps, encapsFrom |
sign / verify, signDeterministic |
| Prehash | signPrehashed / verifyPrehashed, signDigest / verifyDigest |
|
| Serialize | encode / decode |
encode / decode |
Prehash signing (HashML-DSA, FIPS 204 §5.4; HashSLH-DSA, FIPS 205 §10.2) hashes
the message first. It is domain-separated from pure signing, so the two never
cross-verify. Use signPrehashed/verifyPrehashed for a whole message, or
signDigest/verifyDigest for a Digest computed elsewhere (for example over
a streamed file that never fits in memory):
import Quantex.Sign (contextEmpty, keypair, signPrehashed, verifyPrehashed)
import Quantex.Sign.MLDSA (MLDSA65)
import Quantex.Sign.Prehash (PrehashAlgorithm (SHA2_256))
main :: IO ()
main = do
(public, secret) <- keypair @MLDSA65
signed <- signPrehashed secret contextEmpty SHA2_256 "attack at dawn"
print (verifyPrehashed public contextEmpty SHA2_256 "attack at dawn" signed) -- Right ()Every operation has a deterministic form (keypairFrom, encapsFrom,
signFrom, signDeterministic) over validated seeds, so no hidden randomness
enters the core. Serialization is raw encode/decode with strict length
validation on decode:
import Quantex.Codec (decode, encode)
decode (encode public) == Right publicFor an ML-KEM secret key, store the 64-byte generation seed instead of the
expanded decapsulation key: it is smaller and is the FIPS 203 canonical form.
Persist seedBytes and rebuild with secretKeyFromSeed on load.
Important
secretKeyBytes, seedBytes, and sharedSecretBytes copy secrets into
ordinary, non-zeroizing memory. Treat every such copy as radioactive and keep
it under the same protection as the key. Codec covers only public-material
types, so encode/decode never crosses this boundary implicitly. To
consume a secret without crossing it at all, use withSecretKey and
withSharedSecret: they lend the raw zeroizing buffer to a callback, so a KDF
or AEAD key never materializes an unzeroized copy. See
SECURITY.md.
Ten self-contained programs, one per application domain: secure transport, file and message encryption, firmware and release signing, IoT provisioning, key escrow, and bearer tokens. Each reads top to bottom as one realistic scenario, prints a narrated transcript, and exits non-zero on any deviation, so the set doubles as a smoke test.
They ship in the repository, not in the published package. See
examples/
for the full matrix of executables, schemes, and what each one demonstrates.
- Cryptographic math is never implemented in Haskell. Every primitive call
crosses the FFI into vendored C: mlkem-native (ML-KEM), mldsa-native
(ML-DSA), slhdsa-c (SLH-DSA), Monocypher (X25519 inside X-Wing). Upstream
commits and local patches are recorded in
quantex/cbits/README.md. - Phantom types carry the scheme, so keys and ciphertexts of different schemes never mix.
- The core is deterministic over seeds.
IOwrappers add entropy at the edge. - Secret material carries no
Show; public material shows byte counts only, and nothing serializes implicitly.
Warning
Constant-time and side-channel resistance are inherited from the vendored C, not established by this library. mlkem-native, mldsa-native, and slhdsa-c avoid secret-dependent branches by construction but are not hardened against microarchitectural attacks; Monocypher documents constant-time X25519. Zero-on-GC is best-effort. quantex is pre-release.
On top of the upstream primitives, the wrapper adds no secret-dependent
branching, constant-time secret equality, zeroizing and page-locked storage from
the moment a secret is created, and strict length plus FIPS 203 §7.2/§7.3 key
checks at every decode boundary. Full threat model, zeroization details, and the
reporting process live in
SECURITY.md;
known-answer test vectors are pinned and documented in
quantex/test/vectors/README.md.
Note
Pre-release, API under construction. Serialization is raw encode/decode
with strict length validation in Quantex.Codec. DER and OID encodings are
out of scope for v0.1.
BSD-3-Clause; see
LICENSE.
Vendored C keeps its upstream licenses; see
quantex/LICENSE.third-party.