Elea Notes.

词条 · 加密与分布式 · 入门

区块

一批交易打包成一页账,配一个约 80 字节的头,头里记着上一页的指纹

也称:区块、区块头、块、block、block header、blocks

下面从一个不需要任何密码学知识的麻烦讲起。

先看一个麻烦:一叠纸条怎么排先后

假设有一百个人在同一个大厅里做买卖。每成交一笔,就写一张纸条丢进大厅中间的箱子里:“甲付给乙 3 块钱”。

到了晚上要结账,你把箱子倒出来,一堆纸条散在桌上。麻烦来了:你不知道它们的先后顺序

顺序不是可有可无的细节,它决定对错。甲账上只有 3 块钱,却写了两张纸条:一张付给乙,一张付给丙。两张单独看都合法,谁先谁后决定了哪一张该被认、哪一张该被拒。顺序错了,账就错了。

而且纸条是散的,你没法证明桌上这一堆和箱子里原来那一堆是同一堆。有人抽掉一张、塞进一张、把两张对调,你事后完全看不出来。

朴素尝试为什么都不够

顺着”给纸条排队”这个思路,读者第一反应通常是下面几招。它们全都不够用,而每一次不够都指出了一条必须满足的要求。

招数一:写纸条的时候自己标上时间。 每张纸条上写”18:42:07”。问题是这个时间是写纸条的人自己填的,他想填几点就填几点。要作弊的人当然会把对自己有利的那张标早一点。谁都能改的时间戳不构成证据。

招数二:给纸条编号,1、2、3 排下去。 这就要求有个人守着箱子发号。可他一个人说了算:他可以把某张纸条的号往后挪,也可以事后重编一遍全部号码。这只是把”信谁”从一百个人挪到了一个人身上,没有解决问题,只是搬了个地方。

招数三:不排单张,改成一叠一叠地排。 每十分钟把箱子里的纸条捆成一捆,捆与捆之间有明确先后,捆内部不分先后。这一招方向对了——它把”给一万张纸条排序”这个难题缩成了”给一百捆排序”,问题小了两个数量级。但光捆起来还不行:捆和捆之间靠什么连?如果只是摞在一起,抽掉中间一捆照样看不出来。

招数四:把每一捆的全部内容抄一份存档,谁想查就比对。 能防篡改了,代价是每个人都要存下全部纸条的副本,还得逐张比对。这正是哈希函数那一条里说过的循环:验证不能依赖”已经拥有你想验证的东西”

四次失败凑出三条要求。一页账必须:

  1. 捆内不排序、捆间排序——把排序的粒度放粗,问题才有解;
  2. 每一捆牢牢咬住上一捆——抽掉或改动任何一捆,后面全部失效;
  3. 咬住的方式必须很小——不能靠”存一份副本”来咬,否则回到招数四。

满足这三条的那一捆,就叫区块。

机制:头 80 字节,身子不限长

区块分两部分,只有头是定长的:

区块 N
├─ 区块头(固定 80 字节,链条真正咬合的地方)
│    版本号        4 字节
│    前块哈希     32 字节   → 上一个区块头的哈希,这就是"咬住"
│    Merkle 根    32 字节   → 一个指纹概括下面全部交易
│    时间戳        4 字节
│    难度目标      4 字节
│    随机数        4 字节   → 挖矿时反复改的就是它
└─ 交易列表(不算在 80 字节里,长度不限)
     交易 1、交易 2、……、交易 2000

4 + 32 + 32 + 4 + 4 + 4 = 80。这个数字是白皮书第 7 节自己算的:按十分钟一个区块,一年 6 × 24 × 365 = 52,560 个头,80 × 52,560 = 4,204,800 字节,约 4.2 MB 一年。

对比一下身子:2000 笔交易、每笔约 250 字节,是 500,000 字节。头只占 80 / 500,000 = 0.016%。头小到可以人人都存,这正是轻节点能成立的前提。

现在手算”咬住”是怎么生效的。用一个小号哈希H(前一个值, 本页内容) = (前一个值 × 31 + 本页内容) % 1000% 是取余数)。三页账的内容分别压成 5、12、7:

第 1 页:h1 = (0   × 31 + 5)  % 1000 = 5
第 2 页:h2 = (5   × 31 + 12) % 1000 = (155  + 12) % 1000 = 167
第 3 页:h3 = (167 × 31 + 7)  % 1000 = (5177 + 7)  % 1000 = 184

现在有人偷偷把第 1 页的内容从 5 改成 6,重算一遍:

第 1 页:h1 = (0   × 31 + 6)  % 1000 = 6      ← 5 变成 6
第 2 页:h2 = (6   × 31 + 12) % 1000 = 198    ← 167 变成 198
第 3 页:h3 = (198 × 31 + 7)  % 1000 = 145    ← 184 变成 145

只动了最早那一页里的一个数,后面每一页的指纹全都变了。这就是招数三缺的那样东西:捆与捆之间不是摞着,是算出来的。想改历史里任何一页,就得把它之后所有页重算一遍。

回头看:这一页账就是那张小纸条,用了两次

哈希函数那一条讲过一张”很小、牵一发动全身、不可还原”的小纸条。区块头里那两个 32 字节的字段,是同一张纸条的两种用法,方向刚好垂直:

  • Merkle——把这一页里全部交易压成一个指纹,管的是纵向:这页内部有没有被动过。做法见 Merkle 树
  • 前块哈希——把上一页的头压成一个指纹,管的是横向:页与页的先后有没有被动过。

两者合起来才封死了整个空间。少了 Merkle 根,可以在页内换交易;少了前块哈希,可以在页间换顺序。所以区块头不是一堆字段的随意集合,它是”用一个定长指纹咬住不定长内容”这一招在两个方向上各用了一次。

另外注意,招数二的毛病并没有被”技术”解决,而是被换掉了:不再由某个人发号,而是谁先算出合规的头谁的页就接上去。定序权从身份转移到了工作量,这是工作量证明那一条的事。

边界与常见误解

80 字节说的是头,不是整个区块。 一个区块通常是几百 KB 到一两 MB,绝大部分是交易数据。白皮书写 “a block header with no transactions would be about 80 bytes”,那个 “with no transactions” 是关键限定语。混淆这两个数会得出”整条链一年只长 4.2 MB”的荒谬结论。

同一区块内的交易顺序不是时间顺序。 清单里的排列由打包者决定,不代表谁真的先发生。区块链定的是区块之间的序,块内只保证”这些交易同时生效且互不冲突”。想靠交易在清单里的位置论先后,是误用。

区块头里的时间戳同样是打包者填的,仍然可以撒谎。 招数一的毛病没有消失,只是被限制住了:网络会拒绝偏离太远的时间。所以那个字段是个粗略区间,不是可信的精确时刻,不要拿它做需要秒级精度的判断。

“区块被打包”不等于”这笔钱确定到账”。 一页账刚接上去时可能被更长的链替换掉,见分叉与孤块。要确定性,得等后面再压上几页。