🔐 Dilithium (ML-DSA) Module-Lattice Digital Signature
📋 Quick Navigation
📖 Overview
Dilithium is NIST's standardized Module-Lattice-Based Digital Signature Algorithm (ML-DSA), providing post-quantum secure digital signatures. It offers strong security guarantees against both classical and quantum attacks with reasonable signature sizes and excellent performance.
✨ Key Features
Quantum-Resistant
Based on Module-LWE and Module-SIS problems
High Performance
Fast signing and verification operations
Deterministic
Reproducible signatures with hedged variant
NIST Standardized
Official FIPS 204 ML-DSA standard
Multiple Variants
Dilithium2, 3, and 5 for different security levels
Strong Unforgeability
Secure against signature forgery attacks
🎯 Common Use Cases
🔏 Digital Identity
- Certificate authorities and PKI
- Code signing for software distribution
- Document authentication and integrity
- Legal and financial document signing
🌐 Network Security
- TLS certificate signing
- Firmware and update authentication
- Blockchain and distributed ledger signatures
- IoT device authentication
🔧 Algorithm Details
📊 Dilithium Variants
📏 Size Comparison
| Variant | Public Key | Private Key | Signature | Security Level |
|---|---|---|---|---|
| Dilithium2 | 1312 bytes | 2528 bytes | 2420 bytes | NIST Level 2 |
| Dilithium3 | 1952 bytes | 4000 bytes | 3293 bytes | NIST Level 3 |
| Dilithium5 | 2592 bytes | 4864 bytes | 4595 bytes | NIST Level 5 |
🧮 Algorithm Structure
Dilithium operates over polynomial rings with the following structure:
R_q = Z_q[X]/(X^256 + 1), q = 8380417
Uses NTT for efficient polynomial arithmetic and rejection sampling for security.
💻 Implementation
Dilithium provides quantum-resistant digital signatures with multiple security levels. Choose the appropriate variant based on your security requirements and performance constraints.
Basic Signing and Verification
Security Level Selection
FIPS 204 Hedged Signing
🔒 Security Considerations
⚠️ Important Security Guidelines
- Choose appropriate security level for your threat model
- Use fresh randomness for hedged signing mode
- Implement proper key storage and management
- Monitor signature generation failure rates
- Validate all public keys before use
Secure Implementation Patterns
⚡ Performance Analysis
Operation Benchmarks
| Operation | Dilithium2 | Dilithium3 | Dilithium5 |
|---|---|---|---|
| Key Generation | 200 μs | 350 μs | 500 μs |
| Sign | 500 μs | 1.2 ms | 1.8 ms |
| Verify | 200 μs | 350 μs | 500 μs |
Comparison with Classical Algorithms
| Algorithm | KeyGen | Sign | Verify | Signature Size | Quantum Safe |
|---|---|---|---|---|---|
| Dilithium3 | 350 μs | 1.2 ms | 350 μs | 3293 bytes | ✅ Yes |
| RSA-2048 | 100 ms | 5 ms | 0.2 ms | 256 bytes | ❌ No |
| Ed25519 | 25 μs | 50 μs | 100 μs | 64 bytes | ❌ No |
| ECDSA-P256 | 200 μs | 300 μs | 600 μs | 64 bytes | ❌ No |
🔄 Integration Guide
Document Signing System
🚀 Migration Strategy
Gradual Migration Path
Phase 1: Add Dilithium alongside existing
Begin generating Dilithium signatures alongside classical ones
Phase 2: Require both signatures
Validate both classical and post-quantum signatures
Phase 3: Migrate to PQ-only
Phase out classical signatures and use Dilithium exclusively
Hybrid Signing Implementation
⚠️ Common Pitfalls
1. Signature Malleability
2. Incorrect Security Level Selection
3. Reusing Randomness in Hedged Mode
📚 References
- NIST FIPS 204 - ML-DSA Standard
- Original Dilithium Paper - Algorithm details
- Security Analysis - Dilithium security properties
- NIST PQC Project - Standardization process
🔗 Related Algorithms
🔧 Implementation Details
Core Components
- Polynomial Arithmetic
- Ring: R_q = Z_q[X]/(X^256 + 1)
- Modulus: q = 8380417 (23-bit prime)
- Uses NTT for efficient multiplication
- Rejection Sampling
- Ensures signatures don’t leak private key
- May require multiple attempts
- Constant-time implementation
- Hashing
- Uses SHAKE-128 and SHAKE-256
- Domain separation for different uses
- Expandable output for sampling
- Compression
- Reduces signature size
- Hint-based representation
- Maintains security level
Security Features
- Strong Unforgeability: Cannot forge signatures even with signature oracle
- Side-Channel Protection: Constant-time operations where critical
- Fault Attack Resistance: Verification in signing prevents certain attacks
- Hedged Signing: Additional randomness for enhanced security
Algorithm Structure
- Polynomial Arithmetic
- Ring: R_q = Z_q[X]/(X^256 + 1)
- Modulus: q = 8380417 (23-bit prime)
- Uses NTT for efficient multiplication
- Rejection Sampling
- Ensures signatures don’t leak private key
- May require multiple attempts
- Constant-time implementation
- Hashing
- Uses SHAKE-128 and SHAKE-256
- Domain separation for different uses
- Expandable output for sampling
- Compression
- Reduces signature size
- Hint-based representation
- Maintains security level
Security Features
- Strong Unforgeability: Cannot forge signatures even with signature oracle
- Side-Channel Protection: Constant-time operations where critical
- Fault Attack Resistance: Verification in signing prevents certain attacks
- Hedged Signing: Additional randomness for enhanced security
| Operation | Dilithium2 | Dilithium3 | Dilithium5 |
|---|---|---|---|
| Key Generation | 200 μs | 350 μs | 500 μs |
| Sign | 500 μs | 1.2 ms | 1.8 ms |
| Verify | 200 μs | 350 μs | 500 μs |
| Algorithm | KeyGen | Sign | Verify | Signature Size |
|---|---|---|---|---|
| Dilithium3 | 350 μs | 1.2 ms | 350 μs | 3293 bytes |
| RSA-2048 | 100 ms | 5 ms | 0.2 ms | 256 bytes |
| Ed25519 | 25 μs | 50 μs | 100 μs | 64 bytes |
| ECDSA-P256 | 200 μs | 300 μs | 600 μs | 64 bytes |
from metamui_crypto import Dilithium3, SHA256
import datetime
import json
class DocumentSigner:
def __init__(self, private_key):
self.private_key = private_key
def sign_document(self, document, metadata=None):
# Create signing structure
signing_data = {
"document_hash": SHA256.hash(document).hex(),
"timestamp": datetime.datetime.utcnow().isoformat(),
"metadata": metadata or {}
}
# Serialize for signing
to_sign = json.dumps(signing_data, sort_keys=True).encode()
# Create signature
signature = Dilithium3.sign(to_sign, self.private_key)
return {
"document": document,
"signing_data": signing_data,
"signature": signature.hex()
}
@staticmethod
def verify_document(signed_doc, public_key):
# Reconstruct signing data
to_verify = json.dumps(
signed_doc["signing_data"],
sort_keys=True
).encode()
# Verify signature
signature = bytes.fromhex(signed_doc["signature"])
is_valid = Dilithium3.verify(
signature,
to_verify,
public_key
)
# Verify document hash
doc_hash = SHA256.hash(signed_doc["document"]).hex()
hash_valid = doc_hash == signed_doc["signing_data"]["document_hash"]
return is_valid and hash_valid
from metamui_crypto import Dilithium3, Ed25519
class HybridSigner:
def __init__(self):
self.classical_keypair = Ed25519.generate_keypair()
self.pq_keypair = Dilithium3.generate_keypair()
def sign(self, message):
# Create both signatures
classical_sig = Ed25519.sign(
message,
self.classical_keypair.private_key
)
pq_sig = Dilithium3.sign(
message,
self.pq_keypair.private_key
)
return {
"classical": classical_sig,
"post_quantum": pq_sig,
"public_keys": {
"classical": self.classical_keypair.public_key,
"post_quantum": self.pq_keypair.public_key
}
}
@staticmethod
def verify(message, hybrid_signature):
# Both must verify
classical_valid = Ed25519.verify(
hybrid_signature["classical"],
message,
hybrid_signature["public_keys"]["classical"]
)
pq_valid = Dilithium3.verify(
hybrid_signature["post_quantum"],
message,
hybrid_signature["public_keys"]["post_quantum"]
)
return classical_valid and pq_valid
- Phase 1: Add Dilithium alongside existing signatures
# Existing code rsa_signature = rsa_sign(message) # Add Dilithium dilithium_signature = Dilithium3.sign(message, dilithium_key) store_both_signatures(rsa_signature, dilithium_signature) - Phase 2: Require both signatures
def verify_transitional(message, signatures): # Both must be valid during transition return ( verify_rsa(signatures["rsa"], message) and Dilithium3.verify(signatures["dilithium"], message) ) - Phase 3: Phase out classical signatures
# Eventually move to PQ-only signature = Dilithium3.sign(message, private_key)
# Dilithium signatures may have multiple valid forms
# Always use the signature as-is from signing
# Bad: Modifying signature representation
# signature_modified = modify_signature_format(signature)
# Good: Use signature exactly as returned
signature = Dilithium3.sign(message, private_key)
store_signature(signature) # Store exactly as-is
# Bad: Using Dilithium2 for high-security applications
# keypair = Dilithium2.generate_keypair() # Only 91-bit PQ security
# Good: Choose appropriate security level
keypair = Dilithium3.generate_keypair() # 128-bit PQ security
# or
keypair = Dilithium5.generate_keypair() # 186-bit PQ security
# Bad: Reusing randomness
# rand = os.urandom(32)
# sig1 = Dilithium3.sign_hedged(msg1, key, rand)
# sig2 = Dilithium3.sign_hedged(msg2, key, rand) # Same randomness!
# Good: Fresh randomness each time
sig1 = Dilithium3.sign_hedged(msg1, key, os.urandom(32))
sig2 = Dilithium3.sign_hedged(msg2, key, os.urandom(32))
🧪 Test Vectors
# NIST test vector example (Dilithium3)
test_vector = {
"seed": "7c99935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2db",
"message": "d81c4d8d734fcbfbeade3d3f8a039faa2a2c9957e835ad55b22e75bf57bb556ac8",
"signature": "ae09...", # 3293 bytes
"public_key": "b301...", # 1952 bytes
}
# Verify implementation
from metamui_crypto import Dilithium3
keypair = Dilithium3.keypair_from_seed(bytes.fromhex(test_vector["seed"]))
message = bytes.fromhex(test_vector["message"])
signature = Dilithium3.sign(message, keypair.private_key)
# Verify signature matches test vector
assert signature.hex() == test_vector["signature"]
✅ Best Practices
✅ Use Appropriate Variant
Choose Dilithium2/3/5 based on security requirements and performance constraints.
✅ Handle Failures Gracefully
Implement retry logic for rejection sampling failures with monitoring.
✅ Secure Key Storage
Use proper key management systems for larger post-quantum keys.
✅ Performance Optimization
Use batch verification and cache validations when possible.
- Use Appropriate Variant
- Dilithium2: Moderate security, smaller signatures
- Dilithium3: Recommended for most applications
- Dilithium5: High security requirements
- Handle Failures Gracefully
- Signing may fail due to rejection sampling
- Implement appropriate retry logic
- Monitor failure rates
- Secure Key Storage
- Private keys are larger than classical
- Use secure key management systems
- Consider key compression for storage
- Performance Optimization
- Batch verification when possible
- Cache public key validations
- Use appropriate variant for use case
- NIST FIPS 204 - ML-DSA Standard
- Original Dilithium Paper - Algorithm details
- Security Analysis - Security properties
- Implementation Notes - Implementation details
- NIST PQC Project - Standardization process