开发者如何利用 CKB-VM 进行智能合约开发

Nervos 底层公链 CKB 的虚拟机(CKB-VM)是基于 RISC-V 打造的区块链虚拟机。在前三节课中,咱们介绍了 CKB 虚拟机的设计理念及优点。那么,怎样才能利用 CKB-VM 更好的开发呢?本文是实现 CKB 背后脚手架-技术系列的最后一篇文章,CKB-VM 设计者肖雪洁会以三种不一样的方式展现 CKB-VM 的合约示例,它会帮助你更好的在 CKB-VM 上玩耍~

秘猿科技区块链小课堂第 24 期git


最简化智能合约示例

如下代码示例为能够在 CKB-VM 上运行的最简化智能合约:github

int main()
{
 return 0;
}

如下代码能够经过 GCC 编译:算法

riscv64-unknown-elf-gcc main.c -o main

CKB 的智能合约是一个遵循传统 Unix 调用方式的二进制文件。能够经过 argc/argv 输入参数,以 main 函数的返回值来表示输出结果。编程

若返回值为 0 表示合约调用成功,返回值为其它表示合约调用失败。安全

为了简化说明,咱们以 C 语言为例来实现示例中的合约。但实际上任何能够编译成 RISC-V 指令集的语言都可以直接用来开发 CKB 的智能合约ruby

  • 最新版的 Rust Stable 已经有 RISC-V
  • Go 语言的 RISC-V 支持也在开发中:
  • 对于更高级的语言,咱们能够直接将其 C 语言的实现编译为 RISC-V 二进制文件,并经过 「VM 中的 VM」 技术,在 CKB 上启用以这些语言编写的智能合约。举个例子,咱们能够将 mruby 编译为 RISC-V 二进制文件,来启用基于 Ruby 的合约开发。基于 MicroPython 的 Python 语言或基于 Duktape 的 JavaScript 语言也可使用一样的方式,在 CKB 上开发智能合约。

即便是编译为 EVM 字节码或 Bitcoin 脚本的智能合约也能够编译为 CKB-VM 字节码。固然咱们能够清晰地看到这些传统合约迁移到更有效字节码上的优点,而且,与使用较低级编程语言实现的智能合约相比,在 CKB 上的这些合约可能具备更大的运行开销(CPU cycles),可是对于一些不一样的应用场景来讲,这里节省下来的开发时间以及安全性优点,可能比在运行开销更有价值。less

CKB 还知足了哪些需求?

除了最简化的合约以外,CKB 也会提供一个系统库来知足以下需求:编程语言

  • 支持 libc 核心库;
  • 支持动态连接,以减小当前合约占用的空间,好比能够经过动态连接在 VM 中加载 system Cell 的方式来调用库;
  • 读取交易数据后,CKB-VM 中会有相似比特币的 SIGHASH 功能,以最大限度地提升合约的灵活性。

下图显示了基于前面系统库的 CKB 智能合约验证模型:ide

图片描述

如上图所示,CKB 的交易由 Input 和 Output 构成。虽然交易也可能包含 Deps(包含运行合约时所需的数据或代码的依赖项),但它们仅会影响智能合约的实现,而且会从交易模型中删除。函数

交易的每一个 Input 都会引用一个现有 Cell,一笔交易能够覆盖、销毁或生成一个 Cell。共识规则强制规定交易中全部 Output Cell 的容量总和不能超过全部 Input Cell 的容量总和。

验证智能合约的标准

CKB-VM 使用如下标准来验证智能合约:

  • 每一个 Input 中都会包含一个解锁脚本(Unlock Script),用于验证交易发起者是否可使用当前 Input 所引用到的 Cell。Unlock Script 中包含由交易发起者生成的签名,且 Unlock Script 一般会有指定的签名算法(如 SIGHASH-ALL-SHA3-SECP256K1)。CKB 会经过 VM 运行 Unlock Script 进行验证:智能合约会经过一个 API 来读取交易数据以实现 SIGHASH 相关的计算,从而提供最大的灵活性。
  • 每一个 Cell 中包含一个验证脚本,用于验证当前 Cell Data 是否知足先前指定的条件,例如咱们能够建立一个 Cell 来保存用户自定义代币(User Defined Token,简称 UDT)。在 Cell 建立完毕后,咱们须要验证 Input Cell 中全部 Token 总和是否大于或等于 Output Cell 中全部 Token 的总和,以确保交易中不会生成新的 Token。为加强安全性,CKB 的合约开发人员还能够利用特殊合约来确保建立 Cell 后,Cell 的验证脚本不会被修改。

Input Cell 验证示例

基于上述对 CKB-VM 安全模型的描述,咱们能够首先实现一个完整的 SIGHASH-ALL-SHA3-SECP256K1 合约,来验证所提供的签名以及验证提供签名的交易发起者是否可使用当前的 Cell:

// For simplicity, we are keeping pubkey in the contract, however this
// solution has a potential problem: even though many contracts might share
// the very same structure, keeping pubkey here will make each contract
// quite different, preventing common contract sharing. In CKB we will
// provide ways to share common contract while still enabling each user
// to embed their own pubkey.
char* PUBKEY = "this is a pubkey";
int main(int argc, char* argv[])
{
 // We need 2 arguments for this contract
 // * The first argument is contract name, this is for compatibility issue
 // * The second argument is signature for current contract input
 if (argc < 2) {
   return -1;
 }
// This function loads current transaction into VM memory, and returns the
 // pointer to serialized transaction data. Notice ckb_mmap might preprocess
 // the transaction a bit, such as removing signatures in all tx inputs to
 // avoid chicken-egg problem when signing signature.
 int length = 0;
 char* tx = ckb_mmap(CKB_TX, &length);
 if (tx == NULL) {
   return -2;
 }
// This function dynamically links sha3 library to current VM memory space
 void *sha3_handle = ckb_dlopen("sha3");
 void (*sha3_func)(const char*, int, char*) = ckb_dlsym("sha3_256");
// Here we run sha3 on all the transaction data, simulating a SIGHASH_ALL process,
 // a different contract might choose to deserialize and only hash certain part
 // of the transaction
 char hash[32];
 sha3_func(tx, length, hash);
// Now we load secp256k1 module.
 void *secp_handle = ckb_dlopen("secp256k1");
 int (*secp_verify_func)(const char*, int, const char*, int, const char*, int) =
     ckb_dlsym("secp256k1_verify");
int result = secp_verify_func(argv[1], strlen(argv[1]),
                               PUBKEY, strlen(PUBKEY),
                               hash, 32);
if (result == 1) {
   // Verification success, we are returning 0 to indicate contract succeeds
   return 0;
 } else {
   // Verification failure
   return -3;
 }
}

在此示例中,咱们首先将全部的交易数据读入 CKB-VM 中以获取交易数据的 SHA3 哈希,并将此交易数据的 SHA3 哈希、指定的公钥和交易发起者提供的签名提供给 secp256k1 模块,以验证合约中指定的公钥是否已对提供的交易数据进行了签名。

若是此验证成功,则交易发起者可使用当前 Input 引用的 Cell,合约成功执行。 若是此验证不成功,则合约执行和交易验证会失败。

用户自定义代币(UDT)示例

下面的示例中,演示了一个 Cell 验证脚本实现相似 ERC-20 用户自定义代币的过程。Cell 验证脚本也能够实现其余 UDT 功能,为简单起见,如下示例中仅包含 UDT 的转移功能:

int main(int argc, char* argv[]) {
 size_t input_cell_length;
 void* input_cell_data = ckb_mmap_cell(CKB_CELL_INPUT, 0, &input_cell_length);
size_t output_cell_length;
 void* output_cell_data = ckb_mmap_cell(CKB_CELL_OUTPUT, 0, &output_cell_length);
if (input_cell_data == NULL || output_cell_data == NULL) {
   return -1;
 }
void* udt_handle = ckb_dlopen("udt");
 data_t* (*udt_parse)(const char*, size_t) =
     ckb_dlsym(udt_handle, "udt_parse");
 int (*udt_transfer)(data_t *, const char*, const char*, int64_t) =
     ckb_dlsym(udt_handle, "udt_transfer");
data_t* input_cell = udt_parse(input_cell_data, input_cell_length);
 data_t* output_cell = udt_parse(output_cell_data, output_cell_length);
if (input_cell == NULL || output_cell == NULL) {
   return -2;
 }
ret = udt_transfer(input_cell, from, to, tokens);
 if (ret != 0) {
   return ret;
 }
int (*udt_compare)(const data_t *, const data_t *);
 if (udt_compare(input_cell, output_cell) != 0) {
   return -1;
 }
 return 0;
}

在这段代码中,首先咱们经过调用系统库读取了 Input 与 Output Cell 中的内容,而后咱们动态加载了 UDT 的实现,并使用转移方式对 Input 进行转换。

转换后,Input 与 Output Cell 中的内容应该彻底匹配,不然咱们获得的验证结果会是:当前交易不符合验证脚本中指定的条件,合约执行即为失败。

注意:以上示例仅用于展现 CKB-VM 的功能,并不表明此实现方式为 CKB 上 UDT 实现的最佳实践。

在 Ruby 中的 Unlock Script 示例

虽然上面的示例都是经过 C 语言来编写的,可是实际上,CKB-VM 上编写智能合约并不只限于用 C 语言。例如咱们能够将 mruby 这个针对嵌入式平台的 Ruby 实现编译为 RISC-V 二进制文件,并以它做为通用系统库,这样咱们就可使用 Ruby 在 CKB 上编写智能合约。Unlock Script 示例以下:

if ARGV.length < 2
 raise "Not enough arguments!"
end
tx = CKB.load_tx
sha3 = Sha3.new
sha3.update(tx["version"].to_s)
tx["deps"].each do |dep|
 sha3.update(dep["hash"])
 sha3.update(dep["index"].to_s)
end
tx["inputs"].each do |input|
 sha3.update(input["hash"])
 sha3.update(input["index"].to_s)
 sha3.update(input["unlock"]["version"].to_s)
 # First argument here is signature
 input["unlock"]["arguments"].drop(1).each do |argument|
   sha3.update(argument)
 end
end
tx["outputs"].each do |output|
 sha3.update(output["capacity"].to_s)
 sha3.update(output["lock"])
end
hash = sha3.final
pubkey = ARGV[0]
signature = ARGV[1]
unless Secp256k1.verify(pubkey, signature, hash)
 raise "Signature verification error!"
End

社区驱动的 CKB-VM

以上为 CKB-VM 上三种不一样方式的智能合约实现示例,它们也许会帮助你更好的在 CKB-VM 上玩耍。经过 CKB-VM 的设计,咱们的目标是创建一个围绕 CKB 的社区,该社区能够自由地发展和适应新技术的进步,而且能够最大限度地减小人工干预(例如硬分叉)。 咱们相信 CKB-VM 能够实现这一愿景。

注:CKB-VM 与 CKB 同样为开源项目,目前 CKB-VM 仍在开发过程当中,尽管 CKB-VM 的大部分设计已经敲定,但某些设计也可能会在未来因为你的加入而有新的进步。这篇文章是为了让咱们的社区更加了解 CKB-VM,这样人人均可以在里面更好的玩耍并作出贡献啦!

CKB-VM:https://github.com/nervosnetw...

相关文章
相关标签/搜索