深入学习比特币脚本之 OP_PUSH_TX(1)

了解比特币脚本(Bitcoin Script)的朋友都知道,它是由一串计算指令(即操作码 Opcode)和数据组成的。脚本执行就是使用指令对数据进行运算的过程,换言之,这个程序的输入在它执行之前就已经确定了。也正是因为这种设定,很多人下意识地认为它的能力范围也是非常有限的,因为它不够“灵活”。

今天想给大家介绍的东西实则是突破了这种刻板印象,也让我们可以更加深入地理解脚本的强大和有趣。

我们设计了一个算法,使脚本可以访问正在被执行的脚本所在的当前 transaction 数据。我们称之为OP_PUSH_TX,它可以像一个伪操作码一样把当前 transaction 放入栈中。我们用 sCrypt 实现了它,sCrypt 是一个可以编译成比特币原生脚本的的高级语言。我们会通过一个例子来演示 OP_PUSH_TX 的用法。

OP_CHECKSIG

OP_CHECKSIG 是用来验证 ECDSA签名的操作码。理论上讲,它包括两个步骤:

  1. 根据 当前 transaction 计算出一个哈希值。
  2. 对这个哈希值进行签名校验。

注意,步骤1中的哈希值只能是当前 transaction 的哈希值,所以只有当被签名的数据是当前 transaction 的哈希值时 OP_CHECKSIG 才会正常工作。对任何其他数据做的签名都无法正常工作。

OP_PUSH_TX

概览

通常,OP_CHECKSIG 中使用的签名是链下生成的,作为参数传入解锁脚本中。为了访问当前 transaction,我们改为在链上用脚本直接计算出签名。为此,作为参数传入解锁脚本的不再是签名,而是当前 transaction。用于计算签名的 ECDMA 公私钥对也作为参数被传入解锁脚本。通过当前 transaction 参数和私钥参数,可以计算出一个 ECDMA 签名,OP_CHECKSIG 就可以用公钥参数对这个计算出的签名进行校验了。如果校验通过,我们就能确定,传入解锁脚本的当前 transaction 参数是真实的当前 transaction,因为OP_CHECKSIG 只有在被签名数据为当前 transaction 的哈希值时才会通过

详情

用脚本实现 OP_PUSH_TX 的算法如下:

  1. 把当前 transaction 放到栈顶;
  2. 把私钥放到栈顶(该私钥公开在脚本中,仅用于计算签名,不控制任何比特币);
  3. 运行脚本中的 ECDSA 签名算法,用第1步和第2步放入栈中的 transaction 和私钥计算出签名;
  4. 把第2步的私钥对应的公钥放到栈顶;
  5. 运行 OP_CHECKSIG;

第1、2、4步在解锁脚本中,第3、5步在锁定脚本中。

如果第5步的 OP_CHECKSIG 校验签名通过,那我们就可以确认在第1步放入栈中的 transaction 参数确实是真实的当前 transaction,因为只有当前 transaction 的签名才会被 OP_CHECKSIG 校验通过1。OP_CHECKSIG 并不关心签名是在链下外部程序生成的(如 P2PKH 的情况)还是在链上脚本中生成的(如 OP_PUSH_TX 的情况)。

值得注意的是第2步中的私钥,通常私钥都是要保密的,但它是公开在脚本中的。这样做没问题,因为这个私钥并不控制比特币,而只是参与签名计算以证明第1步的参数确实是当前 transaction。实际上,这个私钥甚至可以重复使用。

Sighash Preimage

更准确地说,第1步中的 transaction 并不是完整的当前 transaction 数据,而是由完整数据生成的 preimage,在后续的计算中还会再做两次 SHA256 运算。preimage 的格式规定如下:

sighashPreimage
注意,input 脚本没有包括在内。

实现

sCrypt 实现了 OP_PUSH_TX 算法,并把它封装成标准合约函数 Tx.checkPreimage,用于校验传入参数是否为当前 transaction 的 preimage。下面演示一个例子,我们用它开发一个名为 CheckLockTimeVerify 的合约,该合约可以确保里面的币在某个时间之前不能被花费,类似于 OP_CLTV。通过验证(即require(Tx.checkPreimage(sighashPreimage)))后,你就可以访问当前 transaction 中的数据了。

contract CheckLockTimeVerify {
    
    
    int matureTime;

    public function spend(bytes sighashPreimage) {
    
    
        // this ensures the preimage is for the current tx
        require(Tx.checkPreimage(sighashPreimage));

        // parse nLocktime
        int len = length(sighashPreimage);
        int nLocktime = this.fromLEUnsigned(sighashPreimage[len - 8 : len - 4]);

        require(nLocktime >= this.matureTime);
    }

    function fromLEUnsigned(bytes b) returns (int) {
    
    
        // append positive sign byte. This does not hurt even when sign bit is already positive
        return unpack(b + b'00');
    }
}

引申

使用 OP_PUSH_TX 可以让合约代码访问整个 transaction 数据,包括所有的 input 和 output。我们可以在合约中对这些数据设置任何约束条件。这为在比特币网络上运行各种智能合约开辟了无限可能,以后我们会展示更多示例。

本文是一系列文章中的第一篇,这个系列会告诉大家比特币智能合约能做什么以及如何做。

鸣谢

特别感谢 nChain 提供的原创想法。



  1. 除非出现了哈希碰撞(两段不同信息的哈希值相同),这种碰撞被认为是几乎不可能出现的。在数字签名中,被签名的数据是信息的哈希值,不是信息本身。 ↩︎

猜你喜欢

转载自blog.csdn.net/freedomhero/article/details/107306604
今日推荐