区块链学习笔记(三)——比特币机制的原理
22 Jan 2018
本文是对 coursera 的 Bitcoin and Cryptocurrency Technologies课程笔记。
比特币共识机制保证了:
-
只增账本。
-
去中心化的协议。
-
矿工来验证交易。
3.1 比特币交易
上图中,每个 block 只有一笔交易。交易 1 为造币交易,没有输入,也不需要签名。交易 2 中,Alice 转给 Bob 17 个币,转给自己 8 个币,并且签名。
地址转换 (change address)
对于第 2 笔交易,25 个币完全被消耗,17 个给 Bob,多余的 8 个币转给自己的另外的地址,这个过程为地址转换。
有效性验证 (transaction valid)
对于第 4 笔交易,如何验证其合法性?需要验证其输入,来自于第 2 笔交易的第 2 个输出,8 个币转给 Alice(自己)。
验证新的交易是否有足够的币支付,只需要通过哈希指针向后查找。
合并资金 (merge value)
新建交易,交易中有两个输入(分别是之前交易中收到的 17 个币和 2 个币),一个输出(19 个币都指向自己的地址)这样就可以将其合并起来了。
共同支付 (join payment)
上图中的交易 4 有两个输入(分别是之前交易中指向 Carol 的 6 个币和指向 Bob 的 2 个币),一个输出(指向 David),最后需要 Carol 和 Bob 两个签名。
比特币真实交易数据
- 元数据 (metadata)
存放内部信息。此交易哈希值(hash
),交易的规模(size
),输入(vin_sz
)和输出数量(vout_sz
),以及锁定时间(lock_time
)。
- 输入列表 (inputs)
列表中的每个输入包含:前一个交易的输出(prev_out
),它包括两个属性,之前那笔交易的哈希值(hash
),以及那笔交易中当前输出的索引(n
);输入还包含一个签名(signature
),用来证明支配此输出的签名。
- 输出列表 (output)
列表中的每个输出包含:币值(value
),scriptPubKey
中有一个的公钥和一个比特币脚本。
3.2 比特币脚本 (Bitcoin scripts)
堆栈式脚本语言,非图灵完备,无函数功能。
执行过程
-
前两条为数据指令(data instruction),直接会被至于栈顶
-
OP_DUP
指令,将最上层的<pubKey>
复制,并置于栈顶 -
OP_HASH160
指令对栈顶的指令计算哈希值<pubKeyHash>
,并将结果替换当前栈顶的<pubKey>
-
向栈顶推送数据:此交易发送方指定公钥的哈希值,用来生成签名来兑换比特币。
-
栈顶两个数据,发送者指定的公钥哈希值,接收者获取比特币时声明的公钥的哈希值。执行
EQUALVERIFY
指令,来检查顶部两个数值是否相等,如果不相等则抛出错误并停止执行。假如值相等,说明接收者使用的公钥正确。指令会移除栈顶的两条数据。 -
现在栈顶只剩
sig
和pubKey
。接下来使用OP_CHECKSIG
指令检查签名的有效性,同时移除这两条数据。签名实际上是对整个交易签名,所以指令实际上是验证整个交易是否有效。
比特币脚本有 256 个指令(instructions),15 个目前不可用,75 个被保留(将来可能会扩展)。执行成功则交易有效,执行出错则交易失败。
还有一个多重签名验证指令 OP_CHECKMULTISIG
: 指令要求指定 n 个公钥和一个参数 t。指令执行成功的条件是,在 n 个公钥中至少有 t 个签名是有效的。
脚本的意思是,只有拥有此比特币的人,使用其私钥创建 <sig>
,结合 <pubKey>
,才能使得脚本运行通过,也就是才能使用这笔比特币。
一个真实的比特币交易细节:Bitcoin - Transaction Details
比特币重要 bug:OP_CHECKMULTISIG
指令会从对战中弹出一个额外的数值,需要在使用额外的虚拟变量存储,然后忽略掉。修复的成本很高,所以一直存在。
销毁证明 (proof of burn)
销毁证明用于证明比特币已经被消耗,不能再用于支付。使用 OP_RETURN
来实现,它会以抛出错误的形式结束脚本,所有指令都不会执行。同时会将 40 字节的数据嵌入到交易中。
有两个用处:
-
销毁少量的币和一定的交易费(0.0001 BTC)写入某些信息或留言
-
存在性证明 proof of existence,数字产权的验证 Monegraph,智能资产协议 Mastercoin 等。
脚本哈希值 (hash of the script)
为了简化多重支付地址 MULTISIG 的情况,比特币加入了脚本哈希值(P2SH)。交易的发送者不需要发送复杂脚本,只需要发送包含脚本哈希值的简单脚本。简单脚本会读取栈顶的值,验证是否与兑换脚本的哈希值相等。验证之后再将兑换脚本反序列化,并作为脚本执行,此时会进行签名验证。
解决两个问题:
-
使得发送方支付更简单。收款方只需要通知发送方一个哈希地址即可。
-
由于矿工必须查找没有被消费掉的输出脚本,所以 P2SH 通过将脚本简化为哈希值,提升了矿工的效率。
3.3 比特币脚本应用 (Applications of Bitcoin Scripts)
托管交易 (escrow transaction)
为避免在线交易过程中出现分歧,可以加入第三方当做裁判,由 MULTISIG
实现。比如三个人中,只需其中一个人和裁判签名,交易即为有效。
绿色地址 (green addresses)
接收方不在线,或者在线但没有时间,无法通过查看区块链的更新来确认交易。比特币使用第三方银行,银行从账户扣钱,然后从银行的绿色地址转账给接收者。接收者相信银行的话,则可以确信迟早会收到比特币。实际上,由于信任银行或交易所也有风险,这种交易并不靠谱。
高效小额支付 (efficient micro-payments)
例如,Bob 是 Alice 的手机通讯供应商,每分钟按照流量计费,但是每分钟支付一次不仅浪费算力,交易手续费也很高。于是引入合并支付,也就是通过连续的小额支付。在 Alice 开始打电话时,会发起一个可能花费的最大金额的 MULTISIG
交易。这个交易需要 Alice 和 Bob 双方的签名才能生效。随着 Alice 打电话的过程,每隔一分钟签名一次,挂机时,通知 Bob 切断服务,同时 Bob 也会进行签名并将交易发布到区块链上。
在技术上,所有这些交易都是双重支付(double spends)。但实际中,Bob 只有收到最后一个 Alice 签名时,才会进行签名,所以网络中并不会真实存在双重支付。
锁定时间 (Lock Time)
上述情景问题,如果 Bob 一直不签字,则这些比特币将一直存在于第三方。锁定时间可以解决:在发起交易时,可以添加 lock_time
字段,在 t 时间后,Bob 还没有签字的话,将会发生退款。lock_time
实际告诉矿工,在 t 时间之后这笔交易才能生效,并发布出去。
智能合约 (Smart Contract)
智能合约可以利用技术手段进行交易处理和合约保存。具体涉及到区块链 2.0 技术等,可参考这里。
步骤:
-
多方用户共同参与制定一份智能合约;
-
合约通过 P2P 网络扩散并存入区块链;
-
区块链构建的智能合约自动执行。
3.4 比特币区块 (Bitcoin blocks)
比特币区块包含两种数据结构。上面的部分是基于哈希的区块链。每个区块的区块头部都包含一个哈希指针指向上一个区块。第二种数据结构是梅克尔树(Merkle tree),它将包含的交易用高效的方式(log(n))组织起来。
区块头部除了以很多 0 开头的哈希值,还有时间戳、随机数以及一个标记挖掘难度的点数。区块头部是挖矿过程中唯一进行哈希计算的,因此要验证区块,只需要查看区块头部即可。区块头部唯一与交易有关的是梅克尔树的树根 mrkl_root
。
特别的是,梅克尔树的交易中有一个特殊的交易,币基交易(Coinbase transaction),这个交易创造了比特币。
特点:
-
只有一个单一的输入和单一的输出。
-
输出的值为 25 个币多一点,25 个币是矿工的收入,另一小部分是交易手续费。
-
此交易不消耗之前的比特币,因此,
prev_out
参数为空指针。 -
Coinbase的参数由矿工任意指定。
创世区块被创造的时候,该参数引用了伦敦《泰晤士时报》的一则报道:2009 年 1 月 3 日,财政大臣拯救银行。
可以在 blockchain.info 查看真实的区块数据结构,类似于下图。
3.5 比特币网络 (Bitcoin network)
比特币的 P2P 网络:
-
特定的协议(基于 TCP 端口 8333)
-
以随意的拓扑结构连成网络
-
所有的节点平等
-
新的节点随时可加入
-
无响应的节点 3 小时后会被遗忘
整个网络共同维护区块链。使用“泛洪”算法(flooding algorithm)来实现。
假设 Alice 支付比特币给 Bob,某个节点接收到了这个交易,于是,它会将这个交易广播给所有与之连接的节点。有点像八卦,所以也叫八卦(gossip)协议。其他节点经过验证后,也会将交易广播给所有与其连接的节点。当节点接收到一个交易后,会存放到一个交易池中,里面的交易还没有被打包放入区块链。如果收到的交易已存在于池中,便不会广播出去。
校验标准:
-
验证交易在当前区块链是有效的
-
只会接收和广播在白名单中的标准脚本
-
是不是已经在交易池中(避免死循环)
-
是否为双重支付
同一个币支付给不同的接收者,产生的两笔交易,节点会忽略后接收到的交易。但是如果网络中不同的节点接收到了不同的交易。如下图
这样节点就被分成两派,这称为“竞态条件”(race condition)。
这取决于挖掘出下一个区块的矿工,接收了是哪个交易。打包出的区块包含其受到的第一条交易,广播出去,其他区块会把另一条交易当做双重支付放弃掉。
泛洪算法延迟情况,下图表示被网络中不同数量的节点接收所花费的时间。三条线分别代表被 25%、50%、75% 的节点接收所需要的时间。
完全有效节点(fully validating node)会把完整的共识区块链保存下来。同时未消费的比特币的完整列表(UTXO)需要保存在内存中,保证能够快速验证有效性。
轻量节点(lightweight node),也叫简单付款节点(simple payment verification clients,SPV)。只存储需要进行校验的部分交易。没有所有的交易历史记录,也没有未被消费的比特币列表。可能只需要几十兆的数据。
3.6 限制和优化 (Limitations & improvements)
比特币中的限制
-
平均 10 分钟产生一个区块
-
区块大小限定为 1 MB
-
每个区块最多 20000 个签名操作
-
每个比特币为 1 亿聪(satoshis)
-
2100 万总量
-
50、25、12.5 的挖矿回报
每个区块 1MB,每个交易大概 250 字节,所以每块最多为 4000 个交易,十分钟一个交易,相当于每秒只能 7 个交易。另外,签名算法(ESDSA)也可能在将来被破解。
优化
硬分叉 (hard-forking)
强行引入新的特性,使得之前版本的协议失效。现实中不可能所有的节点都及时升级,那老节点和新节点会分别维护并扩展其区块链。
软分叉 (soft forks)
引入新的特性,使得验证规则更严格。加入大部分的节点都运行了新协议,并使用更严格的新规则,使得老节点会挖到一些无效的区块,因为这些区块会包含一些在新规则下无法被验证有效的交易。而新节点产生的区块,老节点都会验证通过。这样老节点便不得不去更新协议。不会产生硬分叉,但会临时产生一些小型分叉。
P2SH 便是软分叉的经典例子。对于老节点,一个有效的 P2SH 会被验证通过,它只会验证这个哈希值跟前一笔交易输出的哈希值是否一样,但不会去进一步验证脚本是否合法,而新节点会去做这个校验。
软分叉可能性:
-
新的签名格式
-
额外的元数据(指定币基参数格式、区块中包含 UTXO 的梅克尔树等)
其他的修改可能需要硬分叉,特别是区块大小的问题。