1 智能合约应用示例
通过本章节,您将学会如何在区块链上开发一个简单的智能合约。恒为区块链的智能合约采用LUA/JS语言编写。区块链节点中集成了LVM/V8,它们作为智能合约执行的虚拟机环境。
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"}"
}]
}