BlindPost BlindPost ← All Posts
English简体中文繁體中文

Why BlindPost can host 100,000-member groups

WhatsApp caps groups at 1,024 members. Signal at 1,000. Telegram supergroups stretch further by sharding. Most messengers cap somewhere in the low thousands because their servers have to fan out every group message to every member — duplicate the envelope, route it N times, deliver N pushes.

BlindPost works differently. For us, sending a group message is closer to publishing an article than to mailing letters. The author publishes once. Anyone who has the article's address — and the key to read it — can come and fetch it. The reader count has no theoretical limit, because the publishing step doesn't get any harder when more people read.

Our server holds the encrypted "article." Anyone holding the group's key (its members) can come fetch and decrypt. No fan-out, no per-member duplication, no membership list anywhere. Here's how that works at the standard-crypto level.

How a normal messenger handles a group message

When you post in a WhatsApp group, the server does roughly this:

  1. The message lands at the server, addressed to "group ABC."
  2. The server looks up the membership list for group ABC — let's say 1,000 entries.
  3. The server pushes a per-recipient envelope into each of the 1,000 members' delivery queues.
  4. Each member's phone receives one push.

This is server-side fanout. One incoming message becomes N outgoing pushes. The server's CPU, bandwidth, and storage scale with member count, multiplied across every message ever sent in every group ever created. Past a few thousand members the math turns ugly fast.

How a BlindPost group is built

A BlindPost group is, mechanically, just an X25519 public key. That's all. The thing our server stores in its "group" column is literally a base58-encoded public key.

When you create a group, your client:

  1. Generates an X25519 keypair locally.
  2. Uses the public half as the group's permanent identifier.
  3. Distributes the private half, encrypted end-to-end, to each invited member.

Membership, on BlindPost, is defined operationally: anyone who holds the group's private key. There is no list of members anywhere on our server. Members come and go by being added to or removed from the private-key circle, and our server has no way to enumerate that circle.

Two layers of identifier

Sharp readers may have noticed an awkward consequence of "a group is just an X25519 public key": if the key ever rotates (and we will, later in this post, to remove members), the cryptographic identifier changes too. From the server's point of view that's fine. From the user's point of view ("wait, did my group get a new ID?") it isn't.

So each BlindPost client maintains two layers of identifier for every group:

When we say "the group's public-key identifier" elsewhere in this post, we mean the cryptographic layer. The stable local label is purely for the UI to keep continuity when the underlying key changes.

How a group message travels

When you send a message to a group:

  1. Your client picks an ephemeral X25519 keypair, derives a shared secret via ECDH against the group's public key, encrypts the message once.
  2. Sends one envelope to the server, addressed to the group's public-key identifier.
  3. The server stores that envelope in the group channel. Done.

There is no fanout step. Your sender bandwidth is O(1) — you send the same single envelope whether the group has 5 members or 50,000.

How members read it: sparse pull

Members pull on their own schedule. Each member's client tracks a cursor — the last sequence number it read from the group channel — and periodically asks the server: "any new envelopes in group ABC since seq N?"

This is what we call sparse pull. Each client decides:

The server holds messages on disk, indexed by the group's public-key identifier. It serves whatever ranges it's asked for. It never tracks who's reading, who's a member, or who's behind. From the server's view, every fetch is just "some client requesting envelopes since seq N in group ABC." That client could be any of 100,000 humans, or one human pulling from five devices, or someone who just got hold of the public key out of curiosity.

The cost structure flips:

"Member count" is a number that doesn't exist anywhere in our infrastructure.

A free privacy bonus

We didn't set out to hide group membership — we just don't have a place to write it down. The server has no membership table at all. It cannot answer "who is in group ABC" because the question has no data backing it.

The most a subpoena can extract from us is: "encrypted bytes flowed between IP A and group public-key B during this window." Whether A is a member of B's group, or just an automated client asking for envelopes by guessing public keys — we have no way to say.

The trade-offs

We pay for this in a few places:

These trade-offs buy you a server that doesn't know your social graph, and group sizes limited by your phone's rendering performance — not our cluster's fanout capacity.

Try BlindPost