How Developers Use CKB-VM to Develop Intelligent Contracts

Time:2019-9-9

Nervos underlying public-chain CKB virtual machine (CKB-VM) is a block-chain virtual machine based on RISC-V. In the first three lessons, we introduced the design concept and advantages of CKB virtual machine. So, how can we make better use of CKB-VM? This is the last article in the Scaffolding-Technology Series for CKB. CKB-VM designer Xiao Xuejie will show examples of CKB-VM contracts in three different ways. It will help you play better on CKB-VM.

Secret Ape Science and Technology Block Chain Lesson 24


Simplified Smart Contract Example

The following code example is the simplest smart contract that can run on CKB-VM:

int main()
{
 return 0;
}

The following code can be compiled through GCC:

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

CKB’s intelligent contract is a binary file that follows the traditional Unix invocation. The output result can be represented by the return value of main function through argc/argv input parameters.

If the return value of 0 indicates that the contract call is successful, the return value of 0 indicates that the other contract call failed.

To simplify the explanation, we take C language as an example to implement the contract in the example. But in factAny language that can be compiled into RISC-V instruction set can be directly used to develop intelligent contracts for CKB.

  • The latest version of Rust Stable already has RISC-V
  • RISC-V support for the Go language is also under development:
  • For higher-level languages, we can directly compile their C language implementation into RISC-V binary files, and use “VM in VM” technology to enable intelligent contracts written in these languages on CKB. For example, we can compile mruby into RISC-V binaries to enable Ruby-based contract development. MicroPython-based Python language or Duktape-based JavaScript language can also use the same way to develop intelligent contracts on CKB.

Even intelligent contracts compiled into EVM bytecodes or Bitcoin scripts can be compiled into CKB-VM bytecodes. Of course, we can clearly see the advantages of migrating these traditional contracts to more efficient bytecodes, and these contracts on CKB may have greater CPU cycles than smart contracts implemented in lower-level programming languages, but for some different application scenarios, this section The savings in development time and security advantages may be more valuable than running costs.

What other needs does CKB meet?

In addition to the simplest contracts, CKB will also provide a system library to meet the following requirements:

  • Support libc core library;
  • Dynamic links are supported to reduce the space occupied by current contracts, such as the way to call libraries by loading system Cell in VM through dynamic links.
  • After reading the transaction data, the SIGHHASH function similar to Bitcoin will be implemented in CKB-VM to maximize contract flexibility.

The following figure shows the KB intelligent contract validation model based on the previous system library:

How Developers Use CKB-VM to Develop Intelligent Contracts

As shown in the figure above, the transaction of CKB consists of Input and Output. Although transactions may also contain Deps (including dependencies on data or code needed to run contracts), they only affect the implementation of smart contracts and are removed from the transaction model.

Each Input of a transaction refers to an existing Cell, and a transaction can overwrite, destroy, or generate a Cell. Consensus rules mandatory that the total capacity of all Output Cells in a transaction should not exceed the total capacity of all Input Cells.

Standards for Verifying Intelligent Contracts

CKB-VM uses the following criteria to validate smart contracts:

  • Each Input contains an Unlock Script to verify that the transaction initiator can use the Cell referenced by the current Input. Unlock Script contains signatures generated by the initiator of the transaction, and Unlock Script usually has a specified signature algorithm (such as SIGHASH-ALL-SHA3-SECP256K1). CKB runs Unlock Script through VM to verify that smart contracts provide maximum flexibility by reading transaction data through an API to implement SIGHASH-related calculations.
  • Each Cell contains a validation script to verify that the current Cell Data meets the previously specified conditions, such as creating a Cell to hold user-defined tokens (UDT). After the Cell is created, we need to verify that the total of all Tokens in Input Cell is greater than or equal to the total of all Tokens in Output Cell to ensure that no new Tokens are generated in the transaction. To enhance security, contract developers of CKB can also use special contracts to ensure that Cell validation scripts are not modified after Cell is created.

Input Cell validation example

Based on the above description of the CKB-VM security model, we can first implement a complete SIGHASH-ALL-SHA3-SECP256K1 contract to verify the signature provided and whether the transaction initiator who provides the signature can use the current 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;
 }
}

In this example, we first read all the transaction data into the SHA3 hash of the transaction data in CKB-VM to obtain the transaction data, and provide the SHA3 hash of the transaction data, the designated public key and the signature provided by the transaction initiator to the secp256k1 module to verify whether the specified public key in the contract has already provided the transaction data. Signed.

If this validation is successful, the transaction initiator can use the current Input reference Cell to successfully execute the contract. If this validation is unsuccessful, contract execution and transaction validation will fail.

User-defined token (UDT) example

In the following example, a Cell validation script is demonstrated to implement user-defined tokens similar to ERC-20. Cell validation scripts can also implement other UDT functions. For simplicity, the following examples contain only UDT transition functions:

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;
}

In this code, first we read the contents of Input and Output Cell by calling the system library, then we dynamically loaded the UDT implementation, and used the transfer mode to convert Input.

After conversion, the contents of Input and Output Cell should match perfectly, otherwise we will get the verification result that the current transaction does not meet the conditions specified in the verification script, and the contract execution will fail.

Note: The above examples are only used to demonstrate the functionality of CKB-VM, and do not represent a best practice for UDT implementations on CKB.

Unlock Script example in Ruby

Although the above examples are written in C language, in fact, writing smart contracts on CKB-VM is not limited to C language. For example, we can compile mruby, a Ruby implementation for embedded platforms, into RISC-V binary files and use it as a general system library, so that we can use Ruby to write intelligent contracts on CKB. An example of Unlock Script is as follows:

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

Community-driven CKB-VM

These are examples of three different ways of implementing smart contracts on CKB-VM, which may help you play better on CKB-VM. Through the design of CKB-VM, our goal is to build a community around CKB, which can freely develop and adapt to the progress of new technologies, and minimize manual intervention (such as hard bifurcation). We believe that CKB-VM can achieve this vision.

Note: CKB-VM is an open source project like CKB. At present, CKB-VM is still in the process of development. Although most of the designs of CKB-VM have been finalized, some designs may make new progress in the future due to your participation. This article is to let our community know more about CKB-VM, so that everyone can play better in it and contribute!

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