传统的交易账本大都类似于下图的形式,每一笔交易信息记录了支付者和接收者的信息。
但是,这种方式有一个问题:我们如何确定每一笔交易都是有效的。假设某笔交易的发起者Alice的资金不足以支付,我们应该如何检查?最直接的方式是回溯交易记录,找到所有与Alice有关的记录,计算并验证Alice的支付信息即可。但是这种方式的开销太大了。
实际上,比特币的账本不是基于上述记账的类型的。交易会记录下一组输入和输出。我们可以把输入看做将要消费的比特币(上一个区块产生的比特币);把输出看成将要生成的比特币。对于新建立的区块来说,没有需要进行消费的比特币。每一笔交易都有一个唯一的标识。输出的后缀从0开始索引,因此第一个输出应该是output[0]。下图的方式就很类似比特币的交易方式。
在上图中,“交易1”没有输入,因为这是新建立的区块,新建立的区块产生25个比特币,比企鹅这25个比特币归区块创立者Alice所有,并且这笔交易不需要签名。Alice想要给Bob支付17个比特币,那么她需要建立“交易2”,。Alice需要显式指定前一笔交易的比特币的来源。这里,她制定了output[0]作为“交易1”的输入(也是“交易1”唯一的输入来源),这也是ALice获得了25比特币的那个。她也必须指定交易的输出地址,在本例中,她给了Bob地址17个比特币,给了自己地址8个比特币。当然,Alice对整个交易进行了签名。
Change Address。在比特币系统中,整个交易的输出必须是一个新的交易的输入,正如我们之前提到的那样;要么就是整个交易 没有任何输出,即不生成比特币。Alice给Bob17个比特币,但是她拥有一笔价值25比特币的输出。因此她需要创建一个新的输出,把其余的比特币发给自己。这个25个比特币可能是其他地址拥有的25个比特币,但是Alice会拥有这25个比特币。可以这么理解这句话:假设Alice发给Bob17个币之前,有一笔与Alice无关的交易,同样有25个比特币作为输出,那么ALice可以用这个交易的25个比特币作为自己的,并继续执行支付Bob的操作。这种方式称为”Change Address“。
Efficient cerification。哈希指针给我们提供了一个可以高效验证交易是否有效的方案。因为我们只需要根据上一个区块的哈希值,就可以计算出上个区块是否包含25个比特,从而验证本区块的交易的有效性;不再像传统的方式那样,需要从第一区块逐一检测。因此这种方式极大地提高了验证效率。
Consolidating funs,合并资产。一笔交易可能有多个输入和多个输出,那么分割或合并这些价值会变得很容易。假设Bob收到了两笔来,自不同交易的比特币,一个有17个BTC、另一个有2BTC。他只需要重新创建一笔交易,把这两笔交易作为输入,把输出地址写成自己想要的地址即可。
Joint payments。假设Bob和Carlo都要给David支付比特币。它们可以创建一个有两个输入和一个输出的交易,这两笔交易仅需要两个人的签名即可。
Transaction syntax。一笔交易的数据如下所示:
结构说明:
实际上,每一个交易的输出不是单独的一个公钥,而是一个脚本。那么,我们需要明白的是:什么是脚本、为什么使用脚本。
比特币交易中最常见的一种交易类型:验证前一笔交易的的输出使用了正确的key。也就是说前一笔交易必须有人签名认证。但是,地址是公钥的哈希值,根据哈希计算的原理,我们不能根据哈希值来推算出公钥实际值,因此也就无法检查签名了。因此比特币脚本就是为了解决上述的签名认证问题。
最常见的哈希输出脚本类型:
输入的数据也是包含的脚本,而不是仅仅一个签名。为了验证一笔交易的正确性,我们把新的交易输入脚本和之前的输出脚本联合到一起,合并后的脚本必须能正确运行,这样才能保证交易是合法的。这两个脚本分别是”scriptPubKey”和”scriptSig”
比特币脚本语言:一种基于栈的语言,仅支持顺序执行,不支持递归、循环操作。一个简单的例子:
图中,我们通过把输出交易的”scriptPublicKey”追加到”scriptSig”上,来创建一个联合脚本。脚本执行只有成功和不成功。成功表示通过验证。一下给出一些常用的符号说明:
脚本的执行。
基本的执行顺序如上图所示,仅需要在最后检查计算的哈希值是否相等即可。
、、、、、、、、、、、、、、、、、、、、、、
还有其他内容暂时不理解,后期添加
、、、、、、、、、、、、、、、、、、、、、、
托管交易。 假设下面一个场景,Alice想要去Bob的网站购物。Alice想货到付款,Bob想款到付货,那么就要用托管交易来完成这个看似矛盾的过程,引入第三方进行托管。假设有第三方Judy,那么需要使用MULTISIG来完成这个交易。三个人中,只要有两个人对交易进行签名,那么交易就是有效的。假设ALice和Bob都是诚实的,那么Bob会给Alice发货,Alice收到Bob的货物后,会进行签收。假设出现了争端,比如Alice不满意货物,货物运输过程中丢失了,或者Bob不想让Alice退货等,那么Alice和Bob只会有一个人同意。这时候,需要让Judy来裁决到底把钱支付给Alice还是Bob。因此,托管方只有当Alice和Bob出现纠纷的时候才使用。
Green Address。假设Alice向Bob支付的时候,Bob一直不在线。或者Alice订一份外卖之类的,因为区块链为了防止双重支付的攻击,要进行6个区块的价格才能进行确认,这要花费1个小时,对于外卖来说,这是不太现实的。在这里,我们需要引入“银行”的概念。这种类型的支付不通过区块链进行,而是Alice向“银行”支付一定数额的比特币,然后银行把相应的比特币给Bob,银行保证这里没有双重支付攻击。注意,在这里的银行系统的保证就不是比特币系统的保证了,而是现实世界中的一种信任保证。为了系统能正常运作,Bob也需要信任银行。系统的之中地址称为“Green Address。引入这种机制是为了快速支付,但是使用的不是特别多,因为这种方式使人们必须信任银行!
Efficient micro-payments。高效的小型支付。假设Alice使用了Bob的网络服务,而且是计时收费的,就像我们打电话需要移动公司提供信号一样。我们需要根据时间进行计费。计费方式存在一种问题:假设Alice每一分钟就生成一笔账单,而且账单一直累加;如果创造了太多笔交易,那么交易费会增多,这样对Alice不利。
如果我们能够把每一分钟的小账单组合成一个大账单,可以节约很大一笔交易费。我们建立一个MULTISIG交易,这笔账单需要Alice和Bob共同签名,以获得相应数目的比特币。这样,每一分钟,Alice就签署一笔交易,给Bob应有的支付,其余的发给自己。注意,这些交易仅仅与Alice签名,Bob未签名,它们不会被发送到区块链上。当Alice使用完Bob的服务后,向Bob发出停止提供服务的请求,并且Alice停止签署账单。之后,Bob停止服务,并且他要收取相应的费用,同时把剩余的比特币给Alice。但是,上面的那些交易不会被系统所接受,因为只有Alice自己进行了签名,而Bob没有签名。
但是这种方式有很多大量的潜在双重支付,它不像green address那样对双重支付有很强的防范性。实际上,Bob可以仅签署最后一个交易,这样就不会有任何双重支付的潜在企图了。
但是,上面那种方式有一个缺陷,如果Bob永远不签署最后一个交易呢???那么这笔交易会被一直托管,而且Alice无法得到她应得的剩余的比特币。,为了解决这个问题,引入Lock Time机制。
Lock Time。为了防止Bob不签署的行为,在开始支付协议之前,Alice和Bob先签署一个协议,该协议规定了Alice会得到她所有支付的比特币,但是该协议必须要在一个规定的时间t后才有效。如果在t之前,Bob签署了最后一笔交易,那么交易会按照正常顺序进行,而且该协议会失效,否则就执行该协议,Alice会得到所有的比特币。
智能合约。上述的所有的类型的协议都可以看做智能合约。上面所有的执行方式都是由比特币系统自动执行的,而不是由法律或者法庭等的执行。如果比特币脚本语言可以有其他更多的功能,可以执行更多的合约等,这些都在探索中。
实际的区块链不是以实际交易数目作为区块数量的,这样会大幅度降低系统接受交易的速率。区块链把大量的交易放到一个区块上,这样可以大幅提高速率。
如下图所示,区块链有两部分以哈希为基础的数据结构。第一是区块头,区块头的一个哈希指针指向前一个区块,另一个指向某一个交易的信息,这是一个Merkle树,有关操作的复杂度是对数级别的。
一个区块头包括一个mining puzzle,包含一个矿工可以更改的nonce,一个时间戳,一个bits(用于表示找到该区块的难度)。只对区块头进行哈希运算,因此为了验证区块链,我们只要知道Merkle树根即可。
“coinbase”交易。这是创造新比特币的一种记录,与一般交易信息有一下不同:
比特币网络中所有结点的地位是对等的,它们是P2P网络,运行于TCP协议之上。结点之间的拓扑结构是任意的。任何一个结点可以在任何时间加入或者离开。如果我们想要加入网络,首先要找到网络中的一个任一个结点结点,该结点称为“种子结点”,通过种子结点向其他结点建立链接,以此类推,可以向所有的结点建立链接。
这种链接的优势在于,如果我们要向网络广播一条消息,所有的结点都会收到。这个过程通过 “flooding算法”(也称为”gossip 协议”)实现。假设Alice向Bob支付一些BTC,那么算法过程如下:
说明,因为每个交易都有唯一的哈希值,因此检查它们是否存在还是很容易的。
结点通过下面几点决定是否接受消息:
上述的规则不是必须的,这是个P2P网络,任何结点都可以加入或者退出,这些要靠结点自己完成。
由于网络中存在延迟,因此结点的缓冲池会有一些差异,这对双重支付有较大的影响。假设Alice同时向Bob和Carlo支付了比特币,也就是双重支付。一些结点会先接收到支付给Bob的,另一些先接收到给Carlo的。结点会把先接收到的交易信息加入到缓冲池中(假设检验通过了),如果结点发现有双重支付的企图,那么结点会丢掉后来的交易信息。因此,结点会对把哪个交易信息加入区块链产生歧义,称之为“Race Condition”。
但是,由于这是去中心化的系统,上述的规则不是必须的,没有中心结构强制执行这一规则,那么可能有结点不是按照上述规则执行的。
之前我们提到过“零置信交易”,接受方只要接收到消息就立刻执行,不等6个区块的时间进行确认。由于矿工首先接受第一次监听到的消息,因此这在一定程度可以缓和双重支付攻击。
可能有结点不是接受先监听到的信息,比如有些结点会接受交易费高的那一个。那么这样会让双重支付在“零置信交易”中更容易实现。
验证区块的有效性币验证交易的有效性更加复杂。验证区块的有效性包括下面几个步骤:
flood算法的延迟。延迟主要取决于网络的速度。同时,区块的尺寸越大,延迟越高。
存储要求: 完全有效的结点必须永久的链接在网络上,以监听所有的数据。离线的时间越长,那么一个结点再加入网络时需要做的事情就越多。永久有效结点需要存储整个区块链,同时能工监听每一笔新的交易,并且转发有效交易给其他结点。这样的结点必须存储所有未花费的交易输出。很明显,这需要存储在RAM中,只有这样才能在接受新的交易后,快速查找交易输出,然后运行脚本,验证签名是否是有效的,如果是有效的就把交易加入到交易缓冲池中。
轻量级结点: 与全连接结点相反,比特币网络中有许多轻量级结点,它们称为thin client或者simple payment verification(SPV),它们的数量远远多于全连接结点。这些结点只存储他们关心的需要验证的交易。一个钱包程序一般都是SPV结点,这类结点仅下载区块头部和指向我们地址的交易。
SPV结点没有像全连接结点那样的安全等级,因为它们只有区块头部,这可以验证区块的有效性,但是不能检查区块中的每个交易都是真的有效的,因为他没有交易历史,而且也不知道未花费的交易的输出的集合。SPV只能验证影响它们自身的交易。因此它们必须信任全连接结点已经验证了它们没有验证的部分。
SPV结点的存储开销较小,一般是是兆级别的,而完全 结点是GB级别的。因此SPV一般完全可以由手机等移动端设备运行。
比特币的总数和挖矿的奖励不能轻易改变,否则会付出较大的代价。比特币系统每秒大约能处理7笔交易,这与当前中心化交易系统没法抗衡。加密算法可能会被破解,这也是人们担心的问题。同时,随着交易总数越来越多,存储空间占用也会越来越多。。
更改协议,如果我们要为比特币增加新的特性,由于这是去中心化的系统,那么有些结点会同意,有些结点会不更新。面对这种现象,有两种解决方案:硬合并和软合并。
hard fork:让新的客户端软件进行判定,新的区块是合法的,旧的区块是不合法的。这样,大部分结点会更新,少部分不会更新。这样会有问题:最长的区块链会被旧的结点视为不合法的。旧的结点会丢弃包含新特性的结点,并且在不包含新特性的区块链的分支上继续运行。那么,旧结点会把它们的分支区块链是最长的,除非它们更新自己的软件。这两个区块链是永远无法合并的。
soft fork: 给有效验证规则添加限制。这样,旧的规则会接受所有的原来有效区块,而新的规则会拒绝一部分。这样,可以避免hard fork中造成的区块链永远分裂。这样的风险在于,旧的结点会发现自己的一些区块被拒绝,即使它们不知道原因。这样,它们会逐步更新自己的软件。