使用 sCrypt 实现有状态的比特币智能合约

比特币智能合约

比特币脚本的能力通常被认为是很有限的,无法实现复杂的智能合约。一个经常被诟病的点就是其无法实现有状态的智能合约。以太坊出现的一个主要原因,就是为了克服这个问题。

有些合约的确是有状态的,因为这些合约需要参与者在合约的多个阶段与之交互,并依赖随时间变化的状态,比如链上投票或游戏。接下来我们将展示一种在比特币智能合约中管理状态的通用机制。我们还将用 sCrypt 语言来实现一个有状态的合约,sCrypt 语言是可以编译成比特币脚本语言的高级语言。

预备知识:OP_PUSH_TX

在研究如何在比特币智能合约中管理状态之前,我们再来回顾一种强大的技术 OP_PUSH_TX。你可以把它看成一个伪操作码,这个伪操作码把当前 transaction 放入栈里,这样就可以在运行时使用。更准确地说,能让我们查看在 BIP143 中定义的签名验证中使用的哈希前数据(preimage)。preimage 的格式如下:

sighashPreimage

实现有状态合约

一旦我们可以通过 OP_PUSH_TX 技术访问到合约当前的 transaction 上下文,我们就可以对它的 inputs 和 outputs 设置任意的约束。

在合约中实现状态的一种方法是把锁定脚本分成两部分:数据和代码。数据部分就是状态。代码部分则包含了状态转换规则,也就是合约的商业逻辑。数据部分可以通过OP_RETURN <data>或者OP_PUSHDATA <data> OP_DROP的方式附加到代码后面。虽然数据部分不会被运行,但它前面的代码部分会使用它,所以数据部分还是会对合约产生影响。

用 OP_PUSH_TX,我们从第5条可以获得被花费的 output 的锁定脚本内容,从第8条可以获得新的 output 内容。为了管理状态,我们要求锁定脚本的代码部分不能变(即合约规则不能变),数据(状态)部分的变化则必须符合代码部分规定的状态转换规则。

code_and_data
这类似于面向对象编程中的对象的概念,代码部分是对象的方法,数据部分是对象的成员变量。对象方法是不可变的。成员变量被封装起来,只能通过对象方法来改变它们。对象方法通过解锁脚本来调用,解锁脚本选择调用哪个方法并传入对应方法的参数。

一个示例合约:计数器

我们来看一个有状态合约的简单实例:计数器合约,记录合约的 increment() 方法被调用了多少次。合约代码及注释如下:

import "util.scrypt";

contract Counter {
    
    
	public function increment(bytes txPreimage, int amount) {
    
    
        require(Tx.checkPreimage(txPreimage));

        bytes scriptCode = Util.scriptCode(txPreimage);
		int scriptLen = length(scriptCode);
		// last byte contains the state, i.e., counter
		int counter = unpack(scriptCode[scriptLen - 1 :]);
		// increment counter
		bytes scriptCode_ = scriptCode[: scriptLen - 1] + num2bin(counter + 1, 1);
		bytes hashOutputs = Util.hashOutputs(txPreimage);
		// output: amount + scriptlen + script
		Sha256 hashOutputs_ = hash256(num2bin(amount, 8) + Util.writeVarint(scriptCode_));
		// ensure output is expected: amount is same with specified
		// also output script is the same with scriptCode except counter incremented
		require(hashOutputs == hashOutputs_);
    }
}

首先,确保 preimage 确实为当前 transaction:

require(Tx.checkPreimage(txPreimage));

扫描二维码关注公众号,回复: 12303309 查看本文章

接着,我们得到了前一个锁定脚本内容,也就是前文提到的 preimage 第5条的 scriptCode

bytes scriptCode = Util.scriptCode(txPreimage);

然后,从 scriptCode 中提取出了前一个计数器的状态(也就是计数器的值):

int counter = unpack(scriptCode[scriptLen - 1 :]);

接下来,把计数器值加1并放在新的锁定脚本中。请注意,计数器值是锁定脚本中唯一更改的部分:

bytes scriptCode_ = scriptCode[: scriptLen - 1] + num2bin(counter + 1, 1);

最后,确保当前 transaction 的 output 中包含了新的锁定脚本:

bytes hashOutputs = Util.hashOutputs(txPreimage);
Sha256 hashOutputs_ = hash256(num2bin(amount, 8) + Util.writeVarint(scriptCode_));
require(hashOutputs == hashOutputs_);

这里有部署合约的代码。重复调用合约方法 increment(),计数器从0到9递增的合约实例可以在这里看到:0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9。请注意,计数器状态数据在每个 transaction 的第一个 output 的脚本末尾处。如下图所示:

counter

总结

本文的目的是展示比特币智能合约能做什么以及如何实现。许多所谓的脚本限制是因为没有意识到它的潜力。随着我们进一步解释和演示脚本可以实现什么时,人们将会发现它具有极强的可扩展性、通用性和面向未来的特性。我们将展示取消人为限制的比特币网络可以运行任何能在其他区块链运行的智能合约,同时具备无限扩容的能力。再加上一些经济激励,可以让许多跨行业的应用更加高效和安全。

猜你喜欢

转载自blog.csdn.net/freedomhero/article/details/107307306