sCrypt 合约开发调试技巧: 定位及解决 checkSig / checkPreimage 异常

在 sCrypt 合约的开发调试过程中,最常见也最头疼的两个问题就是碰到 checkSig 和 checkPreimage 异常。虽然我们可以在 Debug 过程中定位到源码中错误的具体位置,但对于为什么失败以及如何修复总是感觉一头雾水。今天我们就来聊聊如何快速定位和修复这两类问题的一些技巧,希望能对大家有所帮助。

sCrypt boilerplate 项目中包含了一些 sCrypt 合约的具体示例代码且在不断更新中,所以有时可能会碰到配置失效导致无法正常完成 Debug 的情况。下面我们以该项目中的 tokenUtxo 合约为例来看看如何定位及解决这两类问题。

注意:本文中使用的 sCrypt 插件版本为 0.4.3

checkPreimage 异常

首先看下 tokenUtxo.scrypt 的 Debug 启动配置(位于 .vscode/launch.json 中,为方便查看故省略了部分数值):

{
            "type": "scrypt",
            "request": "launch",
            "name": "Debug tokenUtxo",
            "program": "${workspaceFolder}/contracts/tokenUtxo.scrypt",
            "constructorParams": "",
            "entryMethod": "split",
            "entryMethodParams": "Sig(b'304402200...'), PubKey(b'0251c866a29a93b6eb51197be1e9ccdcc5e822caa69c7593905347e3ec310bebad'), 60, 22222, PubKey(b'0291e61f25a92c94103f0f4ef1f70bf3582f44cff95d497ceb3efdb945f4ce3cbe'), 40, 22222, SigHashPreimage(b'0100000028bc...')",
            "txContext": {
                "hex": "01000000015884e5...",
                "inputSatoshis": 100000,
                "opReturn": "029a77564154c6ed13ffcc387342692480e7e15f2e3ad832cf2ac6de1c3ccf28230a5a",
                "inputIndex": 0
            }
        }

上述配置指定了 Debug 的启动函数为 split,并在 entryMethodParams 中指定了若干启动参数;同时在 txContext 中指定了 tx 相关的上下文参数。 当我们在 vscode 中启动这个配置准备进行 Debug 时,却发现 Debug Console 里输出了以下异常:

Execution failed with error SCRIPT_ERR_NULLFAIL.
Stacktrace:
  /Users/hero/work/boilerplate/contracts/tokenUtxo.scrypt:14:in 'Token.split'

这里显示的异常位置是在 14 行,其代码是 require(Tx.checkPreimage(txPreimage));,由此可以推断是 txPreimage 出了问题,但具体是什么原因呢?

之前的文章中,我们介绍过 Sighash Preiamge,它被称为交易的原像,可由交易 tx 计算出来。这里的 Tx.checkPreimage 失败,说明在启动配置参数 entryMethodParams 中传入的数值与使用 txContext 中各项参数所计算出的结果不一致。

sighashPreimage

如上图所示,Sighash Preimage 由多个部分组合而成,如果两个原像不一致,一定是其中某些字段不相同。究竟是哪个字段的问题呢?让我们再来看看接下来的日志:

----- CheckPreimage Fail Hints Begin -----
You should check the differences in detail listed below:

Fields with difference | From preimage in entry method params | From preimage calculated with tx
md5(scriptCode) | 148dd2b3fcc09d6baf15c9fcf5d961d3 | 6393778445f442466414464ee3be7cc7

Preimage calculated with tx:
0100000028bce...

----- CheckPreimage Fail Hints End -----

这段日志为我们提供了关于 checkPreimage 异常的更多细节,主要是对比了前文提到的两个原像的具体差异。这里显示二者的 scriptCode 的 MD5 值有区别,即说明二者本身的 scriptCode (对应 input 的锁定脚本)是不一致的。

至此基本找到了问题的所在,鉴于近期的一些改动,推测是 entryMethodParamstxContext 的某些配置参数可能失效了。于是重新计算并且更新了 preimagetxContext.hextxContext.opReturn 等参数后,Debug 终于得到了正确的结果。

checkSig 异常

还有是一类常见的错误是 checkSig 异常,通常是由于签名问题导致的。这里我们可以通过随意修改下 senderSig 的参数值来模拟一个签名错误问题,之后再启动 Debug 就可以看到如下提示信息:

Execution failed with error SCRIPT_ERR_NULLFAIL.
Stacktrace:
  /Users/hero/work/boilerplate/contracts/tokenUtxo.scrypt:25:in 'Token.split'
 
----- CheckSig Fail Hints Begin -----
You should make sure the following check points all passed:
1. private key used to sign should be corresponding to the public key 028f46cb8ec957dcda049ac549fc46d451e0095a5b6f95950bc58830a7dc21167c
2. the preimage of the tx to be signed should be 0100000028bcef7e73248aa273...

上述提示信息涵盖了解决签名错误时的主要检查点,即:

1. 确定生成签名所使用私钥是否正确;

2. 确认待签名 tx 的 preimage(根据 txContext 自动计算得到)与传入参数是否一致。

为了对比两个 preimage 是否一致,可以使用 SigHashPreimagetoJSON() 方法查看其内部细节,得到类似下面的结果:

{
  nVersion: 1,
  hashPrevouts: '1029c58f269f3a1f0165149921e7a726d13bf29883f80ec3bb08c75fabaa06ad',
  hashSequence: '3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044',
  outpoint: {
    hash: '5884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4',
    index: 0
  },
  scriptCode: 'fd860f5101400...',
  amount: 100000,
  nSequence: 4294967295,
  hashOutputs: '1029c58f269f3a1f0165149921e7a726d13bf29883f80ec3bb08c75fabaa06ad',
  nLocktime: 0,
  sighashType: 'SigHash.ALL | SigHash.FORKID'
}

这里的小技巧是:在生成输入参数 preimage 的地方插入一段代码,与上述异常提示中输出的 preimage 进行对比,进而找出二者可能存在的差异。如以下代码所示:

const {
    
     getPreimage, SigHashPreimage, signTx } = require('scryptlib');

...

const preimage = getPreimage(tx_, token.lockingScript.toASM(), inputSatoshis, inputIndex)
const sig = signTx(tx_, privKey, token.lockingScript.toASM(), inputSatoshis)

// compare two preimages for debugging purpose
const preimage2 = new SigHashPreimage('fd860f51014001760...'); // use hex from checkSig fail hints
console.log(preimage2.toString() === preimage.toString())
console.log(preimage.toJSON())
console.log(preimage2.toJSON())

这里需要再次提醒大家的是,启动配置 txContext 属性下的字段都会影响 preimage 的计算,所以在排查问题时需要逐一对比确认是否一致。

猜你喜欢

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