開發者如何利用 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...

相關文章
相關標籤/搜索