@@ -62,7 +62,7 @@ is cached.
6262| Mode | Requirement to accept a candidate |
6363| -------- | ------------------------------------------------------------------------------- |
6464| ` off ` | No verification. Route-only behavior (default). |
65- | ` signed ` | The narinfo must verify against the serving upstream's configured ` public_key ` . |
65+ | ` signed ` | The narinfo must verify against one of the serving upstream's configured signer keys . |
6666| ` quorum ` | A verified signature ** plus** at least ` threshold ` matching claims (below). |
6767
6868<!-- markdownlint-enable MD013-->
@@ -77,16 +77,18 @@ Every time ncro verifies a signature it records a **trust claim** in SQLite (see
7777vouched for what:
7878
7979- ` signer_key ` / ` signer_name ` -> the Nix signing key that verified.
80- - ` upstream_url ` -> which upstream served this observation.
80+ - ` upstream_url ` -> the serving upstream for a local claim, or the
81+ ` mesh://<relay-address> ` that supplied a relayed claim.
8182- the signed content tuple: ` nar_hash ` , ` nar_size ` , ` references ` (and the
8283 ` store_path ` / ` narinfo_hash ` ).
8384- provenance-only fields recorded for audit but ** not** covered by the
8485 signature: ` deriver ` , ` ca ` , ` file_hash ` , ` file_size ` , and the raw ` narinfo `
8586 bytes.
8687
8788Claims are what make ` quorum ` and the mesh integration possible: they are a
88- durable, queryable record of "signer X, via upstream Y, attests that path H has
89- this NAR hash."
89+ durable, queryable record that signer X attests that path H has this NAR hash.
90+ For relayed claims, the relay address is recorded rather than an unverified
91+ assertion about the original upstream.
9092
9193## Quorum counting
9294
@@ -114,7 +116,7 @@ The signature check alone would be theatre.
114116ncro therefore counts a claim toward a quorum ** only if its ` signer_key ` is in
115117the trusted set** :
116118
117- $$ \text{trusted set} = \left\{ \text{upstream}_i.\text{public \_ key } \right\} \cup
119+ $$ \text{trusted set} = \left\{ \text{all configured upstream and enabled fallback signing keys } \right\} \cup
118120 \text{trust.trusted\_keys} $$
119121
120122- Locally-recorded claims always use a configured upstream key, so a single node
@@ -124,11 +126,6 @@ $$\text{trusted set} = \left\{ \text{upstream}_i.\text{public\_key} \right\} \cu
124126 signed by an unknown key is dropped (and never even stored). This is what
125127 bounds a quorum to _ trusted_ signers rather than _ any_ signer.
126128
127- ` trust.claim_ttl ` (optional, e.g. ` "30d" ` ) bounds how old a claim may be and
128- still count. Claims whose ` last_seen ` is older than the TTL are ignored when
129- evaluating the quorum, so a signer that has long stopped serving a path cannot
130- prop up a quorum forever. Unset means claims never expire.
131-
132129> [ !WARNING]
133130> Quorum needs more than one independent signer to ever see the same path. On a
134131> single node that only races caches sharing one signing key, a ` threshold > 1 `
@@ -166,33 +163,29 @@ With `mesh.gossip_trust_claims = true`, a node:
166163 set (above). A claim from any other key is dropped immediately, this is
167164 what stops an attacker from minting throwaway keys to forge a quorum, and
168165 it also bounds how many claims a hostile peer can write to the database.
169- - ** Valid signature.** The claim carries the original narinfo bytes, and the
170- receiver re-verifies that narinfo against the claim's ` signer_key ` . The
171- peer's packet signature only proves _ which peer relayed it_ , not that the
172- content was signed; re-verifying means a peer cannot forge a claim under a
173- trusted key it does not actually hold.
166+ - ** Valid signature and canonical fields.** The claim carries the original
167+ narinfo bytes. The receiver re-verifies that narinfo against the claim's
168+ ` signer_key ` and rebuilds every signed field from it, so a peer cannot
169+ substitute a different path or content tuple.
174170
175171This means ** a peer becomes a witness, never a substitute for a real Nix
176172signature.** A malicious or compromised peer cannot inflate a quorum: a claim
177173signed by an untrusted key is dropped, and a claim claiming a trusted key but
178174without a valid signature fails re-verification. A quorum therefore counts
179175distinct _ trusted Nix signers_ , whether those signatures were observed locally
180- or relayed by a peer. Peers are also allowlisted by their mesh public key
181- ( ` mesh. peers[].public_key ` ), so unknown senders are rejected before any claim is
182- even parsed .
176+ or relayed by a peer. Configure ` mesh.peers[].public_key ` to also allowlist mesh
177+ senders; peers without configured keys are accepted at the transport layer but
178+ still cannot alter the signed claim tuple .
183179
184180## Inspecting Trust (At Runtime)
185181
186- Two read endpoints expose the recorded claims:
182+ The trust endpoint exposes the recorded claims:
187183
188184- ` GET /trust/<hash>.narinfo ` - the current trust decision for a path: the mode,
189185 threshold, the count of matching claims, whether the path is currently
190186 trusted, and the full list of claims.
191- - ` GET /provenance/<hash>.narinfo ` - the same claim list, framed as an audit of
192- who has vouched for the path (including the unsigned provenance fields).
193-
194- Both are useful for confirming that claims are propagating across a mesh: after
195- a path is resolved on one node, it should appear in ` /trust ` on a peer within a
187+ It is useful for confirming that claims are propagating across a mesh: after a
188+ path is resolved on one node, it should appear in ` /trust ` on a peer within a
196189gossip interval or two.
197190
198191## Configuration Summary
@@ -203,7 +196,6 @@ mode = "quorum" # off | signed | quorum
203196threshold = 2 # matching claims required in quorum mode
204197require_distinct_signers = true # count distinct signer keys, not mirrors
205198fail_closed = true # reject (true) vs serve+warn (false)
206- claim_ttl = " 30d" # optional: ignore claims older than this
207199
208200# Signer keys trusted to vouch in quorum mode, in addition to upstream keys.
209201# In a mesh, list the OTHER nodes' upstream signer keys here.
0 commit comments