Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against
Источник:
dev/fera-research/docs/oc5/lattice-no-trust-anchor-defined.md(read-only; сгенерировано scripts/sync-docs.mjs)
Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against
Section titled “Lattice defines signature verification but never defines a trust anchor — lttc verify has no key to verify against”- kind: risk
- area: trust / modules / security
- severity: mid
- evidence:
~/dev/fera/lattice/SPEC.md:517-534(§5.4, frozen) — full signature mechanics:key_id=BLAKE3("lttc-key-v1" || ed25519_pubkey),sig_input=BLAKE3-keyed(context = "lttc-sig-v1")over"lttc-slot" || slot_index || subtree_name || superblock[0..160] || super_node_digest, signed with Ed25519SPEC.md:701-714(§10.1, frozen) — disagreement policy, “fail closed”,LttcError::SignatureDisagreementSPEC.md:793-796(§11 CLI) —lttc verify <plugin.lttc>— “verify superblock, manifest, Merkle root, signatures; exit 0 on success”SPEC.md:17— “use it, with or without native code, under its own trust rules”~/dev/fera/lattice/crates/lttc-format/src/error.rs:31-36—SignatureInvalid { slot },SignatureDisagreement { key_ids, digests }— both implemented as error variants- grep of
SPEC.mdfortrust root/trusted key/key store/keystore/revocation/revoke/TOFU/trust anchor/pre-pinned/known key/embedded key/out-of-band→ zero hits ~/dev/fera/lattice/OPEN-DECISIONS.md— Round 1 closed, all decisions moved to SPEC.md §14; no trust-model question was ever opened- contrast:
~/dev/fera/project-design/context/ADR.md:81-87(ADR-003) — the host does own a trust model (“A root Ed25519/SSH keypair (ours) is embedded in the launcher binary”; “Users manage a local trusted-key list”)
Substrate
Section titled “Substrate”The Lattice container format is cryptographically thorough. Domain separation per slot, replay protection between subtrees, a frozen fail-closed disagreement policy, a detached-signature mode that binds the blob to the specific container (§5.4, “a valid detached signature from a different container cannot be replayed”), and reader rules ordered so cheap arithmetic rejects precede any hash or signature work. On the mechanics this spec is better than most shipping container formats.
What it does not define, anywhere, is the one question that makes any of it mean anything: which keys does a reader trust?
Concretely, lttc verify <plugin.lttc> (§11) is specified as
“verify … signatures; exit 0 on success”. Verification requires a
public key. The container carries a key_id — which, per §5.4:521, is
BLAKE3("lttc-key-v1" || ed25519_pubkey), i.e. a digest of a key the
container names. That is an identifier, not an authority. A reader
that “verifies” by looking up the key the container itself supplies
verifies nothing at all: an attacker generates their own keypair,
packages it with their content, and the signature checks out.
Every one of the following greps returns nothing in SPEC.md:
trust root, trusted key, key store, keystore, revocation,
revoke, TOFU, trust anchor, pre-pinned, known key, embedded key, out-of-band. And OPEN-DECISIONS.md — the file whose entire
purpose is to hold unresolved trust questions — has no such entry. The
question was never asked.
This is not an oversight in the sense of a missing paragraph. It is a
correct scoping decision made implicitly and never written down:
Lattice is a container format, and its job is to be verifiable against
whatever key policy the host has. The host’s policy is ADR-003’s.
SPEC.md:17 gestures at this — “under its own trust rules”.
The problem is the seam has no name and no test, and the spec’s own
language pulls the reader toward the wrong reading. §5.4 and §11 say
“verify signatures” without ever saying “against a caller-supplied
trusted key set”. LttcError::SignatureInvalid { slot } is implemented
as if a single trusted key exists. And §10.1’s disagreement policy
compares two signatures against each other — which is a consistency
check between the container’s own signature and the ADR-021 manifest
signature, again both named by content the container references.
So the concrete risk is an implementation trap, not a hole in the format:
the first person to write lttc verify will implement the only thing
the spec describes — pull the key from the container (or from a
hardcoded test vector), verify, exit 0 — and ship a verifier that
passes for attacker-generated bundles. And lttc verify is exactly the
command a user or a CI job runs to decide “is this plugin safe?”.
Secondary point: there is no revocation story in Lattice. Even with a
correct host keyring, the format has no way to express “this key was
compromised on date X”, so revocation is entirely the host’s problem
(ADR-021 does specify “Key revocation, keyed by the existing key_id” —
so the host has it, and Lattice inherits it. Fine — but again, nowhere
in the spec is that inheritance stated).
Options (tradeoffs)
Section titled “Options (tradeoffs)”- State the scoping explicitly in §5.4. One sentence: “Lattice
defines verification only. The caller supplies a trusted
key_id → ed25519_pubkeymap; a container naming a key absent from that map MUST fail withSignatureInvalid { slot }. Verification against a key supplied by the container is not verification.” Cost: ~1 paragraph. Kills the trap. - Make it normative in the reader’s parse order. Add a reader rule
beside §3.2 step 9 (“Signature verification”) requiring the key map
as an input to
lttc verify. Cost: makes the CLI take a--trusted-keysargument, which is the honest interface anyway. - Add a trust-model entry to OPEN-DECISIONS even if the answer is “delegated to the host” — so the next reader sees it was decided rather than missed. Cost: trivial. Value: high, because the absence currently reads as an oversight and will be “fixed” by an implementer who assumes it.
- Bundle a trust store into Lattice. Wrong at this layer: it would
duplicate ADR-003 and create two keyrings (which is the failure mode
in
trust-tier-flat-keyring-authority-collapse.md).
Proposed solution
Section titled “Proposed solution”Options 1 + 3, and 2 if the CLI is built soon.
- Add to §5.4 a normative “Trust anchor” paragraph stating that
verification is parameterized over a caller-supplied trusted-key map,
that a
key_idnot present in that map isSignatureInvalid, and that the format performs no trust establishment of its own. - Add to §10.1 (or a new §10.3) the note that revocation is the host’s
concern, keyed on
key_idper ADR-021 — so the two specs reference each other explicitly instead of by coincidence. - Open an OPEN-DECISIONS entry recording the scoping decision, so the next implementer reads “decided: host-supplied” rather than “unspecified, therefore my problem”.
- Add a CI gate: a test that a container signed by a freshly
generated, unknown key must fail
lttc verifyagainst a trust store that does not contain it. Without such a test, any verifier built from this spec will pass its own test suite while accepting attacker bundles — the tests would use the same key to write and verify.
Note: severity is mid rather than high because the Fera host does have
a real trust model (ADR-003) and QA.md already gates “the trust manifest
itself is signature-verified before any of its contents are trusted”
(QA.md:135-137). The exposure is the standalone tool and any future
second host — which is exactly what “other hosts: their choice”
(SPEC.md:713) invites.