Fragments and receipts

A message never travels whole. It is split into fragments that go through different nodes, and each delivered fragment produces an anonymous receipt: proof that the work was done, without saying for whom.

Reed–Solomon 3 of 5

Each padded envelope is split into stripes of k = 3 of n = 5 with Reed–Solomon over GF(256): out of every five pieces, any three are enough. Two nodes can drop out and the message still arrives.

Scheme Overhead Delivery probability if each route succeeds 95% of the time
4 of 4, no redundancy 1.00× 81.5%
4 + 1 parity (v1) 1.25× 97.7%
3 of 5 (v2) 1.67× 99.9%

The piece size depends on the bucket: a third of the bucket rounded to 1 KB, with a maximum of 24 KB. Large envelopes use several stripes.

Shard = { id, msg, mailbox, stripe, idx, k, n, bucket, data, rh, exp }
id    = sha512(msg ‖ stripe ‖ idx)[0..32]       msg is random on every send

The piece doesn't carry the message's real size (that's inside the envelope, encrypted) or anything about who sent it.

Rotating mailboxes

Fragments wait in a mailbox. Each device has its own mailbox seed, and its mailbox changes every day:

mailbox(day) = hex(HMAC-SHA512(mailboxSeed, "day:" + floor(ts / 86 400 000)))[0..40]

The sender uses the recipient's mailbox for today; the recipient opens today's and yesterday's. A node can't link Monday's mailbox to Tuesday's.

Only the owner opens their mailbox

Opening a mailbox isn't signed with the identity. The sender includes in each piece an mbAuth tag derived from the recipient's mailbox secret. To open, the owner presents a signed proof over velum/v2/open|<mailbox>|<ts> that the node checks against that tag. So only the owner collects their pieces and the node doesn't learn who they are.

Proof of Delivery: the anonymous receipt

Inside the envelope (which only the recipient can open) there's a random seed rseed. For each piece:

secret_i = HMAC-SHA512(rseed, shardId)[0..32]
rh_i     = SHA-512(secret_i)          ← travels in the piece, in the open

When reassembling the message, the recipient reveals each secret_i. Anyone can check that SHA-512(secret) == rh, but nobody else could have known the secret: a node can't fake receipts.

{
  "shard": "7a0c…",          // shard id
  "mailbox": "c2e81f…",      // today's rotating mailbox (40 hex)
  "rh": "…128 hex…",         // sha512(secret), already inside the shard
  "secret": "…64 hex…",      // HMAC-SHA512(rseed, shardId)[0..32], only the recipient can compute it
  "carrier": "vel…",         // node that accepted the shard
  "courier": "vel…",         // node that delivered it
  "ts": 1793577600000
}

The receipt carries no signature and no address of the recipient. Each receipt earns credits:

  • 1 credit to the carrier (the node that accepted the piece).
  • 1 credit to the courier (the node that delivered it).
  • If they are the same node, 2 credits.

From receipts to claims

With billions of messages a day, receipts can't go into blocks one by one. Each node gathers its own into a Merkle tree (SHA-512) and publishes only the root and the total (work.claim). In the next epoch, the beacon picks 16 random receipts and the node must show them with their secret and path (work.prove). If one fails, it loses that epoch's reward. Details in Run a node.

Anti-cheating

  • The same piece counts only once.
  • Cap on credits per epoch and per (node, mailbox) pair: 400.
  • Receipts more than ±15 minutes away from the block time don't count.
  • Nobody can claim more than 1.5 times what is expected for their weight in the lottery.
  • Faking traffic doesn't pay: generating receipts requires opening envelopes, and only a real recipient can do that.
  • The nodes that carry each piece come from a public lottery of 3 candidates; the client only picks the closest of those three.

Deletion

As soon as a node sees a valid receipt for a piece, it deletes it. The receipt spreads through the network and every node holding a copy deletes it too. If nobody collects a piece, it is destroyed after 7 days.

The journey, summed up