上一篇我们分析了计数器合约的代码,其中最关键的问题是:如何保证sighashPreimage
参数的真实性。要弄清楚这个问题,先要从比特币脚本的操作码OP_CHECKSIG
说起。
OP_CHECKSIG用于验证ECDSA签名。ECDSA签名的验证需要如下三个参数:
- 公钥:签名私钥对应的公钥。
- 被签名数据:被签名的数据的Hash值。
- 签名。
如果签名是由公钥对应的私钥针对数据进行签署的,那么签名验证成功,否则失败。而OP_CHECKSIG验证的ECDSA签名,只需要公钥和签名两个参数。为什么没有“被签名数据”参数呢?因为在OP_CHECKSIG中,“被签名数据”参数是一组固定数据的Hash值,也就是说OP_CHECK只检测对特定数据的签名。这组固定数据来自新(花费)老(被花费)两组TX,是这样构成的:
- nVersion of the transaction (4-byte little endian)
- hashPrevouts (32-byte hash)
- hashSequence (32-byte hash)
- outpoint (32-byte hash + 4-byte little endian)
- scriptCode of the input (serialized as scripts inside CTxOuts)
- value of the output spent by this input (8-byte little endian)
- nSequence of the input (4-byte little endian)
- hashOutputs (32-byte hash)
- nLocktime of the transaction (4-byte little endian)
- sighash type of the signature (4-byte little endian)
上述5、8两项是不是看着有点眼熟?是的,这就是上一篇里从sighashPreimage
参数里解析出来的两个数据。其实,上面这组数据就是sighashPreimage
。只是,这里的sighashPreimage
是在验证签名时,由比特币的实现代码(如全节点代码)从老TX和新TX中获取的,是真实的,我们称之为真实sighashPreimage
。而计数器合约中的sighashPreimage是调用者传入合约的解锁参数之一,不一定与真实的一致,我们称之为传入sighashPreimage
。而如何保证传入sighashPreimage
与真实sighashPreimage
是一样的,正是计数器合约要解决的关键问题。
接下是最巧妙的部分,计数器合约用比特币脚本做了如下两步操作:
- 选择一个对公私钥privateKey和publicKey,用私钥对
传入sighashPreimage
进行签名,得到签名数据,用公式表达为:signature = sig(privateKey, hash(传入sighhashPreimage))
- 用上述公钥对上述计算出的签名进行OP_CHECKSIG签名校验,用公式表达为:
checkResult = op_checksig(publicKey, hash(真实sighashPreimage), signature)
。(再次强调,OP_CHECKSIG操作码规定,被签名的数据一定是hash(真实sighashPreimage)
,否则OP_CHECKSIG的行为就不符合协议,相当于实现OP_CHECKSIG的代码错了。)
如果第2步的签名通过,也就是说checkResult
为true
,那么传入sighashPreimage
与真实sighashPreimage
相同。计数器合约正是这样巧妙地运用OP_CHECKSIG操作码来验证了传入sighashPreimage
的真实性。
从上面的计算过程我们可以知道,这里公私钥对的选择并不重要,部署合约时随机生成一对就可以。因为公私钥对要写到合约脚本里公开,所以千万不要选择可以花费币的私钥。
这种检测传入sighashPreimage
真实性的方案有个专有名字,叫OP_PUSH_TX。虽然以OP开头,但这并不是BSV协议中的基本操作码。可以把它理解为用基本操作码编写的一个函数。sCrypt语言中用如下两行代码来实现OP_PUSH_TX:
Tx tx = new Tx();
require(tx.validate(sighashPreimage));
nChain公司已申请了该方案及相关技术的专利。运用OP_PUSH_TX的智能合约例子还有:
用脚本直接计算ECDSA签名,用OP_CHECKSIG验证传入数据的真实性。这是十年来在比特币上开的最大脑洞之一,脑洞的另一端也许是一个更有趣的世界。
参考资料
下一篇:OP_PUSH_TX背后的故事
上一篇:计数器合约代码分析