Pure Rust implementation of:
- CRYSTALS-Dilithium (
dilithium2,dilithium3,dilithium5) - ML-DSA (
ml_dsa_44,ml_dsa_65,ml_dsa_87)
The codebase is based on the reference implementation and includes test coverage against internal vectors and ACVP-style datasets.
This project has not undergone a formal third-party security audit. Use in production should include your own security review.
cargo build --releaseuse crystals_dilithium::dilithium2::Keypair;
let seed = [42u8; 32];
let msg = b"hello world";
let keypair = Keypair::generate(Some(&seed)).unwrap();
let signature = keypair.sign(msg);
let is_verified = keypair.verify(msg, &signature);
assert!(is_verified);use crystals_dilithium::ml_dsa_44::Keypair;
use crystals_dilithium::RandomMode;
let seed = [7u8; 32];
let msg = b"hello world";
let keypair = Keypair::generate(Some(&seed)).unwrap();
let sig = keypair.sign(msg, None, RandomMode::Deterministic).unwrap();
assert!(keypair.verify(msg, &sig, None));ML-DSA APIs support three signing/verification flows:
- Pure mode
- Sign:
sign(msg, ctx, rand) - Verify:
verify(msg, sig, ctx)
- Pre-hash mode
- Sign:
prehash_sign(msg, ctx, rand, ph) - Verify:
prehash_verify(msg, sig, ctx, ph) phis one of the supported pre-hash algorithms fromprehash::PH.
- Internal mode (ACVP/internal testing only)
- Requires feature:
acvp-internal - Sign/verify pre-built inputs:
sign_internal,verify_internal,sign_mu,verify_mu
RandomMode controls nonce/randomness behavior in ML-DSA signing:
RandomMode::Deterministic: deterministic signingRandomMode::Hedged: randomized (RNG-backed) signingRandomMode::Fixed(bytes): fixed randomness stream (acvp-internalonly)
Notes:
ctxis optional context data and must be at most 255 bytes.- API variants are available in
ml_dsa_44,ml_dsa_65, andml_dsa_87.
Pure mode:
use crystals_dilithium::ml_dsa_65::Keypair;
use crystals_dilithium::RandomMode;
let kp = Keypair::generate(Some(&[9u8; 32])).unwrap();
let msg = b"pure mode";
let sig = kp.sign(msg, Some(b"app:v1"), RandomMode::Deterministic).unwrap();
assert!(kp.verify(msg, &sig, Some(b"app:v1")));Pre-hash mode:
use crystals_dilithium::ml_dsa_65::Keypair;
use crystals_dilithium::prehash::PH;
use crystals_dilithium::RandomMode;
let kp = Keypair::generate(Some(&[10u8; 32])).unwrap();
let msg = b"prehash mode";
let sig = kp
.prehash_sign(msg, None, RandomMode::Hedged, PH::SHA256)
.unwrap();
assert!(kp.prehash_verify(msg, &sig, None, PH::SHA256));Internal mode (acvp-internal) is intended for ACVP/internal vectors only.
Key generation and key deserialization now return Result to avoid panics on malformed input.
use crystals_dilithium::dilithium2::PublicKey;
fn parse_public_key(bytes: &[u8]) -> Result<PublicKey, crystals_dilithium::Error> {
PublicKey::from_bytes(bytes)
}The same applies to:
Keypair::generate(...)Keypair::from_bytes(...)SecretKey::from_bytes(...)PublicKey::from_bytes(...)
cargo testACVP internal test vectors are feature-gated:
cargo test --test acvp_internal --features acvp-internalCriterion benchmarks:
cargo benchcargo doc --openGPL-3.0-only. See LICENSE.