天天看点

《区块链开发指南》一一1.5 合约应用案例

本节书摘来自华章计算机《区块链开发指南》一书中的第1章,第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。如果我们设置以下脚本,则一个输出需要两个签名:

22checkmultisigverify

有如下两种通用的方式可用来安全地创建合约。

在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,使用如下输出脚本并且广播该交易。

23checkmultisigverify

现在这些币被锁定了,如果要解锁这些币,需要使用以下几种方式。

客户和商家同意(无论是成功的交易,还是在没有调解的情况下商家同意回退给客户)。

客户和调解者同意(失败的交易,调解者认同客户,客户得到退款)。

调解者和商家同意(商品已经发送,尽管有争议,商家还是得到币)。

输入签名时,内容被设为相关联的输出。这样,为了从这个交易中得到币,客户要创建包含两个签名位的脚本,自己签一个,再把未完成的交易发给商家或调解员,请求第二个签名。

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岁生日之后,孙子广播了这个交易并且得到他的币。孙子可以在这个时间之前广播该交易,但他不会提前得到币,有些节点会在内存池中把这种交易丢弃掉,因为锁定时间在遥远的将来。

死亡条件则很难判断,因为比特币节点不会检测主观条件,我们必须依靠预言,预言是指具有密钥对的服务器,当用户自定义的表达式被证明是真的,它就能按照要求对交易进行签名。

以下是例子,老人创建了一个交易花掉了他的币,把输出设为:

op_drop2checkmultisig

这是一个预言脚本,具有与众不同的形式——它把数据推到堆栈中,然后立即删除。公钥发布在预言服务器的网站上,为公众所知,hash值设为用户自定义表达式的hash值,该表达式以预言服务器能够理解的方式进行编写,以确认老人已经死亡。举个例子,可能是以下字符串的hash值:

以上语言是假设的,由预言服务器定义该语言,其可能是任何一种语言。返回值是一个输出:币数量和孙子的地址。

再一次,老人创建了一个交易,没有广播而是直接给了孙子,他另外还提供了一个表达式和预言服务器的名字,hash值被写入交易,预言服务器则能解锁表达式。算法的具体实现如下。

1)预言服务器接受评估请求,请求包括了用户提供的用户自定义表达式、输出脚本和部分完成的交易,交易中除了scriptsig签名之外,其他部分都已经完成,签名仅包括了孙子的签名,还不足以解锁输出。

2)预言服务器检查表达式,得到其hash值,并且与交易中的hash值对照,如果两者不一致,则返回错误。

3)预言服务器评估表达式,如果表达式的结果不是交易输出的目标地址,则返回错误。

4)预言服务器签名交易,返回签名给用户。

对一个比特币交易进行签名时,输入脚本被设为相关联的输出脚本。原因是当op_checksig操作起作用时,包含该操作码的脚本被放到输入中,脚本并不包含签名本身。预言服务器并不知道完整的输出,也没有必要知道,因为它知道输出脚本、自己的公钥和表达式的hash值,这就足以使它检查输出脚本并且完成交易。

5)用户接受新签名,插入到交易的scriptsig项中,广播交易。

当且仅当预言服务器认为老人死了,孙子才能广播两个交易(合约和申报),并且得到币。

预言服务器可以评估任何事情,然而区块链中的输出脚本的形式总是一样的,可考虑以下的可能性。

给定一个日期,假定mtgox比特币的美元价格在12.5~13.5之间:

打赌我会做一些我从来不会做的事情(比如,获得奥林匹克金牌):

欧洲电视台的歌唱比赛,选择两个优胜者之一进行打赌:

这些条件可使预言服务器的签名变得任意复杂,但是区块链仅需要包含一个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

合约输入的脚本如下所示:

由合约输入脚本中的1或0来决定应该使用哪种方式。如果为1,则跨链交易的脚本执行第一段代码:

2 2 checkmultisigverify

如果是0,则跨链交易的脚本执行第二段代码:

checksigverify sha256 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])中已经有详细描述。

参考资料

继续阅读