@hackage / quantex

NIST post-quantum cryptography: ML-KEM, ML-DSA, SLH-DSA, X-Wing

Latest0.1.0.0

About

Metadata

  • Last updated , by d3z
  • License BSD-3-Clause
  • Categories Cryptography
  • Maintained by: d3z.the.dev@gmail.com

  • Lottery factor: 1

Links

Installation

Tested Compilers

  1. 9.10.3

Readme

quantex

post-quantum cryptography for Haskell
type-safe C bindings to the NIST standards

CI Status: pre-release GHC 9.10, 9.12, 9.14 FIPS 203, 204, 205


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

Highlights

  • Phantom-typed PublicKey a, SecretKey a, Ciphertext a, Signature a. Mixing schemes does not compile.
  • Pure Either QuantexError API. No exceptions in pure code.
  • Deterministic seed-based core, with IO wrappers 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.

Install

From Hackage
cabal install quantex

Or 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.

From a GitHub release

Download quantex-<version>.tar.gz from the releases page and install the tarball directly:

cabal install quantex-<version>.tar.gz
From source

Point a cabal.project at the repository:

source-repository-package
  type: git
  location: https://github.com/d3z-the-dev/quantex
  subdir: quantex

Then add quantex to your build-depends.

Quickstart

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 ()
API at a glance
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

More usage

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 public

For 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.

Examples

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.

Design

  • 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. IO wrappers add entropy at the edge.
  • Secret material carries no Show; public material shows byte counts only, and nothing serializes implicitly.

Security

[!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.

Status

[!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.

License

BSD-3-Clause; see LICENSE. Vendored C keeps its upstream licenses; see quantex/LICENSE.third-party.