区块链究竟是什么?狭义地说,区块链就是比特币的底层技术;不过,经过7年的发展,区块链已经不再“依附于”比特币,而是独立地发展成为了一种革命性的技术,比特币则是区块链最大、最成功的应用。
从技术层面来看,区块链是一个基于共识机制、去中心化的公开数据库。共识机制是指在分布式系统中保证数据一致性的算法;去中心化是指参与区块链的所有节点都是权力对等的,没有高低之分,同时也指所有人都可以平等自由地参与区块链网络,唯一的限制就是个人自己的选择;公开数据库则意味着所有人都可以看到过往的区块和交易,这也保证了无法造假和改写。基于以上特性,可以总结得出:区块链由许多对等的节点组成,通过共识算法保证区块数据和交易数据的一致性,从而形成一个统一的分布式账本。
从价值层面来看,区块链是一个价值互联网,用于传递价值。目前的互联网仅用来传递消息,但是还不能可靠地传递价值;而比特币区块链却可以在全球范围内自由地传递比特币,并且能够保证不被双花、不被冒用。从这个角度来说,区块链是记录价值、传递消息和价值本身转移的一个可信账本。
这里要提一下区块链在维基百科上的官方定义:一个区块链是一个基于比特币协议的不需要许可的分布式数据库,它维护了一个持续增长的不可篡改的数据记录列表,即使对于该数据库节点的运营者们也是如此。简而言之,区块链就是区块用某种方式组织起来的链条。在区块链中,信用或记录被放在各个区块中,然后用密码签名的方式“链接”到下一个区块。这些区块在系统的每一个节点上都有完整的副本,所有的信息都带有时间戳,是可追溯的。事实上,在区块链创建之初,我们在大多数情况下谈论的区块链都是比特币的底层实现方式。
基于区块链的系统和以往的其他系统存在很多不同之处,以区块链技术为核心的系统包括如下四大最主要的特点。
Distributed(分布式的)
区块链是全球化的,系统上的节点是运行在太平洋某个小岛的笔记本电脑上还是运行在中国某个小镇的服务器上,对系统本身来说都是一样的,除了网络连接速度有区别之外,其他没有任何区别。区块链没有中心节点,数据分布式地存储在各个节点上,即使绝大部分节点毁灭了,只要还有1个节点存在,就可以重新建立并还原区块链数据。
Autonomous(自治的)
区块链是一种去中心化的、自治的交易体系,这种自治性表现在两个方面:1)所有节点都是对等的,每个节点都可以自由加入和离开,并且这一行为对整个区块链系统的运行没有任何影响。所有的节点都是按照相同的规则来达成共识,且无需其他节点的参与。2)区块链系统本身一旦运行起来,就可自行产生区块并且同步数据,无需人工参与。
Contractual(按照合约执行的)
区块链是按照合约执行的,第一体现在各个节点的运行规则(指的是交易、区块链或协议)上,按照既定的规则执行,一旦出现违背规则的行为,就会被其他节点所抛弃;第二体现在智能合约上,智能合约是一种可程序化的合同条款、规则或规定,包含在每个交易中,交易验证时必须先运行智能合约,只有通过了验证的交易才能被接受。
Trackable(可追溯的)
区块链的数据是公开透明的,不能被篡改,而且相关交易之间有一定的关联性,因而很容易被追溯。比如比特币区块链,每一枚比特币都有其特定的来源,通过输入可以追溯到上一个交易,或者通过输出追溯到下一个交易。
此外,区块链代码本身也是可追溯的,区块链系统是开源软件,其对于所有的人都是公开的,因此任何人都可以查看并修改这些代码,不过修改后的代码需要经过开源社区上其他程序员的审核。
本书主要讨论区块链技术,这不仅包括了比特币区块链技术,还包含了比特币区块链所没有的一些技术,本章接下来将对区块链的一些基本知识做一个详细的介绍,包括交易和交易链、区块、挖矿、矿池、脚本、智能合约等。
1.1 交易和交易链
交易是签过名的数据结构,该数据结构会在区块链网络中广播,并被收集到区块中。它会引用以前的交易,从该交易中发送特定数量的比特币到一个或多个公钥中(即比特币地址),并且交易未被加密(比特币体系中没有加密任何数据)。多个交易可组成一个区块(block),这些区块同样也会在区块链网络中传播,一个区块会引用上一个区块,简而言之,区块链就是由区块(block)用某种方式组织起来的链条(chain)。区块链包括成千上万个区块,而一个区块内又包含一个或多个交易,上下关联的交易组成了一个交易链,一个交易链内部可能又包含了多个交易,下面的章节将会对这些知识点进行详细解释。
1.1.1 比特币地址
比特币地址是一个由数字和字母组成的字符串,可以与任何想给你比特币的人分享。由公钥(一个同样由数字和字母组成的字符串)生成的比特币地址以数字“1”开头。下面是一个比特币地址的例子:
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
在交易中,比特币地址通常是以收款方的形式出现。如果把比特币交易比作一张支票,那么比特币地址就是收款人,也就是我们要写入“收款人”一栏的内容。一张支票的收款人可能是某个银行账户,也可能是某个公司、机构,甚至是现金支票。支票不需要指定一个特定的账户,而是可以用一个普通的名字作为收款人,这使得它成为一种相当灵活的支付工具。与此类似,比特币地址的使用也使比特币交易变得很灵活。比特币地址可以代表一对公钥和私钥的所有者,也可以代表其他东西。
比特币地址是由公钥经过单向的Hash函数生成的。用户通常所见到的比特币地址是经过“Base58Check”编码的,这种编码使用了58个字符(一种Base58数字系统)和校验码,提高了可读性、避免了歧义,并有效地防止了在地址转录和输入中产生错误。Base58Check编码也被用于比特币的其他地方,例如私钥、加密的密钥和脚本Hash中,用来提高可读性和录入的正确性。图1-1描述了如何从公钥生成比特币地址。
*In a standard base conversion, the 0x00 byte on the left would be irrelevant (like writing '052' instead of just '52'), but in the BTC network the left-most zero chars are carried through the conversion. So for every 0x00 byte on the left end of the binary address, we will attach one '1' character to the Base58 address. This is why main-network addresses all start with '1'
etotheipi@gmail.com / 1Gffm7LKXcNFPrtxy6yF4JBoe5rVka4sn1
图1-1 比特币地址生成流程
1.1.2 交易的本质
交易实质上就是包含一组输入列表和输出列表的数据结构,也可称之为转账记录,这其中就包括了交易金额、来源和收款人等信息,表1-1就是一个比特币交易的数据格式。
表1-1 比特币交易数据格式
下面以一个具体的例子来说明一个区块链上的交易构成。假设有一个带有一个交易及一个输出的交易A,其中的输入列表和输出列表如下所示:
Input: Previous tx: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6 Index: 0 scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d10 90db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501 Output: Value: 5000000000 scriptPubKey: OP_DUP OP_HASH160404371705fa9bd789a2fcd52d2c580b65d35549d OP_EQUALVERIFY OP_CHECKSIG
上文表示,交易A的输入0从交易f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b 009ca73dd04470b9a6的0号输出中导入了50个比特币,然后该输出发送50个比特币到一个比特币地址的公钥Hash值(404371705fa9bd789a2fcd52d2c580b65d35549d,该公钥Hash值是十六进制表示,而非正常的base58表示)。
如果接收者想花掉这笔钱,那么他首先得创建自己的交易B,再引用该交易A的0号输出作为交易B的输入。
1.1.3 输入和输出
输入是对其他交易输出的引用,一个交易中通常列有多个输入。所有被引用的输出值相加,得出的总和值会在该交易A的输出中用到。Previous tx是以前交易的Hash值,Index是被引用交易的特定输出号,ScriptSig是一个脚本的前一半(脚本将在后文中详细讨论)。
脚本包含两个部分,一个签名和一个公钥,公钥属于交易输出的收款人,并且表明交易创建者允许收款人获得的输出金额;另一个部分是ECDSA签名,是通过对交易的Hash值进行ECDSA签名而得到的。签名和公钥一起,证明原地址的真正所有者创建了该支付交易。
输出中包含了发送比特币的指令,金额(Value)是以聪(Satoshi, 1BTC = 100000 000聪)为单位的数值。ScriptPubKey是脚本的另一半(这点将在后文中详细讨论),可以有多个输出,它们共享了输入金额。一个交易中的每一个输出都只能被后来的交易当成输入引用一次。如果你不想丢币,那就需要把所有输入值的总和值发送到一个输出地址。如果输入是50BTC,但你仅想发送25BTC,那么比特币将创建2个25BTC的输出:一个发往目标地址,另一个则回到你的地址(称之为“找零”,详见1.1.5节)。在交易过程中,会产生一笔交易费,作为交易费支付的任何比特币都不能被赎回,生成这个区块的矿工将获得这笔交易费。
为了验证某个交易的输入已经被授权,可以收集被引用的输出中的所有金额。比特币体系使用了一个类似于Forth的脚本系统,其目的是验证从某地址发出的比特币是否真正属于该地址的拥有人,输入的scriptSig和被引用的输出scriptPubKey会按顺序运行。如果scriptPubKey返回真,则输入被授权,证明是地址拥有人发出了比特币。通过脚本系统,发送者可以创建非常复杂的发送条件,人们为了收到金额,首先必须满足这些条件。举个例子,可以创建一个能被任何人赎回而无需授权的输出,也可以创建一个需要10个不同签名的输入,或者无需公钥仅由密码赎回的输出。
1.1.4 交易类型
根据目标地址的不同,可以把交易分为如下几种类型。
(1)支付到公钥Hash
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG scriptSig: <sig><pubKey>
一个比特币地址只是一个Hash值,因而发送者无法在scriptPubKey中提供完整的公钥,当要赎回比特币时,接收者需要同时提供签名scriptSig和公钥scriptPubKey,脚本系统会验证公钥的Hash值与scriptPubKey中的Hash值是否匹配,同时还会检查公钥和签名是否匹配。检查过程见4.2.5节。
(2)支付到脚本Hash
该类交易非常有意义,未来应该会在某些场合频繁使用。该类交易的接受地址不是通常意义上的地址,而是一个多签地址,以3开头。比如,三对公钥对可以生成一个多签地址。需要在生成过程中指定n of 3中的n, n的范围是[1, 3],若n = 1,则仅需要一个私钥签名即可花费该地址的币,若n = 3,则需要三把私钥依次签名才可以。
地址以3开头,可以实现多方管理资产,极大地提高安全性,也可以轻松实现基于比特币原生的三方交易担保支付。一个m-of-n的模式如下:
m {pubkey}...{pubkey} n OP_CHECKMULTISIG
其中,m和n需要满足:1 ≤ n ≤ 20, m ≤ n。
m和n可以是1 of 1、1 of 2、2 of 3等组合,通常选择n = 3。
1 of 3,最大程度私钥冗余。防丢私钥损失,3把私钥中任意一把即可签名发币,即使丢失2把也可以保障不受损失。
2 of 3,提高私钥冗余度的同时解决单点信任问题。3把私钥中的任意2把私钥可签名发币,对于三方不完全信任的情形,即中介交易,非常适用。
3 of 3,最大程度解决资金信任问题,无私钥冗余。必须3把私钥全部签名才能发币,适用于多方共同管理的重要资产,但是任何一方遗失私钥均会造成严重损失。
多签地址的交易构造、签名、发送过程与普通交易类似。
(3)挖矿交易
挖矿(coinbase)交易用于凭空产生比特币。挖矿交易只有一个输入,该输入有一个“coinbase”参数,没有scriptSig,“coinbase”中的数据可以是任意内容,它不会被使用。比特币把压缩的当前目标Hash值和任意精确度的“extraNonce”都存储在这儿,区块头中的Nonce每次一溢出,它们就会增长。extraNonce有助于扩大工作量证明函数的范围,矿工很容易修改Nonce(4字节)、时间戳和extraNonce(2~100字节)。
挖矿交易的输出金额在一段时间内是固定值,初始是50个比特币,每21万个区块后减半,目前已经经历了两次减半,因而是12.5个比特币。输出地址可以是任何地址,一般是矿工或矿池的比特币地址。
Nonce溢出是指在对一个块进行散列时,Nonce从0开始,每计算一次Hash都要增长一次,因而有可能会出现超过数值范围的情况,这时,extraNonce就要相应增长以存储Nonce。
1.1.5 找零地址
在实际的区块链交易中,假设A拥有一个比特币地址,里面包含着还没有花费过的10个比特币。B也有一个比特币地址,里面一分钱也没有。当A想向B支付10个比特币时,A地址里的未花费输出变为零,而B的则会变为10 BTC。如果A想支付的金额与所拥有的相同,自然不会存在需要找零钱的问题。不过当手头的金额比要支付的大时,找零自然也是天经地义的事情。
假设A的地址上有35个比特币(如图1-2所示),当A想向B支付8个比特币时,如图1-2所示,只需要使用包含着20个比特币的那一笔未消费支出,并设置好要支持的金额即可,剩下的12个比特币则会返回给A,以便A在将来可以继续使用。
图1-2 找零示意图
这样就有了一个找零机制,实际上,比特币在交易时会把消费时所用的地址(消费地址)的余额设置为零。当需要支付的金额小于可用余额时,在交易信息中必须告诉比特币网络零钱将要被发送至哪个地址,即“找零地址”。找零地址可能是也可能不是原先的发送地址。除此之外,发送地址所留下的剩余款项将由网络作为交易费支付给矿工。在上面的例子里,A可以选择将找回的零钱发送到一个新创建的找零地址上,或者将原先发送的地址设置为找零地址,并将零钱返回。虽然将发送地址作为找零地址对A而言是方便了管理,不过这也可能会造成A的隐私性降低,在一定程度上还可能会影响到B的匿名性。
根据设计,每一笔比特币交易将在一个称为“区块链”的全球性的公共总账上永久可见,这就意味着任何人随时都可以在上面进行跟踪查询。通过将某个比特币地址与其使用者关联起来,好事者都可以据此绘制关于这个人与他人之间的资金转移的关系图。但如果是将找回的零钱发送至一个新创建的地址,那么就可以让这种追踪变得更加困难。
要理解这一点,可以参考图1-3。假设从地址A发送比特币到地址B后,零钱返回地址为A,则区块链会揭示地址A向地址B支付了一笔钱。同样的道理,如果有两个或两个以上地址参与其中,任何涉及这个接收零钱的找零地址都会揭示A作为支付方的交易。假如某个控制着的任何接收地址或付款地址的人其身份是众所周知的,那么其他有过交易往来的各方的身份也有可能被推断出来。
图1-3 两种找零方案
现在想象一下,地址A发起付款到地址B,但此时将找零地址更改为新生成的地址C,如图1-4所示。如果没有进一步的信息,那么其他人所能知道的,只是有一个交易拆分了地址A的余额至地址B和C。而地址B或C的主人可能是也可能不是A。由于新地址C的加入,让整个交易的真相变得更加扑朔离迷:哪一个地址代表着被支付方,哪一个地址代表着找回的零钱呢?
图1-4 重新生成找零地址
当所有各方都将零钱发送至新创建的地址时,要想将个人身份与地址相关联,就必须收集更多的信息,并耗费更多的资源。
1.2 区块和区块链
比特币网络中,数据会以文件的形式被永久记录,我们称这些文件为区块。一个区块是一些或所有最新比特币交易的记录集,且未被其他先前的区块记录。可以将区块想象为一个城市记录者其记录本上单独的一页纸(对房地产产权的变更记录),或者是股票交易所的总账本。在绝大多数情况下,新区块会被加入到记录的最后(在比特币中的名称为区块链),一旦写上,就再也不能改变或删除。每个区块记录了它被创建之前发生的所有事件。
1.2.1 区块结构
一个区块的结构如表1-2所示。
表1-2 区块结构示意图
每个区块都包括了一个被称为“魔法数”的常数0xD9B4BEF9、区块的大小、区块头、区块所包含的交易数量及部分或所有的近期新交易。在每个区块中,对整个区块链起决定作用的是区块头,如表1-3所示,接下来本章将会对每一个字段都做出比较详细的解释。
表1-3 区块头描述
这里的hashPrevBlock就是区块之所以能够连成区块链的关键字段,该字段使得各个区块之间可以连接起来,形成一个巨大的“链条”。每个区块都必须要指向前一个区块,否则无法通过验证。这个区块链条会一直追溯到源头,也就是指向创世区块。很显然,创世区块的hashPrevBlock的值为零或为空。在区块头中,最关键的一个数据项是一个随机数Nonce,这串数字是一个答案,而这个答案对于每一个区块来说都是唯一的,它的特点具体如下。
这个答案很难获得。
有效答案有多个,不过我们只需要找到一个答案就可以了。
其他节点对有效答案的验证很容易。
正是因为问题很难解答,没有固定的算法可以求出答案,所以唯一的做法就是不断尝试,找寻这个答案的做法就是“挖矿”,可以想象,会有很多人同时都在“挖矿”,他们之间是相互竞争的关系。
区块内包含许多交易,它们通过Merkle根节点间接被散列,以保证矿工能及时追踪一个正在打包的区块内交易的变化情况。一旦生成Merkle根节点,那么对包含一个交易的区块做散列所花的时间,与对包含1万个交易的区块做散列所花的时间是一样的。
目标Hash值的压缩格式是一个特殊的浮点编码类型,首字节是指数(仅使用了5个最低位),后3个字节是尾数,它能表示256位的数值。一个区块头的SHA-256(一种单向函数的算法,可形成长度为256位的串)值必定要小于或等于目标Hash值,该区块才能被网络所接受。目标Hash值越低,产生一个新区块的难度就越大。
Merkle树是Hash的二叉树。在比特币中会两次使用SHA-256算法来生成Merkle树,如果叶子个数为奇数,则要重复计算最后一个叶子的两次SHA-256值,以达到偶数叶子节点的要求。
计算过程:首先按照区块中交易的两次SHA-256进行散列,然后按照Hash值的大小进行排序,生成最底层。第二层的每个元素则是相连续的两个Hash值的两次SHA-256的Hash值。之后,会重复这个过程,直到某一层只有一个Hash值为止,这就是Merkle根。举例来说,想象有3个交易,a、b、c,那么Merkle根的生成过程如下所示:
d1 = dhash(a) d2 = dhash(b) d3 = dhash(c) d4 = dhash(c) # 只有3个元素,是奇数,因而将最后一个元素重算一次 d5 = dhash(d1 concat d2) d6 = dhash(d3 concat d4) d7 = dhash(d5 concat d6)
这里的d7就是以上三个交易的Merkle根。需要注意的是,Merkle树的Hash值是小头位序(即高位在后,是数字在计算机中的一种表示形式)。对于某些实现和计算来说,在散列计算前应该先按位反转,之后在散列计算后再反转一次。
1.2.2 创世块
创世块(Genesis Block)是指区块链的第一个区块,现在的比特币客户端版本把创世区块号定为0,以前的版本把该区块号定为1。以下是创世块的一种表示形式,它出现在以前的比特币代码的注释中,第一个代码段定义了创建该块所需要的所有变量,第二个代码段是标准的区块类格式,还包含了第一个代码段中缩短版本的数据。
GetHash()= 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f hashMerkleRoot = 0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b txNew.vin[0].scriptSig = 4866047994 0x736B6E616220726F662074756F6C69616220646E6F63657320666F206B6E697262206E6F20726 F6C6C65636E61684320393030322F6E614A2F33302073656D695420656854 txNew.vout[0].nValue = 5000000000 txNew.vout[0].scriptPubKey = 0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06 279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704 OP_CHECKSIG block.nVersion = 1 block.nTime = 1231006505 block.nBits = 0x1d00ffff block.nNonce = 2083236893 CBlock(hash=000000000019d6, ver=1, hashPrevBlock=00000000000000, hashMerkleRoot= 4a5e1e, nTime=1231006505, nBits=1d00ffff, nNonce=2083236893, vtx=1) CTransaction(hash=4a5e1e, ver=1, vin.size=1, vout.size=1, nLockTime=0) CTxIn(COutPoint(000000, -1), coinbase 04ffff001d0104455468652054696d65732030 332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206 261696c6f757420666f722062616e6b73) CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) vMerkleTree: 4a5e1e
coinbase参数(看上面的十六进制)中包含了“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks.”这句话。
这句话翻译过来就是“2009年1月3日,首相第二次对处于崩溃边缘的银行进行紧急救助”,这句话正是泰晤士报当天的头版文章标题(如图1-5所示)。这应该是一个该区块在2009年1月3日或之后创建的一个证据,同时也是对银行系统采用部分准备金制度导致不稳定性的一个说明。
图1-52009年1月3日的泰晤士报
创世块50BTC的收益被发送到如下地址:1A1zP1eP5QGef i2DMPTfTL5SLmv7DivfNa,我们称该交易为创世交易。
创世块的收益花不掉,原因如下:比特币客户端把区块和交易分别存储在两个数据库中,当客户端发现区块数据库为空时,就会用代码直接生成一个创世块,但是没有把创世交易存储到客户端的交易数据库中,比特币网络一旦收到要花掉创世交易输出的交易时,因为在交易数据库中找不到创世交易,因而都会拒绝,也就是说花不掉这50个币了。出现这种情况很可能是中本聪为了纪念创世交易,故意而为的。
创世块的数据结构如下所示。
01000000:指版本号。
0000000000000000000000000000000000000000000000000000000000000000:为prev block。
3BA3EDFD7A7B12B27AC72C3E67768F617FC81BC3888A51323A9FB8AA4B1E5E 4A:为Merkle根。
29AB5F49:时间戳。
FFFF001D:目标Hash值。
1DAC2B7C:随机数。
01:交易个数。
01000000:版本。
01:输入个数。
0000000000000000000000000000000000000000000000000000000000000000FFFFFF FF:前一个输出。
4D:脚本长度。
04FFFF001D0104455468652054696D65732030332F4A616E2F32303039204368616E636 56C6C6F72206F6E206272696E6B206F66207365636F6E64206261696C6F757420666F722062616E6B73:scriptsig脚本。
FFFFFFFF:序列号。
01:输出个数。
00F2052A01000000:50 BTC的收益。
43:指脚本scriptPubKey的长度。
4104678AFDB0FE5548271967F1A67130B7105CD6A828E03909A67962E0EA1F61DE B649F6BC3F4CEF38C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5F AC:脚本scriptPubKey。
00000000:锁定时间。
JSON版本的创世块如下所示:
{ "hash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", "ver":1, "prev_block":"0000000000000000000000000000000000000000000000000000000 000000000", "mrkl_root":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", "time":1231006505, "bits":486604799, "nonce":2083236893, "n_tx":1, "size":285, "tx":[ { "hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", "ver":1, "vin_sz":1, "vout_sz":1, "lock_time":0, "size":204, "in":[ { "prev_out":{ "hash":"0000000000000000000000000000000000000000000000000000000000000000", "n":4294967295 }, "coinbase":"04ffff001d0104455468652054696d65732030332f4a616e2f3230303920 4368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722 062616e6b73" } ], "out":[ { "value":"50.00000000", "scriptPubKey":"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e 0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG" } ] } ], "mrkl_tree":[ "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" ] }
1.2.3 区块链原理
区块链是所有比特币节点共享的交易数据库,这些节点基于比特币协议参与到比特币网络中来。区块链包含每一个曾在比特币系统执行过的交易,根据这个信息,人们可以找到任何时候任一个地址中的币数量。
由于每个区块包含前一个区块的Hash值,这就使得从创世块到当前块形成了一条块链,每个区块必定按时间顺序跟随在前一个区块之后,区块链结构如图1-6所示。因为不知道前一块区块的Hash值,因此没法生成当前区块,所以要改变一个已经在块链中存在了一段时间的区块,从计算上来说是不可行的,如果它被改变,那么它之后的每个区块都必须随之改变。这些特性使得双花比特币非常困难,区块链是比特币的最大创新。
图1-6 区块链示意图
如果一个区块是最长块链的最后一个区块,那么诚实的矿工只会在这个区块的基础上生成后续块(创建新区块时通过引用该区块来实现)。“长度”是指被计算成区块链的所有联合难度,而不是区块的数量,尽管这个区别仅仅在防御几个潜在攻击时有用。如果一个区块链中的所有区块和交易均有效,则该区块链有效,并且要以创世块开头。
对于区块链中的任何区块来说,只有一条通向创世块的路径。然而,从创世块出发,却可能有分叉。当两个区块产生的时间仅相差几秒时,可能会产生包含一个区块的分叉。当出现以上现象时,矿工节点会根据收到区块的时间,在先收到的区块的基础上继续挖矿。哪个区块的后续区块先出现,那么这个区块就会被包括进主链,因为这条块链更长。
短区块链(或有效区块链)中的区块没有作用,当比特币客户端转向另一个长区块链时,短区块链中所有有效的交易都将被重新加入到交易队列池中,并被包括到另一个区块中。短区块链中的区块收益不会在长链中出现,因而这些收益实际上是丢失了,这就是比特币网络设定100个区块成熟时间的原因。
短区块链中的区块经常被称为“孤立”区块,事实上这些区块都有父区块,并且可能还有子区块,只不过这些区块链未被包含进比特币主链,就好像被孤立了一样。
1.3 挖矿、矿池
1.3.1 挖矿原理与区块的产生
比特币的挖矿和节点软件是基于对等网络、数字签名来发起和验证交易的。节点向网络广播交易,这些广播出来的交易需要经过矿工的验证,矿工们会用自己的工作证明结果来表达确认,确认后的交易会被打包到数据块中,数据块会串起来形成连续的数据块链。中本聪本人设计了第一版的比特币挖矿程序,这一程序随后被开发为广泛使用的第一代挖矿软件bitcoind,这一代软件在2009年到2010年期间都比较流行。
每一个比特币的节点都会收集所有尚未确认的交易,并且会将其归集到一个数据块中,这个数据块将和前面一个数据块集成在一起。矿工节点会附加一个随机调整数,并计算前一个数据块的SHA-256 Hash运算值。挖矿节点不断进行重复尝试,直到它找到的随机调整数使得产生的Hash值低于某个特定的目标为止。由于Hash运算是不可逆的,因此寻找到符合要求的随机调整数将会非常困难,因此需要一个可以预计总次数的不断试错的过程。这时,工作量证明机制就发挥作用了。当一个节点找到了符合要求的解,那么它就可以向全网广播自己的结果。其他节点就可以接收这个新解出来的数据块,并检验其是否符合规则。只要其他节点通过计算Hash值发现其确实满足要求(比特币要求的运算目标),那么该数据块就是有效的,其他的节点就会接受该数据块,并将其附加在自己已有的链条之后。
当挖矿时,你会经常对区块头进行散列,你正在挖的区块也会时常进行更新,一个区块头如上文所述的表1-3所示。
表1-3中的大部分数据项对所有用户都是一致的,不过,在时间戳上可能会有些区别。如果当前区块的时间戳大于前11个区块的平均时间戳,并且小于“网络调整时间(Network-Adjusted Time)”+ 2小时,则认为该时间戳是有效的。其中的“网络调整时间”是指与你相连接的所有节点的平均时间。当节点A连接到节点B时,A将从B处得到一个UTC的时间戳,A先将其转换成本地UTC并保存起来,网络调整时间等于所有节点的本地UTC时间+所有相连节点的偏移量平均值,然而,该网络时间永远不会调整到超过本地系统时间70分钟以上。
Nonce随机数通常都不会相同,但是它以严格的线性方式在增长,从0开始,每次执行散列时都会增长,当Nonce溢出时(此事经常发生),挖矿交易的extraNonce项就会增长,其将改变Merkle树的根节点。
假定针对这些数据项,人们经常会独自产生同样序列号的Hash值,那么,最快的矿机通常会赢。然而,两人产生同样的Merkle根节点基本上(或几乎)是不可能的,因为区块中的第一个交易是挖矿交易并且会“发送”到你独一无二的比特币地址上。而你的区块与其他人的区块是有区别的,因此,产生的Hash值肯定也会(几乎可以肯定)不同,你计算的每个Hash值和网络中的其他人一样,都有同样的获胜机会。
比特币使用SHA256(SHA256(区块头))计算Hash值,但要注意字节序。例如,以下的Python代码用于计算某一区块的Hash值,使用的是2011年6月区块号为125552的最小Hash值。该区块头建立在表1-3所示的6个数据项之上,并且以十六进制的小端结尾方式连接在一起。
>>> import hashlib >>> header_hex = ("01000000" + "81cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000" + "e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122b" + "c7f5d74d" + "f2b9441a" + "42a14695") >>> header_bin = header_hex.decode('hex') >>> hash = hashlib.sha256(hashlib.sha256(header_bin).digest()).digest() >>> hash.encode('hex_codec') '1dbd981fe6985776b644b173a4d0385ddc1aa2a829688d1e0000000000000000' >>> hash[::-1].encode('hex_codec') '00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d'
注意
实际的Hash值是一串256位的数值,首部有许多零。当以大头端十六进制常数方式打印或存储时,它的首部有许多零;如果它以小头端打印或存储时,零就会变换到尾部。例如,如果表示成字节串-最低(或者开头)的字节串地址显示最小位的数,那么这就是小头端表示。blockexplorer的输出把Hash值显示为大头端表示的数值,因为数字的表示通常是首部数字是最大的数字(从左向右读)。
1.3.2 挖矿难度
挖矿难度是对挖矿困难程度的度量,即指计算符合给定目标的一个Hash值的困难程度。比特币网络有一个全局的区块难度,有效的区域必须有一个Hash值,该Hash值必须小于给定的目标Hash值。矿池也会有一个自定义的共享难度,用来设定产生股份的最低难度限制。
难度每过2016块就会改变一次,计算公式为:
difficulty = difficulty_1_target/current_target
其中,目标(target)是一个256位长的数值。
测量难度有许多不同的方法,通过这些方法得到的diff iculty_1_target有可能也会不同。传统情况下,它表示一个Hash值,前32位为0,后续部分为1(称之为矿池难度或pdiff),比特币协议把目标Hash值表示成一个固定精度的自定义浮点类型,因而,比特币客户端用该值来估计难度(称之为bdiff)。
难度经常被存储在区块中,每个块存储一个十六制的目标Hash值的压缩表达式(称之为Bits),目标Hash值可以通过预先定义的公式计算出来。例如:如果区块中压缩的目标Hash值为0x1b0404cb,那么十六进制的目标Hash值就为如下形式:
0x0404cb×28×(0x1b -3)= 0x00000000000404CB000000000000000000000
000000000000000000000000000
因而目标Hash值为0x1b0404cb时,难度为:
0x00000000FFFF0000000000000000000000000000000000000000000000000000/
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.420938523983 (bdiff)
或者:
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF/
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.669773817162 (pdiff)
其中:0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FF是挖矿机使用的最大目标Hash值。而0x00000000FFFF0000000000000000000000000000000000000000000000000000则是比特币网络使用的浮点编码类型,后面的位数被缩短了。
下面是一个快速计算比特币难度的方法,这里使用的算法是修改的泰勒序列(读者可以参考wikipedia上的教程),并且依赖记录来转换难度计算。
#include <iostream> #include <cmath> inline float fast_log(float val) { int * const exp_ptr = reinterpret_cast <int *>(&val); int x = *exp_ptr; const int log_2 = ((x >> 23) & 255) -128; x &= ~(255 << 23); x += 127 << 23; *exp_ptr = x; val = ((-1.0f/3) * val + 2) * val -2.0f/3; return ((val + log_2) * 0.69314718f); } float difficulty(unsigned int bits) { static double max_body = fast_log(0x00ffff), scaland = fast_log(256); return exp(max_body - fast_log(bits & 0x00ffffff) + scaland * (0x1d - ((bits & 0xff000000) >> 24))); } int main() { std::cout << difficulty(0x1b0404cb) << std::endl; return 0; }
如果想要了解难度计算的数学原理,那么可以看看如下的Python代码:
import decimal, math l = math.log e = math.e print 0x00ffff * 2**(8*(0x1d -3)) / float(0x0404cb * 2**(8*(0x1b -3))) print l(0x00ffff * 2**(8*(0x1d -3)) / float(0x0404cb * 2**(8*(0x1b -3)))) print l(0x00ffff * 2**(8*(0x1d -3))) - l(0x0404cb * 2**(8*(0x1b -3))) print l(0x00ffff) + l(2**(8*(0x1d -3))) - l(0x0404cb) - l(2**(8*(0x1b -3))) print l(0x00ffff) + (8*(0x1d -3))*l(2) - l(0x0404cb) - (8*(0x1b -3))*l(2) print l(0x00ffff / float(0x0404cb)) + (8*(0x1d -3))*l(2) - (8*(0x1b -3))*l(2) print l(0x00ffff / float(0x0404cb)) + (0x1d -0x1b)*l(2**8)
前难度可以通过http://blockexplorer.com/q/getdiff iculty来获得,下一个难度可以通过http://blockexplorer.com/q/estimate来获得。难度的变化情况可以查看http://bitcoin.sipa.be/。
最大难度大约等于maximum_target/1(因为0会导致无穷大),这是一个非常大的数值,大约为2224;当maximum_target为最小值1时,则最小难度值也为1。
难度可根据以前2016个区块的产生时间而定,每过2016块就会改变一次。预计每隔10分钟产生一个区块,因而产生2016个区块要花费2周时间。如果前2016个区块的产生时间多于两周,则难度会降低;否则难度就会增加。
为了找到新区块,该区块的Hash值必须小于目标Hash值,实际上它是一个在0到2256-1之间的随机数,难度1的偏移量是:
0xffff×2208
难度D的偏移量是:
(0xffff×2208)/D
在难度D下,为了找到新区块,预期要计算的Hash数量是:
D×2256/(0xffff×2208)
或者只是:
D×248/0xffff
难度的设定,是为了以每10分钟一个区块的速度产生2016个区块,因而,在600秒内计算(D×248/0xffff)个Hash,这就意味着产生2016个区块的网络Hash速率(算力)是:
D×248/0xffff/600
可以进一步简化为:
D×232/600
以上公式有较好的精度。
在难度为1时,算力是7Mhashes/秒,难度是5006 860589,这就意味着以前2016个区块被找到,其平均算力是:35.840PHash/s。
5006 860589×232/600≈35.840 PHash/s
发现一个区块的平均时间,可以用以下公式估计:
时间=难度×232/算力
其中,难度是当前的难度,算力是指矿机的计算能力,以hashes/s为单位,时间是你找到两个区块的平均时间。举例来说,使用Python计算,算力为1Ghashes/s的矿机,难度在20000时,产生一个新区块的时间,计算式如下:
$ python -c "print 20000 * 2**32 / 10**9 / 60 / 60.0" 23.85
其中**表示指数,该语句运算的结果就是:找到一个新区块要花费近1天的时间。
1.3.3 矿池原理与商业模式
随着生成区块的难度逐步增加,挖矿变成了一个碰运气的事情,单一节点要生成一个区块需要花费数年的时间(除非这个单一节点拥有大量的计算力)。为了激励计算力较低的用户继续参与挖矿,矿池就出现了。在一个矿池里,许多不同的人贡献出自己的计算力来生成一个区块,然后再根据每个人的贡献比例来分发奖励。通过这种方式,要得到那个50个比特币的奖励就不必等待数年的时间了,小矿工能定期得到属于他们那部分的比特币奖励。一个share(贡献/股份)为一个矿池给客户端的一个合法的工作证明,同时这也是用来生成区块的工作证明,但是没有这么复杂,只需要很少的时间就能达到一个share。
矿池是比特币(Bitcoin)等P2P密码学虚拟货币开采所必需的基础设施,一般是对外开放的团队开采服务器。其存在的意义是提升比特币开采的稳定性,使矿工薪酬趋于稳定,目前国内较为著名的比特币商业矿池有F2Pool、BTCC Pool、BW Pool、BTC.com等。
关于矿池挖矿的方式,目前存在如下几种不同的方式。
Slush方式:Slush矿池基于积分制,较老的shares将比新的shares拥有更低的权重,以减少一轮中切换矿池的投机分子。
Pay-Per-Share方式:该方式为立即为每一个share支付报酬。该支出来源于矿池现有的比特币资金,因此可以立即取现,而不用等待区块生成完毕或确认之后。这样可以避免矿池运营者幕后操纵。这种方法减少了矿工的风险,但将风险转移给了矿池的运营者。运营者可以收取手续费来弥补这些风险可能造成的损失。
Luke-Jr方式:该方式借用了其他方式的长处,如Slush方式一样,矿工需要提供工作证明来获得shares,如Puddinpop方式一样,当区块生成时马上进行支付。但是不像之前的方式,一个区块的shares,会被再次利用来生成下一个区块。为了区分参与矿工的交易传输费用,只有当矿工的余额超过1BTC时才进行支付。如果没有达到1BTC,那么将在下一个区块生成时进行累计。如果矿工在一周内没有提供一个share,那么矿池会将剩下的余额进行支付,不管余额是多少。
Triplemining方式:该方式是将一些中等大小矿池的计算力合并起来,然后将获得奖励的1%按照各个矿池计算力的比例分发给矿池运营者。
P2Pool方式:P2Pool的挖矿节点工作在类似于比特币区块链的一种shares链上。由于没有中心,所以也不会受到DoS攻击。和其他现有的矿池技术都不一样,在该方式下,每个节点工作的区块,都包括支付给前期shares的所有者及该节点自己的比特币。99%的奖励(50BTC +交易费用)会平均分配给矿工,另外0.5%会奖励给生成区块的人。
Puddinpop方式:一种使用“元哈希”技术的方式,使用特定的Puddinpop挖矿软件,现在已经没有矿池使用这种方式了。
目前使用较多的方式为Pay-Per-Share(PPS),矿工使用起来也比较方便。
但从去中心化的角度来说,还是推荐P2Pool,在避免DoS攻击的同时,也能防止个别矿池拥有超大的计算力而对比特币网络造成威胁。不过P2Pool的使用方式较PPS更为复杂。
1.4 脚本系统
比特币在交易中使用脚本系统,与FORTH(一种编译语言)一样,脚本是简单的、基于堆栈的,并且是从左向右处理的,它特意设计成非图灵完整的形式,没有LOOP语句。
一个脚本本质上是众多指令的列表,这些指令记录在每个交易中,若交易的接收者想花掉发送给他的比特币,那么这些指令就是描述接收者是如何获得这些比特币的。一个典型的发送比特币到目标地址D的脚本,要求接收者提供以下两个条件,才能花掉发给他的比特币:
1)一个公钥,当进行散列生成比特币地址时,生成的地址是嵌入在脚本中的目标地址D。
2)一个签名,用于证明接收者保存了与上述公钥相对应的私钥。
脚本可以灵活地改变花掉比特币的条件,举个例子,脚本系统可能会同时要求两个私钥或几个私钥,或者无需任何私钥等。
如果联合脚本中未导致失败并且堆栈顶元素为真(非零),则表明交易有效。原先发送币的一方,控制脚本运行,以便比特币在下一个交易中使用。想花掉币的另一方必须把以前记录的运行为真的脚本,放到输入区。
堆栈保存着字节向量,当用作数字时,字节向量被解释成小尾序的变长整数,最重要的位用于决定整数的正负号。比如,0x81代表-1,0x80则是0的另外一种表示方式(称之为负0)。正0用一个NULL长度向量表示。字节向量可以解析为布尔值,这里False表示为0, True表示为非0。
1.4.1 脚本特点
表1-4至表1-12是脚本的所有关键字列表(命令/函数),一些更复杂的操作码已被禁用,不再考虑,因为钱包客户在这些操作码的程序实现上可能有Bug,如果某个交易使用了这些操作码,那么其将会使比特币块链产生分叉。我们提到脚本的时候,通常省略了这些把数字压入堆栈的关键字。
表1-4 脚本常见关键字字段
表1-12 保留关键字
表1-5所示的是脚本的流程控制。
表1-5 脚本的流程控制
表1-6 脚本的堆栈处理
表1-7展示的是字符串操作码,若有在交易中出现了已禁用的操作码,则必须终止和失败返回。
表1-7 字符串操作码
表1-8展示的是位操作码,若有在交易中出现了已禁用的操作码,则必须终止和失败返回。
表1-8 位操作码
注意
算术逻辑的输入仅限于有符号32位长整数,但输出有可能会溢出。如果任何命令的输入值其长度超过4字节,那么脚本必须中止和失败返回。如果在交易中出现了标记为已禁用的操作码,也必须终止和失败返回。
表1-9 算术逻辑操作码(若在交易中出现已禁用的操作码,则必须终止和失败返回)
表1-10 脚本的加密码
表1-11 脚本中的伪关键字(这些关键字仅供内部使用,辅助进行交易匹配)
1.4.2 脚本运行过程
在这里,我们先讨论单输入单输出的比特币交易,因为这样描述起来更为方便,而且不会影响对脚本的理解,以下面的一个交易Hash值为例:
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
这是一个单输入单输出交易,下面来看下我们要关注的数据。
Hash:
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
对于输入交易,需要关注如下值。
前导输入的Hash:
437b95ae15f87c7a8ab4f51db5d3c877b972ef92f26fbc6d3c4663d1bc750149
输入脚本scriptSig:
3045022100efe12e2584bbd346bccfe67fd50a54191e4f45f945e3853658284358d9c062ad02200 121e00b6297c0874650d00b786971f5b4601e32b3f81afa9f9f8108e93c752201038b29d4fbbd12619d 45c84c83cb4330337ab1b1a3737250f29cec679d7551148a
对于输出交易,需要关注如下值。
转账金额:0.05010000 btc
输出脚本scriptPubKey:
OP_DUP OP_HASH160 be10f0a78f5ac63e8746f7f2e62a5663eed05788 OP_EQUALVERIFY OP_CHECKSIG
假设Alice是转账发送者,Bob是接受者。那么输入交易表明了Alice要动用的比特币的来源,交易输出表明了Alice要转账的数额和转账对象——Bob。那么,有读者可能会问,数据中的输入脚本和输出脚本是不是就是题和解?答对了一半!
在Bitcoin Wiki中提到:原先发送币的一方,控制脚本运行,以便比特币在下一个交易中使用。想花掉币的另一方必须把以前记录的运行为真的脚本,放到输入区。
换句话说,在一个交易中,输出脚本是数学题,输入脚本是题解,但不是这道数学题的题解。我开始看Wiki的时候,在这里遇到了一些障碍,没法理解输入脚本和输出脚本的联系。但是在考虑到交易间的关系之后,就明白了。
假设有这么一系列交易,如图1-7所示。
图1-7 三对交易示范图
那么,这一系列交易具有如下特征。
三个交易都是单输入单输出交易。
每个输入交易输出交易中,都包含对应的脚本。
交易a为Alice转账给Bob;交易b为Bob转账给Carol;交易c为Carol转账给Dave。
当前交易的输入都引用前一个交易的输出,如交易b的输入就是引用交易a的输出。
按照之前的说法,交易a中的输出脚本就是Alice为Bob出的数学题。那么,Bob想要引用交易a输出交易的比特币,就要解开这道数学题。题解是在交易b的输入脚本里给出的!Bob解开了这道题,获得了奖金,然后在交易b中为Carol出一道数学题,等待Carol来解……
所以说,在图1-8中相同颜色的输出和输入才是一对题和解。
图1-8 输入输出一一对应图
1.4.3 脚本操作码解读
要理解比特币脚本,先要了解堆栈,这是一个后进先出(Last In First Out)的容器,脚本系统对数据的操作都是通过它完成的。比特币脚本系统中有两个堆栈:主堆栈和副堆栈,一般来说主要使用主堆栈。下面就来列举几个简单的例子,看下指令是如何对堆栈进行操作的。
常数入栈:指把一段常数压入到堆栈中,这个常数成为栈顶元素,如图1-9所示。
图1-9 常数入栈
OP_DUP:复制栈顶元素,如图1-10所示。
图1-10 复制栈顶元素
OP_EQUALVERIFY:用于检查栈顶两个元素是否相等,如图1-11所示。
图1-11 检查栈顶元素是否相等
1.4.4 脚本执行过程
Alice在转账给Bob的时候,输出交易中给出了Bob的钱包地址(等价于公钥Hash),当Bob想要转账给Carol的时候,他要证明自己拥有与这个钱包地址对应的私钥,所以在输入交易中给出了自己的公钥及使用私钥对交易的签名。下面来看个实例。
交易a:
9c50cee8d50e273100987bb12ec46208cb04a1d5b68c9bea84fd4a04854b5eb1
交易b:
62fadb313b74854a818de4b4c0dc2e2049282b28ec88091a9497321203fb016e
交易b中有一个输入交易引用了交易a的输出交易,它们的脚本是一对题与解。
题:交易a的输出脚本,若干个脚本指令和转账接收方的公钥Hash。
OP_DUPOP_HASH160be10f0a78f5ac63e8746f7f2e62a5663eed05788OP_EQUALVERIFY OP_CHECKSIG
解:交易b的输入脚本,这么一长串只是两个元素,签名和公钥(sig & pubkey)。
3046022100ba1427639c9f67f2ca1088d0140318a98cb1e84f604dc90ae00ed7a5f9c61cab02210 094233d018f2f014a5864c9e0795f13735780cafd51b950f503534a6af246aca301 03a63ab88e75116 b313c6de384496328df2656156b8ac48c75505cd20a4890f5ab
下面来看下这两段脚本是如何执行完成解题过程的。
首先执行的是输入脚本。因为脚本是从左向右执行的,那么先入栈的是签名,随后是公钥。接着,执行的是输出脚本。从左向右执行,第一个指令是OP_DUP——复制栈顶元素(如图1-12所示)。
图1-12 复制栈顶元素
OP_HASH160用于计算栈顶元素Hash,得到pubkeyhash,如图1-13所示。
图1-13 栈顶元素Hash160
然后将输出脚本中的公钥Hash入栈,为了与前面计算中所得到的Hash区别开来,这里称它为pubkeyhash',如图1-14所示。
图1-14 公钥Hash入栈
OP_EQUALVERIFY则会检查栈顶前两个元素是否相等,如果相等则继续执行,否则中断执行,返回失败,如图1-15所示。
图1-15 检查Hash值是否相等
OP_CHECKSIG使用栈顶前两个元素执行签名校验操作,如果相等,则返回成功,否则返回失败,如图1-16所示。
图1-16 返回结果
这样一串指令执行下来,就可以验证这道数学题是否做对了,也就是说,验明了想要花费钱包地址中比特币的人是否拥有对应的私钥。上面的执行过程是可以在脚本模拟器中进行的,并且能够看到每一步执行的状态。
1.5 合约应用案例
1.4节的脚本系统,详细说明了脚本的运行原理,本节将描述在实际应用场景中如何使用脚本系统构建合约应用。
1.5.1 合约应用原理
每个比特币交易都有一个或多个输入和输出,每个输入或输出都有一个小的纯函数与之相关联,称为脚本,脚本可包含简化形式交易的签名。
每个交易都有一个锁定时间,使得该交易处于特定状态并且可被新交易替换,直至锁定时间来临。预定时间可以是块索引或时间戳(这两个因素使用同一个内存项,小于5亿是块索引,大于5亿是时间戳)。当一个交易的锁定时间到了,则称之为终结。
每个交易的输入都有一个序列号,对于正常发送币的交易,该序列号为UNIT_MAX,锁定时间为0。如果锁定时间还未达到,但所有的序列号为UNIT_MAX,则该交易也被认为是终结。序列号用来发布交易的新版本,无须验证其他输入的签名。例如:在一个交易中,每个输入都来自于不同的一方,每个输入的序列号都是0,这些序列号可以独立增加。
签名验证是很灵活的,因为交易的签名方式可以通过SIGHASH符号来控制,该符号附加在签名后面。通过这种方式能够构建特殊的合约,交易的每个输入方只对交易的一部分进行签名,因而每个输入方都能够单方面改变该交易的一部分内容,而无需其他输入方的参与。SIGHASH符号分为两部分,一种模式和一个ANYONECANPAY指示器。签名模式包含如下几种模式。
SIGHASH_ALL:这是默认模式。它指示一个交易除输入脚本之外,所有部分都被签名。对输入脚本进行签名显然是不可能的,那样将无法构建一个交易,所以脚本总是不被签名。尽管如此,需要注意的是,输入的其他属性如输出、序列号等都会被签名。直观地说,它的意思是“如果每个人都把他们的钱放进去,并且输出的正是我想要的,那么我也同意把我的钱放进去。”
SIGHASH_NONE:输出没有被签名,可以是任何内容。使用这种模式意味着“如果每个人都把他们的钱放进去,我也同意把我的钱放进去,但我不关心输出的是什么。”这种模式使得其他输入方可以通过改变输入序列号来更新交易。
SIGHASH_SINGLE:与SIGHASH_NONE一样,输入被签名,但序列号没有被签名。因而其他人可以创建交易的新版本,然而,唯一的输出也要被签名。该模式说明“如果输出的正是我想要的,那么我同意把钱放进去,但我不关心其他人的输入。”
ANYONECANPAY指示器可以与以上三种模式联合使用,当设置了ANYONECANPAY时,仅仅是该输入被签名,其他输入可以是任意内容。
脚本可以包括CHECKMULTISIG操作码,该操作码提供了n-of-m的签名验证,即:你可以提供多个公钥m,定义必须出现的有效签名个数n,签名个数n可以小于公钥数量m。如果我们设置以下脚本,则一个输出需要两个签名:
2<pubkey1><pubkey2>2CHECKMULTISIGVERIFY
有如下两种通用的方式可用来安全地创建合约。
在P2P网络之外传递部分完成或无效的交易。
使用两个交易:创建一个交易(合约交易),先签名但不会马上广播,在达成合约并且被锁定在内存中之后,广播另一个交易(支付交易),最后再广播合约。
采用以上的方式即可保证人们能够知道他们达成的合约内容。这些特性可以让我们在区块链的基础上创建有趣的、创新的金融手段。
1.5.2 示例1:提供押金证明
想象一下,你在一个网站(论坛或WIKI)上注册了一个账号,现在你希望在网站运营者处建立你的信用,但是你没有以前的名誉来支撑你的信用。一个解决方案就是向网站付点钱购买信用,但是如果你关闭了账号,可能会想要回这部分钱。你对该网站的信任程度不足以让你将钱存到该网站,因为你担心网站会花掉你的钱。另一个风险是,某一天该网站有可能会消失。
建立信用度的目的是你做出某种奉献,让网站知道你不是一个垃圾机器人。但是你不想让网站花掉你的钱。如果网站运营者消失了,你最终想把钱要回来,而无需他们的任何许可。
对于该问题,可以通过合约来解决,具体步骤如下。
1)用户和网站相互发送各自新生成的公钥。
2)用户创建交易TX1(支付交易),该交易支出10个BTC到网站地址,用户创建了TX1但不广播。
3)用户把TX1交易的Hash值发送给网站。
4)网站使用TX1的Hash值创建交易TX2(合约),TX2花掉TX1的钱并且支付到用户地址。注意,TX2需要双方签名,因而该交易并不完整,nLockTime被设置成未来时间(比如六个月之后),输入的序列号为0。
5)最终,这个不完整的交易TX2(一半已签名)被回送给用户,用户检查合约是否如预期的一样在执行,即六个月后10BTC最终会回到他的地址(除非情况有变)。序列号为0,表示如果双方同意,则合约可以被修订。现在,该交易的输入脚本还不完整,因为用户未签名,所以要等用户对合约进行签名并且把签名放到合适的位置上。
6)用户先广播TX1,再广播TX2。
在这个阶段,用户和网站都不能单独得到10BTC。六个月之后,合约完成,即使网站消失了,用户也能得到币。
如果用户想要提早关闭账号,又该怎么处理呢?网站创建新版的TX2, nLockTime设为0,并且输入的序列号设为UINT_MAX,重新签名,把该交易发回用户,用户签名后广播该交易,就能提早结束合约并且释放10BTC。
如果六个月快到了,而用户还想保留他的账号,又该怎么办呢?类似的事情发生后,合约会使用新的nLockTime,序列号比以前的序列号大1,双方重新签名,广播232次。当然,无论发生什么,双方都必须同意,才能真正改变合约。
显然,如果该用户被证明是存在恶意行为的(例如:垃圾邮件发送者),那么网站不会同意提早结束合约。如果用户有太多的滥用行为,则网站可以要求增加存款数量,或者要求延长合约时间。
1.5.3 示例2:担保和争端调解
一个买家想和他不认识或不信任的某人进行交易,一般情况下若交易能够正常进行时,买家不想任何第三方参与。但是当交易出现问题时,他想有一个第三方——也许是一个专业的争端调解服务来决定谁能拿到钱。
注意
这个概念同时适用于买家和卖家。例如,调解员可向商家要求邮资证明,以判断是否发货。
换句话说,某人想锁定某些币时,这些币要在第三方同意的情况下,才能被花掉。
该示例的实现步骤具体如下。
1)和商家一起引入一个调解员(如:ClearCoin)。
2)得到商家的公钥K1,得到调解员的公钥K2,创建自己的公钥K3。
3)把K2发给商家,商家生成一个随机数挑战调解员,调解员用K2的私钥签名,用来证明K2确实属于调解员。
4)创建一个交易TX1,使用如下输出脚本并且广播该交易。
2<K1><K2><K3>3CHECKMULTISIGVERIFY
现在这些币被锁定了,如果要解锁这些币,需要使用以下几种方式。
客户和商家同意(无论是成功的交易,还是在没有调解的情况下商家同意回退给客户)。
客户和调解者同意(失败的交易,调解者认同客户,客户得到退款)。
调解者和商家同意(商品已经发送,尽管有争议,商家还是得到币)。
输入签名时,内容被设为相关联的输出。这样,为了从这个交易中得到币,客户要创建包含两个签名位的脚本,自己签一个,再把未完成的交易发给商家或调解员,请求第二个签名。
1.5.4 示例3:保证合约
保证合约是建造公众商品时的集资办法,公众商品是指一旦建成,任何人都可以免费享受到好处的商品。标准的例子是灯塔,所有人都认同应该建造一个,但是对于航海者个人来说灯塔太贵了,而且灯塔不只是他一个人用得着,同时也会方便其他的航海者。
一个解决方案就是向所有人集资,只有当筹集的资金超过所需的建造成本时,每个人才真正付钱;如果集资款不足,则谁都不用付钱。
在保证合约集资方面,包括频繁的、小额的、经常自动进行的集资,例如互联网电台的资金和网页翻译等,比特币要优于传统的支付方式。假设有一个浏览器的插件可供你发送一点币,它能检测当前页面的语言并且广播一个集资请求,用于把该页面翻译成你的语言。如果使用该插件的许多用户同时查看该页面(例如:该页面从高流量的网站链接过来),那么足够的集资请求就能广播出去,到达一定的金额时,可自动付钱给一个高质量的翻译公司,当翻译完成后该页面将自动在你的浏览器中加载。
我们能以比特币的方式建立以下模型,具体步骤如下。
1)主办方创建新的捐赠地址,宣布如果筹集资金超过1000BTC,则将建造该商品,任何人都可以捐赠。
2)捐赠者创建一个新交易,把一定数量的钱打到集资地址上,但是他们并不广播该交易。该交易与常规的交易相似,但有三个不同点:首先,不能做任何改变,如果你没有正确的输出金额1000BTC,那么你必须先创建一个;第二,输入脚本要以SIGHASH_ALL|SIGHASH_ANYONECANPAY的模式签名;最后,输出值是1000BTC,注意,这不是一个有效的交易,因为输出值比输入值大得多。
3)把交易上传到主办方的服务器上,他们把交易保存到磁盘上,随时更新捐赠的币数量。
4)一旦服务器获得了足够的币,它将把所有捐赠者上传的独立交易合并成一个新的交易,该交易只有一个输出,仅仅是把钱付到捐赠地址,该输出与每个捐赠者的交易的输出部分相同,而输入部分则是所有捐赠者输入的集合。
5)广播完整的交易,发送捐赠的币到捐赠地址中。
这样的场景依靠了协议的几个方面,首先使用了SIGHASH符号,SIGHASH_ALL是默认模式,意味着要签名所有交易的内容,除了输入脚本。SIGHASH_ANYONECANPAY是附加的指示器,意味着签名仅覆盖自己的输入部分,而不会覆盖其他人的输入,这样一来,其他人的输入可以留空。使用这些符号,我们能创建这样一个签名,即使在添加进其他输入之后,该签名依旧是有效的。但如果输出内容或其他的交易部分被改变了,那么该签名就会无效了。第二,输入值小于输出值的交易是无效的(原因很明显),这也就意味着捐赠者把发送币的交易发送给主办方是安全的,因为主办方不可能得到这些捐赠币,除非再加上其他的输入值等于或超过输出值。
不使用SIGHASH_ANYONECANPAY指示器也可以创建保证合约。不需捐赠者创建交易的集资方式有两个步骤,一旦达到集资的总金额,主办方就会创建包含所有捐赠者输入的交易,然后依次在捐赠者中传递,每个捐赠者都对该交易进行签名。但是,先使用SIGHASH_ANYONECANPAY指示器,然后合并交易,这样可能会更方便一些。
保证合约可以保证下一个块的资金网络安全,通过这种方式,即使一个块中的交易数量比较少,挖矿也能挣钱(因为保证合约的交易一般占用空间较大,因而需要付出更多的网络转账费)。
Tabarrok在他的论文“The private provision of public goods via dominant assurance contracts”中详尽地描述了保证合约的概念,在一个通用的保证合约中,如果合约失败了(在预定时间内集资不足),主办方会给捐赠者支付网络转账费,这种类型的合约旨在鼓励捐赠者积极参与。
1.5.5 示例4:使用外部状态
脚本被设计成纯函数,它们不能访问外部服务器,也不能导入任何会改变的外部状态,因为攻击者会利用该特性突破区块链。而且,脚本语言的功能被严格限制了。幸运的是,我们可以以其他的方式创建交易,从而与外部世界相联系。
考虑一个例子,老人想让他的孙子继承遗产,继承时间是在他死后,或者在孙子年满18岁时,无论先满足哪个条件,他的孙子都可以得到遗产。
为了解决这个问题,老人首先向他自己发送孙子要继承的资产数量,以便有一个正确的继承数量的唯一输出;接着,他创建了一个带有锁定时间的交易,该交易的意思是:在孙子18岁生日时,把币支付到孙子的地址中,老人对该交易签名,不进行广播,直接把该交易给了孙子。当过了孙子的18岁生日之后,孙子广播了这个交易并且得到他的币。孙子可以在这个时间之前广播该交易,但他不会提前得到币,有些节点会在内存池中把这种交易丢弃掉,因为锁定时间在遥远的将来。
死亡条件则很难判断,因为比特币节点不会检测主观条件,我们必须依靠预言,预言是指具有密钥对的服务器,当用户自定义的表达式被证明是真的,它就能按照要求对交易进行签名。
以下是例子,老人创建了一个交易花掉了他的币,把输出设为:
<hash>OP_DROP2<sonspubkey><oraclepubkey>CHECKMULTISIG
这是一个预言脚本,具有与众不同的形式——它把数据推到堆栈中,然后立即删除。公钥发布在预言服务器的网站上,为公众所知,Hash值设为用户自定义表达式的Hash值,该表达式以预言服务器能够理解的方式进行编写,以确认老人已经死亡。举个例子,可能是以下字符串的Hash值:
if(has_died('johnsmith', born_on=1950/01/02))return(10.0,1JxgRXEHBi86zYzHN2U4KMy RCg4LvwNUrp);
以上语言是假设的,由预言服务器定义该语言,其可能是任何一种语言。返回值是一个输出:币数量和孙子的地址。
再一次,老人创建了一个交易,没有广播而是直接给了孙子,他另外还提供了一个表达式和预言服务器的名字,Hash值被写入交易,预言服务器则能解锁表达式。算法的具体实现如下。
1)预言服务器接受评估请求,请求包括了用户提供的用户自定义表达式、输出脚本和部分完成的交易,交易中除了scriptSig签名之外,其他部分都已经完成,签名仅包括了孙子的签名,还不足以解锁输出。
2)预言服务器检查表达式,得到其Hash值,并且与交易中的Hash值对照,如果两者不一致,则返回错误。
3)预言服务器评估表达式,如果表达式的结果不是交易输出的目标地址,则返回错误。
4)预言服务器签名交易,返回签名给用户。
注意
对一个比特币交易进行签名时,输入脚本被设为相关联的输出脚本。原因是当OP_CHECKSIG操作起作用时,包含该操作码的脚本被放到输入中,脚本并不包含签名本身。预言服务器并不知道完整的输出,也没有必要知道,因为它知道输出脚本、自己的公钥和表达式的Hash值,这就足以使它检查输出脚本并且完成交易。
5)用户接受新签名,插入到交易的scriptSig项中,广播交易。
当且仅当预言服务器认为老人死了,孙子才能广播两个交易(合约和申报),并且得到币。
预言服务器可以评估任何事情,然而区块链中的输出脚本的形式总是一样的,可考虑以下的可能性。
给定一个日期,假定mtgox比特币的美元价格在12.5~13.5之间:
today()==2011/09/25&&exchange_rate(mtgoxUSD)>=12.5&&exchange_rate(mtgox- USD)<=13.5
打赌我会做一些我从来不会做的事情(比如,获得奥林匹克金牌):
google_results_count(site:www.google.com/hostednews'MikeHearn'Olympicgoldmedal)>0
以上函数通过Google来搜索MikeHearn这个人获得奥林匹克金牌(Olympicgoldmedal)的块数
欧洲电视台的歌唱比赛,选择两个优胜者之一进行打赌:
if(eurovision_winner()=='Azerbaijan') return 1Lj9udBVDwptFffGSJSC2sohCfudQgSTPD; else return 1JxgRXEHBi86zYzHN2U4KMyRCg4LvwNUrp;
这些条件可使预言服务器的签名变得任意复杂,但是区块链仅需要包含一个Hash值即可。
1.信任最小化:挑战
有许多方法可以降低对预言服务器的信任程度。
回到我们的第一个例子,预言服务器还没有看到孙子想解锁的交易,因为该交易从没被广播过。这样,它不会支持孙子赎回币,因为它不知道该交易是否存在。我们能做到,并且也应该做到,以自动方式定期挑战预言服务器,确保它总是按照我们想象的方式进行输出。挑战无须花费任何币,因为要签名的交易是无效的(例如:与一个并不存在的交易进行关联),预言服务器没法知道签名请求是随意的还是真实的,如何以一个尚未成真的条件来挑战预言服务器,是一个开放的研究课题。
2.信任最小化:多个独立预言服务器
如果需要,CHECKMULTISIG中的签名个数n可以增加至能够达到n-of-m的预言服务器模式(即m个预言服务器中需要有n个预言服务器签名才能使交易有效)。当然,检查预言服务器是独立的而非串通的,这一点也是很重要的。
3.信任最小化:可信的硬件
使用合适的硬件,即可进行信任计算。比如,以INTELTXT或AMD等硬件来建立一个封闭的运行环境,然后用TPM芯片向第三方证实其可信度。第三方可判定硬件是否处于所需状态。如果硬件失败,则需要有人介入CPU程序的执行过程,甚至在极端的情况下,内存总线没有数据流过(如果程序足够小,则完全可以使用缓存来运行程序)。
4.信任最小化:亚马逊AWS预言服务器
最终,也许是目前最现实的方法就是使用亚马逊的网页服务,截至2013年11月,最合适的预言服务器的解决方案是“this recipe for creating a trusted computing environment using AWS”,该方案基于“this project for doing selective SSL logging and decryption”。基本思想是:预言服务器使用亚马逊作为其信任根,并且能用亚马逊API证明其是值得信任的,该预言服务器记录在线银行接口的加密SSL会话,如果以后产生交易争端,那么会话记录将被解密,争端就可以得到解决。
1.5.6 示例5:跨链交易
比特币技术可以用来创建多个独立的货币,与比特币实现理念相同的山寨币,可以在有限信任的条件下与比特币进行自由交易。域名币(Namecoin)就是一个例子,它与比特币的运作规则有些不同,它可以在域名空间中租用域名。
举个例子,想象一个财团发行了欧元币(EURCoins),即一种以财团的银行存款1:1支持的加密货币。这样的货币与比特币存在不同的交易集:更中心化,但没有外汇风险。人们可能希望在比特币与欧元币之间来回交易,为了实现这个想法,可以使用TierNolan提出的协议。实现步骤具体如下。
1)A产生一些随机数据X(秘密)。
2)A产生TX1交易(支付)包含了带跨链交易脚本的输出。它允许币以A和B共同签名的方式释放,也可以以私密X和B签名的方式释放,该交易未广播,块链的释放脚本包含了私密的Hash值,并非真正的私密X本身。
3)A产生TX2(合约),花掉TX1并且输出到A的地址,该交易有个未来的锁定时间,输入的序列号为0,因而可以被替换。A签名TX2并且发送给B, B给TX2签名后发送回A。
4)A广播TX1和TX2, B可以看到币但是不能花掉它们,因为并没有输出到B的地址,该交易还没有终结。
5)B在山寨币块链上执行相同的操作,B的锁定时间应该大于A的锁定时间,双方的交易都待定但未完全。
6)因为A知道私密X, A能马上申报他的币,然而,A在申报币的过程中,向B释放了私密X,所以B可以以私密X和签名B来完成山寨币块链的交易。
自动化交易完全是以点对点的方式进行的,其能确保货币的流动性,该协议使得这种交易更为灵活。跨链交易的脚本如下所示:
IF 2 <keyA> <keyB> 2 CHECKMULTISIGVERIFY ELSE <keyB> CHECKSIGVERIFY SHA256<hashofsecretx> EQUALVERIFY ENDIF
合约输入的脚本如下所示:
<sigA> <sigB> 1
或者:
<secretx> <sigB> 0
由合约输入脚本中的1或0来决定应该使用哪种方式。如果为1,则跨链交易的脚本执行第一段代码:
2 <keyA> <keyB> 2 CHECKMULTISIGVERIFY
如果是0,则跨链交易的脚本执行第二段代码:
<keyB> CHECKSIGVERIFY SHA256<hashofsecretx> EQUALVERIFY
参考“Atomic cross-chain trading”(见参考资料[9])能够得到更详细的说明。
注意
欧元币是一个很自然的想法,还有其他方法也能够实现点对货币的交易(把比特币换成菲亚特汽车,反之亦然),看“Ripple currency exchange”(见参考资料[10])可以得到更多的信息。
Sergio Demian-Lerner提出了P2PTradeX协议,一种块链交易的解决方案,该方案需要把一个块链中的确认规则有效地编码进另一个块链的确认规则中。
1.5.7 示例6:支付证明合约
在示例4中,我们看到了如何基于任意程序的输出来实现条件支付,这些程序非常有用,能够实现任何常规程序所能实现的功能,比如获取网页。缺点是需要一个第三方(预言服务器),尽管我们可以用技术来降低预言服务器的信任度,但谁都不能降低到0。
对于受限的程序、纯函数,新加密技术已经出现,这些技术能从将信任度降低到0,无需第三方参加。这些程序不能进行任何I/O操作,但是在许多情况下,这种限制被证明并不重要,或者可以以其他的方式绕过,比如给程序一个签过名并且打过时间戳的文档作为输入,无需程序自己从网上下载。
若想进一步详细了解,可以阅读一下该协议的解释“Zero Knowledge Contingent Payment”(见参考资料[11])。
1.5.8 示例7:特定对象的快速调整(微)支付
与传统支付系统相比,比特币交易的费用非常便宜,但是还是需要一些费用以便矿工来挖矿,以及融合到区块链上。有些情况下,你可能想要快速和便宜地调整发送到某个特定地址的货币金额,而不会导致产生广播交易的费用。
举个例子,有一个你尚不信任的互联网接入点,就像你从来没有去过的咖啡屋中的一个Wi-Fi热点一样。每使用10K字节的流量你得向咖啡屋支付0.001BTC,而无须注册咖啡屋账号。零信任解决方案意味着可以全自动完成整个过程,因而你在月初预先转了一笔钱到你的手机钱包中,然后你的手机会自动与AP协商并且按需给AP支付费用,咖啡屋也希望任何人都能来付钱给它,而无须担心被诈骗。
为了达到这个目标,可以使用下面的协议。该协议依赖nLockTime与原设计不同的行为,从2013年开始,时间锁定的交易被认为是非标准协议,不能进入内存池,这样就不能在锁定时间过期之前广播出去。当nLockTime的行为恢复回中本聪的初始设计时,就需要修改以下讨论的协议了。
可定义客户端为发送币的那一方,服务端为接收币的另一方,以下协议的实施过程是从客户的角度来进行的,具体步骤如下。
1)创建公钥K1,向服务端请求公钥K2。
2)创建、签名,但不广播交易T1,支付10BTC到输出,需要服务端和你自己的公钥,使用OP_CHECKMULTISIG是一个好办法。
3)创建退款交易T2,与T1的输出相关联,发送所有币回到你的地址。该交易有一个锁定时间,例如几小时后,不签名并且把该交易发送给服务端,常规来说,输出脚本是“2 K1 K2 2 CHECKMULTISIG”。
4)服务端用K2签名T2,返回给客户,注意,服务端目前还未看到T1,仅仅看到了T1的Hash值(该Hash值在未签名的T2中)。
5)客户验证服务端的签名是否正确,如果不正确则中止。
6)客户签名T1并且把签名返回给服务端,服务端广播T1(如果他们双方有联系的话,每一方都可以广播T1),币被锁定。
7)客户创建一个新交易T3,与T1相联系,类似于退款交易,有K1和K2两个输出,把所有币分配给第一个输出K1,它做了与退款交易相同的事情,但是没有锁定时间,客户签名T3,发送给服务端。
8)服务端验证输出到它的地址的币数量是正确的,验证客户提供的签名是正确的。
9)当客户想付钱给服务端时就调整T3,向服务端的输出增加足够的币,同时减少他自己的币数量,然后重新签名T3,发送给服务端。客户无须发送整个交易,只需要发送签名和增加的币数量即可。服务端调整T3内容与新的币数量相吻合,验证客户的签名并且继续。
整个过程会持续到会话结束,或者到1天的时间快到时,此时,AP会签名和广播最终版本的交易,向它自己分配最终的币数量。退款交易需要处理服务端消息中断和未分配币的情况,如果发生了这些事件,一旦锁定时间过期,客服就可以广播退款交易,拿回所有的钱。
这个协议已经用bitcoinj实现了。
当nLockTime的交易能够进入内存池、交易替换重新启用时,本协议必须被修改。在这种情况下,无需退款交易。T3有一个序列号,每次都比前一次大1, T3的锁定时间与之前的时间相一致。每次的支付都使得序列号增加1,以确保会优先发生最后版本。如果没有正常关闭通道协议,则意味着向服务端支付币不会成功,直到锁定时间过期,客户拿回所有币为止。为了避免这种情况,双方共同签名T3交易,序列号为0xFFFFFFFF,导致立即确认,而不用考虑nLockTime的值。
锁定时间和序列号可以避免一种攻击,在这种攻击中:AP提供连通性,客户使用TX2的第一版本双花,使币回到他自己的地址中,阻止咖啡屋申报账单。如果客户尝试这么做,该交易不会马上被包括进去,AP在一段时间内可以观察到该交易被广播,然后广播它看到的最后一个版本,就能推翻客户的双花企图。
后一种协议依赖交易替换,具有更大的灵活性,因为在通道的生命周期中,只要客户能收到服务端的签名,协议就能使客户为自己分配的币的数量越来越少。但是在许多用例中,这个功能是不必要的。交易替换同样允许配置更复杂的超过两方的通道,如何在这种使用场景下详尽地描述协议,就留给读者作为练习吧。
1.5.9 示例8:多方去中心化彩票
使用示例6的一些技术和一些高级的脚本,使得构建无人值守的多方彩票系统成为可能。准确协议在“Secure multiparty computations on Bitcoin”(见参考资料[12])中已经有详细描述。
参考资料
[1]http://www.8btc.com/bitcoin_block_chain
[2]http://www.360doc.com/content/14/1228/10/18005502_436322050.shtml
[3]http://www.8btc.com/bitcoin-change-addresses-explanation
[4]http://8btc.com/article-1786-1.html
[5]http://www.8btc.com/bitcoin-transactions
[6]http://8btc.com/article-1775-1.html
[7]http://8btc.com/article-1767-1.html
[8]http://www.8btc.com/understand-bitcoin-script
[9]https://en.bitcoin.it/wiki/Atomic_cross-chain_trading
[10]https://en.bitcoin.it/wiki/Ripple_currency_exchange
[11]https://en.bitcoin.it/wiki/Zero_Knowledge_Contingent_Payment