Personas prove what goes to relays; the server says what it reads

FEP-521a and FEP-8b32. Every persona has an Ed25519 key of its own (Avatar.SigningKey; migration 014 gives the earlier
ones theirs), named in its actor's assertionMethod as a Multikey, the terms defined in the actor's own context. A
persona's activity going to a relay carries an eddsa-jcs-2022 proof (JSON canonicalised by RFC 8785, Jcs), so what
Activity-Relay forwards reaches Mastodon, which verifies it with its own code. Nothing else carries one: Mitra takes a
proof over the HTTP signature and refuses one by a key it has not read, without reading the actor again. Received: an
actor's own Multikeys are kept, and a forwarded activity whose proof one of them verifies is taken as it came instead of
being read again from its origin.

Discovery: WebFinger for the server's origin links its instance actor (FEP-d556), NodeInfo links it as the application
actor (FEP-2677), and actors name RFC 9421 under implements (FEP-844e).

Checked live: relay 16 (Activity-Relay's forward of alice's post reaches Mastodon), Mitra, GoToSocial and Mastodon
unchanged (165 in all).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-06 09:01:21 +02:00
1 parent c47b6e5533
commit 9ab87b2779
22 files changed
+702 -30

No files matched your search

+13 -6
View File
@@ -825,7 +825,10 @@ without a port, before it gives out its OAuth client.
### Mitra 5.9.1
- Signs its deliveries with RSA (draft-cavage) and adds an FEP-8b32 proof made with its Ed25519 key (FEP-521a), which
PrivaPub does not verify yet; the HTTP signature is enough for what it delivers itself.
PrivaPub verifies when the activity comes forwarded; the HTTP signature is enough for what it delivers itself.
- **Prefers a proof to the HTTP signature:** an activity delivered with a proof by a key Mitra has not read is refused
(401, "key not found in cache"), and Mitra does not read the actor again. So nothing PrivaPub delivers directly carries
a proof; only what goes to relays does.
- Its Mastodon API resolves an account elsewhere only when the search is not limited to a type
(`/api/v2/search?resolve=true`, no `type=accounts`); reactions go through Pleroma's route
(`PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`).
@@ -840,12 +843,16 @@ without a port, before it gives out its OAuth client.
follower whose actor ends in `/relay` (LitePub's way) gets an `Announce` instead.
- **aode-relay** takes either kind and announces the post from its own actor.
- A forwarded post carries its author's LD signature when Mastodon wrote it; PrivaPub does not verify LD signatures,
so it reads every relayed post again from its origin (one signed request each). Verifying them, or FEP-8b32 proofs,
would save that request.
- **Pasture evidence (2026-10-05, `tools/pasture/scenarios/relay.sh`):** 13 checks pass: PrivaPub's instance actor
so it reads such a relayed post again from its origin (one signed request each). One carrying a FEP-8b32 proof by its
author's Ed25519 key (Mitra's, Fedify's, PrivaPub's) is taken as it came.
- Mastodon 4.7 takes what Activity-Relay forwards only with an LD signature or a FEP-8b32 proof; personas' activities
going to a relay carry a proof, so their public posts reach Mastodon through it too. Mastodon reads a known account's keys again at
most daily, so a persona's key reaches a server that held its actor before at its next refresh.
- **Pasture evidence (2026-10-06, `tools/pasture/scenarios/relay.sh`):** 16 checks pass: PrivaPub's instance actor
subscribes to each relay and takes its Accept; Mastodon subscribes; a public post of a Mastodon account nobody here
follows reaches the federated timeline, forwarded by Activity-Relay and announced by aode-relay (as its author's,
never as the relay's boost), and nobody's home; nothing of a persona's goes to a relay.
never as the relay's boost), and nobody's home; a persona's public post goes to the relays, nothing less public, and
reaches Mastodon through both (forwarded on its proof, and announced).
### Smithereen 1.0.3
@@ -1165,7 +1172,7 @@ snapshots; `/api/privapub/v1/cdns` and `/cdns/:domain` group servers by CDN, wee
| 400 vs 401 | A 400 or 401 makes Mastodon 4.7 and WordPress retry with the other scheme | 401 only for signature failures (we do this); 400 only for bodies that are really malformed | P1 (keep) |
| Temporary key failure | Mastodon answers 503 | We should answer 503 too, and treat a 503 as a retry in delivery | P2 |
| Keys | `publicKey` can be an array (Mastodon 4.6). FEP-521a `assertionMethod` Multikey is FINAL (Ed25519 `z6Mk…`). GoToSocial key ids have no `#` and point at a stub. | Read all of these | P2 |
| Integrity proofs | FEP-8b32 `eddsa-jcs-2022`: JCS, no JSON-LD. Sent by Mitra, Streams, Hubzilla, Fedify and others; Mastodon verifies them from 4.7 | Verify, so relayed or forwarded objects need no refetch | P2 |
| Integrity proofs | FEP-8b32 `eddsa-jcs-2022`: JCS, no JSON-LD. Sent by Mitra, Streams, Hubzilla, Fedify and others; Mastodon verifies them from 4.7; Mitra prefers a proof to the HTTP signature and refuses one by a key it has not read | Verify, so relayed or forwarded objects need no refetch. **Done 2026-10-06**, both ways: personas' activities to relays carry one, forwarded ones with a valid proof are taken as they came | P2 |
| LD signatures | Mastodon still sends `RsaSignature2017` | Ignore, and refetch from origin (we do) | — |
| Query string | GoToSocial, Akkoma 3.20 and Misskey 2026.10 sign it; GoToSocial retries without it | Verify both ways | P1 |
| `hs2019` | The algorithm comes from the key; some senders hash with SHA-512 | Try rsa-sha256, then sha512 | P2 |