Post-quantum signatures for audit logs: why records signed today must still check in 2046

Audit records outlive the algorithms that sign them. NIST's draft schedule retires EdDSA after 2035, so a log signed with Ed25519 today needs a second, post-quantum seal long before then.

The short answer

If your audit log is signed with Ed25519 and the records must stay checkable for ten years or more, add a second, post-quantum signature now. NIST’s draft transition plan, IR 8547, lists EdDSA, which covers Ed25519, as disallowed after 2035.1 A record written in 2026 and kept for twenty years will be checked in 2046. By then its Ed25519 signature alone may not be accepted, and once an algorithm can be forged, anyone holding a forgery can make a record that looks authentic.

The fix is not to swap algorithms. It’s to carry both: the Ed25519 signature every current tool reads, and an ML-DSA signature (FIPS 204) beside it. The hard part is doing that without letting someone strip the new signature off, and without breaking every record you already wrote.

The rules below apply to any signed log. Deedglass, the gate we build, implements them, so the commands and output are real.

Each action ends in a signed record: what ran, who approved it, what the check found. Records are chained, each one carrying the fingerprint of the one before, and they can be checked offline. Made-up record.

Why signatures have a shelf life

Encryption and signatures fail differently. With encryption, the worry is “harvest now, decrypt later”: someone stores your traffic today and reads it once they can break the key. With signatures, nothing secret leaks. The risk is that the signature stops meaning anything. If Ed25519 can be forged in 2040, a signed record from 2026 no longer tells a reader in 2040 that it is the record you wrote.

Audit records are the worst case for this, because their whole job is to be checked long after they were written. The reader often arrives years later, with only the record and your public keys.

Three things now exist that make a second seal practical:

  • The algorithm. ML-DSA is NIST’s standard post-quantum signature, FIPS 204.
  • The wire format. RFC 9964 (May 2026) defines ML-DSA for JOSE and COSE: COSE algorithm −49 for ML-DSA-65, key type AKP with the public key in pub, and an empty context string.2
  • The library. Go 1.27’s standard library includes crypto/mldsa, with 1,952-byte ML-DSA-65 public keys and 3,309-byte signatures.3 No new dependency.

Five rules for a second seal

Adding a second signature field is easy. Adding one that an attacker can’t simply delete takes rules. These are the five we wrote down, with attack cases, in the first pull request, before any of the new format’s code.

1. The signed body lists every seal it must carry. If the second signature sits only in the envelope, beside the body, nothing signed says it exists. Strip it, and you’re left with a record that looks like a valid old-format record. So the list of required seals goes inside the body, and every signature covers that list. In Deedglass, every seal of a v2 record names its algorithm, and the record lists every seal it must carry, so a removed seal is detected.4

2. Each algorithm signs a different, tagged input. Prefix the bytes with the format version and the algorithm name, so a signature made for one purpose is never accepted for another. In the spec, the Ed25519 seal signs deedglass/deed/v2/ed25519 plus the body hash, and the ML-DSA-65 seal signs deedglass/deed/v2/ml-dsa-65 plus the same hash.

3. Decide by the record’s date, not the verifier’s clock. A rule like “Ed25519 alone is fine until 2035” must read the date written in the record. Otherwise the same file gives one answer in 2026 and another in 2046, and nobody can reproduce an old check. From 2035-01-01 on, a record sealed with Ed25519 alone is refused (seal_missing); the date is read from the record’s issued_at, never from the verifier’s clock.4

4. An algorithm you can’t check is “cannot decide”, never a pass. A verifier from 2027 will meet records sealed with algorithms invented later. It shouldn’t call them altered, and it must not call them verified. It should say it can’t decide, and exit with a different code.

5. Every seal present must pass. One good seal never covers a bad one. If either signature fails, the record fails.

And one rule about the past: old records must keep verifying exactly as before. Records and bundles written before deed v2 verify as before: one bundle from every release since 0.1.0 is verified on every pull request.4 Every release keeps one sealed export bundle in the repo, and a required CI job verifies all of them with the CLI and the browser verifier, so a change that stops an old release’s records from verifying fails a required check.4 We call it the 2046 test.

What it looks like when it works

Here is deedglass verify from the v0.9.0 release, run against the published v2 test vectors. These are sealed with test keys, and the CLI says so. First, a dual-sealed record:

Code What the screen or file shows, copied as is.

$ deedglass verify testdata/deeds/v2/valid/03-dual-sealed.json --keys testdata/deeds/v2/keys.json
✓ 1 deed · seals intact · chain not checked · 2 seal keys
○ These deeds were sealed by Deedglass's published test keys. They mean nothing outside tests.
  next       Verify deeds from a real issuer against its published keys.

The same record with its ML-DSA-65 seal removed (rule 1):

Code What the screen or file shows, copied as is.

$ deedglass verify testdata/deeds/v2/invalid/pq-seal-stripped/deed.json --keys testdata/deeds/v2/keys.json
✕ Deed 1 is missing a seal it must carry (no ML-DSA-65 seal by sk_01m3xxf0m4wz8mwd5h76p09nn7).
  deed       deed_01m3xxj291vb6nxg25pympae2g (line 1)
  next       Treat this deed as altered, or ask the issuer for the deed as it was sealed.

A downgrade: rewritten as an old-format record and re-sealed with the Ed25519 key alone. It fails because the key set shows an ML-DSA-65 key that was active when the record says it was written:

Code What the screen or file shows, copied as is.

$ deedglass verify testdata/deeds/v2/invalid/pq-downgrade-to-v1/deed.json --keys testdata/deeds/v2/keys.json
✕ Deed 1 is missing a seal it must carry (the key set has a ML-DSA-65 key active at 2026-10-02T09:00:30.000Z, and this deed has no ML-DSA-65 seal).
  deed       deed_01m3xxj291vb6nxg25pympae2g (line 1)
  next       Treat this deed as altered, or ask the issuer for the deed as it was sealed.

Ed25519 alone, dated after the cut-off (rule 3):

Code What the screen or file shows, copied as is.

✕ Deed 1 is missing a seal it must carry (Ed25519 alone is accepted only for deeds issued before 2035-01-01T00:00:00.000Z).

And an algorithm this verifier doesn’t implement (rule 4). Exit code 2, not 1:

Code What the screen or file shows, copied as is.

○ Deed 1 is sealed with an algorithm this verifier can't check (SLH-DSA-SHA2-128s).
  next       Update deedglass, or check it with a verifier that supports that algorithm.

The same checks run in the browser. The browser verifier checks both seals offline with no runtime dependency.5 It checks ML-DSA-65 with its own dependency-free code and agrees with Go’s crypto/mldsa on every v2 vector and the 200-case v2 parity corpus. It is not a FIPS 140 validated module.6

What it costs

Post-quantum signatures are big and slower to check. Plan for both.

Size. An ML-DSA-65 signature is 3,309 bytes against Ed25519’s 64.3 Every record carries one, so storage grows by several kilobytes per record.

Speed. This is where we lose. Dual-sealed records are about 3.4 times slower to verify than Ed25519-only ones: 100,000 took 5.54 s against 1.63 s on an 8-core laptop. That misses our own 5 s budget for 100,000 records. Memory stays within 256 MiB.6 We publish the miss on our limitations page rather than relax the budget.

Operations. Turning it on is one command where the server’s database URL is set, then a restart of every instance with the new key file before the time it prints:

Code What the screen or file shows, copied as is.

$ deedglass keys rotate --alg ML-DSA-65

The Ed25519 key stays as it is (seal keys). Until you run that, nothing changes: an issuer with only an Ed25519 key keeps writing the old format, byte for byte, so older verifiers keep reading it.

What a second seal can’t do

Be clear about the limit. A date rule only helps against forgeries dated after your post-quantum key became active. Someone who can forge Ed25519 can also write any issued_at they like. So a forger in 2040 could write a fake record dated 2025, before your ML-DSA key existed, and the date rule alone would accept it.

What protects older history is the chain. In a hash-chained log, each record covers the hash of the one before it. A dual-sealed record therefore covers every record before it. A reader who keeps a recent dual-sealed head, or an outside timestamp, will see a forged earlier record as a broken link. That only works if the second seal starts early. Adding it in 2034 leaves eight years of records hanging on Ed25519 alone.

Two more limits. Our export-bundle manifest and the signed head we publish are still sealed with Ed25519 only, though the records inside a bundle are dual-sealed (limitations). And none of this says the action in a record was right or safe. A signature tells you a record is the one the issuer wrote. Whether the agent should have done it is a separate question, covered in why logs aren’t evidence.

A checklist

Whatever signs your logs, ask:

  1. How long must a record stay checkable? If the answer passes 2035, you need a second algorithm before then.
  2. Does the signed body list the seals it requires, so stripping one is detected?
  3. Does each algorithm sign a tagged input that names the format and the algorithm?
  4. Are date rules read from the record, never from the verifier’s clock?
  5. Is an unknown algorithm reported as “cannot decide”, separately from “altered”?
  6. Must every seal present pass?
  7. Is one record from every past release re-verified on every change?
  8. Have you measured verify time and storage with both seals, and written down the number?

If you can’t answer the first, start there.

To see records and their seals on a real Postgres, try the playground. To see which of your tables Deedglass could check, run npx deedglass scan --postgres-dsn-env DATABASE_URL (the grade, explained). The full rules are in the deed spec, and the agent action record glossary entry explains what a record holds.

Written with AI assistance and checked against our claims register.

The takeaway

Audit records outlive their signatures: NIST’s draft retires EdDSA after 2035, so logs you keep that long need a second, post-quantum seal.

Read nextWhy logs aren't evidence of what your AI agent did Try it: install in 7 s

Related on this site

More posts

Sources

  1. 1NIST IR 8547 (initial public draft): Transition to Post-Quantum Cryptography Standards · csrc.nist.gov · checked 25 October 2026
  2. 2RFC 9964: ML-DSA for JOSE and COSE · rfc-editor.org · checked 25 October 2026
  3. 3Go 1.27 release notes · go.dev · checked 25 October 2026
  4. 4deedglass/deedglass #62: deed v2, every seal names its algorithm · github.com · checked 25 October 2026
  5. 5deedglass/deedglass #64: the ML-DSA-65 dual seal · github.com · checked 25 October 2026
  6. 6Deedglass docs: Limitations of records · docs.deedglass.com · checked 25 October 2026
  7. 7Deedglass docs: Deed spec · docs.deedglass.com · checked 25 October 2026

Published 25 October 2026, sources last checked 25 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing