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
即便是编译为 EVM 字节码或 Bitcoin 脚本的智能合约也能够编译为 CKB-VM 字节码。固然咱们能够清晰地看到这些传统合约迁移到更有效字节码上的优点,而且,与使用较低级编程语言实现的智能合约相比,在 CKB 上的这些合约可能具备更大的运行开销(CPU cycles),可是对于一些不一样的应用场景来讲,这里节省下来的开发时间以及安全性优点,可能比在运行开销更有价值。less
除了最简化的合约以外,CKB 也会提供一个系统库来知足以下需求:编程语言
下图显示了基于前面系统库的 CKB 智能合约验证模型:ide
如上图所示,CKB 的交易由 Input 和 Output 构成。虽然交易也可能包含 Deps(包含运行合约时所需的数据或代码的依赖项),但它们仅会影响智能合约的实现,而且会从交易模型中删除。函数
交易的每一个 Input 都会引用一个现有 Cell,一笔交易能够覆盖、销毁或生成一个 Cell。共识规则强制规定交易中全部 Output Cell 的容量总和不能超过全部 Input Cell 的容量总和。
CKB-VM 使用如下标准来验证智能合约:
基于上述对 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,合约成功执行。 若是此验证不成功,则合约执行和交易验证会失败。
下面的示例中,演示了一个 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 实现的最佳实践。
虽然上面的示例都是经过 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 的社区,该社区能够自由地发展和适应新技术的进步,而且能够最大限度地减小人工干预(例如硬分叉)。 咱们相信 CKB-VM 能够实现这一愿景。
注:CKB-VM 与 CKB 同样为开源项目,目前 CKB-VM 仍在开发过程当中,尽管 CKB-VM 的大部分设计已经敲定,但某些设计也可能会在未来因为你的加入而有新的进步。这篇文章是为了让咱们的社区更加了解 CKB-VM,这样人人均可以在里面更好的玩耍并作出贡献啦!