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;fnparse_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.