1 智能合约应用示例

通过本章节,您将学会如何在区块链上开发一个简单的智能合约。恒为区块链的智能合约采用LUA/JS语言编写。区块链节点中集成了LVM/V8,它们作为智能合约执行的虚拟机环境。

1.1 合约规范

1.2 合约外部API

1.3 合约示例

1.3.1 编写智能合约

1.3.2 上传智能合约

1.3.3 部署智能合约

1.3.4 执行智能合约

1.3.5 查询智能合约

1.4 零知识证明在智能合约中的应用

1.4.1 libsnark零知识证明库的编译

1.4.2 snarkjs安装使用

1.1 合约规范

每个上传的智能合约代码,根据需要须实现如下三个入口函数,才能提供相应的部署,执行,查询合约的功能,具体如下:

1、 部署合约

function deploy(cfg)

参数说明:

cfg是部署合约时,传入的初始配置参数。
如果调用合约时,没有初始配置参数,则cfg是null。

返回值说明:

通常情况下,无需返回值,系统会将cfg作为初始化数据保存下来,并可通过查询合约API查询该初始化数据,即查询合约时,info参数为'init'。

有一个名为ctx的全局变量,该变量是一个对象类型,包含如下信息:

{
   "msg": {
            "sender": <执行合约部署的账号>,
            "amount": <执行合约部署时,传入该合约账号的资产金额>
            "receiver": <新创建的合约账号>
          },
   "ledger": {
            "height": <当前账本的高度>,
   "startTime": <当前账本高度开始的UTC时间>
   }
}

注: 该函数不是必须实现的,如果没有提供此函数,则系统会将传入的配置作为合约初始化数据保存。

2、 执行合约

function run(op, arg)

参数说明:

op是要执行的合约功能的操作码,是一个字符串。
arg是执行合约功能的参数,可选,其可以是一个字符串或对象或其它类型,取决于传入的参数是原始的字符串,还是一个JSON串; 如果没有该参数,则arg为null。

返回值说明:

其返回值由两部分组成: <结果码><结果消息>,其中前者是必须的,类型为整数,且成功为0,其它值表示失败。后者,通常在失败时,给出失败原因的描述。

同样有一个ctx全局变量,与部署合约相比,新增了一个如下字段:

{
   "init": <部署合约时产生的初始化数据>
}

3、 查询合约状态

function query(info, arg)

参数说明:

info是要查询的合约状态码,是一个字符串,其种有个特殊值'init',此时会返回该合约部署时,产生的合约初始化数据。
arg是查询合约状态的参数,可选,其可以是一个字符串或对象或其它类型,取决于传入的参数是原始的字符串,还是一个JSON串; 如果没有该参数,则arg为null。

返回值说明:

其返回值由两部分组成: <结果码><查询结果>,其中前者类型为整数,成功为0,其它值表示失败。后者,在成功时,表示查询的结果,可为对象或字符串等类型,后者在失败时,表示失败的的原因。

同样有一个ctx全局变量,该变量是一个对象类型,包含如下信息:

{
    "init": <部署合约时产生的初始化数据>,
    "state": <合约状态,整数类型,取值有三个,分别是0: 正常, 1: 禁用, 2: 作废>
    "enabler": <能够恢复合约继续使用的账号,当合约状态为1时,有该字段>
    "canceller": <废除合约的账号,当合约状态为2时,有该字段>
}

1.2 合约外部API

用户使用合约大致分为两步:

(1) 上传合约代码,得到代码地址。
(2) 根据代码地址和初始化配置,部署合约,即传入初始化参数,调用合约初始化函数,获得合约账号。

1、 上传合约代码

该步骤是由一个交易完成,其格式中与其它交易的主要不同是:

{
    "TransactionType": "ContractUpload",
    "Account": <上传合约代码的账号,其负责支付交易费用>,
    "Fee": <交易费用,根据代码大小而定,按照Memos的收费规则而定>,
    "Memos": [ {"Memo": { "MemoType":"js", "MemoFormat": "0.0.1", "MemoData": <智能合约代码> }}]
}

说明:

MemoData: 其存储的智能合约代码在RPC请求中采取Hex编码字符串表示,但计算Fee大小是依据实际代码的大小,即Hex编码前的代码大小。交易成功执行完成后,会返回一个表示该合约代码的代码地址,其为256bits的HASH值。

2、 部署合约 该步骤由一个交易完成,其格式中与其它交易的主要不同是:

{
    "TransactionType": "ContractDeploy",
    "Account": <部署合约代码的账号>,
    "Amount": <转入合约账号的初始金额,其不应少于激活账号的最小金额,否则,交易会失败>,
    "CodeAddress": <合约代码地址,即在上传合约代码交易的HASH256>,
    "Memos":[{"Memo":{"MemoType":<没有强制要求>,"MemoFormat":<没有强制要求>,"MemoData": <合约部署时的初始化参数,字符串>}}], // 可选参数
    "Fee": <部署交易的费用>     
}

说明:

(1) 该交易成功执行完成后,会产生并返回一个合约账号,并记录在交易中的Meta数据中的Destination字段中。
(2) 部署交易的费用,取决于交易中部署代码的执行情况,其计算费用如下:
       BaseFee + (TransferNum - 2) * BaseFee + (UpdateNum - 4) * BaseFee
       其中, BaseFee是系统的基础交易费用,对于hwchain为10000 Drops,TransferNum是交易中调用转账API的次数,UpdateNum包括交易中调用合约状态保存API的次数,设置或创建NFToken的次数。

3、 执行合约 该步骤由一个交易完成,其格式的关键内容如下:

{
    "TransactionType": "ContractCall",
    "Account": <执行合约代码的账号,其负责支付交易费用,以及可选的给合约账号转入金额>,
    "Amount": <转入合约账号的金额,是否需要,取决于被执行合约代码的要求>
    "Destination": <部署合约时,创建的合约账号>
    "MessageKey": <执行合约的操作,字符串>
    "Memos":[{"Memo":{"MemoType":<没有强制要求>,"MemoFormat":<没有强制要求>,"MemoData": <执行合约传入的参数,字符串>}}], // 可选参数
    "Fee": <执行交易的费用,具体取决于交易执行的情况,与部署交易中的收费原则相同>
}

4、 查询合约状态 其是一个JSON RPC调用,其参数如下:

{
    "method": "query_contract",
    "params": [{
        "address": <合约账号>,
        "info": <查询合约的信息码>,
        "args": <查询合约时传入的参数>
    ]}
}

说明:

(1) 如果交易执行成功,则在返回结果中的字段“contract_result”中包含了来自合约的查询结果。
(2) 如果交易执行失败,则在返回结果中的字段“error_message”中包含了执行合约的错误信息。

1.3 合约示例

智能合约的开发包括以下步骤:

1.3.1 编写智能合约

本智能合约示例将实现一个数据存储、查询的功能,保存执行时输入的val,查询时返回val。JS合约代码如下:

function deploy (cfg){

}

function run (op, arg){
    var ret;
    if (op == 'setValue')
        ret = saveContractStatus('storage', arg.val);
    else
        return [-1, 'unsupport operation ' + op];
    return [0, ret];
}

function query (info, arg){
    var ret;
    if (info == 'getValue')
        ret = getContractStatus ('storage');
    return [0, ret];
}

将上面的合约代码进行Hex十六进制编码:

66756e6374696f6e206465706c6f792028636667297b0a0a7d0a0a66756e6374696f6e2072756e20286f702c20617267297b0a20202020766172207265743b0a20202020696620286f70203d3d202773657456616c756527290a2020202020202020726574203d2073617665436f6e7472616374537461747573282773746f72616765272c206172672e76616c293b0a20202020656c73650a202020202020202072657475726e205b2d312c2027756e737570706f7274206f7065726174696f6e2027202b206f705d3b0a2020202072657475726e205b302c207265745d3b0a7d0a0a66756e6374696f6e2071756572792028696e666f2c20617267297b0a20202020766172207265743b0a2020202069662028696e666f203d3d202767657456616c756527290a2020202020202020726574203d20676574436f6e747261637453746174757320282773746f7261676527293b0a2020202072657475726e205b302c207265745d3b0a7d

1.3.2 上传智能合约

上传智能合约,就是通过区块链的交易,编译智能合约代码,并写入到账本中的过程。其中"MemoType": "6A73",即js。"MemoFormat": "302E302E31",即0.0.1,MemoData即上面编码后的智能合约代码。

请求:

{
    "method": "submit",
    "params": [{
        "secret": "srdXH3D7WrBwoatsGcAXVwHE43Nas",
        "tx_json": {
            "TransactionType": "ContractUpload",
            "Account": "hU1w61QYLeqENreieEcQHfNUzYbYiLBtk2",
            "Fee": "10000",
            "Memos": [{
                "Memo": {
                    "MemoFormat": "302E302E31",
                    "MemoType": "6A73",
                    "MemoData": "66756e6374696f6e206465706c6f792028636667297b0a0a7d0a0a66756e6374696f6e2072756e20286f702c20617267297b0a20202020766172207265743b0a20202020696620286f70203d3d202773657456616c756527290a2020202020202020726574203d2073617665436f6e7472616374537461747573282773746f72616765272c206172672e76616c293b0a20202020656c73650a202020202020202072657475726e205b2d312c2027756e737570706f7274206f7065726174696f6e2027202b206f705d3b0a2020202072657475726e205b302c207265745d3b0a7d0a0a66756e6374696f6e2071756572792028696e666f2c20617267297b0a20202020766172207265743b0a2020202069662028696e666f203d3d202767657456616c756527290a2020202020202020726574203d20676574436f6e747261637453746174757320282773746f7261676527293b0a2020202072657475726e205b302c207265745d3b0a7d"
                }
            }]
        }
    }]
}

交易成功后,返回智能合约代码地址"code_address":"C6C286F394D6BEEC76FF6785726B045326F1463F93A4208EA6D50B392D29D794",这个代码地址。将作为部署智能合约的寻址入口。

1.3.3 部署智能合约

部署智能合约,就是通过区块链的交易,把智能合约代码生成合约地址的过程。其中"MemoType": "6A73",即js。"MemoFormat": "302E302E31",即0.0.1,"MemoData": "7b7d",即{}。

请求:

{
    "method": "submit",
    "params": [
        {
            "secret": "srdXH3D7WrBwoatsGcAXVwHE43Nas",
            "tx_json": {
                "TransactionType": "ContractDeploy",
                "Account": "hU1w61QYLeqENreieEcQHfNUzYbYiLBtk2",
                "Amount": "30000000",
                "CodeAddress": "C6C286F394D6BEEC76FF6785726B045326F1463F93A4208EA6D50B392D29D794",
                "Memos": [
                    {
                        "Memo": {
                            "MemoType": "6A73",
                            "MemoFormat": "302E302E31",
                            "MemoData": "7b7d"
                        }
                    }
                ],
                "Fee": "10000"
            }
        }
    ]
}

交易成功后,返回合约地址,即合约账号:"contract_address":"hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph"。它将作为执行、查询智能合约的寻址入口。

1.3.4 执行智能合约

执行智能合约,就是通过区块链的交易,执行智能合约代码的过程。其中"Destination": "hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph",即合约账号,"MessageKey": "73657456616c7565",即setValue,"MemoType": "6A73",即js。"MemoFormat": "302E302E31",即0.0.1,"MemoData": "7b2276616c223a2268656c6c6f227d",即{"val":"hello"}。

请求:

{
    "method": "submit",
    "params": [
        {
            "offline": false,
            "secret": "srdXH3D7WrBwoatsGcAXVwHE43Nas",
            "tx_json": {
                "TransactionType": "ContractCall",
                "Account": "hU1w61QYLeqENreieEcQHfNUzYbYiLBtk2",
                "Destination": "hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph",
                "MessageKey": "73657456616c7565",
                "Memos": [
                    {
                        "Memo": {
                            "MemoType": "6A73",
                            "MemoFormat": "302E302E31",
                            "MemoData": "7b2276616c223a2268656c6c6f227d"
                        }
                    }
                ],
                "Fee": "10000"
            }
        }
    ]
}

1.3.5 查询智能合约

查询智能合约。

请求:

{
    "method": "query_contract",
    "params": [{
        "address": "hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph",
        "info": "getValue",
        "arg": ""
    }]
}

返回结果,"contract_result":"hello"。

智能合约中可以调用区块链提供复杂的智能合约接口函数,完成复杂的智能合约业务。详细见智能合约函数定义

1.4 零知识证明在智能合约中的应用

1.4.1 libsnark零知识证明库的编译

1、获取代码

git clone https://github.com/hwelltech/libsnark.git
cd libsnark/
git submodule init && git submodule update

2、Ubuntu 18.04 LTS, Ubuntu 20.04 LTS 操作系统

安装必备的包。

sudo apt install build-essential cmake git libgmp3-dev libprocps-dev python3-markdown libboost-program-options-dev libssl-dev python3 pkg-config

创建Makefile

mkdir build && CD build && cmake ..

编译libsnark

make

3、Windows 操作系统

进入 MSYS2 MinGW 64-bit 命令行,

cd libsnark  
mkdir VS2017
cd VS2017/
cmake -G  'Visual Studio 15 2017 Win64' -DUSE_ASM=OFF -DWITH_PROCPS=OFF -DGMP_INCLUDE_DIR=C:\\msys64\\mingw64\\include -DBoost_INCLUDE_DIR=D:\\local\\boost_1_73_0 ..

4、生成验证密钥、公有输入、证明的示例

#include <stdlib.h>
#include <fstream>
#include "libsnark/gadgetlib1/pb_variable.hpp"

#include <libff/common/default_types/ec_pp.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>

using namespace libsnark;
using namespace std;

std::string StrToHex(const std::string &str)
{
    unsigned char c;
    char buf[3];
    std::string result = "";
    std::stringstream ss;
    ss << str;
    while (ss.read((char*)(&c), sizeof(c)))
    {
        sprintf(buf, "%02x", c);
        result += buf;
    }
    return result;
}

int main() {
    typedef libff::Fr<default_r1cs_gg_ppzksnark_pp> FieldT;
    libff::default_ec_pp::init_public_params();

    // Create protoboard
    protoboard<FieldT> pb;

    // Define variables
    pb_variable<FieldT> x;
    pb_variable<FieldT> y;
    pb_variable<FieldT> out;

    // Allocate variables to protoboard
    // The strings (like "x") are only for debugging purposes
    out.allocate(pb, "out");
    x.allocate(pb, "x");
    y.allocate(pb, "y");

    // This sets up the protoboard variables
    // so that the first one (out) represents the public
    // input and the rest is private input
    pb.set_input_sizes(1);

    // Add R1CS constraints to protoboard
    // x*y = out
    pb.add_r1cs_constraint(r1cs_constraint<FieldT>(x, y, out));

    // Trusted setup
    const r1cs_constraint_system<FieldT> constraint_system = pb.get_constraint_system();

    const r1cs_gg_ppzksnark_keypair<default_r1cs_gg_ppzksnark_pp> keypair = r1cs_gg_ppzksnark_generator<default_r1cs_gg_ppzksnark_pp>(
        constraint_system);

    // Add witness values
    pb.val(x) = 3;
    pb.val(y) = 8;
    pb.val(out) = 24;

    // Create proof
    r1cs_gg_ppzksnark_proof<default_r1cs_gg_ppzksnark_pp> proof = r1cs_gg_ppzksnark_prover<default_r1cs_gg_ppzksnark_pp>(
        keypair.pk, pb.primary_input(), pb.auxiliary_input());

    //保存公共输入
    std::ostringstream onstream;
    onstream << pb.primary_input();
    std::string str = onstream.str();
    std::string ret;
    std::fstream f_out("hw_inputs.raw", std::ios_base::out);
    ret = StrToHex(str);
    f_out << ret;
    f_out.close();

    //保存验证密钥
    f_out.clear();
    f_out.open("hw_vk.raw", std::ios_base::out);
    onstream.clear();
    std::ostringstream onstream3;
    onstream3 << keypair.vk;
    str = onstream3.str();
    ret = StrToHex(str);
    f_out << ret;
    f_out.close();

    //保存证明
    f_out.open("hw_proof.raw", std::ios_base::out);
    std::ostringstream onstream4;
    onstream.clear();
    onstream4 << proof;
    str = onstream4.str();
    ret = StrToHex(str);
    f_out << ret;
    f_out.close();
    return 0;
}

5、恒为链通过合约验证

查询智能合约。

请求:

{
    "method": "query_contract",
    "params": [{
        "address": "hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph",
        "info": "zksnark",
        "arg": "{"vk":"文件hw_vk.raw的内容","proof":"文件hw_proof.raw的内容","inputs":"文件hw_inputs.raw的内容"}"
    }]
}

1.4.2 snarkjs安装使用

1、安装snarkjs和circom

npm install -g snarkjs

git clone https://github.com/hwelltech/circom.git
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
cd circom
cargo build --release
cargo install --path circom

2、新建文件 circuit.circom

pragma circom 2.0.0;

template Multiplier() {
   signal input a;
   signal input b;
   signal output c;
   c <== a*b;
}

component main = Multiplier();

3、生成验证密钥、公有输入、证明的操作

通过 Circom 编译器来编译电路,该编译器将生成一个 wasm 和 rlcs 文件。

circom circuit.circom --r1cs --wasm --sym –c
使用这些选项,我们生成三种类型的文件:
--r1cs:生成 circuit.r1cs ( R1CS 电路的二进制格式的约束系统)
--wasm:生成 circuit _js 目录其中包含Wasm 代码(circuit.wasm) 和生成见证所需要的其他文件
--sym:生成 circuit.sym(以注释方式调试和打印约束系统所需的符号文件)

创建一个名为 input.json 的文件,其中包含以标准 json 格式编写的输入:

{"a": 3, "b": 11}

将生成 ẁitness.wtns 见证文件, 该文件以与 snarkjs兼容的二进制格式编码,

cd circuit_js/
node generate_witness.js circuit.wasm input.json witness.wtns

创建新的“tau 的权力”仪式

snarkjs powersoftau new bn128 12 pot12_0000.ptau -v

为仪式做出贡献

snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v

生成一个 .zkey文件,其中包含证明和验证密钥以及所有的贡献。

snarkjs groth16 setup ../circuit.r1cs pot12_final.ptau circuit_0000.zkey

为仪式做出贡献:

snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v

导出验证密钥

snarkjs zkey export verificationkey circuit_0001.zkey verification_key.json

生成与电路和见证人相关联的 zk-proof,生成 Groth16 证明并输出两个文件:proof.json: 它包含了证明,public.json: 它包含公共输入和输出的值。

snarkjs groth16 prove circuit_0001.zkey witness.wtns proof.json public.json

使用我们之前导出的文件 verify_key.json、proof.json 和 public.json 来检查证明是否有效。如果证明有效,则命令输出 OK。

snarkjs groth16 verify verification_key.json public.json proof.json

5、恒为链通过合约验证

查询智能合约。

请求:

{
    "method": "query_contract",
    "params": [{
        "address": "hQKUvRKomfGdY5fzXEiqUDqsMtUtxkpcph",
        "info": "zksnark",
        "arg": "{"vk":"文件verify_key.json的内容","proof":"文件proof.json的内容","inputs":"文件public.json的内容","algo":"snarkjs"}"
    }]
}

results matching ""

    No results matching ""