Skip to content

F.11: Identity attestation layer#18

Open
kh0pper wants to merge 1 commit intof8-peertube-bundlefrom
f11-identity-attestation
Open

F.11: Identity attestation layer#18
kh0pper wants to merge 1 commit intof8-peertube-bundlefrom
f11-identity-attestation

Conversation

@kh0pper
Copy link
Copy Markdown
Owner

@kh0pper kh0pper commented Apr 12, 2026

Summary

First non-bundle PR in the Phase 2 rollout. Stacked on F.8 (PeerTube). Ships the core plumbing for tying per-app federated handles to Crow's root Ed25519 identity with publicly-verifiable signatures. Attestation, not key replacement — each federated app still uses its own keys; the Crow root key signs only the binding (crow_id, app, external_handle, version).

Also finally creates the moderation_actions table that federated bundles have been writing to since F.1 (they gracefully returned queued_unavailable when absent).

What lands

Core sign/verifyservers/shared/identity-attestation.js:

  • canonicalPayload() — deterministic JSON for signing (sorted keys, nullish omitted)
  • signAttestation() / verifyAttestation() — Ed25519 over the canonical JSON
  • verifyCrowIdBinding() — catches swap attacks (crow_id = first-8-bytes-of-sha256(ed25519_pub) → base36; valid signature with wrong pubkey doesn't bind to claimed crow_id)
  • Revocation sign/verify with a separate canonical payload
  • SUPPORTED_APPS constant — the 8 federated bundles from F.1-F.8, prevents typos

DB schemascripts/init-db.js:

  • moderation_actions (bundle_id, action_type, payload_json, idempotency_key UNIQUE) — finally exists
  • identity_attestations (crow_id, app, external_handle, app_pubkey?, sig, version, revoked_at) — UNIQUE(crow_id, app, external_handle, version) so rotation = new row, not overwrite
  • identity_attestation_revocations (attestation_id FK CASCADE, revoked_at, reason, sig) — signed
  • contacts extended with external_handle + external_source

MCP tools — added to servers/sharing/server.js:

  • crow_identity_attest(app, external_handle, app_pubkey?, confirm)
  • crow_identity_verify(crow_id, app, external_handle, max_age?)
  • crow_identity_revoke(attestation_id, reason?, confirm)
  • crow_identity_list(include_revoked?, app?, limit?)

Gateway endpoints — paginated, rate-limited:

  • GET /.well-known/crow-identity.json (256 per page, ?cursor=N)
  • GET /.well-known/crow-identity-revocations.json
  • Both behind a shared identityLimiter (60 req/min/IP) — the verify endpoint becomes a DoS vector otherwise.

Docs — new skills/crow-identity.md skill, trigger row in skills/superpowers.md, DB schema + Skills Reference entries in CLAUDE.md.

Design notes

  • No gossip over crow-sharing. .well-known publication only; revisit after F.12.
  • Pinned-post attestation stays manual. Automation would open forgery vectors.
  • Publication is effectively permanent. Revocations themselves are public. Consent text in tool descriptions warns about this explicitly.
  • Swap-attack protection. verifyCrowIdBinding() catches the case where a signature validates against a pubkey that doesn't derive to the claimed crow_id.
  • Key rotation: bundles rotate app keys → chain crow_identity_revoke + crow_identity_attest with incremented version. Version counter auto-increments in crow_identity_attest.

Test plan

  • node --check on all files
  • node scripts/init-db.js runs cleanly, creates all 3 tables + adds both columns on existing DBs
  • createSharingServer() boots with the 4 new tools
  • Sign/verify/tamper-rejection/binding-check round-trip exercised
  • npm run check passes
  • Live HTTP fetch of /.well-known/crow-identity.json (deferred — bundled gateway is hostile to sandboxed test environment; manual verification at deploy time)
  • Cross-instance verification (two Crow instances, one attests, the other fetches .well-known and verifies) — integration test for a follow-up
  • Operator UX: Nest panel for attestations — intentionally deferred; the crow_identity_list tool surfaces this via the AI until a dedicated panel lands

Rollout position

  • F.0 → F.8 shipped
  • F.11 (this PR) — 9th of 11 Phase 2 PRs
  • Next: F.12 cross-app bridging (matrix-bridges meta-bundle + crow-native crosspost). F.12 can now assume moderation_actions + identity_attestations tables exist and the .well-known publication surface is live.

🤖 Generated with Claude Code

Ninth in the Phase 2 rollout; first non-bundle PR in the series. Stacked
on F.8 (PeerTube). Ships the core plumbing for tying per-app fediverse
handles (Mastodon @user@host, Funkwhale channels, Matrix MXIDs, etc.)
to Crow's root Ed25519 identity with publicly-verifiable signatures.

**This is attestation, not key replacement.** Each federated app still
uses its own keys for federation; the Crow root key signs only the
binding (crow_id, app, external_handle, version).

New files:

- servers/shared/identity-attestation.js — Core Ed25519 sign/verify
  helpers. canonicalPayload() produces sorted-key JSON for deterministic
  signing. verifyCrowIdBinding() catches swap attacks (crow_id is
  derived from the root pubkey; a valid sig with the wrong pubkey
  doesn't bind to the claimed crow_id). signRevocation/verifyRevocation
  for the revocation list. SUPPORTED_APPS constant caps the attestation
  surface to the 8 federated bundles shipped in F.1-F.8 (prevents typos
  like "matodon").
- skills/crow-identity.md — Full workflow skill: when to attest, when
  NOT to attest (ephemeral identities, pseudonymous accounts), endpoint
  surface, payload format, key-rotation flow, safety notes around
  permanent publication.

Modified files:

- scripts/init-db.js — Three new tables:
  - moderation_actions (bundle_id, action_type, payload_json,
    requested_at, expires_at, status, idempotency_key UNIQUE). Finally
    creates the table the federated bundles have been writing to since
    F.1; before F.11 the queueModerationAction() helper returned
    "queued_unavailable" gracefully when the table was absent.
  - identity_attestations (crow_id, app, external_handle, app_pubkey?,
    sig, version, created_at, revoked_at). UNIQUE(crow_id, app,
    external_handle, version) — each rotation adds a new version row
    rather than overwriting. Partial index for active (non-revoked)
    lookups.
  - identity_attestation_revocations (attestation_id FK CASCADE,
    revoked_at, reason, sig). Signed revocations stored separately
    so the revocation list itself is cryptographically verifiable.
  - contacts extended with external_handle + external_source columns
    (idempotent via addColumnIfMissing) so federated contacts can
    be linked into the existing contacts table.

- servers/sharing/server.js — Four new MCP tools:
  - crow_identity_attest(app, external_handle, app_pubkey?, confirm) —
    creates row, signs with root key, returns attestation_id + sig +
    publish_url. Confirms permanence explicitly.
  - crow_identity_verify(crow_id, app, external_handle, max_age?) —
    fetches latest non-revoked row, verifies sig AND verifies the
    crow_id→pubkey binding. Currently only verifies attestations
    belonging to THIS instance; cross-instance verification uses the
    gateway's /.well-known endpoint instead (rate-limited there).
  - crow_identity_revoke(attestation_id, reason?, confirm) — signs a
    revocation, updates the attestation row's revoked_at, publishes via
    the revocation list. Refuses to revoke attestations that belong to
    a different crow_id.
  - crow_identity_list(include_revoked?, app?, limit?) — surfaces what
    this instance has attested.

- servers/gateway/index.js — Two new public endpoints:
  - GET /.well-known/crow-identity.json — paginated active attestations
    (256 per page, cursor-based). Returns root_pubkey + active_attestations
    array. Cache-Control: 60s.
  - GET /.well-known/crow-identity-revocations.json — paginated
    revocations with signed proofs. Same pagination.
  - Shared identityLimiter (60 req/min/IP) on both endpoints to prevent
    verification-storm DoS against the attestation surface.

- skills/superpowers.md — Trigger row added (EN+ES): attest identity,
  prove I am, link my mastodon, verify handle, keyoxide, revoke
  attestation.

- CLAUDE.md — DB schema docs for moderation_actions +
  identity_attestations + identity_attestation_revocations. Skills
  Reference entry for crow-identity.md.

Security design points:

- No gossip over crow-sharing. Attestations publish only via .well-known
  for now (per plan Q3). Revisit after F.12 lands.
- Pinned-post attestation (Keyoxide pattern) remains manual — automation
  would open forgery vectors (plan Critical #3 answer).
- Publication is effectively permanent. Revocations themselves are
  public. Consent text in the tool descriptions warns about this
  explicitly.
- Rate-limited at the HTTP surface to 60 req/min/IP — the verify
  endpoint can be turned into a DoS vector against small hosts otherwise.
- Pagination capped at 256 entries/page per plan §Publication.

Verified:

- node --check on all modified + new files
- node scripts/init-db.js runs cleanly; the three new tables + two
  new contacts columns land without conflicts on existing DBs
- createSharingServer() boots with the four new tools registered
- Core sign/verify/tamper-rejection/binding-check round-trip exercised
  via ad-hoc script
- (Live gateway .well-known HTTP test deferred — the bundled gateway
  is hostile to the sandboxed test environment on this machine)

Next:

- F.12 cross-app bridging (now the final piece) — matrix-bridges
  meta-bundle + crow-native crosspost with 60s-delay consent UX. F.12
  can now assume the moderation_actions + identity_attestations tables
  exist and the .well-known publication surface is live.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant