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

    科技2022-07-13  127

    在 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 中各项参数所计算出的结果不一致。

    如上图所示,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 的锁定脚本)是不一致的。

    至此基本找到了问题的所在,鉴于近期的一些改动,推测是 entryMethodParams 和 txContext 的某些配置参数可能失效了。于是重新计算并且更新了 preimage、txContext.hex、txContext.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 是否一致,可以使用 SigHashPreimage 的 toJSON() 方法查看其内部细节,得到类似下面的结果:

    { 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 的计算,所以在排查问题时需要逐一对比确认是否一致。

    Processed: 0.012, SQL: 8