Skip to content

Hybrid post-quantum (Ed25519+ML-DSA-65) narinfo signatures - #202

Closed
Tristan-Stoltz-ERC wants to merge 1 commit into
NixOS:masterfrom
Tristan-Stoltz-ERC:hybrid-binary-cache-signatures
Closed

Hybrid post-quantum (Ed25519+ML-DSA-65) narinfo signatures#202
Tristan-Stoltz-ERC wants to merge 1 commit into
NixOS:masterfrom
Tristan-Stoltz-ERC:hybrid-binary-cache-signatures

Conversation

@Tristan-Stoltz-ERC

Copy link
Copy Markdown

This proposes an optional, additive Sig-PQC: narinfo field carrying a
hybrid Ed25519+ML-DSA-65 signature, so binary caches can begin dual-signing
years ahead of any cryptographically-relevant quantum computer (CRQC)
existing, without breaking any existing nix client.

Why now

Nix's binary-cache trust is 100% classical Ed25519. Store-path/NAR hashing
(SHA-256) retains a large security margin under Grover's algorithm, but
Ed25519 is broken outright by Shor's algorithm given a CRQC -- and every
narinfo a compromised key ever signed becomes forgeable, retroactively.
Signing keys and trusted-public-keys configuration are long-lived, so
there's real value in caches dual-signing well before enforcement is ever
needed, rather than racing a migration under pressure later.

What this RFC does and doesn't propose

  • Standardizes the wire format: an additive Sig-PQC: field, paired
    with the existing Sig: line by key name. Existing nix versions need
    zero changes -- narinfo parsing already silently ignores unrecognized
    fields.
  • Leaves enforcement policy (a new require-pqc-sigs setting) as an
    implementation detail, separate from the format itself.
  • Does not propose changing the on-disk SQLite schema, JSON path-info
    format versions, or nix-store --generate-binary-cache-key's default
    output.
  • Does not make cache.nixos.org itself PQC-signed, or touch Nix's
    C++/Rust source at all yet -- see "Unresolved questions" for the
    cppnix vs tvix question.

Supporting prototype

A working, tested, AGPL-3.0-or-later licensed prototype backs this
proposal: https://github.com/Luminous-Dynamics/nix-pqc-cache-proxy

It independently reimplements Nix's real narinfo fingerprint algorithm and
verifies it byte-for-byte against a real cache.nixos.org narinfo and its
real published key; dual-signs a local cache and runs a small reverse
proxy demonstrating the mechanics; and proves, via nix store verify
against a real unmodified nix binary, that a client trusting only the
new hybrid key correctly trusts a dual-signed path and correctly refuses
an untrusted one. It ships a JSON test-vector suite so an independent
implementation (e.g. in tvix) can check itself without reading the
prototype's source, and builds/tests cleanly from a fresh clone with zero
private dependencies.

The prototype's crypto is explicitly unaudited -- it exists to prove
the wire format and trust-model mechanics work, not as a production
crypto library.

All claims in the RFC about current Nix source (file paths, function
names, trust logic) were verified directly against NixOS/nix at a
pinned commit, cited in the RFC text.

Where I'd like the most help

  • Whether cppnix or tvix is the more sensible first real
    implementation target (see "Unresolved questions" in the RFC).
  • Whether the specific Sig-PQC: field name/encoding should change before
    this is considered close to final.
  • Any prior PQC-for-Nix discussion this RFC should be citing that I
    haven't found.

Happy to nominate myself or find shepherds as this moves forward.

Proposes an optional, additive Sig-PQC: narinfo field carrying a
hybrid Ed25519+ML-DSA-65 signature, so binary caches can begin
dual-signing years ahead of any cryptographically-relevant quantum
computer, without breaking existing nix clients.

Supporting prototype: https://github.com/Luminous-Dynamics/nix-pqc-cache-proxy
@grahamc

grahamc commented Jul 10, 2026

Copy link
Copy Markdown
Member

Note that some prior art exists here in case it is useful.

In NixOS/nix#15926 we upstreamed the start of our work for post-quantum cryptography which we implemented in Determinate Nix in DeterminateSystems/nix-src#449. That work embeds additional signatures under the existing narinfo format. It looks like this:

nix key generate-secret --extra-experimental-features cnsa --key-name cache1.example.org --key-type ml-dsa-87 > secret
nix key --extra-experimental-features cnsa convert-secret-to-public < ./secret > ./public
nix key --extra-experimental-features cnsa convert-secret-to-pem < ./secret > secret.pem
nix store sign --extra-experimental-features cnsa --key-file ./secret $(which nix)
nix path-info --json $(which nix)

...producing:

"signatures": [
      "cache.flakehub.com-3:Dc9ktQmrTuBn+zFVdhJfR3RgsFKf7UBa9AOnYi36LI9kY6W0oKadSzVJ7gJPTQYsH61gW8msZ9Sl7B39CTylAg==",
      "cache.flakehub.com-4:AI+8XSHt2KuHveplP9q4dzKf9zj/v0H/OisqZ5Nt9NRR81cEeo6F8Y4IJQf9lt73Rx6GYyQTpg5Dtu4XcfiqCg==",
      "cache1.example.org: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"

@shlevy

shlevy commented Jul 11, 2026

Copy link
Copy Markdown
Member

Why is “PQC” special from the Nix config’s perspective? Why not just extend existing signatures field to allow for extensible algo types and then add whatever algorithms are needed?

@Tristan-Stoltz-ERC

Tristan-Stoltz-ERC commented Jul 12, 2026

Copy link
Copy Markdown
Author

Thanks @grahamc and @shlevy. I reviewed both NixOS/nix#15926 and the fuller implementation in DeterminateSystems/nix-src#449.

I agree with the central point: PQC does not appear to require a special narinfo field. Extending the existing signature and trusted-key machinery by algorithm type is cleaner, more general, and already has working prior art. My original motivation for Sig-PQC: was additive compatibility and making the hybrid relationship explicit, but I no longer think that justifies introducing a parallel signature field.

The review did expose a related distinction that may still be worth specifying: supporting multiple signature algorithms is not necessarily the same as requiring a hybrid signature policy.

In the current verification path, a narinfo containing both Ed25519 and ML-DSA signatures is accepted when any trusted signature verifies. That provides algorithm agility, but it does not express “one valid classical signature AND one valid post-quantum signature are required.” Without explicit policy semantics, the post-quantum signature could be removed and the classical signature could still satisfy verification, so the combination would not provide downgrade-resistant hybrid authentication.

The current RFC leaves enforcement policy as an implementation detail. After reviewing this prior art, I think that is backwards: the existing signature representation should probably be reused, while the potentially useful RFC contribution is the policy and migration model.

I’m planning to revise the proposal along these lines:

  • use ordinary existing signature entries rather than Sig-PQC:;
  • incorporate the Determinate implementation as prior art;
  • define whether and how trusted-key groups can be composed with AND/OR or threshold semantics;
  • count distinct trusted key identities rather than raw signature entries;
  • specify classical-only, hybrid-optional, hybrid-required, and eventual post-quantum-only migration states;
  • update the prototype to exercise those semantics and downgrade cases.

Does that sound like a useful scope for this RFC, or would the signature-policy portion be better pursued through the configurable-verification work proposed in NixOS/nix#14451, or as a separate proposal?

@Tristan-Stoltz-ERC

Copy link
Copy Markdown
Author

Thank you to everyone who reviewed this proposal, particularly @grahamc and @shlevy for pointing me toward the existing polymorphic-signature work and questioning whether post-quantum signatures required special treatment in the narinfo format.

After reviewing the prior art and developing the prototype further, I agree that post-quantum algorithms should use Nix’s extensible signature representation rather than a parallel Sig-PQC: field.

The work exposed a more general issue: supporting multiple signature algorithms does not by itself provide a way to require composed authorization—for example classical AND post-quantum signatures, thresholds, distinct authorities, identity binding, or cryptographic-family diversity.

I am therefore closing #202 as superseded rather than rejected. I am preparing a new proposal, “Composable signature authorization for Nix store paths,” which is algorithm-neutral and focuses on authorization semantics, downgrade resistance, migration, recovery, and compatibility with existing Nix signature machinery.

I appreciate the feedback that helped redirect this work toward a more general and useful design.

Tristan-Stoltz-ERC added a commit to Luminous-Dynamics/nix-signature-policy that referenced this pull request Jul 15, 2026
docs/PRIOR_ART_AND_DESIGN_DELTA.md records the actual upstream research
behind the previous commit: NixOS/rfcs#202 (this project's own prior
submission, closed 2026-07-14 as superseded after real feedback from two
NixOS/nix maintainers -- @grahamc pointed at nix#15926/DeterminateSystems'
nix-src#449 as already doing algorithm-agility without a parallel narinfo
field, @shlevy asked directly why PQC needed special treatment), the
still-open nix#14451 proposal this project builds on (including its own
author flagging the exit-code-vs-structured-output ambiguity we resolve,
and real precedent citations: pam_exec, mod_authnz_external, OpenVPN
--tls-verify, OpenSSH AuthorizedKeysCommand), and a maintainer's real,
unresolved architectural concern (point-wise vs. closure-wide trust) that
gets acknowledged as a genuine limitation rather than ignored. It states
plainly what's reused, what's missing, and what's explicitly not being
proposed. Open-source states (nix#15926, nix-src#449) are anchored to a
verification date rather than described as "current," since they can
change independently of this document.

docs/CALLER_SAFETY.md documents src/caller.rs's behavior as the spec a
future C++ implementation should mirror, maps the original 18-item hostile-
helper brainstorm to what's actually tested vs. covered by fuzzing vs.
explicitly out of scope, and replaces the community's order-of-magnitude
performance estimate with a real measurement: 1755 cold-start invocations
of the release binary on 2026-07-15, reported as a full latency
distribution (min/p50/p90/p95/p99/max/mean, not just a mean) with the
measurement environment and a reproduction command, since the tail matters
more than the mean for an admission path.

Also appends one clearly-speculative, non-binding paragraph to
docs/PROJECT_HISTORY.md noting that this shape composes with a broader
NIST SP 800-207-style model if that ever proves useful beyond Nix --
recorded so the idea isn't lost, not proposed as a roadmap, a rename, or
scope for this project.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants