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,這樣人人均可以在裏面更好的玩耍並作出貢獻啦!