没有太多值得细读的内容(如果了解比特币的基本知识的话)。
和第 1 章类似。只有下条:
Bitcoin peers are commonly called “full verification nodes,” or full nodes for short.
从这里可以看到“全节点”的定义。
可能很多人(包括我自己以前的)理解是,全节点的“全”指的是完整保存了数据;但实际上,在比特币的语境中,全节点指的是“可完全验证的节点”。
介绍了比特币协议和比特币实现的关系(例如 Bitcoin Core)。主要介绍的是 Bitcoin Core 的操作。部署节点的时候还是推荐看文档,本章的描述可以搭配着看。
除了 Bitcoin Core 之外,也列举了其他客户端实现,例如 btcd、rust-bitcoin 等等。
第 4 章开始,内容逐渐进入正题。
首先是公私钥。
私钥的本质是一个随机数,当然是在非常大范围内随机地选择出来一个。这个范围是
程序具体的实现过程通常是用一个可信的随机源产生一个比 256 位更大一些的字符串,然后放入到安全的 SHA256 哈希函数中,产生的这个 256 位的数据可以被认为是一个随机数。
之后还介绍了具体的一些椭圆曲线算法。
最早的公钥是 65 字节的,但因为公钥是在
值得注意的是,一个早期很有意思的想法和实现:早期版本的比特币软件支持将 IP 地址作为收款地址。但后来因为需要保持在线、隐私和 NAT 映射的问题等,这个功能被移除掉了(这次笔记里第 1 次出现设计对于隐私的考量)。
同样在白皮书里出现的 P2PK 的地址也和付款给 IP 一样,其实没有大规模的使用过。后来使用的更多的是 P2PKH。
P2PKH 对公钥的承诺是把公钥先 SHA256、然后 RIPEMD-160。这么做的原因,中本聪没有说明过。
P2PKH 和 P2SH 是仅有的 2 个使用 base58check 编码的地址(脚本模板),目前主要都改用为 bech32 地址。
bech
的意思是 BCH (震惊!),但其实这个 BCH 是在 1959、1960 年发现循环纠错编码的三位科学家名字(Bose–Chaudhuri–Hocquenghem)的简称。32
代表使用了 32 种字符。也因为这个纠错的能力,比 base58check 更可靠。
另有一个有趣的优化例子,Bech32 倾向于全部使用小写字母,但如果改成全部大写的话,编码成二维码所需要的空间会更小。
bech32 | bech32m | |
---|---|---|
Witness Version | bc1q | bc1p |
Witness Program | 20 或 32 字节 | 32 字节(但未来可能有其他长度) |
checksum | 6 字节 | 6 字节 |
和压缩公钥类似,也有压缩私钥的概念。
但私钥本身是一个
而且压缩私钥反而长度会比普通私钥(WIF,Wallet Import Format)多 1 个字节,用来表示压缩公钥的 y
值正负。
私钥的不同格式:
格式 | 固定前缀 | 格式 |
---|---|---|
16 进制 | 无固定 | 64 位长度的 16 进制的数字 |
WIF | 5 | Base58 |
WIF-compressed | K 或 L | 增加 0x01 这么 1 个字节的后缀 |
尽管理论上有了私钥就可以推导出公钥了,但为了使用上的方便和隐私,钱包需要使用分层确定钱包等方式来维护多个公私钥对。因此,钱包采用什么方式来恢复就成了一个很重要的技术。
为了能有多个地址,早期的钱包需要把所有使用到的私钥保存下来。这么做需要保存的量比较大。
确定性生成更多密钥,在主密钥上加上一个数字,生成一个新的密钥。这项技术被称为 key tweak:
而具体怎么加这个数字,可以采用树形的结构,逐层来添加,并且层级没有限制。因此,可以无限生成(但恢复时也就无法全部遍历,因此备份路径也就很重要了,后文会提到)。
有比较流行的几种对随机种子生成助记词(recovery code)的方案:
口令短语(passphrase)可以与助记词(recovery code)同时使用,这在 BIP39、Electrum v2、Aezeed、SLIP39中都支持。
每个口令短语会导致生成一个种子,进而形成不同的 BIP32 的树。
所以其实也没有“错误口令”,因为每个口令都会指向一个(一系列)钱包。
可以在不设置口令生成的 BIP32 地址中放入一些少量的资金,在设置口令的地址中放入剩余的大量资金。
如果有一天发现不设置口令地址中的钱被转移走了,则说明这份助记词已经被泄漏,需要尽快转移有口令地址中的资金。
因为这个实践其实未必安全!因为从上面可以看到,助记词和口令短语必须配合使用才能恢复。所以从实际使用的效果来看,这个就类似 2-2 的多签,不如使用其他更安全的多签方案(参考曾汨老师在亿聪哲史播客里的介绍)
除了密钥数据外,钱包可能还要备份非密钥数据,例如交易备注。这样方便在不同钱包间迁移数据,以及用于会计软件等。
BIP329 就是用来统一设置这个格式的。
另外还有对于 LN 数据的备份等。
备份派生路径有 2 种思路,一种是都使用标准的派生路径;另一种是显示的和助记词一起备份(包括路径、花费条件等)
标准 | 脚本 | BIP32 路径 |
---|---|---|
BIP44 | P2PKH | m/44'/0'/0' |
BIP49 | Nested P2WPKH | m/49'/1'/0' |
BIP84 | P2WPKH | m/84'/0'/0' |
BIP86 | P2TR 单密钥 | m/86'/0'/0' |
记住助记词不等于脑钱包。
脑钱包一般指的是人为挑选的一些有意义的词语,因此随机性比较差,相对不安全。
BIP39 的具体过程如下:
扩展密钥:密钥 + chain code
“扩展”的意思也可以被理解为是未来可以扩展更多密钥。
扩展子密钥的过程可以递归的进行:
输入:
输出:
使用私钥,而不是公钥来派生子密钥
具体的过程除了看书,还有不少资料可以参考,例如 BTC Study 上的 BIP32 拓展密钥图解
因为选不同的索引数字,会对应到不同的子密钥,而这一过程又是可以不断重复下去的,所以需要一套规则来识别出来。
i'
的形式来表示另外,主密钥中也跟随了密码学上的惯例:
m
表示从主私钥派生出来的私钥M
表示从主公钥派生出来的公钥因为子密钥可以无限、任意的生成下去,所以需要一套标准来统一设置路径:
m / purpose' / coin_type' / account' / change / address_index
其中:
主要讨论了交易的序列化和结构、以及交易延展性问题。
OP_CHECKSEQUENCEVERIFY
的含义做了修改,改为 RBF 和相对时间锁,见下文中的输入部份的序列号字段。)序列号(sequence)字段:
序列号:
包括:
有输出后,这笔交易未花费前就是一个 UTXO,需要保存
如果有了粉尘攻击或铭文之后, UTXO 集会很大。所以还有一种解决办法是 Utreexo,一般节点只保存一个 UTXO 集的承诺。但仍然需要某些节点保存全部 UTXO 数据。
另外,脚本中有一个特例是 OP_RETURN,不可花费(所以一般也都是 0 金额的),所以也就不用占用 UTXO 集。一般用来携带数据。
Malleability,也被翻译成 “熔融性”,在密码学上是指给定一个签名,对方/敌手可以修改这个签名(虽然不知道私钥),使其成为不同但仍然有效的一个签名。
但比特币这里的延展性也会涉及到包括:
OP_2
、OP_PUSH1 0x002
等等),从而与原交易产生冲突在这篇 2014 年的文章 Bitcoin Transaction Malleability and MtGox 里也提到过延展性的问题。MtGox 声称攻击者就是用延展性攻击的办法耗尽了交易所资金。
但延展性也不完全是坏事,有些交易就是希望被其他人来扩展和修改,例如用 SIGHASH
来让其他人补完交易内容(代付手续、众筹)等。所以书里也强调,如果说是延展性攻击,可以用 “unwanted malleability”。
早在 2011 年,开始者就知道怎么解决上面提到的循环依赖、第二方、第三方延展性问题。思路也很直接:既然是因为会产生不同的
输入脚本里的数据,一个更抽象的名字是 “见证”(witness):了解了某项知识才能解锁资金。所以上面这种办法也叫做是 “隔离见证”。
虽然思路很简单,但如何实现升级却需要仔细考虑:
这样锁定-解锁关系就有了以下的演变:
一类特殊的交易,就是 “挖矿” 的收益。
另有一个特殊之处在于 “成熟度要求”(maturity rule):100 个区块确认后才可花费。
交易的手续费计费单位为:交易重量,或者是虚拟字节(Vbyte。4 个单位的虚拟字节等于一个实际的字节)
虽然书里没有过多展开,但这也和隔离见证联系比较紧密,因为隔离见证区域里的数据在计算上是有折扣。更多可以参考:见证数据的折扣:为什么有些字节比别的字节 “更轻”。
授权与鉴权。这部分延续上一章的交易内容,重点介绍了脚本
现在就可以用脚本里的 OP_CHECKMULTISIG
来实现多签了,比如 2-3 的多签(当然还有更灵活、成本更低的无脚本方式)。
这里有一个有意思的事情是,Bitcoin 最早的开发者(就是指中本聪?),在实现 OP_CHECKMULTISIG
的时候,还需要堆栈里额外 pop 一个没有实际意义的哑元。可以是任何一种内容,但通常使用 OP_0
。
所以实际的全部脚本会类似于:
OP_0 <Sig B> <Sig C> 2 <Pubkey A> <Pubkey B> <Pubkey C> 3 OP_CHECKMULTISIG
书里也不清楚这种设计到底是一个 bug 还是一个(为未来升级预留的)feature,因此书里称其为 “oddity”。但因为已经是共识规则了,所以未来的 OP_CHECKMULTISIG
都必须沿用。这可能也算是软分叉升级的一种技术债?
BIP13 提出的,将一个脚本哈希作为地址。
这其实也可以算作另一种账户抽象或智能合约账户的实现。
OP_RETURN
及应用这里又提了一次 OP_RETURN
。除了不可花费、不用包含在 UTXO 集中之外,还提到了一些应用.
例如:Proof of Existence 电子公证服务,使用固定的前缀 DOCPROOF
。
包括绝对时间锁、相对时间锁
实现层面包括:交易层面、脚本 opcde 层面
sequence
字段OP_CLTV
(绝对时间锁)、OP_CSV
(相对时间锁)segwit v0 版本的见证程序可以包括:P2WPKH、P2WSH
区别是:
另外有一点,P2WPKH 必须从压缩公钥的哈希来构造出来。这应该也体现了对节约链上空间的极度追求?(因为花费的见证结构里要提供公钥)
P2WPKH、P2WSH 同样都可以嵌入在一个 P2SH 地址中,成为 nested P2WPKH 和 nested P2WSH。
这样做的好处是,兼容原有 UTXO 输出(例如发送方没升级),同时享受到低手续费(例如接收方已经升级)。
书里的这个例子里就是:
MAST 可以包含更多的程序内容或条件,而实现这种方式,只需要对各种花费条件做一个承诺,所以 MAST 仍然用到了 Merkle 树。这样做的好处:
除了增加复杂度之外,BIP114、BIP116 提议的这种方式没其他缺点。但后面有了更好的实现——Taproot。
另外,“MAST” 其实有两个含义:
Pay to Contract,也是 BIP32 里提到的派生密钥的一种使用方法。作用:
书中的例子:因为某个事项,Bob 付款给 Alice
这样:
不过目前更多使用的 P2C 是在 Taproot 里的稍微不太一样的形式。
比脚本方式:
和 P2C 略有区别,tweak 用的哈希是对一个程序代码的承诺。承诺的方式是对 一个 MAST 的树根
Taproot 使用的和比特币脚本里的略微有些差别,因此被称为是 tapscript。主要区别包括:
OP_CHECKMULTISIG
和 OP_CHECKMULTISIGVERIFY
,因为和 schnorr 签名批量验证的方式没法很好的结合;对应的,增加了 OP_CHECKSIGADD
,成功验证一个签名就加一,用来数有多少个签名通过校验了ECDSA:使用 DER 格式
schnorr:使用一种更简单的序列化格式
虽然算法不一样,但比特币里都遵循相同的签名流程:
其中:
签名可以只签交易内容的一部份
SIGHASH flag | 值 | 描述 |
---|---|---|
ALL | 0x01 | 签名适用于所有的输入和输出 |
NONE | 0x02 | 签名适用于所有的输入,不适用于任何输出 |
SINGLE | 0x03 | 签名适用于所有的输入,只适用于和这个输入对应的(即相同 index)的输出 |
基本类型可以被 SIGHASH_ANYONECANPAY
所修饰,产生 3 种新类型
SIGHASH flag | Value | Description |
---|---|---|
ALL|ANYONECANPAY | 0x81 | 签名适用于一个输入和所有的输出 |
NONE|ANYONECANPAY | 0x82 | 签名适用于一个输入,不适用于输出 |
SINGLE|ANYONECANPAY | 0x83 | 签名适用于一个输入和对应的(即相同 index)的输出 |
第二版里其中有一个图,显示的更直观。不确定为什么在第三版里删除了。
ALL|ANYONECANPAY
:众筹类的交易。输出已经确定,但输入可以被多个人所添加知道满足输出的金额NONE
:空白支票,任何人都可以在输出部分写入自己的地址来获得这笔资金NONE|ANYONECANPAY
:粉尘回收器。铭文其实就可以基于这个 SIGHASH 来回收掉还有一些提案在准备扩充 SIGHASH,例如 BIP118 想增加的:
SIGHASH_ANYPREVOUT
:
SIGHASH_ANYPREVOUT
给其中一笔的签名,也可以被复制并用在另外一笔花费到相同地址的交易上SIGHASH_ANYPREVOUTANYSCRIPT
:
SIGHASH_ANYPREVOUTANYSCRIPT
给其中一笔的签名,也可以被复制并用在另外一笔花费到相同地址的交易上Schnorr 签名的特点:
与 Schnorr 签名对应的还有一个概念是 Schnorr 身份协议。本书里提供了一个简化后的例子。
Schnorr 其实和 ZKP 也有关系。
Schnorr 身份识别协议也算一种交互式的 ZKP:与挑战者交互(承诺、挑战、证明)后,证明者可以证明出来他知道一些见证。
Schnorr 签名是用 Fiat-Shamir 转换把 Schnorr 身份识别协议变成非交互式的。办法是:
这样就有了 1)可以证明知道某个见证 2)与这个证据相关的、对消息的承诺。
比特币使用的是 BIP340——secp256k1 的 Schnorr 签名
因为有非强化的密钥派生(用公钥来派生),所以可以在公钥签过名的消息上,也增加用来派生的消息,然后产生一个可以通过验证的新签名。所以 BIP340 需要也对公钥进行承诺,也就是
因为线性可加,多签产生非常简单。聚合公钥
但还可能会有一些问题,例如密钥抵消攻击(key cancellation attack)。
有很多办法解决,例如最简单的方案是各方在共享公钥前,需要先对公钥进行承诺(但这样也在非强化的密钥派生情况下会失效)。除此之外,还有对 nonce 的攻击等等。
所以,为了安全的多签,需要建立一些协议。一些常用的协议包括:
签名用到的 k 值有讲究:
所以可以看到,以太坊的 nonce 和密码学上的 nonce 其实不算同一个概念(待确认)。
为了避免重复:
因为验签需要做大量哈希运算,哈希运算数量和签名数量成平方关系,在以前的版本里,攻击者可以构造出一个有大量签名的交易,来 DoS 比特币网络。
隔离见证在 BIP143 中使用了一种改进后的承诺哈希算法,哈希运算数量和签名数量成线性关系
介绍了手续费的来源(输出 - 输入),手续费的单位等。
另外也介绍了 RBF、CPFP 两种追加手续费的方案,以及包中继等。
某种程度上也可以认为是一种递进关系?
在闪电网络中,CPFP 可能会更有用。因为在不合作的关闭通道时,没办法新签名一笔交易来 RBF 了,只能使用 CPFP;但如果父交易都没办法进入 mempool,则只能依靠包中继了。
下面,重点在于这些手续费引发的一些问题以及如何解决的。
Transaction Pinning,攻击者有时候可以阻止受害者或让他很难追加手续费。
为解决上面的问题,开发者提出了一个例外规则:CPFP carve out,允许增加第 25 个交易(体积不超过 1,000 vbyte)
目前大多数的闪电网络实现都通过锚点输出的交易模板来支持 carve out。
锚点输出在书里没有展开。还是可以参考上面的文章:什么是 “锚点输出”?。锚点输出是一笔承诺交易的两个输出(各属于通道的其中一方),这样任何一方都可以安全地为承诺交易追加手续费,而不必担心交易被对方钉死。
另外还有一些解决方案在讨论中,例如临时锚点(ephemeral anchors)等。
是一种偏理论的攻击。矿工试图重写历史区块,从而打包未来更多的高费率交易,让自己受益。
除了书里介绍的问题外,还有例如今年(2023)10 月份披露出来的对闪电网络的 “替代循环攻击”。具体可以参考 mononautical 的解释和我之前的记录。
网络这章主要将比特币的网络架构、数据包传输、节点间的请求等。
首先提了一个很有意思的观察(注意下面这句的大小写):目前的互联网架构(internet architecture) 是更层级化的,但互联网协议(Internet Protocol) 仍然是扁平化的。
下面讲解了致密区块(compact block)以及 FIBRE。
有一个注意的:轻客户端可以验证存在一笔交易,但无法确定是否有双花,因为它没有全部的交易信息
为了降低轻客户端与全节点之间的通讯,引入了 Bloom 过滤器。
轻客户端送到 bloom 过滤器里的包括:交易 ID、每个交易输出的数据部分、每个交易的输入、签名。
但问题在于缺乏隐私。全节点可以根据每次请求的 bloom 过滤器分析出来地址之间的关联。Bitcoin Core 已经将 bloom 过滤器仅限于节点的白名单地址中。
虽然名字都有 “致密”(compact),但是和致密区块没关系。
由服务器(节点)产生过滤器,而不是客户端(钱包、轻客户端)发送请求过去,因此更加隐私。另外:
2 种区块标识符:区块头哈希、区块高度
区块哈希:
区块高度:
然后本章讲解了 merkle 树。
2023 年的 Coinbase 交易一般有 2 个输出:
MTP:Median Time Past
有两个重要且有区别的时间概念:实际时间(wall time)、共识时间(consensus time)。
由于分布式系统很难同步时间,另外矿工有可能因为经济利益而对时间撒谎(把时间往未来调,这样来操控降低挖矿难度)。因此有两个共识规则:
共识时间(MTP)通常比实际时间晚 1 个小时(11 个区块的中位数)。
BIP68 相对时间锁激活的时候,也对锁相关的 “时间” 计算有了改变。
矿工会尽可能使用最晚的时间(因为可以打包更多时间锁的交易)。为了进一步确保安全,MTP 目前(BIP113 之后)是共识时间,用来计算各种时间锁。因为中位数更难被操控。
书里有一点值得注意:更短的区块间隔,并不意味着更早的结算。
因为这只意味着接收人愿意承担更弱的安全保证。
所以目前大多数人仍然倾向于比特币的 10 分钟区块间隔。
介绍了一些矿池的技巧:
Stratum:
share chain:
这里有一个值得注意的:双花攻击只能用在攻击者自己的交易商,因为只有攻击者自己才能产生合法的签名。
和很多传统系统甚至大多数区块链网络不一样的地方。
硬分叉一般包括 4 个阶段:软件分叉、网络分叉、挖矿算力分叉、链分叉
软分叉其实不算一个分叉,因为是对共识规则的向前兼容(?)修改,运行未升级的客户端继续在新的规则下运行。有很多办法可以实现,如利用 NOP
操作符(已有两次软分叉升级利用了 NOP
)。
但软分叉也不是百利而无一害的,会引入技术债,而且实际上是无法回退的(因为算是引入了新的约束条件。如果回退,则使用新规则下的交易资金会有损失)。
另外,就算是软分叉升级,方式也是多种多样的。历史上采用了多种形式。
BIP34 本身是定义了区块头比较包含区块高度(见之前笔记里的 “区块头和区块高度”)。它采用了一个两阶段的激活机制,基于 1,000 个区块的滑动窗口期。如果准备好了,矿工将他们的区块版本从 1
改为 2
。
2
,那么 BIP34 规定的 “区块头比较包含区块高度” 的规则必须开始在标 2
的区块中包含;但 1
版本的区块仍然被接受2
,1
版本的区块则不再被接受。所有区块都必须符合新的共识规则后来还有两次升级使用了 BIP34 的方式:
3
4
但这几次软分叉升级后, BIP34 采用的这种方式就被替代掉了。因为有些缺点:
流程是:
但后来也发现了一些问题:
和 BIP9 基本相同,只增加了一个 MUST_SIGNAL
阶段,矿工必须表态。
一些人认为就是因为要表态会使用 BIP8,taproot 升级才能最终成功激活。但这也无法证明或证伪,以及看出来 BIP8 到底在这次升级中贡献了多少。
快速试错(speedy trial):“fail fast or succeed eventually”。
最后为了激活 taproot,介于 BIP9 和 BIP8 的一种方式被提议使用,一种修改版的 BIP9:
不过未来的升级中,还不太确定会使用哪种方式,例如是否还会再用快速试错。
考虑到最近的 BIP119 等提议,估计届时又会有很多的关于激活方式的讨论。
但本章最后有一句话很有深意:
The one constant characteristic of consensus software development is that change is difficult and consensus forces compromise.
安全这章感觉写的其实一般。不如直接参考曾汨老师的比特币多签指南等安全指导文章。
这部分也包括了 RGB、Taproot Assets 等,概念还是包括一次性封条、P2C、客户端验证
和 RGB 的区别:
介绍了很多,不过又是一个大的 rabbit hole。也可以参考我之前写的闪电网络的学习记录。
附录:白皮书
很有意思的是,紧急着白皮书就是对白皮书的 “勘误”。
虽然不能完全算是勘误,因为不全是错误,大部分都是因为技术迭代、有了新的研究发现或术语定义发生了变化等,白皮书里的内容已经不符合现在的实际情况了。这一点我和阿剑老师在 Hash Out 42 Podcast 上也会提到。
因此,学习比特币真的不能把白皮书看成那么重要的材料,尽管仍然是一份很好的、很 high-level 的入门概述材料。
BIP1 里定义了三种 BIP 提案: