区块链中的脚本系统

前一段时间看一下最近特别火的区块链,对于它的脚本系统最感兴趣,这两天特意研究了一下,下面的代码分析是基于bitcoin 0.1的版本

BitCoin中的脚本

Script是BitCoin中的一个特色,感觉它也借鉴了汇编中的操作符与栈的概念。但是Script中用于存放操作符也对象的不是一个”栈”,看它的定义

class CScript : public vector<unsigned char>
{
   protected:
   CScript& push_int64(int64 n)
   {
      if (n == -1 || (n >= 1 && n <= 16))
     {
         push_back(n + (OP_1 - 1));
     }
     else
     {
         CBigNum bn(n);
         *this << bn.getvch();
     }
     return (*this);
    }

}

CScript& operator<<(char b) { return (push_int64(b)); }

其中操作符<<是我们在后面的代码中常见的一个符号。引用这段代码的目的是想说明, 在执行 << 的时候,其实是执行vector的push_back, 查看一下push_back的说明:

push_back是算法语言里面的一个函数名。如c++中的vector头文件里面就有这个push_back函数,在vector类中作用为在vector尾部加入一个数据。
即它是将数据放在vector的队尾。

2. Script脚本中的数据的输入

2.1 说明

对于Bitcoin而言,它是通过创建一个对象CTransaction,将输入,输出(找零,与花费)放在其中,其CTransaction的定义:

class CTransaction
{
public:
    int nVersion;
    vector<CTxIn> vin;
    vector<CTxOut> vout;
    int nLockTime;
}

其中的vin与vout是比较重要的两个地方,其中的Scrip信息都是存放在vout与vin中。

2.2 过程

这里写图片描述
整个过程可以分为上面几个步骤。下面分别进行说明。

  1. UI界面的触发
    这里写图片描述
    当点击Send按钮之后,会调用ui.cpp中的函数
    CSendDialog::OnButtonSend(wxCommandEvent& event)
    整个过程的发起是在 CSendDialog::OnButtonSend(wxCommandEvent& event)(基于0.1.5版本), 其中有一段函数:
// Parse bitcoin address
uint160 hash160;
bool fBitcoinAddress = AddressToHash160(strAddress, hash160);

if (fBitcoinAddress)
{
   // Send to bitcoin address
   CScript scriptPubKey;
   scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG;

   if (!SendMoney(scriptPubKey, nValue, wtx))
      return;
}

以知道此时scriptPubKey中的信息如下
这里写图片描述
接着开始会调用main.cpp中的SendMoney()函数

  1. 开始调用main.cpp中的SendMoney()函数,所有的动作都可以看成是在SendMoney()中完成。
  2. 创建Transaction(), 这个函数的主要作用是找到可以用于支持coin, 在这个函数中,有两个变量(vin与vout)和一个税率fee需要注意。
    先说一下税率fee, BitCoin的交易是有税率的,但是这个税率的支付与用于支付的coin相关,因此,这里它做了一个处理,首先把fee设置为0,将需要支付的coin信息获取出来,然后再调用GetMinFee(true)计算一次。其流程图:
    这里写图片描述
    另外两个值就是vin与vout,支持信息都会保存在这里。它是CTransaction的成员变量,其定义如下:
class CTransaction
{
    public:
    int nVersion;
    vector<CTxIn> vin;
    vector<CTxOut> vout;
    int nLockTime;
}

对于vout的script信息比较容易找,它是在createTransaction()的时候,直接设置进去,其代码如下:

 // Fill vout[0] to the payee              wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));

而vin的script就比较麻烦。在另外的文档中进去解析。只需要了解到的是vin中的script信息是:
scriptSigRet << vchSig << vchPubKey;
对应的信息:

这里写图片描述

3. 脚本的验证过程

在Bitcoin 0.1.5中的验证过程,代码逻辑不是太清晰,相对于0.15的代码,这一块就很清楚。可以直接查看对应的api就可以知道:https://bitcore.io/api/lib/script#Interpreter,这里还是以0.1.5为分析模板进行分析。

3.1 解析过程

系统在校验的时候,是将vin与vout的脚本合起来进行校验的。其:
VS = IS + OS
其中VS(validation script),就是组合后的脚本, 其组合后的信息如下,其校验过程如下,其校验代码在script.cpp中的EvalScript()
1. 在校验之前, 会有一个空的stack,这个stack是实际的运行结果保存位置
这里写图片描述

  1. 指针首先指向sign值,它是一个立即数,这个数会压入执行队列stack的队尾,执行后结果如下:
    这里写图片描述

  2. 此时vs指针指向了PK值,而PK也是一个立即数,与sign值一样,也会取出来,然后放到执行队列stack中,如下:
    这里写图片描述

  3. 执行队列执行OP_DUP了,此时执OP_DUP,是一个操作符,此时它的执行过程是将获取出stack最后一个值(不是出队列),然后将这个值赋给一个新的变量,再将这个变量push_back进去。
    这里写图片描述

  4. 经过OP_DUP之后,指针指向了OP_HASH160了,它会取出stack最后一个值(出队列),利用这个值计算hash160的值,计算结果然后写回到stack最后一个
    这里写图片描述

  5. 现在指针指向了hash160,这也是一个立即数,直接取出来,然后压栈
    这里写图片描述

  6. 此时指针指向了OP_EQUALVERIFY了,它是比较stack最后两个项的值。
    这里写图片描述

  7. 此时就到最后一个项OP_CHECKSIG了,此时获取sign的值,与PK值,并通过PK值vin, vout的值,计算签名,如果计算出来的值与stack中的值相同,则会压入true,否则就会压栈false

  8. 系统就检查stack最后一个值,如果为true,表明正确,如果为false就不正确

总结

在以前的项目中,也会碰到类似的需要进行校验的,一般的想法就是在各个节点放一个脚本或者一个jar包,但是BitCoin给了另外一种思路,让校验过程镶嵌到消息中,很是比较牛B

参考:

https://www.liaoxuefeng.com/article/001482718603696a6b6eb2bebc74211ab967146a952ae0c000
http://davidederosa.com/basic-blockchain-programming/bitcoin-script-language-part-one/

猜你喜欢

转载自blog.csdn.net/eyoulc123/article/details/79230965