End-to-end encrypted content sharing with efficient revocation on a public blockchain
Yappr stores all data on Dash Platform—a public blockchain. Unlike a traditional database where you can set permissions on who sees what, blockchain data is visible to everyone. Anyone running a node can read every document ever created.
So how do you share content with only specific people when the storage medium is inherently public?
Traditional social networks solve this with access control: their servers check who you are before showing you content. But Yappr has no servers. There's no gatekeeper to enforce "only show this to my approved followers."
The answer is cryptography. Instead of controlling who can access the data, we control who can understand it. Private posts are encrypted before they're stored on the blockchain. Only approved followers have the keys to decrypt them.
But this creates new problems. How do you share keys with hundreds of followers efficiently? And crucially—how do you revoke access when you no longer want someone reading your posts?
Before explaining how private feeds actually work, let's explore why the obvious approaches fail. Understanding these constraints helps appreciate why the real solution is designed the way it is.
The Idea: When you create a private post, encrypt it separately for each follower using their public key. This is how encrypted email works—each recipient gets their own encrypted copy.
The Fatal Flaw: Storage explodes. With 1,000 followers, every post requires 1,000 separate encrypted copies stored on the blockchain. A 500-character post becomes 500KB of data. Post ten times a day for a year, and you've used nearly 2GB of blockchain storage—just for your posts.
Cost: O(N) storage per post, where N is your follower count.
The Idea: Generate a single "private feed key" and share it with all your approved followers. They all use the same key to decrypt your posts.
The Fatal Flaw: Revocation is impossible. Once someone has your key, they have it forever. You can't "un-share" a secret. If you change the key, you'd need to re-share it with all remaining followers—and they'd lose access to all your old posts encrypted with the previous key.
Cost: Revocation requires O(N) key distribution.
The Idea: When you revoke someone, generate a new key, re-encrypt all your past posts with it, and share the new key with everyone still approved.
The Fatal Flaw: This requires modifying every historical post on every revocation. With 1,000 posts and 1,000 followers, revoking one person means 1,000 re-encryption operations plus 999 key distributions.
Cost: O(posts × followers) work per revocation.
| Approach | Post Cost | Revoke Cost |
|---|---|---|
| Encrypt per-follower | O(N) | O(1) |
| Share one key | O(1) | O(N) or impossible |
| Re-encrypt on revoke | O(1) | O(posts × N) |
| What we need | O(1) | O(log N) |
The pattern is clear: these approaches all have the wrong scaling factor somewhere. We need O(1) cost for creating posts AND efficient revocation. This seems impossible—but it's not.
The breakthrough comes from reframing the problem. Instead of thinking about encrypting content for people, think about managing keys that unlock content.
Here's the insight: encrypt your posts with a single key (the Content Encryption Key, or CEK). Then separately manage who has access to that CEK. When you revoke someone, you don't touch the content—you update the key and control who learns the new one.
This is called "broadcast encryption" or "multicast key management." It's the same problem cable companies solved decades ago: they can't send different signals to each home, so they send one encrypted signal and manage who has the keys to decrypt it. When you stop paying, they update the keys without telling you.
The specific technique Yappr uses is called a Logical Key Hierarchy (LKH)—a binary tree structure that makes revocation cost O(log N) instead of O(N).
Imagine a building where a vault sits at the center, but to reach it you must pass through a series of locked doors. Each employee gets keys for a unique path of doors leading to the vault.
When someone leaves the company, you don't change every lock in the building. You only change the locks on the doors they knew about. Then you pass new keys to remaining employees through adjacent hallways they can still access.
This is exactly how our key tree works.
The key tree is a binary tree with 1,024 leaf positions—each leaf can be assigned to one follower. The tree looks like this:
Binary key tree structure with 1,024 leaf slots
When Alice is approved for your private feed, she's assigned a leaf slot (say, slot 1). She then receives the keys for every node on the path from her slot to the root—about 10 keys total:
Alice knows these keys (slot 1):
Alice does NOT know: Node B, Node D, Node E, etc. (these are on other followers' paths)
Every follower reaches the root through a different path. They all know the root key (which unlocks content), but they reach it via different intermediate keys. This is what makes efficient revocation possible.
The root key doesn't directly encrypt posts. Instead, it unlocks a Content Encryption Key (CEK) for the current "epoch." Each time you revoke someone, the epoch advances, and a new CEK is used for future posts.
CEKs are connected in a clever way called a hash chain:
Epoch numbers increase over time: 1 → 2 → 3 → ... (higher = newer content)
Epoch numbers increase over time (1 → 2 → 3 ...). Higher epochs correspond to newer content. The chain has a crucial property:
Why does this matter? When we revoke someone at epoch 3, we advance to epoch 4. The revoked user has CEK[3], so they can still derive older keys (CEK[2], CEK[1]) and read historical posts. But they can't derive CEK[4]—they're locked out of all future content.
Now for the clever bit. When you revoke Alice, you need to:
But how do you share a new key with Bob without Alice intercepting it? Remember, everything goes on the public blockchain.
The answer uses the tree structure. Here's what happens:
(Epoch 1)
(Epoch 2)
new_Node2_v2 encrypted under Bob_leafnew_Root_v2 encrypted under new_Node2_v2new_Root_v2 encrypted under old_Node3_v1CEK[epoch2] encrypted under new_Root_v2Alice can read these packets, but can't decrypt ANY of them. She doesn't have Bob's leaf key, Node3, or the new versions.
The result: approximately 20 small "rekey packets" posted to the blockchain. Every remaining follower can decrypt one of them, learn the new keys, and access future content. Alice is completely locked out.
Let's walk through each operation with concrete examples. Alice is the feed owner; Bob and Carol are followers.
Bob receives ~500 bytes containing everything needed to decrypt all current and past private posts.
Total cost: 2 blockchain operations + local computation
Four document types power the private feed system:
Created once when enabling private feed. Contains the encrypted seed (for owner recovery), tree capacity, and initial state. ~300 bytes.
Created for each approved follower. Contains their leaf slot assignment and an encrypted bundle of path keys + current CEK. ~500 bytes per follower.
Created on each revocation. Contains rekey packets (new keys encrypted for sibling subtrees), the new epoch number, and a state snapshot for recovery. ~1-2 KB.
Private posts add fields to the standard post document: encryptedContent, epoch, nonce, and optional public teaser.
| Purpose | Algorithm |
|---|---|
| Content encryption | XChaCha20-Poly1305 256-bit key, 192-bit nonce |
| Key derivation | HKDF-SHA256 Context strings prevent misuse |
| Epoch chain | SHA256 hash chain 2000 epochs pre-generated |
| Key exchange | ECIES (ECDH + XChaCha20-Poly1305) secp256k1 curve (same as Dash) |
| Limit | Value |
|---|---|
| Maximum private followers | 1,024 |
| Maximum epochs (revocations) | 2,000 |
| Rekey packets per revocation | ~20 |
| Grant document size | ~500 bytes |
| Rekey document size | ~1-2 KB |