碎片与回执

消息从不完整地传输。它被拆成碎片,经由不同节点传递;每个送达的碎片都会产生一张匿名回执:证明工作已完成,却不透露是为谁完成的。

Reed–Solomon 3/5

每个填充后的信封按 k = 3、n = 5 分条,用 GF(256) 上的 Reed–Solomon 编码:每五个碎片中,任意三个就够了。即使有两个节点失联,消息照样送达。

方案 空间占用 每条路径成功率 95% 时的送达概率
4/4,无冗余 1.00× 81.5%
4 + 1 校验(v1) 1.25× 97.7%
3/5(v2) 1.67× 99.9%

碎片大小取决于档位:档位的三分之一,向上取整到 1 KB,最大 24 KB。大信封会使用多个条带。

Shard = { id, msg, mailbox, stripe, idx, k, n, bucket, data, rh, exp }
id    = sha512(msg ‖ stripe ‖ idx)[0..32]       msg 每次发送随机生成

碎片既不包含消息的真实大小(它加密存放在信封内),也不包含发件人的任何信息。

轮换的信箱

碎片在信箱中等待。每台设备都有自己的信箱种子,信箱每天更换:

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

发件人使用收件人当天的信箱;收件人打开今天和昨天的信箱。节点无法把周一的信箱和周二的信箱联系起来。

只有主人能打开信箱

打开信箱不用身份签名。发件人会在每个碎片中放入一个由收件人信箱秘密派生的标记 mbAuth。打开时,主人出示对 velum/v2/open|<信箱>|<ts> 的签名证明,节点用这个标记核验。因此只有主人能取走碎片,而节点并不知道主人是谁。

投递证明:匿名回执

信封内部(只有收件人能打开)放着一个随机种子 rseed。对每个碎片:

秘密_i = HMAC-SHA512(rseed, shardId)[0..32]
rh_i   = SHA-512(秘密_i)          ← 随碎片公开传输

拼合消息时,收件人公开每个 秘密_i。任何人都能验证 SHA-512(秘密) == rh,但除了收件人,没人可能知道这个秘密:节点无法伪造回执。

{
  "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
}

回执没有签名,也没有收件人地址。每张回执带来积分:

  • 给 carrier(接收该碎片的节点)记 1 个积分。
  • 给 courier(投递该碎片的节点)记 1 个积分。
  • 如果是同一个节点,记 2 个积分。

从回执到工作申报

每天有数十亿条消息时,回执不可能逐张写进区块。每个节点把自己的回执汇成一棵 Merkle 树(SHA-512),只公布树根和总数(work.claim)。下一个纪元,信标会随机抽取 16 张回执,节点必须连同秘密和路径一起出示(work.prove)。只要有一张出错,就失去该纪元的奖励。详见搭建节点。

防作弊

  • 同一个碎片只计一次。
  • 每个纪元、每对(节点,信箱)的积分上限为 400。
  • 与区块时间相差超过 ±15 分钟的回执不计。
  • 任何节点申报的工作量都不能超过其抽签权重预期值的 1.5 倍。
  • 刷假流量没有收益:要产生回执就必须打开信封,而只有真实的收件人能做到。
  • 携带每个碎片的节点来自一次公开抽签选出的 3 个候选节点;客户端只能在这三个中选择最近的一个。

删除

节点一旦看到某个碎片的有效回执,就会删除它。回执在网络中传播,所有持有副本的节点也随之删除。无人领取的碎片 7 天后清除。

旅程回顾