fix(identity): a distinct peer identity REQUIRES distinct key material (#402) - #1360
Merged
Merged
Conversation
#402) Multi-agent-in-one-home minted a fresh random peer_id while REUSING the home's `identity.key`. One machine, one keypair, N peer identities — and the wire cannot represent that. WHY IT BREAKS, from the transport's own code: `PinnedServerVerifier` (airc-transport/src/lan_tcp/verifier.rs) authenticates by KEY. It extracts the presented ed25519 pubkey and resolves it via `PeerKeyRegistry::find_peer`, which returns EXACTLY ONE peer; anything else is rejected as `peer_identity_mismatch`. So pubkey→peer_id is assumed to be a FUNCTION. The identity mint violated that assumption, and the damage lands in every peer's trust store: one row per peer_id, all carrying the same pubkey. Measured on the live store — 73 rows / 59 pubkeys, and only 4 rows with ANY endpoint. The 2,502-unconfirmed peer of #401 is the endpoint-less twin of a fully dialable row with the same key. That is why sends went nowhere. Two agents sharing a keypair ARE the same principal on the wire; no session can tell them apart. Giving them separate peer_ids is a claim the transport cannot uphold. Fresh key material is exactly what a fresh identity means. FIX - `agent_key_path(home, agent)` — the DEFAULT agent keeps `identity.key` verbatim, so every existing home loads unchanged; a named agent gets `identity-<agent>.key`. - `generate_and_save_agent_with_existing_key` → `..._with_own_key`: generates its own keypair instead of borrowing the home's. - `load_with_metadata` reads the agent's own key when present, else falls back to the shared legacy file — so an ALREADY-enrolled named agent keeps its pubkey and peers need no re-enrolment. New agents can never be phantoms. TESTS — and note I changed two EXISTING assertions, deliberately. `load_or_generate_as_records_agent_name` and `load_or_generate_as_adds_named_agent_to_existing_scope` each asserted `assert_eq!(secret_bytes, secret_bytes)` immediately below `assert_ne!(peer_id, peer_id)`. They pinned the contradiction itself — distinct peer_ids with identical key material — i.e. the defect was written down as a requirement. Both are now `assert_ne!` with the reason inline. New: `two_agents_in_one_home_are_cryptographically_distinct` — distinct peer_ids AND distinct pubkeys, plus a reload proving an existing agent never rotates its key. 12/12 airc-identity tests pass; `cargo check --workspace` clean. SCOPE — this stops NEW phantoms; it does not heal the 73 rows already in the live store. That collapse (and whether a same-pubkey row may adopt a sibling's endpoints) is the follow-up half of #402, tracked separately, because it rewrites rows in a trust store and deserves its own review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
Multi-agent-in-one-home minted a fresh random
peer_idwhile reusing the home'sidentity.key. One machine, one keypair, N peer identities.The wire cannot represent that.
PinnedServerVerifierauthenticates by key: it extracts the presented ed25519 pubkey and resolves it throughPeerKeyRegistry::find_peer, which returns exactly one peer and rejects anything else aspeer_identity_mismatch. pubkey→peer_id is assumed to be a function; the identity mint violated it.The damage lands in every peer's trust store — one row per peer_id, all carrying the same pubkey. Measured on the live store:
The 2,502-unconfirmed peer in #401 is the endpoint-less twin of a fully dialable row with the same key. That is why sends went nowhere.
Two agents sharing a keypair are the same principal on the wire — no session can tell them apart. Separate peer_ids for them is a claim the transport can't uphold.
The fix
agent_key_path(home, agent)— DEFAULT agent keepsidentity.keyverbatim (every existing home loads unchanged); a named agent getsidentity-<agent>.key.generate_and_save_agent_with_existing_key→..._with_own_key: generates its own keypair rather than borrowing the home's.load_with_metadataprefers the agent's own key, falling back to the shared legacy file — an already-enrolled named agent keeps its pubkey, so peers need no re-enrolment.Tests
Two existing assertions changed, deliberately. Both tests asserted
assert_eq!(secret_bytes, secret_bytes)directly belowassert_ne!(peer_id, peer_id)— pinning the contradiction itself (distinct ids, identical key). The defect was written down as a requirement. Both nowassert_ne!with the reason inline.New:
two_agents_in_one_home_are_cryptographically_distinct— distinct peer_ids and distinct pubkeys, plus a reload proving an existing agent never rotates its key.12/12airc-identity tests pass;cargo check --workspaceclean; rebased on current canary.Scope
This stops new phantoms. It does not heal the 73 rows already in the live store — that collapse (and whether a same-pubkey row may adopt a sibling's endpoints) is the follow-up half of #402, kept separate because it rewrites rows in a trust store and deserves its own review.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo