比特币源码分析 脚本(二)

代码分析

script.h中,存放着类CScript的成员函数,其中最关键的是函数GetOp

bool GetOp(const_iterator& pc, opcodetype& opcodeRet, vector<unsigned char>& vchRet) const

GetOp的参数包括程序计数器PC、操作码OPCode及立即数返回值。该函数的作用是从脚本PC处读出操作码,调整PC指向下一个操作码;若当前读取的操作码为立即数操作码,则也一并读出立即数。

script.cpp中,存放着与脚本密切相关的函数,其中最基础、最关键的是函数EvalScript

bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType,
                vector<vector<unsigned char> >* pvStackRet)

EvalScript的参数包括待执行的程序(待执行的脚本),运行的环境(待填充的交易交易输入的索引等)以及运行的结果

EvalScript主要局部变量及解析如下:

CScript::const_iterator pc = script.begin();				//程序计数器,用于指向下一个执行的指令
CScript::const_iterator pend = script.end();				//用于判断程序结束
CScript::const_iterator pbegincodehash = script.begin();	//与操作码OP_CODESEPARATOR一起用于辅助定位待签名脚本
vector<bool> vfExec;										//条件判断结果
vector<valtype> stack;										//主运行栈,用于运行时存放中间数值
vector<valtype> altstack;									//次栈,未找到具体用法

EvalScript主要逻辑是从待执行脚本中取出操作码并执行,直至取完、执行过程中遇到OP_RETURN、执行过程中VERIFY类验证失败、执行过程中遇到错误(例如操作数不足等)才会结束执行。
各类操作码大致行为如下表:

类型 操作
立即数 往主栈压入立即数
流程控制 对操作数进行条件判断;根据条件判断结果控制指令解析执行;对条件判断结果进行操作
栈操作 对栈顶某些元素进行复制、反转、交换等操作
切片运算 对栈顶某些元素进行拼接、位移等操作
位运算 对栈顶某些元素进行位运算
数值运算 对栈顶某些元素进行数值运算
加密运算 对栈顶某些元素进行计算哈希值、验签等密码学运算
边界符
模板
非法字节码

其中,边界符、模板、非法字节码三类操作码并无实际的执行代码(即这三类操作码在输入到脚本执行前已被预处理)。

所有指令中,较难理解的是OP_CHECKSIGVERIFYOP_CHECKMULTISIGVERIFY可看作升级版OP_CHECKSIGVERIFY

case OP_CHECKSIGVERIFY:
{
    
    
    // (sig pubkey -- bool)
    // OP_CHECKSIGVERIFY输入参数为sig及pubkey,输出结果bool
    if (stack.size() < 2)
        return false;

    valtype& vchSig    = stacktop(-2);
    valtype& vchPubKey = stacktop(-1);

    // debug print
    //PrintHex(vchSig.begin(), vchSig.end(), "sig: %s\n");
    //PrintHex(vchPubKey.begin(), vchPubKey.end(), "pubkey: %s\n");

    // Subset of script starting at the most recent codeseparator
    // 从最近的分隔符OP_CODESEPARATOR一直到结束的脚本需要进行验签
    CScript scriptCode(pbegincodehash, pend);

    // Drop the signature, since there's no way for a signature to sign itself
    // 去掉签名
    scriptCode.FindAndDelete(CScript(vchSig));

	// 跟进 CheckSig -> SignatureHash,最终在SignatureHash中,将scriptCode填充到txTo的vin[nIn]中的scriptSig,
	// 序列化后在进行计算哈希值,并对该哈希值进行验签
    bool fSuccess = CheckSig(vchSig, vchPubKey, scriptCode, txTo, nIn, nHashType);

    stack.pop_back();
    stack.pop_back();
    stack.push_back(fSuccess ? vchTrue : vchFalse);
    if (opcode == OP_CHECKSIGVERIFY)
    {
    
    
        if (fSuccess)
            stack.pop_back();
        else
        	// 验签失败直接返回
            pc = pend;
    }
}
break;

猜你喜欢

转载自blog.csdn.net/u013434801/article/details/120636272