Post-quantum crypto liboqs-backed. Every NIST PQC algorithm digger picks up at runtime — FIPS-finalized, Round 4, and the additional signature on-ramp.

Why PQC?

Classical signatures (RSA, ECDSA) become forgeable once a sufficiently large quantum computer exists. Evidence stores you sign today are expected to remain forensically valid for years; signing them with a post-quantum algorithm now means the signature is durable across that horizon (harvest-now-decrypt-later doesn't apply to signatures the way it does to KEM-encapsulated secrets, but durable non-repudiation does).

digger uses post-quantum signatures for evidence-chain attestation and post-quantum KEM + AES-256-GCM hybrid encryption for case-bundle archival.

Backend

We delegate to liboqs via the oqs-python binding. liboqs is the canonical reference implementation tracking every algorithm in the NIST PQC standardization process. Re-implementing PQC primitives is a footgun; we don't.

The set of algorithms actually usable on your host is exactly whatever your installed liboqs build exposes:

$ digger pqc info --mode fips

or, in code:

from digger.crypto import available_kems, available_sigs
print(available_sigs())   # list[str]
print(available_kems())

Algorithm coverage

FIPS-finalized

The algorithms NIST has published as Federal Information Processing Standards. These are the only algorithms allowed under FIPS mode.

FIPSFamilyAlgorithms
203Module-Lattice KEM (Kyber)ML-KEM-512, ML-KEM-768, ML-KEM-1024
204Module-Lattice DSA (Dilithium)ML-DSA-44, ML-DSA-65, ML-DSA-87
205Stateless Hash-Based DSA (SPHINCS+)SLH-DSA-SHA2-{128,192,256}{s,f}, SLH-DSA-SHAKE-{128,192,256}{s,f}
206 (draft)FN-DSA (Falcon)Falcon-512, Falcon-1024, Falcon-padded-{512,1024}

NIST Round 4

Algorithms still under active review for additional KEM diversity.

Signature on-ramp

Round 1 of the additional-signature competition that NIST opened to diversify beyond lattice-based signatures. Names below track liboqs identifiers — digger autodetects whatever your build enables.

Included in our tables (and matched against availability): CROSS (RSDP / RSDPG, balanced/fast/small variants), MAYO-1/2/3/5, OV / UOV (Is, Ip, III, V with pkc / pkc-skc), SNOVA (24_5_4, 37_17_2, …), HAWK-512 / 1024, FAEST (regular and EM variants), LESS, MiRitH, MQOM (gf31, gf251), PERK, QR-UOV, RYDE, SDitH (gf256, gf251), SQIsign-I / III / V.

This is a moving target. Upgrade liboqs to pick up new ones — digger needs no code changes.

Signing the evidence chain

The signed payload is canonical bytes derived from the chain tip: the current artifact-table chain hash, the current finding-table chain hash, and the case_id. This binds the signature to a specific case state.

digger pqc sign --case-dir ./case-1 \
       --algorithm ML-DSA-65 \
       --key /secrets/digger.sk \
       --note "investigator: analyst; engagement: q2-2026-ir"
--algorithm
Default ML-DSA-65. Anything in available_sigs().
--key
Secret key file. Auto-generated (chmod 600) plus .pub if it doesn't exist.
--note
Free-form note baked into the signature bundle.

Output is case_signature.json in the case directory:

{
  "algorithm": "ML-DSA-65",
  "public_key_b64": "…",
  "signature_b64": "…",
  "message_sha256": "…",
  "created": 1747780000.0,
  "note": "investigator: analyst; engagement: q2-2026-ir",
  "scheme": "NIST-PQC"
}

To verify later:

digger pqc verify --case-dir ./case-1

Exit code 0 on success, 1 on failure.

Hybrid PQC-KEM + AES-256-GCM encryption

For archiving a case bundle to untrusted storage, use the hybrid encryption helpers in digger/crypto/pqc.py:

from digger.crypto import PQCBackend, hybrid_encrypt, hybrid_decrypt

backend = PQCBackend(kem_alg="ML-KEM-768")
pk, sk = backend.generate_kem_key()                # recipient keypair

blob = hybrid_encrypt(b"<evidence bytes>", pk)    # → dict with KEM ciphertext + AES-GCM ciphertext
plaintext = hybrid_decrypt(blob, sk)               # recovers original

The shared secret from the KEM is run through HKDF-SHA256 to derive a 256-bit AES-GCM key with a per-message random nonce. Associated-data binding prevents cross-context replay.

FIPS-mode interaction

When FIPS mode is on, PQCBackend.generate_signing_key() and generate_kem_key() call assert_approved_sig() / assert_approved_kem() which raise FIPSViolation for non-finalized algorithms:

$ digger --fips-mode pqc sign --case-dir x --algorithm CROSS-rsdp-128-balanced ...
digger.fips.mode.FIPSViolation: signature algorithm 'CROSS-rsdp-128-balanced' is not FIPS-approved.
In FIPS mode you must use one of ['Falcon-1024', 'Falcon-512', 'Falcon-padded-1024',
'Falcon-padded-512', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'SLH-DSA-SHA2-128f', ...]

When FIPS mode is off, every algorithm liboqs supports is fair game.

Notes on actual FIPS 140-3 compliance

Important. This module is a user-mode wrapper. True FIPS 140-3 compliance also requires: digger's FIPS mode enforces algorithm restrictions and runs known-answer tests, but does not by itself constitute a validated cryptographic module.