GAP-Q1: Post-Quantum Signature Hardening - EPCC Implementation Plan
Version: 1.0.0 Created: 2025-12-27 Gap ID: GAP-Q1 Severity: MEDIUM (Future-proofing) Estimated Effort: 16-24 hours Dependencies: GAP-M4 (BIP-322 signatures) Status: ✅ IMPLEMENTED (2025-12-28)
Executive Summary
This plan implements post-quantum cryptographic (PQC) signature hardening for AEGIS governance workflows using NIST-standardized algorithms. The implementation uses a hybrid approach combining classical Ed25519 signatures with ML-DSA (Dilithium) post-quantum signatures for defense-in-depth against future quantum computing threats.
Current State
- Override workflow uses Ed25519 signatures (128-bit classical security)
- BIP-322/Schnorr planned for GAP-M4 (also quantum-vulnerable)
- No quantum-resistant signature capability
- Signatures stored as 64-byte blobs
Target State
- Hybrid signature scheme: Ed25519 + ML-DSA-44 (128-bit post-quantum security)
- Both signatures required for valid override
- Backward-compatible verification for migration period
- Future-proofed against cryptographically relevant quantum computers (CRQC)
1. Threat Model
1.1 Quantum Computing Timeline
| Timeframe | Threat Level | Action |
| 2025-2030 | Low | Monitor, plan migration |
| 2030-2035 | Medium | Implement hybrid signatures |
| 2035+ | High | Full PQC required |
NIST Recommendation: Begin migration planning now; hybrid deployment by 2030.
1.2 Attack Scenarios
| Scenario | Classical Risk | Post-Quantum Risk | Mitigation |
| Signature forgery | Infeasible | Shor's algorithm | ML-DSA hybrid |
| Key recovery | Infeasible | Grover's algorithm | 256-bit keys |
| Harvest-now-decrypt-later | N/A | High for long-lived data | Immediate concern for audit trails |
| Audit trail tampering | Infeasible | Could forge historical overrides | Hybrid signatures protect history |
1.3 Regulatory Drivers
- NSA CNSA 2.0: Requires PQC for national security systems by 2035
- NIST SP 800-208: Recommends hybrid signatures during transition
- PCI-DSS 4.0: Anticipates cryptographic agility requirements
- EU Cyber Resilience Act: May require quantum-safe cryptography
2. Algorithm Selection
2.1 NIST Standardized Algorithms
| Algorithm | FIPS | Type | Security | Signature Size | Public Key | Private Key |
| ML-DSA-44 | 204 | Lattice | 128-bit | 2,420 bytes | 1,312 bytes | 2,560 bytes |
| ML-DSA-65 | 204 | Lattice | 192-bit | 3,309 bytes | 1,952 bytes | 4,032 bytes |
| ML-DSA-87 | 204 | Lattice | 256-bit | 4,627 bytes | 2,592 bytes | 4,896 bytes |
| SLH-DSA-128f | 205 | Hash | 128-bit | 17,088 bytes | 32 bytes | 64 bytes |
2.2 Selected Configuration
Primary: ML-DSA-44 (Dilithium Level 2)
Rationale: 1. 128-bit security matches Ed25519 classical security 2. Smallest signature size among lattice schemes 3. Fast signing/verification (~0.5ms) 4. NIST standardized (FIPS 204) 5. Widely implemented in liboqs
2.3 Hybrid Scheme
HybridSignature = Ed25519(message) || ML-DSA-44(message)
Total size: 64 + 2,420 = 2,484 bytes
Verification: Both must pass
3. Architecture Design
3.1 Component Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ Override Workflow │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ DomainLead │ │ RiskOfficer │ │ Verification │ │
│ │ (Signer 1) │ │ (Signer 2) │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
└───────────┼────────────────────┼────────────────────┼───────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ HybridSignatureProvider │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ sign(message, keypair) → HybridSignature │ │
│ │ verify(signature, message, public_keys) → bool │ │
│ │ generate_keypair() → HybridKeyPair │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Ed25519Provider │ │ MLDSAProvider │ │ KeyStorage │
│ (cryptography) │ │ (liboqs-python) │ │ (encrypted) │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
3.2 Data Structures
from dataclasses import dataclass
from enum import Enum
from typing import Optional
class SignatureAlgorithm(str, Enum):
"""Signature algorithm identifiers."""
ED25519 = "Ed25519"
ML_DSA_44 = "ML-DSA-44"
ML_DSA_65 = "ML-DSA-65"
HYBRID_ED25519_ML_DSA_44 = "Ed25519+ML-DSA-44"
@dataclass
class HybridPublicKey:
"""Combined classical + post-quantum public key."""
classical: bytes # Ed25519 public key (32 bytes)
post_quantum: bytes # ML-DSA public key (1,312 bytes)
algorithm: SignatureAlgorithm = SignatureAlgorithm.HYBRID_ED25519_ML_DSA_44
@dataclass
class HybridPrivateKey:
"""Combined classical + post-quantum private key."""
classical: bytes # Ed25519 private key (32 bytes)
post_quantum: bytes # ML-DSA private key (2,560 bytes)
algorithm: SignatureAlgorithm = SignatureAlgorithm.HYBRID_ED25519_ML_DSA_44
@dataclass
class HybridKeyPair:
"""Complete hybrid key pair."""
public: HybridPublicKey
private: HybridPrivateKey
@dataclass
class HybridSignature:
"""Combined classical + post-quantum signature."""
classical: bytes # Ed25519 signature (64 bytes)
post_quantum: bytes # ML-DSA signature (2,420 bytes)
algorithm: SignatureAlgorithm = SignatureAlgorithm.HYBRID_ED25519_ML_DSA_44
def to_bytes(self) -> bytes:
"""Serialize to bytes for storage."""
return self.classical + self.post_quantum
@classmethod
def from_bytes(cls, data: bytes) -> "HybridSignature":
"""Deserialize from bytes."""
return cls(
classical=data[:64],
post_quantum=data[64:],
)
3.3 Database Schema Changes
-- Update WorkflowTransition to handle larger signatures
ALTER TABLE workflow_transitions
ALTER COLUMN signature TYPE BYTEA,
ADD COLUMN signature_algorithm VARCHAR(50) DEFAULT 'Ed25519';
-- Index for algorithm-based queries
CREATE INDEX idx_transition_algorithm ON workflow_transitions(signature_algorithm);
-- Key storage table (encrypted at rest)
CREATE TABLE governance_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id VARCHAR(100) NOT NULL UNIQUE,
public_key_classical BYTEA NOT NULL, -- 32 bytes
public_key_pq BYTEA NOT NULL, -- 1,312 bytes
encrypted_private_key BYTEA NOT NULL, -- Encrypted blob
algorithm VARCHAR(50) NOT NULL DEFAULT 'Ed25519+ML-DSA-44',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
rotated_at TIMESTAMP WITH TIME ZONE,
expires_at TIMESTAMP WITH TIME ZONE
);
4. Implementation Tasks
Phase 1: Foundation (8 hours)
4.1 Library Integration
- [ ] Add
liboqs-python>=0.9.0 to requirements.txt - [ ] Add
oqs type stubs or inline annotations - [ ] Create
src/crypto/ module directory - [ ] Implement
src/crypto/__init__.py with exports - [ ] Implement
src/crypto/pqc.py with ML-DSA wrapper
# src/crypto/pqc.py
"""Post-Quantum Cryptography primitives using liboqs."""
import oqs
from dataclasses import dataclass
from typing import Tuple
ALGORITHM = "Dilithium2" # ML-DSA-44
def generate_ml_dsa_keypair() -> Tuple[bytes, bytes]:
"""Generate ML-DSA-44 key pair.
Returns:
Tuple of (public_key, private_key)
"""
with oqs.Signature(ALGORITHM) as signer:
public_key = signer.generate_keypair()
private_key = signer.export_secret_key()
return public_key, private_key
def ml_dsa_sign(message: bytes, private_key: bytes) -> bytes:
"""Sign message with ML-DSA-44.
Args:
message: Message to sign
private_key: ML-DSA private key
Returns:
Signature bytes (~2,420 bytes)
"""
with oqs.Signature(ALGORITHM, private_key) as signer:
return signer.sign(message)
def ml_dsa_verify(signature: bytes, message: bytes, public_key: bytes) -> bool:
"""Verify ML-DSA-44 signature.
Args:
signature: Signature to verify
message: Original message
public_key: ML-DSA public key
Returns:
True if valid, False otherwise
"""
with oqs.Signature(ALGORITHM) as verifier:
return verifier.verify(message, signature, public_key)
4.2 Hybrid Signature Provider
- [ ] Implement
src/crypto/hybrid.py - [ ] Integrate Ed25519 from existing
cryptography library - [ ] Implement combined sign/verify logic
- [ ] Add serialization/deserialization
# src/crypto/hybrid.py
"""Hybrid signature scheme: Ed25519 + ML-DSA-44."""
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
Ed25519PublicKey,
)
from dataclasses import dataclass
from . import pqc
@dataclass
class HybridSignatureProvider:
"""Provider for hybrid classical + post-quantum signatures."""
def generate_keypair(self) -> HybridKeyPair:
"""Generate new hybrid key pair."""
# Classical (Ed25519)
classical_private = Ed25519PrivateKey.generate()
classical_public = classical_private.public_key()
# Post-quantum (ML-DSA-44)
pq_public, pq_private = pqc.generate_ml_dsa_keypair()
return HybridKeyPair(
public=HybridPublicKey(
classical=classical_public.public_bytes_raw(),
post_quantum=pq_public,
),
private=HybridPrivateKey(
classical=classical_private.private_bytes_raw(),
post_quantum=pq_private,
),
)
def sign(self, message: bytes, private_key: HybridPrivateKey) -> HybridSignature:
"""Create hybrid signature.
Both Ed25519 and ML-DSA signatures are generated.
"""
# Classical signature
ed_key = Ed25519PrivateKey.from_private_bytes(private_key.classical)
classical_sig = ed_key.sign(message)
# Post-quantum signature
pq_sig = pqc.ml_dsa_sign(message, private_key.post_quantum)
return HybridSignature(
classical=classical_sig,
post_quantum=pq_sig,
)
def verify(
self,
signature: HybridSignature,
message: bytes,
public_key: HybridPublicKey,
) -> bool:
"""Verify hybrid signature.
Both signatures must be valid.
"""
# Verify classical
try:
ed_key = Ed25519PublicKey.from_public_bytes(public_key.classical)
ed_key.verify(signature.classical, message)
classical_valid = True
except Exception:
classical_valid = False
# Verify post-quantum
pq_valid = pqc.ml_dsa_verify(
signature.post_quantum,
message,
public_key.post_quantum,
)
return classical_valid and pq_valid
Phase 2: Integration (8 hours)
4.3 Override Workflow Updates
- [ ] Update
OverrideRequest to support hybrid signatures - [ ] Update
_verify_signature() to use HybridSignatureProvider - [ ] Add backward compatibility for Ed25519-only verification
- [ ] Update
add_signature() to create hybrid signatures
4.4 Key Management
- [ ] Implement key generation CLI command
- [ ] Implement secure key storage (encrypted at rest)
- [ ] Add key rotation workflow
- [ ] Document key ceremony procedures
4.5 Database Migration
- [ ] Create Alembic migration for schema changes
- [ ] Add
signature_algorithm column - [ ] Update JSONB serialization for larger signatures
Phase 3: Testing (4 hours)
4.6 Unit Tests
- [ ] Test ML-DSA key generation
- [ ] Test ML-DSA sign/verify
- [ ] Test hybrid signature creation
- [ ] Test hybrid verification (both valid)
- [ ] Test hybrid verification (classical invalid)
- [ ] Test hybrid verification (PQ invalid)
- [ ] Test serialization round-trip
4.7 Integration Tests
- [ ] Test override workflow with hybrid signatures
- [ ] Test backward compatibility with Ed25519-only
- [ ] Test key rotation during active override
- [ ] Test database persistence of large signatures
Phase 4: Documentation (4 hours)
4.8 Documentation
- [ ] Update
override.py docstrings - [ ] Create
docs/security/post-quantum.md - [ ] Update ADR for quantum resistance decision
- [ ] Document key ceremony procedures
- [ ] Add migration guide for existing deployments
5. Migration Strategy
5.1 Migration Phases
Phase A: Preparation (Week 1-2)
├── Deploy liboqs-python to all environments
├── Generate hybrid key pairs for all governance actors
├── Update database schema
└── Deploy new code (backward compatible)
Phase B: Parallel Operation (Week 3-6)
├── New overrides use hybrid signatures
├── Old overrides remain valid (Ed25519 only)
├── Monitor for issues
└── Validate audit trail integrity
Phase C: Cutover (Week 7+)
├── Require hybrid signatures for new overrides
├── Mark Ed25519-only as deprecated
└── Plan Ed25519 sunset date
5.2 Backward Compatibility
def verify_signature(
signature: bytes,
message: bytes,
public_key: bytes,
algorithm: SignatureAlgorithm,
) -> bool:
"""Verify signature with algorithm-specific logic."""
if algorithm == SignatureAlgorithm.ED25519:
# Legacy Ed25519 only
return ed25519_verify(signature, message, public_key)
elif algorithm == SignatureAlgorithm.HYBRID_ED25519_ML_DSA_44:
# Hybrid verification
hybrid_sig = HybridSignature.from_bytes(signature)
hybrid_key = HybridPublicKey.from_bytes(public_key)
return provider.verify(hybrid_sig, message, hybrid_key)
else:
raise ValueError(f"Unknown algorithm: {algorithm}")
6.1 Benchmarks (Expected)
| Operation | Ed25519 | ML-DSA-44 | Hybrid |
| Key generation | 0.03 ms | 0.15 ms | 0.18 ms |
| Sign | 0.04 ms | 0.35 ms | 0.39 ms |
| Verify | 0.08 ms | 0.30 ms | 0.38 ms |
| Signature size | 64 B | 2,420 B | 2,484 B |
6.2 Storage Impact
| Component | Before | After | Increase |
| Signature | 64 B | 2,484 B | 38x |
| Public key | 32 B | 1,344 B | 42x |
| Private key | 32 B | 2,592 B | 81x |
| Override record | ~500 B | ~5.5 KB | 11x |
6.3 Mitigations
- Compression: Signatures compress well (30-40% reduction)
- Batch verification: Verify multiple signatures in parallel
- Lazy loading: Only load full signature when needed
- Archival: Move old overrides to cold storage
7. Security Considerations
7.1 Key Protection
- Private keys encrypted at rest (AES-256-GCM)
- Key derivation using Argon2id
- HSM support for production (future enhancement)
- Key rotation every 12 months
7.2 Side-Channel Resistance
- liboqs uses constant-time implementations
- No secret-dependent branching
- Memory is zeroed after use
7.3 Algorithm Agility
SignatureAlgorithm enum allows future additions - Schema supports algorithm versioning
- Can add ML-DSA-65/87 if security requirements increase
8. Dependencies
8.1 Library Requirements
# requirements.txt additions
liboqs-python>=0.9.0 # Post-quantum algorithms
8.2 System Requirements
- liboqs native library (installed via pip)
- OpenSSL 3.0+ (for Ed25519)
- 64-bit architecture required
8.3 Gap Dependencies
GAP-M4 (BIP-322) ──────► GAP-Q1 (Post-Quantum)
│
├── liboqs-python
├── Database schema update
└── Key management infrastructure
9. Acceptance Criteria
9.1 Functional
- [ ] Hybrid signatures can be generated
- [ ] Hybrid signatures can be verified
- [ ] Override workflow accepts hybrid signatures
- [ ] Backward compatibility with Ed25519-only
- [ ] Key generation produces valid pairs
- [ ] Database stores/retrieves large signatures
9.2 Non-Functional
- [ ] Verification < 1ms per signature
- [ ] No memory leaks in signing loop
- [ ] 100% test coverage for crypto module
- [ ] Security audit passed (internal)
9.3 Documentation
- [ ] API documentation complete
- [ ] Migration guide written
- [ ] Key ceremony documented
- [ ] Security analysis documented
10. Risks and Mitigations
| Risk | Probability | Impact | Mitigation |
| liboqs instability | Low | High | Pin version, monitor CVEs |
| Performance regression | Low | Medium | Benchmark, lazy loading |
| Key management complexity | Medium | High | Clear procedures, automation |
| Algorithm deprecation | Low | High | Algorithm agility in design |
| HSM incompatibility | Medium | Medium | Software fallback |
11. References
Standards
Libraries
Research
Changelog
| Version | Date | Author | Changes |
| 1.0.0 | 2025-12-27 | Claude Code | Initial implementation plan |