Skip to content

Conversation

@seuros
Copy link

@seuros seuros commented Nov 20, 2025

Rendered

This proposal adds a verification_hash field to encrypted events to enable cryptographic verification of reported content without breaking E2EE.

Uses SHA-256(plaintext || ciphertext_hash) construction where the ciphertext hash acts as a unique per-message pepper, preventing rainbow tables and mass surveillance while enabling moderation of reported messages.

@seuros seuros changed the title MSC: Peppered hash verification for E2EE content moderation MSC4382: Peppered hash verification for E2EE content moderation Nov 20, 2025
@seuros seuros marked this pull request as ready for review November 20, 2025 22:19
@turt2live turt2live added e2e proposal A matrix spec change proposal client-server Client-Server API kind:feature MSC for not-core and not-maintenance stuff needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. safety labels Nov 20, 2025
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation requirements:

  • Client
  • Server

- Use ciphertext as pepper instead of hashes.sha256
- Move verification_hash into content (clients can set it)
- Specify base64 encoding for hash output
- Specify canonical JSON encoding for plaintext
- Fix size calculation: 43 bytes base64 + overhead
- Wrap lines to 80 characters
Comment on lines 65 to 89
### Verification Process

When a user reports encrypted content, the server verifies:

```python
plaintext_event = canonical_json(report['plaintext'])
ciphertext = event['content']['ciphertext']

computed = base64(sha256(plaintext_event + ciphertext))

if computed == event['content']['verification_hash']:
# Report verified - plaintext is authentic
else:
# Report is false - reporter is lying
```
Copy link
Contributor

@Gnuxie Gnuxie Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment this ciphertext "pepper" is not helping and is salt because it is public and we can reproduce the hash and start making guesses.

Instead a secret pepper should be embedded in the plaintext event content, and that way it can only be accessed when you forward the entire plaintext event in a report (or decrypt the event).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Problem I'm Solving:

I run servers where users get brigaded, 99 reports against one person based on their identity or something they said in a private room. As a server admin, I face an impossible choice:

  • Believe the mass (potentially unjust)
  • Ask the reported user for their private keys (unacceptable)
  • Ignore reports entirely (enables actual abuse)

Simple hash(plaintext) enables rainbow tables.

With unique ciphertext per message, checking if "buy crypto" appears in 1 million messages requires 1 million fresh hash computations. This doesn't scale for surveillance but works perfectly for verification when I receive a report with claimed plaintext.

When I receive a report, I have:

  • event_id (which message is being reported)
  • claimed_plaintext (what reporter says it says)

I fetch from my database:

  • ciphertext (already stored in the encrypted event)
  • verification_hash (already stored in the encrypted event)

Then I compute: hash(claimed_plaintext || ciphertext)

If it matches the stored verification_hash, the report is authentic.

Alternative i considered we could pepper with user_id: hash(plaintext || user_id)

Your secret pepper approach is more robust cryptographically, but we're not protecting against post-quantum super computers.

We are solving a practical moderation problem: verifying report authenticity without breaking E2EE.

I'm open to changing the design if there is a use case or security concern that the current approach doesn't address.

What specific attack scenario are you protecting against that per-message public pepper doesn't prevent?

@seuros seuros force-pushed the peppered-hash-verification branch from 8efdf32 to 8a835ab Compare November 21, 2025 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client-server Client-Server API e2e kind:feature MSC for not-core and not-maintenance stuff needs-implementation This MSC does not have a qualifying implementation for the SCT to review. The MSC cannot enter FCP. proposal A matrix spec change proposal safety

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants