TESTNET|Data may be reset

How Private Feeds Work

End-to-end encrypted content sharing with efficient revocation on a public blockchain

The Challenge: Privacy 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?

The Core Insight

On a public blockchain, you can't hide data—you can only make it unreadable to those without the right keys.

Why Simple Solutions Don't Work

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.

Solution 1: Encrypt for Each Follower

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.

Solution 2: Share One Key

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.

Solution 3: Re-encrypt Everything

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.

ApproachPost CostRevoke Cost
Encrypt per-followerO(N)O(1)
Share one keyO(1)O(N) or impossible
Re-encrypt on revokeO(1)O(posts × N)
What we needO(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 Solution: Manage Keys, Not Content

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).

Analogy: The Building with Security Checkpoints

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 Structure

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:

RootAll approved followers know thisNode ANode BNode CNode DNode ENode F............Slot 1AliceSlot 2...Slot 1024Bob

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):

Root
The master key that unlocks content
Node A
Intermediate key on Alice's path
Node C
...
Slot 1
Alice's unique leaf key

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.

Epochs: Forward Secrecy Through Time

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:

Hash Chain

CEK[2000]
Pre-generated at setup
SHA256
CEK[1999]
= SHA256(CEK[2000])
SHA256
CEK[2]
After first revocation
SHA256
CEK[1]
Initial epoch (feed starts here)

Epoch numbers increase over time: 1 → 2 → 3 → ... (higher = newer content)

CEK[5] → CEK[4] → CEK[3](CAN derive older)
CEK[3] → CEK[4] → CEK[5](CANNOT derive newer)

Epoch numbers increase over time (1 → 2 → 3 ...). Higher epochs correspond to newer content. The chain has a crucial property:

  • Backward derivation works: If you learn CEK[5], you can compute CEK[4] by hashing. You can keep going back to derive CEK[3], CEK[2], CEK[1]—all the older epochs.
  • Forward derivation is impossible: If you only have CEK[3], you cannot figure out CEK[4] or CEK[5]. There's no mathematical way to reverse SHA256.

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.

Forward Secrecy

Revoked users cannot read posts created after their revocation, even though they still have their old keys.

Revocation: The Elegant Part

Now for the clever bit. When you revoke Alice, you need to:

  1. Advance to a new epoch (so Alice can't derive the new CEK)
  2. Share the new CEK with remaining followers (without telling Alice)

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:

Before Revocation

(Epoch 1)

Root (v1)Node 2 (v1)Node 3 (v1)Alice (v1)↑ REVOKEDBob (v1)Carol (v1)

After Revoking Alice

(Epoch 2)

Root (v2)New!Node 2 (v2)New!Node 3 (v1)SameAlice (v1)Can't decryptBob (v1)Carol (v1)

Step by step:

  1. Identify the revoked path: Alice's path is Leaf→Node 2→Root
  2. Generate new versions: Create new keys for Node 2 (v2) and Root (v2)
  3. Share Node 2 v2 via sibling leaf: Alice and Bob both know Node 2 v1 and Root v1, so we can't encrypt under those. But Bob has his own leaf key that Alice doesn't know! We encrypt "new Node 2 v2" under Bob's leaf key. Bob can decrypt it; Alice cannot.
  4. Share Root v2 via sibling subtrees: Now we need to distribute the new Root key. We encrypt "new Root v2" under "new Node 2 v2" (which Bob just learned) and under "old Node 3 v1" (which Carol knows). Alice doesn't have access to either of these keys.
  5. Encrypt new CEK under new Root: Finally, encrypt CEK[epoch 2] under Root v2.
Rekey packets posted to blockchain:
1
new_Node2_v2 encrypted under Bob_leaf
Bob can decrypt (Alice doesn't know his leaf key)
2
new_Root_v2 encrypted under new_Node2_v2
Bob can decrypt (He just learned Node2 v2)
3
new_Root_v2 encrypted under old_Node3_v1
Carol can decrypt (She knows Node3, Alice doesn't)
4
CEK[epoch2] encrypted under new_Root_v2
Anyone with Root v2 can decrypt

Alice 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.

Cost comparison

  • Naive approach: Contact 999 followers individually = O(N)
  • LKH approach: Post ~20 packets = O(log N)

Putting It All Together

Let's walk through each operation with concrete examples. Alice is the feed owner; Bob and Carol are followers.

Enable Private Feed
  1. 1
    Generate random 256-bit seed (master secret)
  2. 2
    Derive tree node keys from seed
  3. 3
    Pre-compute hash chain: CEK[2000] → CEK[1]
  4. 4
    Encrypt seed to your own public key (for recovery)
  5. 5
    Store encrypted seed on blockchain
  6. 6
    Local state: epoch=1, all 1024 slots available
Grant Access (Alice approves Bob)
  1. 1
    Pick available leaf slot (say, slot 2)
  2. 2
    Compute path keys: slot → intermediate nodes → root
  3. 3
    Bundle: path keys + current CEK + current epoch
  4. 4
    Encrypt bundle to Bob's public key (ECIES)
  5. 5
    Store grant document on blockchain
  6. 6
    Mark slot 2 as assigned to Bob

Bob receives ~500 bytes containing everything needed to decrypt all current and past private posts.

Create Private Post
  1. 1
    Get CEK for current epoch
  2. 2
    Generate random nonce (prevents duplicate keys)
  3. 3
    Derive post key:
    • HKDF(CEK, "post" || nonce || authorId)
  4. 4
    Encrypt with XChaCha20-Poly1305
  5. 5
    Store on blockchain:
    • encryptedContent (ciphertext)
    • epoch (which CEK version)
    • nonce (for key derivation)
    • teaser (optional public preview)
Decrypt Post (Bob reading Alice's post)
  1. 1
    Check post's epoch vs Bob's cached epoch
  2. 2
    If behind: fetch rekey documents, apply them
  3. 3
    Derive CEK for post's epoch:
    • If same as cached: use cached CEK
    • If older: hash backward from cached CEK
  4. 4
    Derive post key:
    • HKDF(CEK, "post" || nonce || ownerId)
  5. 5
    Decrypt with XChaCha20-Poly1305
  6. 6
    If decryption fails: show teaser or "locked" icon
Revoke Access (Alice revokes Bob)
  1. 1
    Look up Bob's leaf slot (slot 2)
  2. 2
    Increment epoch: 1 → 2
  3. 3
    Compute Bob's path to root
  4. 4
    Generate new versions for each node on path
  5. 5
    Create rekey packets (~20 packets):
    • Each new key encrypted under sibling subtree keys
  6. 6
    Encrypt new CEK under new root key
  7. 7
    Store rekey document on blockchain
  8. 8
    Delete Bob's grant document
  9. 9
    Return slot 2 to available pool

Total cost: 2 blockchain operations + local computation

What's Protected (And What Isn't)

Protected

  • Revoked users cannot decrypt posts created after revocation
  • Non-approved users cannot decrypt private posts
  • Content is authenticated (tampering is detected)
  • Each post uses a unique key (compromising one doesn't expose others)
  • Multi-device recovery (seed encrypted for owner's key)

Not Protected

  • Approved followers sharing content (screenshots, copy-paste)
  • Metadata is visible (who follows you, when posts were made, etc.)
  • Revoked users keep access to posts from when they were approved
  • Compromised devices (if malware has your keys, game over)
  • Owner's seed compromise (would expose entire feed)

Design Philosophy

We protect against cryptographic attacks, not social attacks. If someone you trusted shares your content with others, that's a human problem no encryption can solve. Private feeds ensure that only the people you approve can read your posts—what they do with that access is up to them.

Under the Hood

What Gets Written to the Chain?

Four document types power the private feed system:

  • PrivateFeedState

    Created once when enabling private feed. Contains the encrypted seed (for owner recovery), tree capacity, and initial state. ~300 bytes.

  • PrivateFeedGrant

    Created for each approved follower. Contains their leaf slot assignment and an encrypted bundle of path keys + current CEK. ~500 bytes per follower.

  • PrivateFeedRekey

    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.

  • Post(extended)

    Private posts add fields to the standard post document: encryptedContent, epoch, nonce, and optional public teaser.

Cryptographic Primitives

PurposeAlgorithm
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)

Capacity Limits

LimitValue
Maximum private followers1,024
Maximum epochs (revocations)2,000
Rekey packets per revocation~20
Grant document size~500 bytes
Rekey document size~1-2 KB

Menu