← Projects

crypto-pq

Secure, optimized post-quantum cryptography in pure Rust with no dependencies: ML-KEM, ML-DSA, SLH-DSA, FrodoKEM, HSS/LMS and XMSS, plus SHA-2, SHA-3, BLAKE2, BLAKE3, Ascon, HMAC and HKDF.

Version
1.0.0
Rust
1.88 or later
License
Apache-2.0 OR MIT

Why it exists

Post-quantum cryptography needs implementations that can be checked rather than taken on trust. crypto-pq is one.

  • Post-quantum only. Every public-key algorithm is post-quantum. There are no classical or hybrid public-key schemes.
  • Published standards. Each algorithm follows a NIST, ISO/IEC or RFC publication or, for BLAKE3, its specification. No drafts.
  • No third-party code. No dependencies and no build script: the crate uses only Rust’s core and alloc libraries and the operating system.
  • Specified. A public specification fixes every operation, size, byte format and error. The crate is tested against it and against the official test vectors.

Algorithms

Thirteen algorithm families, each a Cargo feature. The default features enable all of them.

Algorithm families with their standards, parameter sets and Cargo features
FamilyStandardParameter setsFeature
Key encapsulation
ML-KEMFIPS 203ML-KEM-512, ML-KEM-768, ML-KEM-1024ml-kem
FrodoKEM, eFrodoKEMISO/IEC 18033-2 Amd 2FrodoKEM-976 and -1344 and their ephemeral eFrodoKEM forms, each with AES or SHAKEfrodokem
Signatures
ML-DSAFIPS 204ML-DSA-44, ML-DSA-65, ML-DSA-87; pure and pre-hash (HashML-DSA)ml-dsa
SLH-DSA, HashSLH-DSAFIPS 205, RFC 990912 sets (SHA2 or SHAKE; 128, 192 or 256; s or f), pure and pre-hash, and the 12 HashSLH-DSA values of RFC 9909slh-dsa
Stateful hash-based signatures
HSS/LMSRFC 8554, RFC 9858, NIST SP 800-208LMS with SHA-256 or SHAKE256, m = 24 or 32, heights 5 to 25; LM-OTS with w = 1, 2, 4 or 8lms
XMSS, XMSSMTRFC 8391, NIST SP 800-208The SP 800-208 sets: SHA-2 or SHAKE256, n = 24 or 32; XMSS heights 10, 16 and 20; eight XMSSMT height and layer pairsxmss
Hashes, XOFs, MACs and KDFs
SHA-2FIPS 180-4SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256sha2
SHA-3FIPS 202, NIST SP 800-185SHA3-224, -256, -384, -512; SHAKE128, SHAKE256; cSHAKE128, cSHAKE256; KMAC128, KMAC256sha3
BLAKE2RFC 7693BLAKE2b-160, -256, -384, -512; BLAKE2s-128, -160, -224, -256; BLAKE2b-MAC, BLAKE2s-MACblake2
BLAKE3BLAKE3 specificationBLAKE3 (XOF), BLAKE3-256, BLAKE3-MAC, BLAKE3-KDFblake3
AsconNIST SP 800-232Ascon-Hash256, Ascon-XOF128, Ascon-CXOF128ascon
HMACFIPS 198-1HMAC-SHA-224, -256, -384, -512hmac
HKDFRFC 5869HKDF-SHA-256, -384, -512hkdf

ML-KEM, ML-DSA and SLH-DSA keys encode as raw bytes, DER and PEM; FrodoKEM keys as raw bytes; HSS/LMS and XMSS public keys as raw bytes, DER and PEM. Every parameter set and its sizes are in Section 5 of the specification.

Getting started

Add the crate to Cargo.toml.

The API documentation is on docs.rs.

[dependencies]
crypto-pq = "1.0.0"

To build only some algorithm families, turn off the default features and name the ones you need:

[dependencies.crypto-pq]
version = "1.0.0"
default-features = false
features = ["ml-kem", "hmac"]

Key encapsulation with ML-KEM-768, followed by key confirmation: the two sides hold the same secret when a MAC tag made with one copy verifies with the other. MAC verification runs in constant time, so the secrets themselves are never compared.

use crypto_pq::{Error, HMAC_SHA_256, KeyGenOptions, ML_KEM_768};

fn main() -> Result<(), Error> {
    let pair = ML_KEM_768.generate_key_pair(&KeyGenOptions::default())?;

    let encapsulation = pair.public_key.encapsulate()?;

    let shared_secret =
        pair.private_key.decapsulate(&encapsulation.ciphertext)?;

    let tag = HMAC_SHA_256
        .digest(&encapsulation.shared_secret, b"key confirmation")?;

    assert!(HMAC_SHA_256.verify(&shared_secret, b"key confirmation", &tag));

    Ok(())
}

The crate is no_std and needs alloc. CPU-specific code for x86-64 and ARM64 is selected at run time without any target CPU flag, with portable code as the fallback on every target. Randomness comes from the operating system; on a target without a source, such as bare metal, operations that draw randomness return Error::RngFailure.

Usage notes

Stateful keys
An HSS/LMS, XMSS or XMSSMT private key keeps its state in a StateStore that the application implements. Its update is an atomic, durable compare-and-swap, and a stored record is never restored from a copy or a backup.
hazmat
The opt-in hazmat feature provides deterministic operations that take seeds and randomness from the caller, for known-answer and interoperability tests.

Security

Secrets are handled in constant time and wiped when they are no longer needed, and randomness comes from the operating system.

  • Constant time. Code that handles secret data has no branch or memory access that depends on a secret. CI checks this under valgrind on x86-64 and ARM64, in the default and the portable builds.
  • Data-independent timing. On ARM64 CPUs with FEAT_DIT, asymmetric secret operations run with DIT set; enable_data_independent_timing() extends it to MACs and KDFs.
  • Wiping. Secret outputs are SecretBytes, which wipe themselves when dropped, and private keys are wiped on drop. Seeds, expanded keys and intermediate values are wiped on success and on every error path.
  • Randomness. Each random value is requested from the operating system for the operation that uses it, with no user-space generator and no fallback.
  • Self-tests. A pairwise consistency test runs at key generation by default and at every KEM private-key import, and every stateful signature is verified before it is released.

Section 14 of the specification states the security requirements. Report vulnerabilities privately through GitHub Security Advisories; the security policy describes the process.

Specification

A public specification, written in RFC style, is the contract that the crate is built and tested against.

It covers the API, the algorithms, the byte formats, the errors, the security requirements and the conformance rules. Its Rust profile states how this crate expresses it in Rust, and a decision log records the reasons behind its choices. Read it on GitHub.

Vectors
NIST ACVP and CAVP vectors, C2SP Wycheproof vectors, RFC test vectors, known-answer tests for Ascon, BLAKE2, BLAKE3 and FrodoKEM, XMSS vectors from the RFC 8391 reference implementation, and cross-check files for every error case, option and format.
Every push
The vectors in the repository, with every file checked against its SHA-256; the constant-time check under valgrind; Miri and AddressSanitizer.
Weekly
The full ACVP sets, the large-data tests, and long robustness runs that feed random and mutated key encodings, signatures, ciphertexts, state records and tree caches to every parser.
Platforms
Linux on x86-64 and ARM64, macOS on ARM64, Windows on x86-64 and ARM64, Linux musl, 32-bit x86, and RISC-V 64 and big-endian s390x under emulation; Rust 1.88 and stable.

Versions and releases

Releases follow Semantic Versioning, and the MAJOR.MINOR of each release is the version of the specification that it implements.

Compatibility
Within 1.x, no public item is removed or renamed and no specified behaviour changes. A minor release can add algorithms, options and error codes, and every 1.x release reads every byte format that an earlier 1.x release wrote.
Support
Only the latest release is supported, and security fixes ship as a new patch release.
Releases
Each starts from a signed tag on main. The crate is built on x86-64 and ARM64 and the two builds must match byte for byte; it gets a build provenance attestation, is published through crates.io trusted publishing after a maintainer’s approval, and is downloaded again and compared with the attested hash.

License and contact

Open source under Apache-2.0 OR MIT, at your option.

Source
github.com/crypterial/crypto-pq
Security
GitHub Security Advisories, privately
Contact
hello@crypterial.com