区块链: 技术驱动金融 - 第3章 比特币的运行机制

    这一章将真正近距离地了解比特币所使用的数据结构、实际脚本及语言,会有大量的细节性信息,极具挑战性,但本章可以帮助我们真正懂得比特币的本质。

3.1 比特币的交易

    比特币的交易过程其实就是不停地创造区块的过程。我们先看一个简单模式的账簿,尽管比特币并不使用这种模式,但有助于我们理解比特币的账簿模式。

    建立一个以账户为核心的系统,可以创造新的币并放入某人的账号中,然后就可以转给其他人了。这样的话,交易的信息如下图所示:

3.1.1 基于账户的账簿中保存的交易信息

    这样就会带来一个问题,当别人要验证一笔交易是否有效,就必须跟踪每一个账户的余额。这样是相当麻烦的,而且还增加了记账的工作量。

    在比特币系统中,每个交易都有输入值输出值。输入可以看成是将被消费掉的币(这些币是前一个交易创造出来的,所以我们回想一下第2章中提到的两类不同的哈希指针),输出可以看成是在本次交易中创造出来的币。每一笔交易都有一个独一无二的ID。每一笔交易可以有多个输出,输出的索引从0开始,所以我们称第一个输出为“输出0”。

    现在让我们来看一下如下图所示的账本。

3.1.2 与比特币类似的基于交易的账本

    交易1是铸造新币的交易,没有输入,有1个输出,输出0:转25个币给Alice

    交易21个输入,2个输出,输入来源于交易1的输出0,而本次交易的输出0:转17个币给Bob,输出1:转8个币给自己。由Alice签名。

    其他的交易类似。

    地址转换。在这个例子中,为何Alice需要把币转给自己?事实上,一个交易中输出的币,要么在另一个交易中被完全消费掉,要么一个都不被消费,不存在只消费部分的情况。在交易2中,Alice只需要转17个币给Bob,那剩下的8个币就需要转到属于她自己的另一个地址上。

    有效验证。在这个例子中,我们要核查Alice引用的交易输出,确认她确实有25个币没有花掉。为此,我们只需要从Alice所引用的交易开始,一直核查到账本上最新的交易记录即可。当然,在这里,Alice还是花费了一部分币。注意,这和前面所说的不存在部分消费的情况是不一样的,前面说的是在一笔交易中,输入可以被分为两部分:一部分用于AliceBob的交易,另一部分是转给了Alice自己,以保证输入等于输出。事实上,在比特币中,还应该有另一部分,用于作为生成区块节点的交易费,否则,提议下一个区块的节点可能不会考虑把这个交易打包到区块中。所以我们可能会听到,在比特币中,输入应该大于输出,其实指的是,输出不包含交易费,也就说,输入=输出+交易费

    资金合并。假设Bob在两笔不同的交易中分别收到17个币和2个币,现在他想把这两笔钱合并起来,那么,他只需要发起以一个交易,交易里有两个输入和一个输出,输出地址是他自己的地址,这样,就把两个交易合二为一了。

    共同支付CarolBob想要共同支付给David,他们可以发起一个交易,交易有两个输入和一个输出。不同的是,两个输入所引用的“上一笔交易”的输出地址不同,因此,这笔交易需要两个签名:Carol的和Bob的。

    交易语法。比特币交易涉及的概念就是以上这些。下面看看比特币在底层是如何实现的。实际上,比特币在网络上传输的数据结构都是一串字符,使用json格式。

3.1.3 一个真实的比特币交易程序段

  • 元数据。存放一些内部处理信息。主要有:hash:这笔交易本身的哈希值,也就是独一无二的IDvin_sz:输入数量;vout_sz:输出数量;lock_time:锁定时间;size:这笔交易的规模。其他的一些字段根据应用需求添加。
  • 输入in:输入的json数组。数组中每个输入包括的内容为:prev_out:上一笔交易的哈希值(hash)+索引(n),用于指明这个输入是来自于哪一笔交易的哪一个输出scriptSig:输入脚本(有的书上称为“解锁脚本”),包含这笔交易创建者的签名(私钥签名)及其公钥。
  • 输出out:输出的json数组。数组中每个输出包含的内容为:value:输出金额;scriptPubKey:输出脚本(有的书上称为“锁定脚本”),包含接收者的公钥(或公钥的哈希值)及一些脚本指令。注意,所有输出的金额之和必须小于等于输入的金额之和,如果输出小于输入,那么这个差额就作为交易费付给为这笔交易记账的矿工。

3.2 比特币的脚本

    最常见的比特币交易,就是通过某人的签名去取得他在前一笔交易(可能是别人发起的向他转账的交易,或自己发起的转到自己地址上的交易如资金合并)中获得的资金,因此,一个交易的输出应该这样描述:凭借哈希值为X的公钥,以及这个公钥所有者的签名,才可以获得这笔资金

    仔细体会这句话,我们就能理解比特币的输入脚本和输出脚本之间的关系。

    假设Alice想要创建一笔交易,来花费掉Bob转给她的钱。让我们从Bob创建的交易(也就是Alice引用的上一笔交易)的输出脚本(即锁定脚本)说起。Bob在输出脚本中放入Alice的公钥的哈希值,并且指定一条规则:为了确保来取这笔钱的是Alice本人,那么必须满足两个条件:正确的公钥哈希值,真正的Alice签名。当然,这条规则是由输出脚本中的多个指令来完成的。现在,让我们来看一下Alice创建的这笔交易到底有没有资格获得这笔钱,或者更直接地说,这笔交易是否正当有效。为此,Alice在输入脚本(即解锁脚本)中放入自己的公钥,并用私钥对整个交易签名。

    当Alice创建了这笔交易并进行广播之后,其他节点就可以对这笔交易进行验证了。验证的方式是把Alice这笔交易的输入脚本和Alice引用的前一笔交易(即Bob创建并签名的交易)的输出脚本合在一起形成串联脚本,并执行这个脚本来验证这笔交易是否正当有效。下图是一个串联脚本的范例。

3.2.1 结合输入脚本和输出脚本的脚本范例

    比特币脚本语言是堆栈式的,每个指令只执行一次。执行比特币脚本只能产生两种结果:要么被成功执行,表示交易有效;要么出现错误,整个交易无效,拒绝记入区块链。下面让我们看一下上图脚本的执行结果是如何的。

    尖括号<>里的是数据指令,每执行一个数据指令,就在堆栈的顶端添加一个数据。OP开头的是工作码指令,执行一些操作。下图是比特币脚本执行的堆栈状态图。

 

3.2.2 比特币脚本执行的堆栈状态图

    结合AliceBob的例子,看看比特币脚本的执行如何确认交易是否正当有效。

  • <sig><pubKey>:把Alice提供的签名和公钥入栈。
  • OP_DUP:复制Alice提供的公钥,入栈。
  • OP_HASH160:计算Alice提供的公钥的哈希值,替换公钥并入栈。
  • <pubKeyHash?>Bob指定的公钥哈希值入栈。
  • OP_EQUALVERIFY:(应该是没有尖括号的)比较Alice提供的和Bob指定的公钥哈希值。如果相等,两个公钥哈希值出栈,继续执行;如果不等,提示出错,整个交易无效。
  • OP_CHECKSIG:验证Alice提供的签名,验证成功,返回true;验证失败,返回false。公钥和签名出栈,返回结果入栈。回想第一章通用数字签名方案,使用公钥验证签名。

    当然了,一笔交易是否有效,除了上面的验证,还有一个直观的条件,就是输出必须小于等于输入

实际情况

    实际中还会用到其他的一些脚本指令,如多重签名MULTISIG,支付给脚本的哈希值Pay-to-script-hash(P2SH)。但除此之外,平常用到的脚本指令并不多,每个节点都有一份标准脚本的白名单,它们会拒绝接受不在名单上的脚本。这倒不是说不能执行其他的脚本,只是使用起来有点麻烦。

销毁证明

    销毁证明(proof of burn)脚本,用于销毁比特币。实际应用中主要用来引导客户使用其他数字货币系统,即将比特币销毁,以便获得另一个数字货币系统发行的新币。

支付给脚本的哈希值

    如前文所述,比特币的工作机制要求发送者在交易时必须明确指定输出脚本(这个输出脚本将被用于下一笔交易)。但想象一下这样的场景:你准备购买一件商品,询问商家“请把付款地址告诉我,我可以付款了”,如果商家使用了多重签名地址,那他会说“我们使用了多重签名地址,你需要支付给一个脚本地址,而不是一个简单的地址”,但是你会说“这个太复杂了,我只会支付给简单的地址”。

    比特币的一个方法是:收款方告诉付款方“请把比特币支付给某个脚本地址,其哈希值为xx,在取款时,我会提供上述哈希值对应的脚本,同时,提供数据通过脚本的验证”。付款方通过P2SH即可实现上述交易。

    P2SH不是比特币的原始设计,是后来加上去的,它解决了两个重要的问题:1.让付款方的支付工作简单化。在上述例子中,商家只需要告诉你一个哈希值即可,你不必关心商家到底用了哪种地址,是否用了多重签名,因为这是商家在支取这笔款项是需要考虑的事情(注:换个角度考虑,当你作为收款人时,你也需要考虑同样的问题)2.实现效率上的提升。矿工的工作是追踪那些还没有被消费掉的输出脚本,采用P2SH的输出脚本就只是一个哈希值而已,所有的复杂性都被放在输入脚本中了。

3.3 比特币脚本的应用

    本节介绍比特币脚本的一些应用场景。

第三方支付交易

    例如,Alice用比特币向Bob购买商品,Alice想货到付款,Bob想见款发货。该如何处理?一个好的方法是使用第三方支付交易(escrow transaction)第三方支付交易可以用多重签名MULTISIG来实现。具体如下:Alice不直接付款给Bob,而是发起一个多重签名的交易,并规定:三个人中有两人签名之后,资金才能被支取。这三个人是AliceBob和第三方仲裁员JudyJudy负责调解可能发生的纠纷。这个交易被纳入区块链之后,资金被第三方监管,这三个人中的任意两人可以决定资金的去向。通常情况下,如果AliceBob都是诚实的,Bob会按照Alice的要求发货,Alice在收货之后和Bob共同签名,把资金转给Bob

    但如果Bob并未发货,或者货物中途丢失,或者Bob发的货不是Alice想要的。这时,Alice不想支付给Bob,而是想从第三方监管账户中取回来。那么,Alice不会签名完成真正付款,而Bob也不会承认问题而主动放弃收款,这时,就需要Judy的仲裁。如果Judy认为Alice被骗了,那么就和Alice共同签名,把资金退回给Alice;如果Judy认为Bob应该收款,那么就和Bob共同签名,把资金转给Bob

绿色地址

    绿色地址(green addresses):假设AliceBob的店里买了一根热狗,并且向Bob支付了一定的比特币。一般来说,一个交易需要获得6次确认,我们才能确信它已经确实被加入到区块链中,但是这大约需要一个小时,这对于仅仅是买一根热狗来说是不可接受的。

    为了解决这个问题,比特币采用第三方银行的做法。实际上,“银行”可能是某个交易所或金融机构。当AliceBob转账时,银行会从Alice的账号中扣钱,然后从银行控制的某个账户——称之为“绿色账户”,转给Bob,而且,银行保证不会双重支付这笔钱,如果Bob也相信这一点,那么当他看到银行签名的交易时,就可以为Alice提供服务,即给Alice一根热狗,因为Bob相信,最终会收到银行转给他的这笔钱,只要区块链确认银行签名的这笔交易。

    注意,这不是比特币系统技术的保证,而是现实世界中银行的保证。银行为了保护它的声誉,不会双重支付比特币。

高效小额支付(efficient micro-payments)

    假设AliceBob的客户,需要持续向Bob支付小额费用,如BobAlice的手机流量提供商,根据Alice每分钟使用的流量计费。但是,每分钟支付一次是不现实的,即使技术上可行,交易手续费也让人吃不消。

    一个有效的做法是,把每分钟的费用累积起来,最后一次性支付。实现的方式是:Alice先发起一个MULTISIG交易,把可能花费的最大金额转到MULTISIG地址上,但这个交易需要AliceBob共同签名才能生效。Alice在使用流量时,每隔一分钟创建和签名一次交易,交易的输入是前面发起的MULTISIG交易的输出,表示要支取这笔款项,一部分用于向Bob支付这分钟所产生的流量费用,剩余的钱转给自己。每分钟充重复一次,直到挂机。这样,一系列的交易就产生了,这些交易中付给Bob的钱是逐渐递增的,表示累积的流量费用,而返还给Alice的钱就越来越少。最后,Bob会在Alice发起的最后一个交易上进行签名,把它放入区块链中,这样就完成了AliceBob一次性支付流量费用。值得注意的是,除了最后一个交易外,所有之前的交易都是我们主动创建的双重支付交易,即都来源于前面发起的MULTISIG交易的输出。实际上,如果双方都是正常的,那么Bob只会在最后一个交易上签名,我们不会在区块链上看到其他中间产生的双重支付交易。

    但是,有一个问题:如果Bob拒绝在最后一个交易上签名呢?Bob可能会说“就让这些比特币留在第三方托管地址中吧”。这样,Alice就会失去她一开始转到MULTISIG地址的所有比特币。解决这个问题的方法是,前面提到的一个代码——锁定时间。

锁定时间(lock_time)

    在开始小额支付之前,AliceBob要达成一个约定,在锁定时间到了之后,如果Bob还没有签名,那么就把所有的比特币从MULTISIG地址返回给Alice。这样,Alice先发起一笔交易将比特币转到MULTISIG地址,即转到第三方托管地址。随后,Alice主动创建一系列小额双重支付交易,用于累积流量费用。最后,当Alice签名最后一笔交易之后,再发起一笔交易将比特币从MULTISIG地址转给自己,即退款交易,且设定了锁定时间t>0。退款交易中的锁定时间,是告诉矿工在记账的时候,要等待t时间之后才能把这笔交易放入区块链。想象一下,如果在给定的时间里,Bob没有签名最后一笔交易,那么退款交易就被执行,从而把MULTISIG地址中的所有比特币转回给Alice。如果Bob签名了最后一笔交易,那么MULTISIG中将不会有多余的比特币(Alice创建的每一个小额支付交易有两个输出:一个支付流量费,一个返回给Alice),退款交易也就无效了,因为输入的比特币是0

3.4 比特币的区块

    在比特币系统中,所有的交易都被打包放入区块,因为如果每一个交易都由矿工单独去共识,那整个系统的交易处理速度将变得非常慢。

    区块链把两个基于哈希值的数据结构结合起来:1.区块的哈希链,每个区块都有一个区块头,里面有一个哈希指针指向上一个区块;2.默克尔树(也翻译成梅克尔树),以树状结构把区块内所有交易的哈希值进行排列存储。为了证明某个交易在某个区块内,可以通过树内路径来进行搜索。如下图所示。

3.4.1 比特币区块链中的两个哈希结构

    区块头部还包含了挖矿谜题的相关信息。区块头部的哈希函数必须以一大堆零开头才有效,还包含一个矿工可以修改的“临时随机数nonce”、一个时间戳、一个点数(用来表示挖矿难度),以及交易树的树根。

    每一个区块的默克尔树上都有一笔特殊的交易,称为“币基交易”,这个交易创造新的比特币,如下图所示,和普通交易有几个区别:

  • 永远只有一个输入和一个输出。
  • 不消费之前交易输出的比特币,即没有指针指向“上一个交易”。
  • 输出值目前大约是25个币多一点点,这个输出就是矿工的挖矿收入。包含两部分:第一部分是奖励的25个比特币(前面提到每产生21万个比特币——大约间隔4年——奖励减半),第二部分是所有交易的交易费。
  • 有一个特别的参数:“币基”参数,矿工可以放入任何值。

3.4.2 币基交易

3.5 比特币网络

    比特币网络是一个点对点网络,沿用了很多已有的点对点网络理论。比特币网络运行在TCP网络上,有任意的网络拓扑结构,每个节点和其他的随机节点相连,新节点可以随时加入,旧节点可以随时离开,只要一个节点有3个小时没有音讯,就会慢慢地被其他节点忘记,通过这种方式,网络非常缓和地处理节点下线问题。

交易的传播

    当你启动一个新节点,你先向一个你知道的节点(称为种子节点)发送简单的消息,询问是否还知道其他节点,然后在链接到一个新节点后,重复多次,直到随机连接了一些节点,这样,你就加入了比特币网络。

    加入网络是为了维护区块链。当我们发起一个交易,我们就通过泛洪(flooding)算法完成向整个网络的传播过程。假设Alice要转账给Bob,她的客户端发起一个交易,然后把这笔交易告知所有和她的客户端相连的其他节点,其他节点会对这个交易进行一系列的核验,决定是否接受并转播这笔交易。如果核验通过,这些节点就把这笔交易传播给与其相连的其他节点,并把这笔交易放入自己的交易池中。如果节点接收到的交易已经在交易池中,就不会再次把它传播出去,这样就确保了泛洪协议会自动终结,而不是让一个交易在网络中循环传播。

    节点核验新交易信息时,有四大关卡:1.最重要的一个验证,验证交易在当前区块链中是有效的,通过运行比特币脚本来确保;2.检查是否有双重支付;3.如前所述,检查交易是否已经在交易池中,因为每一笔交易都有一个独一无二的哈希值,所以很容易查询到交易是否已经存在;4.节点只会接收和传递在白名单上的标准脚本。

    由于网络传递有延迟,每个节点的交易池不尽相同。考虑双重支付的场景:假设Alice想把同一个币支付给BobCharlie,她几乎同时发起两笔交易,Alice->BobAlice->Charlie,并广播给其他节点。有些节点先听到Alice->Bob,有些节点则先听到Alice->Charlie。当节点接收到任何一个,那么就把它放入交易池,之后,这个节点听到另一个交易,看上去像是双重支付交易,那么就会丢弃。结果就是众多节点会对“哪一个交易应该被纳入区块链”产生分歧。这种情况称为竞态条件(race condition)(也可以理解为紊乱)。

    这种分歧将有矿工打破,矿工会决定哪个交易最终被打包进入区块。无论哪一个被打包进区块,节点都会从交易池中删除相应的交易,因为节点收到的要么是双重支付交易,应该剔除;要么是已经被放入区块的交易,没有必要再保留在交易池中,因此从交易池中删除。

区块的传播

    前面是交易的传播,下面看看区块的传播。区块传播和交易传播类似,同样受到竞态条件的限制。如果两个有效区块同时被挖到,只有其中一个区块可以进入长期共识链,哪个被最终纳入长期共识链取决于其他节点选择在哪个区块上扩展区块链,未被纳入的一个即被丢弃(成为孤块)。

    核验一个区块要比核验一个交易复杂得多,除了确认区块头,确定里面的哈希值满足哈希谜题,节点还必须确认区块中的每个交易。最后,一个节点往外传播的区块必须是最长的一条区块链上新加入的区块(当然,“最长的区块链”取决于节点对区块链当前状态的认识)。

网络大小

    比特币网络大小很难测量,因为它随时变化。

存储空间需求

    完全有效的节点(简称全节点)必须永久在线,才能接收到所有的交易数据。一个节点离线越久,当它重新连接到网络后,就需要更多时间来更新所有的交易数据。这些节点还需把完整的共识区块链都存储下来。目前存储空间大约几十个GB,一台台式机就能满足需求。

    完全有效的节点必须维护在交易中产生的(交易的输出)、未被消费掉的比特币的完整列表,这个列表最好存放在内存而非硬盘,这样,在接收到一个交易信息时,节点就可以快速查看、运行脚本,验证签名是否有效,然后把交易放入交易池中。目前,一般的电脑都能满足内存的需求。

轻量节点

    除了完全有效的节点,还有一种轻量节点(nightweight nodes),或者称为轻客户端,也叫简单付款验证(Simple Payment Verification,SPV)客户端。实际上,比特币系统中大部分的节点都是轻量节点,它们不会存储整个区块链,而是只存储所关心的、需要进行核验的部分交易及区块头。

    一个SPV节点的安全等级远不如全节点。但是,作为一个SPV节点可以节省很多空间,区块头部的大小只是整个区块链的千分之一,因此只需几十MB即可,甚至一部智能手机也能成为比特币网络的一个轻量节点。

3.6 限制和优化

    这部分主要讨论由于比特币初始设计带来的一些局限性以及如何优化,包括由于区块容量太小带来的交易处理速度太慢,如何解决硬分叉和软分叉等问题。

posted @ 2019-01-11 18:43  Ipad-李小强  阅读(437)  评论(0编辑  收藏  举报