Post-Quantum Signature

🔐 Dilithium (ML-DSA) Module-Lattice Digital Signature

Security Level 128-256 bit
Performance ⭐⭐⭐⭐ Very Good
Quantum Resistant ✅ Yes
Signature Size 2420-4595 bytes
Key Sizes 1312-2592 bytes (public)
Standardization NIST FIPS 204

📖 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

Dilithium2
NIST Level 2
128-bit classical, 91-bit quantum
Dilithium3
Recommended
192-bit classical, 128-bit quantum
Dilithium5
High Security
256-bit classical, 186-bit quantum

📏 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

Dilithium3 Digital Signatures Python
```python from metamui_crypto import Dilithium3 # Key Generation keypair = Dilithium3.generate_keypair() # Sign a message message = b"Important document to sign" signature = Dilithium3.sign(message, keypair.private_key) # Verify the signature is_valid = Dilithium3.verify(signature, message, keypair.public_key) print(f"Signature valid: {is_valid}") # Check sizes print(f"Public key: {len(keypair.public_key)} bytes") print(f"Private key: {len(keypair.private_key)} bytes") print(f"Signature: {len(signature)} bytes") ```

Security Level Selection

Choosing the Right Dilithium Variant Python
```python from metamui_crypto import Dilithium2, Dilithium3, Dilithium5 def choose_security_level(security_requirement): """ Choose appropriate Dilithium variant based on security needs. """ if security_requirement == "standard": # For moderate security (128-bit classical) return Dilithium2.generate_keypair() elif security_requirement == "recommended": # For recommended security (192-bit classical) return Dilithium3.generate_keypair() elif security_requirement == "high": # For high security (256-bit classical) return Dilithium5.generate_keypair() else: raise ValueError("Invalid security requirement") # Generate keypairs for different security levels keypair_standard = choose_security_level("standard") keypair_recommended = choose_security_level("recommended") keypair_high = choose_security_level("high") # Usage is identical across variants message = b"Critical security document" signature = Dilithium3.sign(message, keypair_recommended.private_key) # Verify signature is_valid = Dilithium3.verify( signature, message, keypair_recommended.public_key ) print(f"Signature verification: {is_valid}") ```

FIPS 204 Hedged Signing

Hedged Signing for Enhanced Security Python
```python from metamui_crypto import Dilithium3 import os import time class SecureSigner: """ Secure signing implementation with hedged mode support. """ def __init__(self): self.keypair = Dilithium3.generate_keypair() def sign_deterministic(self, message): """Standard deterministic signing.""" return Dilithium3.sign(message, self.keypair.private_key) def sign_hedged(self, message): """Hedged signing with fresh randomness.""" # Generate fresh randomness for each signature randomness = os.urandom(32) return Dilithium3.sign_hedged( message, self.keypair.private_key, randomness ) def verify(self, signature, message): """Verify signature (works for both modes).""" return Dilithium3.verify( signature, message, self.keypair.public_key ) # Usage example signer = SecureSigner() message = b"Critical security update" # Both signing modes sig_deterministic = signer.sign_deterministic(message) sig_hedged = signer.sign_hedged(message) # Both signatures are valid assert signer.verify(sig_deterministic, message) assert signer.verify(sig_hedged, message) print("Both deterministic and hedged signatures verified successfully") ```

🔒 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

Best Practices for Dilithium Python
```python from metamui_crypto import Dilithium3 import os import logging class SecureDilithiumHandler: """ Secure wrapper for Dilithium operations. """ def __init__(self, security_level="recommended"): self.logger = logging.getLogger(__name__) self.failure_count = 0 self.max_failures = 10 # Choose appropriate variant if security_level == "recommended": self.dilithium = Dilithium3 else: raise ValueError("Unsupported security level") self.keypair = self.dilithium.generate_keypair() def sign_with_retry(self, message, max_attempts=3): """ Sign with retry logic for rejection sampling failures. """ for attempt in range(max_attempts): try: signature = self.dilithium.sign( message, self.keypair.private_key ) self.failure_count = 0 # Reset on success return signature except Exception as e: self.failure_count += 1 self.logger.warning( f"Signing attempt {attempt + 1} failed: {e}" ) if self.failure_count > self.max_failures: self.logger.error("Excessive signing failures detected") raise raise Exception(f"Failed to sign after {max_attempts} attempts") def verify_with_validation(self, signature, message, public_key): """ Verify signature with public key validation. """ # Validate public key format if not self._is_valid_public_key(public_key): raise ValueError("Invalid public key format") return self.dilithium.verify(signature, message, public_key) def _is_valid_public_key(self, public_key): """Validate public key format and size.""" expected_size = 1952 # Dilithium3 public key size return len(public_key) == expected_size ```

⚡ 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

Complete Document Signing Implementation Python
```python from metamui_crypto import Dilithium3, SHA256 import datetime import json class DocumentSigner: """ Complete document signing system with Dilithium. """ def __init__(self, private_key=None): if private_key: self.private_key = private_key else: keypair = Dilithium3.generate_keypair() self.private_key = keypair.private_key self.public_key = keypair.public_key def sign_document(self, document, metadata=None): """Sign document with metadata and timestamp.""" # Create signing structure signing_data = { "document_hash": SHA256.hash(document).hex(), "timestamp": datetime.datetime.utcnow().isoformat(), "algorithm": "dilithium3", "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(), "public_key": self.public_key.hex() } @staticmethod def verify_document(signed_doc): """Verify signed document integrity and authenticity.""" try: # Reconstruct signing data to_verify = json.dumps( signed_doc["signing_data"], sort_keys=True ).encode() # Verify signature signature = bytes.fromhex(signed_doc["signature"]) public_key = bytes.fromhex(signed_doc["public_key"]) sig_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 { "valid": sig_valid and hash_valid, "signature_valid": sig_valid, "hash_valid": hash_valid, "timestamp": signed_doc["signing_data"]["timestamp"] } except Exception as e: return { "valid": False, "error": str(e) } # Usage example signer = DocumentSigner() document = b"Important contract content" metadata = {"document_type": "contract", "version": "1.0"} # Sign document signed_doc = signer.sign_document(document, metadata) # Verify document result = DocumentSigner.verify_document(signed_doc) print(f"Document verification: {result}") ```

🚀 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

Classical + Post-Quantum Hybrid Signatures Python
```python from metamui_crypto import Dilithium3, Ed25519 class HybridSigner: """ Hybrid signer supporting both classical and post-quantum signatures. """ def __init__(self): self.classical_keypair = Ed25519.generate_keypair() self.pq_keypair = Dilithium3.generate_keypair() def sign_hybrid(self, message): """Create both classical and post-quantum signatures.""" classical_sig = Ed25519.sign( message, self.classical_keypair.private_key ) pq_sig = Dilithium3.sign( message, self.pq_keypair.private_key ) return { "message": message, "signatures": { "classical": classical_sig.hex(), "post_quantum": pq_sig.hex() }, "public_keys": { "classical": self.classical_keypair.public_key.hex(), "post_quantum": self.pq_keypair.public_key.hex() }, "algorithm_info": { "classical": "ed25519", "post_quantum": "dilithium3" } } @staticmethod def verify_hybrid(hybrid_signature, require_both=True): """Verify hybrid signature.""" message = hybrid_signature["message"] # Extract signatures and keys classical_sig = bytes.fromhex( hybrid_signature["signatures"]["classical"] ) pq_sig = bytes.fromhex( hybrid_signature["signatures"]["post_quantum"] ) classical_pubkey = bytes.fromhex( hybrid_signature["public_keys"]["classical"] ) pq_pubkey = bytes.fromhex( hybrid_signature["public_keys"]["post_quantum"] ) # Verify both signatures classical_valid = Ed25519.verify( classical_sig, message, classical_pubkey ) pq_valid = Dilithium3.verify( pq_sig, message, pq_pubkey ) if require_both: return classical_valid and pq_valid else: return classical_valid or pq_valid # Migration example hybrid = HybridSigner() message = b"Important transition document" # Phase 1: Generate hybrid signature hybrid_sig = hybrid.sign_hybrid(message) # Phase 2: Require both signatures result_strict = HybridSigner.verify_hybrid(hybrid_sig, require_both=True) print(f"Strict verification: {result_strict}") # Phase 3: Accept either (migration period) result_flexible = HybridSigner.verify_hybrid(hybrid_sig, require_both=False) print(f"Flexible verification: {result_flexible}") ```

⚠️ Common Pitfalls

1. Signature Malleability

Avoiding Signature Modification Python
```python # ❌ 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 ```

2. Incorrect Security Level Selection

Choosing Appropriate Security Level Python
```python # ❌ 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 for highest security: keypair = Dilithium5.generate_keypair() # 186-bit PQ security ```

3. Reusing Randomness in Hedged Mode

Proper Randomness Management Python
```python # ❌ 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)) ```

📚 References

🔧 Implementation Details

Core Components

  1. Polynomial Arithmetic
    • Ring: R_q = Z_q[X]/(X^256 + 1)
    • Modulus: q = 8380417 (23-bit prime)
    • Uses NTT for efficient multiplication
  2. Rejection Sampling
    • Ensures signatures don’t leak private key
    • May require multiple attempts
    • Constant-time implementation
  3. Hashing
    • Uses SHAKE-128 and SHAKE-256
    • Domain separation for different uses
    • Expandable output for sampling
  4. 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

  1. Polynomial Arithmetic
    • Ring: R_q = Z_q[X]/(X^256 + 1)
    • Modulus: q = 8380417 (23-bit prime)
    • Uses NTT for efficient multiplication
  2. Rejection Sampling
    • Ensures signatures don’t leak private key
    • May require multiple attempts
    • Constant-time implementation
  3. Hashing
    • Uses SHAKE-128 and SHAKE-256
    • Domain separation for different uses
    • Expandable output for sampling
  4. 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
  1. 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)
    
  2. 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)
        )
    
  3. 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.

  1. Use Appropriate Variant
    • Dilithium2: Moderate security, smaller signatures
    • Dilithium3: Recommended for most applications
    • Dilithium5: High security requirements
  2. Handle Failures Gracefully
    • Signing may fail due to rejection sampling
    • Implement appropriate retry logic
    • Monitor failure rates
  3. Secure Key Storage
    • Private keys are larger than classical
    • Use secure key management systems
    • Consider key compression for storage
  4. Performance Optimization
    • Batch verification when possible
    • Cache public key validations
    • Use appropriate variant for use case