<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/docs/per-node-identity-v1.md, branch 0.10</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.10</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.10'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-08-14T15:51:48Z</updated>
<entry>
<title>feat!: identity keys per node — C4's blast radius drops to one operator</title>
<updated>2026-08-14T15:51:48Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-14T15:51:48Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f0984e86d9cb596a282ce6feb7cfc2f075b2794b'/>
<id>urn:sha1:f0984e86d9cb596a282ce6feb7cfc2f075b2794b</id>
<content type='text'>
One keypair was copied to every node its owner joined, so cracking the bundle on
any single node yielded the identity used on all of them: their content on other
operators' machines, and the ability to sign as them anywhere. That lateral reach
was the part of C4 worth attacking.

Each node now gets its own keypair, generated the first time its owner joins it
and left with that node alone. An operator who cracks what sits on their own disk
holds a key that is a stranger to every other node — and on their own node, one
that unlocks nothing they did not already hold: they serve the content, the index
and every byte of it by design.

Nothing changes for the user. A first contact with a node already needed that
operator's code, and the key is created in the same step; a second browser still
recovers it from the node with the passphrase alone. Two operators can also no
longer tell they host the same person by comparing keys.

BREAKING, and deliberately without a compatibility path — the deployment is wiped
for the next demo:

  - users.pk_ed25519 / pk_x25519 dropped (migration a7c31f9e40b2)
  - registration no longer sends or stores a key
  - PUT /v1/users/me/keys and regenerateKeys() gone; rotation is now
    `member unpin` plus a fresh code, decided on the machine that pinned it
  - /pubkeys returns an account id and a node's linking key. It was the directory
    H3 read, and nothing wraps for it any more
  - the pk_user JWT claim is gone

That last one closed a live defect the inventory turned up: the node recorded
pk_user as the uploader's identity and authorized deletion against it, so a hub
issuing a token naming its own key could delete anyone's uploads on any node.
Attribution now uses the key the node itself pinned.

A simplification falls out. Registration generates nothing, so a scripted signup
is a real account: `demo.py bootstrap` takes a wiped hub and node to a working
demo with no browser, which was impossible while keys were born in one.

Also fixes, found by running it on a wiped deployment: the key handed back on a
join now belongs to the group the connection is for, not the group named in the
invitation — an operator pairs node-wide but redeems the code while opening a
group, and expects to read it.

Tests: 343, including the two that state the property — a key pinned by one node
is refused at another, and someone else's code does not admit it. Verified end to
end against a wiped hub and node: bootstrap, pair, invite, join, download,
stream, second browser, revoke.

Design: docs/per-node-identity-v1.md

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
