Reporting and message franking
Pigeon never scans, classifies or flags private messages. Abuse reaches us only when someone in a conversation chooses to report it, and the report has to prove the messages are real without exposing anything else.
Franking commitments Built
Each message carries a commitment to its own content: a fresh 32-byte key per message and com = HMAC-SHA256(k_f, context ‖ body), placed in the MLS authenticated data. Every message and attachment carries one. A receiver hides any message whose commitment doesn't open (This message couldn't be verified and was hidden
), so a sender can't show one thing to the reader and another to a moderator. This is the message-franking construction of Grubbs, Lu and Ristenpart (CRYPTO 2017).
Asymmetric message franking Built in the core, not yet attached to messages
A commitment alone shows the content is consistent, but not who sent it, and with sealed envelopes the server has no sender to vouch for. Asymmetric message franking (AMF) adds a designated-verifier signature that only two parties can check: the receiver, and the moderator acting as judge. To anyone else a real tag is indistinguishable from a forged one, so a leaked report isn't a transferable proof of what someone said.
- Group: Ristretto255 (RFC 9496), where the paper uses a prime-order elliptic-curve group; implemented with curve25519-dalek.
- Fiat–Shamir with SHA-512, binding the full statement.
- Tag size: 480 bytes.
- A report proves “the sender franked this message for some receiver”, not for whom.
- Pigeon holds the judge key, so a franked report is evidence to Pigeon, not to a court.
The primitives and the per-epoch receiver key are built and tested. Attaching AMF tags to messages, and verifying them at intake, is Planned; until then a report carries the franking commitment only.
What a report contains
A report is sealed to the safety team with HPKE (RFC 9180, X-Wing, HKDF-SHA256, ChaCha20-Poly1305). It reveals the messages the reporter selected, their franking openings (and AMF tag, once attached), the sender's account key prefix and device id, a hash standing in for the conversation, and the epoch. For a file, it includes the file. It never includes the group id, its members, its name or size, or any message not selected, and by default not the reporter's account. A report token, a privately verifiable anonymous credential (see tokens), limits how often one account can report without identifying it.
A report holds up to 11 messages or files from one sender. Choosing “I just don't want to see this” sends nothing to anyone. The categories are listed on the security and reporting page.
What's built
| Piece | Status |
|---|---|
| Franking commitments on every message and attachment | Built |
| AMF primitives (480-byte tag; under half a millisecond to frank, verify or judge a 4 KiB message) and the per-epoch receiver key | Built |
| AMF tags attached to messages (needs each account's franking key in its key history) | Planned |
| The sealed report format and its categories | Built |
| Report tokens (one key a week; the urgent route needs none) | Built in the core, not deployed |
| Reporting from the command-line tool | Built, switched off until launch |
| The in-app report button, the intake service that checks reports, appeals, and a transparency report | Planned |
Reporting in the app stays switched off until its legal review is signed off. Until then, abuse reports go by email, as the security page says.
Credit
Pigeon's AMF follows Nirvan Tyagi, Paul Grubbs, Julia Len, Ian Miers and Thomas Ristenpart, “Asymmetric Message Franking: Content Moderation for Metadata-Private End-to-End Encryption”, CRYPTO 2019; full version IACR ePrint 2019/565, made available under a Creative Commons Attribution (CC BY) licence. Pigeon's version changes the group and the hash, as listed above; any errors are ours, not the authors'.
Sources: ADR-0013 (reporting), docs/specs/franking-v1.md, docs/specs/amf-v1.md, docs/specs/report-v1.md, docs/specs/report-token-v1.md, docs/research/amf-source.md; crates/pigeon-core/src/frank.rs, crates/pigeon-core/src/frank/amf.rs.