基本原理和设计
从分布式系统角度看,比特币网络是一个典型的分布式点对点网络,网络中的矿工通过“挖矿”来完成对交易记录的记账过程,维护网络的正常运行。
区块链网络提供一个公共可见的记账本,该记账本记录网络中所有的交易历史,而并非记录各个账户的余额。该设计可以避免重放攻击,即某个交易被多次重复提交。
基本交易过程
比特币中没有账户的概念。因此,每次发生交易,用户需要将交易记录写到比特币网络账本中,等网络确认后即可认为交易完成。
除了挖矿获得奖励的 coinbase 交易只有输出,正常情况下每个交易需要包括若干输入和输出,未经使用(引用)的交易的输出(Unspent Transaction Outputs,UTXO)可以被新的交易引用作为其合法的输入。被使用过的交易的输出(Spent Transaction Outputs,STXO),则无法被引用作为合法输入。
因此,比特币网络中一笔合法的交易,必须是引用某些已存在交易的 UTXO(必须是属于付款方才能合法引用)作为新交易的输入,并生成新的 UTXO(将属于收款方)。
那么,在交易过程中,付款方如何证明自己所引用的 UTXO 合法?比特币中通过“签名脚本”来实现,并且指定“输出脚本”来限制将来能使用新 UTXO 者只能为指定收款方。对每笔交易,付款方需要进行签名确认。并且,对每一笔交易来说,总输入不能小于总输出。总输入相比总输出多余的部分称为交易费用(Transaction Fee),为生成包含该交易区块的矿工所获得。目前规定每笔交易的交易费用不能小于 0.0001 BTC,交易费用越高,越多矿工愿意包含该交易,也就越早被放到网络中。交易费用在奖励矿工的同时,也避免了网络受到大量攻击。
交易中金额的最小单位是“聪”,即一亿分之一(10^-8)比特币。
下图展示了一些简单的示例交易。更一般情况下,交易的输入、输出可以为多方。
交易目的输入输出签名差额
T0
A 转给 B
他人向 A 交易的输出
B 账户可以使用该交易
A 签名确认
输入减输出,为交易服务费
T1
B 转给 C
T0 的输出
C 账户可以使用该交易
B 签名确认
输入减输出,为交易服务费
…
X 转给 Y
他人向 X 交易的输出
Y 账户可以使用该交易
X 签名确认
输入减输出,为交易服务费
需要注意,刚放进网络中的交易(深度为 0)并非是实时得到确认的。进入网络中的交易存在被推翻的可能性,一般要再生成几个新的区块后(深度大于 0)才认为该交易被确认。
比特币采用了 UTXO 模型,相对账户模型,UTXO 模型可以更容易实现并行处理和隐私保护,并追踪完整交易路径;但由于需要存储和检索所有交易记录,对节点存储压力较大。
下面分别介绍比特币网络中的重要概念和主要设计思路。
重要概念账户/地址
比特币采用了非对称的加密算法,用户自己保留私钥,对自己发出的交易进行签名确认,并公开公钥。
比特币的账户地址其实就是用户公钥经过一系列 Hash(HASH160,或先进行 SHA256,然后进行 RIPEMD160)及编码运算后生成的 160 位(20 字节)的字符串。
一般地,也常常对账户地址串进行 Base58Check 编码,并添加前导字节(表明支持哪种脚本)和 4 字节校验字节,以提高可读性和准确性。
注:账户并非直接是公钥内容,而是 Hash 后的值,避免公钥过早公开后导致被破解出私钥。
交易
交易是完成比特币功能的核心概念,一条交易可能包括如下信息:
网络中节点收到交易信息后,将进行如下检查:
检查都通过,则将交易标记为合法的未确认交易,并在网络内进行广播。
用户可以从 blockchain.info 网站查看实时的交易信息,一个示例交易的内容如下图所示。
交易脚本
脚本(Script) 是保障交易完成(主要用于检验交易是否合法)的核心机制,当所依附的交易发生时被触发。通过脚本机制而非写死交易过程,比特币网络实现了一定的可扩展性。比特币脚本语言是一种非图灵完备的语言,类似 Forth 语言。
一般每个交易都会包括两个脚本:负责输入的解锁脚本(scriptSig)和负责输出的锁定脚本(scriptPubKey)。
输出脚本一般由付款方对交易设置锁定,用来对能动用这笔交易的输出(例如,要花费该交易的输出)的对象(收款方)进行权限控制,例如限制必须是某个公钥的拥有者才能花费这笔交易。
认领脚本则用来证明自己可以满足交易输出脚本的锁定条件,即对某个交易的输出(比特币)的拥有权。
输出脚本目前支持两种类型:
以 P2PKH 为例,输出脚本的格式为
scriptPubKey: OP_DUP OP_HASH160
OP_EQUALVERIFY OP_CHECKSIG
其中,OP_DUP 是复制栈顶元素;OP_HASH160 是计算 hash 值;OP_EQUALVERIFY 判断栈顶两元素是否相等;OP_CHECKSIG 判断签名是否合法。这条指令实际上保证了只有 pubKey 的拥有者才能合法引用这个输出。
另外一个交易如果要花费这个输出,在引用这个输出的时候,需要提供认领脚本格式为
scriptSig:
其中, 是拿 pubKey 对应的私钥对交易(全部交易的输出、输入和脚本)Hash 值进行签名,pubKey 的 Hash 值需要等于 pubKeyHash。
进行交易验证时,会按照先 scriptSig 后 scriptPubKey 的顺序进行依次入栈处理,即完整指令为:
OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
读者可以按照栈的过程来进行推算,理解整个脚本的验证过程。
引入脚本机制带来了灵活性,但也引入了更多的安全风险。比特币脚本支持的指令集十分简单,基于栈的处理方式,并且非图灵完备,此外还添加了额外的一些限制(大小限制等)。
区块
比特币区块链的一个区块不能超过 1 MB,将主要包括如下内容:
其中,区块头信息十分重要,包括:
可见,要对区块链的完整性进行检查,只需要检验各个区块头部信息即可,无需获取到具体的交易内容,这也是简单交易验证(Simple Payment Verification,SPV)的基本原理。另外,通过头部的链接,提供时序关系的同时加大了对区块中数据进行篡改的难度。
一个示例区块如下图所示。
创新设计
比特币在设计上提出了很多创新点,主要考虑了避免作恶、采用负反馈调节和基于概率的共识机制等三个方面。
如何避免作恶
基于经济博弈原理。在一个开放的网络中,无法通过技术手段保证每个人都是合作的。但可以通过经济博弈来让合作者得到利益,让非合作者遭受损失和风险。
实际上,博弈论早已被广泛应用到众多领域。
一个经典的例子是两个人来分一个蛋糕,如果都想拿到较大的一块,在没有第三方的前提下,该怎么制定规则才公平?
最简单的一个方案是任意一个人负责分配蛋糕,并且这个人后挑选。
注:如果推广到 N 个人呢?
比特币网络中所有试图参与者(矿工)都首先要付出挖矿的代价,进行算力消耗,越想拿到新区块的决定权,意味着抵押的算力越多。一旦失败,这些算力都会被没收掉,成为沉没成本。当网络中存在众多参与者时,个体试图拿到新区块决定权要付出的算力成本是巨大的,意味着进行一次作恶付出的代价已经超过可能带来的好处。