碎片与回执
消息从不完整地传输。它被拆成碎片,经由不同节点传递;每个送达的碎片都会产生一张匿名回执:证明工作已完成,却不透露是为谁完成的。
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 天后清除。