The transport carries no authority
The most important sentence in our codebase, learned the hard way while
hardening peerit (the gossip engine the p2pbuilders web build reuses):
A record is trusted because of its signature — never because of who
delivered it.
The bug class
Early gossip code made an innocent-looking assumption: records arriving on a
connection from peer X are peer X's records. But gossip forwards — X relays
records authored by Y and Z; that is the entire point. The moment any code
path treated "received from X" as "authored by X", a malicious peer could
inject records under other people's names by just… sending them.
The fix, as invariants
Our merge now admits a record only if all of these hold, regardless of
transport:
- The signature verifies over the canonical encoding of the record.
- The signer is the claimed author — the key that verifies must equal
the record's author field, or it is a well-signed lie.
- The storage key matches the fields — a record claiming to be
vote!abc!carol must actually be a vote by carol on abc; otherwise a
valid record can be replayed at the wrong address to overwrite state.
Check 3 is the one people miss. Signature verification tells you the bytes
are authentic; it does not tell you they belong where the sender put them.
Carry-outs
- Do the checks at ingest — the boundary where bytes become state — not
in the UI, not "sometimes".
- Canonical encoding matters: sign a deterministic serialization, or two
honest peers can disagree about validity.
- Write the adversarial tests (forged sig, wrong author, mis-keyed replay).
Our engine test does exactly this, and it is the test we would rescue from
a fire first.
Hypercore gives you 1 and 2 for free within a core. The moment you gossip
records outside their author's core — we do, for speed — all three checks
are yours again.