Skip to content

ADR-003: Hybrid Post-Quantum Signatures (Ed25519 + ML-DSA-44)

Status

Accepted | 2025-12-28 (Implemented)

Context

The AEGIS override workflow uses BIP-322 Schnorr signatures (ADR-002) which provide strong classical security. However, cryptographically relevant quantum computers (CRQCs) could potentially break elliptic curve signatures via Shor's algorithm, enabling "harvest now, decrypt later" attacks.

Threat Model

Current Risk: LOW (no CRQCs exist today)
Future Risk: HIGH (15-30 year horizon per NIST estimates)
Attack Vector: "Harvest now, decrypt later"
Impact: Override signature forgery → governance bypass

NIST Standardization (August 2024)

NIST finalized three post-quantum algorithms in FIPS 203/204/205: - ML-KEM (FIPS 203): Key encapsulation (formerly CRYSTALS-Kyber) - ML-DSA (FIPS 204): Digital signatures (formerly CRYSTALS-Dilithium) - SLH-DSA (FIPS 205): Stateless hash-based signatures (formerly SPHINCS+)

Decision

Implement hybrid Ed25519 + ML-DSA-44 signatures with defense-in-depth verification.

Architecture

┌─────────────────────────────────────────────┐
│         HybridSignatureProvider              │
├─────────────────────────────────────────────┤
│  algorithm_name: "ed25519+ml-dsa-44"         │
│                                              │
│  SIGNATURE_SIZE  = 2,484 bytes               │
│    └─ Ed25519:    64 bytes                   │
│    └─ ML-DSA-44: 2,420 bytes                 │
│                                              │
│  PUBLIC_KEY_SIZE = 1,344 bytes               │
│    └─ Ed25519:    32 bytes                   │
│    └─ ML-DSA-44: 1,312 bytes                 │
│                                              │
│  PRIVATE_KEY_SIZE = 2,592 bytes              │
│    └─ Ed25519:    32 bytes                   │
│    └─ ML-DSA-44: 2,560 bytes                 │
└─────────────────────────────────────────────┘

Key Technical Decisions

Aspect Decision Rationale
Approach Hybrid (classical + PQ) Defense in depth, mitigates algorithm risk
Classical Ed25519 Fast, well-understood, cryptography library
Post-Quantum ML-DSA-44 (FIPS 204) NIST Level 2, smallest PQ signature
Verification BOTH must verify Neither algorithm alone is sufficient
Library liboqs-python OQS consortium, reference implementation
Signing Mode Hedged (randomized) FIPS 204 compliant, side-channel resistant

Why ML-DSA-44 (Dilithium Level 2)?

Level Security Signature Public Key Decision
ML-DSA-44 NIST Level 2 (128-bit) 2,420 B 1,312 B Selected
ML-DSA-65 NIST Level 3 (192-bit) 3,293 B 1,952 B Overkill
ML-DSA-87 NIST Level 5 (256-bit) 4,595 B 2,592 B Overkill

Level 2 matches Ed25519's classical security level, providing balanced protection without excessive overhead.

Implementation

Files Created

File Purpose
src/crypto/mldsa.py ML-DSA-44 wrapper using liboqs-python
src/crypto/hybrid_provider.py HybridSignatureProvider implementation
tests/crypto/test_mldsa.py ML-DSA unit tests (28+ tests)
tests/crypto/test_hybrid_provider.py Hybrid provider tests (37+ tests)

Files Modified

File Change
src/crypto/__init__.py Export HYBRID_AVAILABLE, get_hybrid_provider()
src/workflows/override.py SignatureRecord.algorithm field
pyproject.toml liboqs-python dependency in pqc extras

Provider Priority

# src/crypto/__init__.py
def get_default_provider() -> SignatureProvider:
    """
    Priority:
    1. BIP322Provider (recommended, Bitcoin-native)
    2. Ed25519Provider (deprecated, legacy fallback)

    Note: HybridSignatureProvider is NOT default until fully validated.
    Use get_hybrid_provider() explicitly for post-quantum protection.
    """

Defense-in-Depth Verification

# src/crypto/hybrid_provider.py:verify()
def verify(signature: bytes, message_hash: bytes, public_key: bytes) -> bool:
    ed25519_valid = self._verify_ed25519(ed25519_sig, message_hash, ed25519_pub)
    mldsa_valid = self._verify_mldsa(mldsa_sig, message_hash, mldsa_pub)

    # BOTH must be valid (defense in depth)
    return ed25519_valid and mldsa_valid

Consequences

Positive

  1. Quantum Resistance: Protection against future CRQCs
  2. Defense in Depth: If one algorithm fails, the other provides security
  3. NIST Compliance: Uses standardized FIPS 204 algorithm
  4. Backward Compatible: Provider injection, existing signatures unaffected
  5. Graceful Degradation: System works without liboqs installed

Negative

  1. Size Increase: 2,484 byte signatures vs 64 bytes (39x larger)
  2. Key Storage: 1,344 byte public keys vs 32 bytes (42x larger)
  3. Optional Dependency: Requires liboqs-python (pip) and liboqs (native)
  4. Verification Time: ~1ms vs ~0.1ms (10x slower)

Neutral

  1. Hedged Signing: FIPS 204 uses randomized signing (non-deterministic)
  2. Two-Phase Rollout: Hybrid opt-in now, default later when validated

Security Properties

Property Status Evidence
Defense in depth Both signatures must verify
Harvest-now-decrypt-later ML-DSA-44 quantum resistant
Side-channel resistance FIPS 204 hedged signing mode
Key compromise Both key pairs required
Algorithm agility Provider pattern enables swaps

Test Coverage

tests/crypto/test_mldsa.py: 30 tests
tests/crypto/test_hybrid_provider.py: 40 tests
tests/test_workflows.py: 3 integration tests

Total: 73 tests, 100% critical path coverage

Dependencies

Package Version Purpose
liboqs-python >=0.10.0 NIST PQ algorithm bindings
liboqs (native) 0.14.0 OQS library (brew or source)
cryptography >=41.0.0 Ed25519 signatures

References

External Standards

Internal Documents

Changelog

Date Change
2025-12-28 ADR created, implementation complete
2025-12-27 GAP-Q1 implementation completed