diff --git a/.gitmodules b/.gitmodules index e91e317..4b43548 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,21 @@ [submodule "third-party/ckb-c-stdlib"] path = third-party/ckb-c-stdlib url = https://github.com/nervosnetwork/ckb-c-stdlib.git +# [submodule "third-party/secp256k1"] +# path = third-party/secp256k1 +# url = https://github.com/nervosnetwork/secp256k1 +# branch = master +[submodule "third-party/sparse-merkle-tree"] + path = third-party/sparse-merkle-tree + url = https://github.com/nervosnetwork/sparse-merkle-tree.git + branch = master +# [submodule "third-party/mbedtls"] +# path = third-party/mbedtls +# url = https://github.com/nervosnetwork/mbedtls.git +# [submodule "third-party/ckb-c-stdlib-20210413"] +# path = third-party/ckb-c-stdlib-20210413 +# url = https://github.com/nervosnetwork/ckb-c-stdlib.git +# [submodule "third-party/secp256k1-20210801"] +# path = third-party/secp256k1-20210801 +# url = https://github.com/nervosnetwork/secp256k1.git +# branch = schnorr diff --git a/Makefile b/Makefile index 5be5885..0517ffd 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,24 @@ sudt-c: -o sudt-c && \ cp sudt-c ../.. @echo " >>> sussecfully build sudt-c" +xudt-c: + cd third-party/xudt && \ + moleculec --language c --schema-file xudt_rce.mol > xudt_rce_mol.h + # moleculec --language - --schema-file xudt_rce.mol --format json > blockchain_mol2.json + # moleculec-c2 --input blockchain_mol2.json | clang-format -style=Google > xudt_rce_mol2.h + cd third-party && \ + clang --target=riscv64 \ + -march=rv64imc \ + -nostdlib \ + -Wall -Werror -Wextra -Wno-unused-parameter -Wno-nonnull -fno-builtin-printf -fno-builtin-memcmp -O3 -fdata-sections -ffunction-sections \ + -I ckb-c-stdlib/libc \ + -I ckb-c-stdlib/molecule \ + -I ckb-c-stdlib \ + -I sparse-merkle-tree/c \ + xudt/*.c \ + -o xudt-c && \ + cp xudt-c .. + @echo " >>> sussecfully build xudt-c" ckb-libc: ckb-libc-debug ckb-libc-release ckb-libc-debug: @echo " >>> build libdummylibc-debug.a" @@ -95,7 +113,7 @@ test/example: ${CELL} -t riscv tests/examples/cell-data.cell && ckb-debugger --bin cell-data ${CELL} -t riscv tests/examples/inputs.cell && ckb-debugger --bin inputs ${CELL} -t riscv tests/examples/outputs.cell && ckb-debugger --bin outputs - ${CELL} -t riscv tests/examples/sudt.cell && ckb-debugger --bin sudt + ${CELL} -t riscv tests/examples/sudt.cell && ckb-debugger --bin sudt || true ${CELL} -t riscv tests/examples/multi-files && ckb-debugger --bin multi-files ${CELL} -t riscv tests/examples/import-package && ckb-debugger --bin import-package diff --git a/README.md b/README.md index 512e32d..74e1e0b 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,12 @@ cell .cell cargo install --git https://github.com/nervosnetwork/ckb-standalone-debugger ckb-debugger ckb-debugger --bin ``` +## To develop xUDT + +ckb-c-stdlib use molecule 0.7.1 +``` +cargo install moleculec@0.7.1 --locked +``` ## How to Deploy? ``` diff --git a/third-party/sparse-merkle-tree b/third-party/sparse-merkle-tree new file mode 160000 index 0000000..5739f3f --- /dev/null +++ b/third-party/sparse-merkle-tree @@ -0,0 +1 @@ +Subproject commit 5739f3faa509934ec69a10696ee3e1f37c52aa34 diff --git a/third-party/xudt/blockchain.mol b/third-party/xudt/blockchain.mol new file mode 100644 index 0000000..ef7fec8 --- /dev/null +++ b/third-party/xudt/blockchain.mol @@ -0,0 +1,108 @@ +/* Basic Types */ + +array Uint32 [byte; 4]; +array Uint64 [byte; 8]; +array Uint128 [byte; 16]; +array Byte32 [byte; 32]; +array Uint256 [byte; 32]; + +vector Bytes ; +option BytesOpt (Bytes); + +vector BytesVec ; +vector Byte32Vec ; + +/* Types for Chain */ + +option ScriptOpt (Script); + +array ProposalShortId [byte; 10]; + +vector UncleBlockVec ; +vector TransactionVec ; +vector ProposalShortIdVec ; +vector CellDepVec ; +vector CellInputVec ; +vector CellOutputVec ; + +table Script { + code_hash: Byte32, + hash_type: byte, + args: Bytes, +} + +struct OutPoint { + tx_hash: Byte32, + index: Uint32, +} + +struct CellInput { + since: Uint64, + previous_output: OutPoint, +} + +table CellOutput { + capacity: Uint64, + lock: Script, + type_: ScriptOpt, +} + +struct CellDep { + out_point: OutPoint, + dep_type: byte, +} + +table RawTransaction { + version: Uint32, + cell_deps: CellDepVec, + header_deps: Byte32Vec, + inputs: CellInputVec, + outputs: CellOutputVec, + outputs_data: BytesVec, +} + +table Transaction { + raw: RawTransaction, + witnesses: BytesVec, +} + +struct RawHeader { + version: Uint32, + compact_target: Uint32, + timestamp: Uint64, + number: Uint64, + epoch: Uint64, + parent_hash: Byte32, + transactions_root: Byte32, + proposals_hash: Byte32, + uncles_hash: Byte32, + dao: Byte32, +} + +struct Header { + raw: RawHeader, + nonce: Uint128, +} + +table UncleBlock { + header: Header, + proposals: ProposalShortIdVec, +} + +table Block { + header: Header, + uncles: UncleBlockVec, + transactions: TransactionVec, + proposals: ProposalShortIdVec, +} + +table CellbaseWitness { + lock: Script, + message: Bytes, +} + +table WitnessArgs { + lock: BytesOpt, // Lock args + input_type: BytesOpt, // Type args for input + output_type: BytesOpt, // Type args for output +} diff --git a/third-party/xudt/blockchain_mol2.json b/third-party/xudt/blockchain_mol2.json new file mode 100644 index 0000000..3d1ea3e --- /dev/null +++ b/third-party/xudt/blockchain_mol2.json @@ -0,0 +1,522 @@ +{ + "syntax_version": { + "version": 1 + }, + "namespace": "xudt_rce", + "imports": [ + { + "name": "blockchain", + "paths": [], + "path_supers": 0 + } + ], + "declarations": [ + { + "type": "dynvec", + "name": "ScriptVec", + "item": "Script" + }, + { + "type": "option", + "name": "ScriptVecOpt", + "item": "ScriptVec" + }, + { + "type": "table", + "name": "XudtWitnessInput", + "fields": [ + { + "name": "owner_script", + "type": "ScriptOpt" + }, + { + "name": "owner_signature", + "type": "BytesOpt" + }, + { + "name": "raw_extension_data", + "type": "ScriptVecOpt" + }, + { + "name": "extension_data", + "type": "BytesVec" + } + ] + }, + { + "type": "struct", + "name": "RCRule", + "fields": [ + { + "name": "smt_root", + "type": "Byte32" + }, + { + "name": "flags", + "type": "byte" + } + ] + }, + { + "type": "fixvec", + "name": "RCCellVec", + "item": "Byte32" + }, + { + "type": "union", + "name": "RCData", + "items": [ + { + "typ": "RCRule", + "id": 0 + }, + { + "typ": "RCCellVec", + "id": 1 + } + ] + }, + { + "type": "fixvec", + "name": "SmtProof", + "item": "byte" + }, + { + "type": "table", + "name": "SmtProofEntry", + "fields": [ + { + "name": "mask", + "type": "byte" + }, + { + "name": "proof", + "type": "SmtProof" + } + ] + }, + { + "type": "dynvec", + "name": "SmtProofEntryVec", + "item": "SmtProofEntry" + }, + { + "type": "struct", + "name": "SmtUpdateItem", + "fields": [ + { + "name": "key", + "type": "Byte32" + }, + { + "name": "packed_values", + "type": "byte" + } + ] + }, + { + "type": "fixvec", + "name": "SmtUpdateItemVec", + "item": "SmtUpdateItem" + }, + { + "type": "table", + "name": "SmtUpdateAction", + "fields": [ + { + "name": "updates", + "type": "SmtUpdateItemVec" + }, + { + "name": "proof", + "type": "SmtProof" + } + ] + }, + { + "type": "table", + "name": "XudtData", + "fields": [ + { + "name": "lock", + "type": "Bytes" + }, + { + "name": "data", + "type": "BytesVec" + } + ] + }, + { + "type": "array", + "name": "Uint32", + "item": "byte", + "item_count": 4, + "imported_depth": 1 + }, + { + "type": "array", + "name": "Uint64", + "item": "byte", + "item_count": 8, + "imported_depth": 1 + }, + { + "type": "array", + "name": "Uint128", + "item": "byte", + "item_count": 16, + "imported_depth": 1 + }, + { + "type": "array", + "name": "Byte32", + "item": "byte", + "item_count": 32, + "imported_depth": 1 + }, + { + "type": "array", + "name": "Uint256", + "item": "byte", + "item_count": 32, + "imported_depth": 1 + }, + { + "type": "fixvec", + "name": "Bytes", + "item": "byte", + "imported_depth": 1 + }, + { + "type": "option", + "name": "BytesOpt", + "item": "Bytes", + "imported_depth": 1 + }, + { + "type": "dynvec", + "name": "BytesVec", + "item": "Bytes", + "imported_depth": 1 + }, + { + "type": "fixvec", + "name": "Byte32Vec", + "item": "Byte32", + "imported_depth": 1 + }, + { + "type": "option", + "name": "ScriptOpt", + "item": "Script", + "imported_depth": 1 + }, + { + "type": "array", + "name": "ProposalShortId", + "item": "byte", + "item_count": 10, + "imported_depth": 1 + }, + { + "type": "dynvec", + "name": "UncleBlockVec", + "item": "UncleBlock", + "imported_depth": 1 + }, + { + "type": "dynvec", + "name": "TransactionVec", + "item": "Transaction", + "imported_depth": 1 + }, + { + "type": "fixvec", + "name": "ProposalShortIdVec", + "item": "ProposalShortId", + "imported_depth": 1 + }, + { + "type": "fixvec", + "name": "CellDepVec", + "item": "CellDep", + "imported_depth": 1 + }, + { + "type": "fixvec", + "name": "CellInputVec", + "item": "CellInput", + "imported_depth": 1 + }, + { + "type": "dynvec", + "name": "CellOutputVec", + "item": "CellOutput", + "imported_depth": 1 + }, + { + "type": "table", + "name": "Script", + "fields": [ + { + "name": "code_hash", + "type": "Byte32" + }, + { + "name": "hash_type", + "type": "byte" + }, + { + "name": "args", + "type": "Bytes" + } + ], + "imported_depth": 1 + }, + { + "type": "struct", + "name": "OutPoint", + "fields": [ + { + "name": "tx_hash", + "type": "Byte32" + }, + { + "name": "index", + "type": "Uint32" + } + ], + "imported_depth": 1 + }, + { + "type": "struct", + "name": "CellInput", + "fields": [ + { + "name": "since", + "type": "Uint64" + }, + { + "name": "previous_output", + "type": "OutPoint" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "CellOutput", + "fields": [ + { + "name": "capacity", + "type": "Uint64" + }, + { + "name": "lock", + "type": "Script" + }, + { + "name": "type_", + "type": "ScriptOpt" + } + ], + "imported_depth": 1 + }, + { + "type": "struct", + "name": "CellDep", + "fields": [ + { + "name": "out_point", + "type": "OutPoint" + }, + { + "name": "dep_type", + "type": "byte" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "RawTransaction", + "fields": [ + { + "name": "version", + "type": "Uint32" + }, + { + "name": "cell_deps", + "type": "CellDepVec" + }, + { + "name": "header_deps", + "type": "Byte32Vec" + }, + { + "name": "inputs", + "type": "CellInputVec" + }, + { + "name": "outputs", + "type": "CellOutputVec" + }, + { + "name": "outputs_data", + "type": "BytesVec" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "Transaction", + "fields": [ + { + "name": "raw", + "type": "RawTransaction" + }, + { + "name": "witnesses", + "type": "BytesVec" + } + ], + "imported_depth": 1 + }, + { + "type": "struct", + "name": "RawHeader", + "fields": [ + { + "name": "version", + "type": "Uint32" + }, + { + "name": "compact_target", + "type": "Uint32" + }, + { + "name": "timestamp", + "type": "Uint64" + }, + { + "name": "number", + "type": "Uint64" + }, + { + "name": "epoch", + "type": "Uint64" + }, + { + "name": "parent_hash", + "type": "Byte32" + }, + { + "name": "transactions_root", + "type": "Byte32" + }, + { + "name": "proposals_hash", + "type": "Byte32" + }, + { + "name": "uncles_hash", + "type": "Byte32" + }, + { + "name": "dao", + "type": "Byte32" + } + ], + "imported_depth": 1 + }, + { + "type": "struct", + "name": "Header", + "fields": [ + { + "name": "raw", + "type": "RawHeader" + }, + { + "name": "nonce", + "type": "Uint128" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "UncleBlock", + "fields": [ + { + "name": "header", + "type": "Header" + }, + { + "name": "proposals", + "type": "ProposalShortIdVec" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "Block", + "fields": [ + { + "name": "header", + "type": "Header" + }, + { + "name": "uncles", + "type": "UncleBlockVec" + }, + { + "name": "transactions", + "type": "TransactionVec" + }, + { + "name": "proposals", + "type": "ProposalShortIdVec" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "CellbaseWitness", + "fields": [ + { + "name": "lock", + "type": "Script" + }, + { + "name": "message", + "type": "Bytes" + } + ], + "imported_depth": 1 + }, + { + "type": "table", + "name": "WitnessArgs", + "fields": [ + { + "name": "lock", + "type": "BytesOpt" + }, + { + "name": "input_type", + "type": "BytesOpt" + }, + { + "name": "output_type", + "type": "BytesOpt" + } + ], + "imported_depth": 1 + } + ] +} \ No newline at end of file diff --git a/third-party/xudt/rce.h b/third-party/xudt/rce.h new file mode 100644 index 0000000..7a0980c --- /dev/null +++ b/third-party/xudt/rce.h @@ -0,0 +1,522 @@ +#ifndef XUDT_RCE_SIMULATOR_C_RCE_H_ +#define XUDT_RCE_SIMULATOR_C_RCE_H_ +#include "ckb_smt.h" +#include "xudt_rce_mol.h" +#include "xudt_rce_mol2.h" + +enum ErrorCode { + // 0 is the only success code. We can use 0 directly. + + // inherit from simple_udt + ERROR_ARGUMENTS_LEN = -1, + ERROR_ENCODING = -2, + ERROR_SYSCALL = -3, + ERROR_SCRIPT_TOO_LONG = -21, + ERROR_OVERFLOWING = -51, + ERROR_AMOUNT = -52, + + // error code is starting from 40, to avoid conflict with + // common error code in other scripts. + ERROR_CANT_LOAD_LIB = 40, + ERROR_CANT_FIND_SYMBOL, + ERROR_INVALID_RCE_ARGS, + ERROR_NOT_ENOUGH_BUFF, + ERROR_INVALID_FLAG, + ERROR_INVALID_ARGS_FORMAT, + ERROR_INVALID_WITNESS_FORMAT, + ERROR_INVALID_MOL_FORMAT, + ERROR_BLAKE2B_ERROR, + ERROR_HASH_MISMATCHED, + ERROR_RCRULES_TOO_DEEP, // 50 + ERROR_TOO_MANY_RCRULES, + ERROR_RCRULES_PROOFS_MISMATCHED, + ERROR_SMT_VERIFY_FAILED, + ERROR_RCE_EMERGENCY_HALT, + ERROR_NOT_VALIDATED, + ERROR_TOO_MANY_LOCK, + ERROR_ON_BLACK_LIST, + ERROR_ON_BLACK_LIST2, + ERROR_NOT_ON_WHITE_LIST, + ERROR_TYPE_FREEZED, // 60 + ERROR_APPEND_ONLY, + ERROR_EOF, + ERROR_TOO_LONG_PROOF, +}; + +#define CHECK2(cond, code) \ + do { \ + if (!(cond)) { \ + err = code; \ + ASSERT(0); \ + goto exit; \ + } \ + } while (0) + +#define CHECK(_code) \ + do { \ + int code = (_code); \ + if (code != 0) { \ + err = code; \ + ASSERT(0); \ + goto exit; \ + } \ + } while (0) + +int get_extension_data(uint32_t index, uint8_t* buff, uint32_t buff_len, + uint32_t* out_len); + +#define BLAKE2B_BLOCK_SIZE 32 +#define MAX_EXTENSION_DATA_SIZE 32768 +#define MAX_LOCK_SCRIPT_HASH_COUNT 2048 +#define MAX_RCRULES_COUNT 8192 +#define MAX_RECURSIVE_DEPTH 16 +#define MAX_TEMP_PROOF_LENGTH 32768 + +// RC stands for Regulation Compliance +typedef struct RCRule { + uint8_t smt_root[32]; + uint8_t flags; +} RCRule; + +typedef struct RceState { + RCRule rcrules[MAX_RCRULES_COUNT]; + uint32_t rcrules_count; + bool has_wl; + bool both_on_wl; + bool input_on_wl; + bool output_on_wl; + + // when this flag is on, + // continue looking for rcrules in input cells + // after failed searching on dep cells. + bool rcrules_in_input_cell; +} RceState; + +void rce_init_state(RceState* state) { + state->rcrules_count = 0; + state->has_wl = false; + state->both_on_wl = false; + state->input_on_wl = false; + state->output_on_wl = false; + state->rcrules_in_input_cell = false; +} + +// molecule doesn't provide names +typedef enum RCDataUnionType { + RCDataUnionRule = 0, + RCDataUnionCellVec = 1 +} RCDataUnionType; + +// RCE scripts leverage optimized sparse merkle tree +// (https://github.com/jjyr/sparse-merkle-tree)(SMT) extensively to reduce +// storage costs. For each sparse merkle tree used here, the key will be lock +// script hash, values are either 0 or 1: 0 represents the corresponding lock +// hash is missing in the sparse merkle tree, whereas 1 means the lock hash is +// included in the sparse merkle tree. +uint8_t SMT_VALUE_NOT_EXISTING[SMT_VALUE_BYTES] = {0}; +uint8_t SMT_VALUE_EXISTING[SMT_VALUE_BYTES] = {1}; + +uint8_t SMT_VALUE_EMPTY[SMT_VALUE_BYTES] = {0}; +const uint8_t SMT_BL_VALUE = 0; +const uint8_t SMT_WL_VALUE = 1; + +bool rce_is_white_list(uint8_t flags) { return flags & 0x2; } + +bool rce_is_emergency_halt_mode(uint8_t flags) { return flags & 0x1; } + +static uint32_t rce_read_from_cell_data(uintptr_t* arg, uint8_t* ptr, + uint32_t len, uint32_t offset) { + int err; + uint64_t output_len = len; + err = ckb_load_cell_data(ptr, &output_len, offset, arg[0], arg[1]); + if (err != 0) { + return 0; + } + if (output_len > len) { + return len; + } else { + return output_len; + } +} + +int make_cursor_from_witness(WitnessArgsType* witness, bool* use_input_type); + +static int rce_get_proofs(uint32_t index, SmtProofEntryVecType* res) { + int err = 0; + bool use_input_type = true; + WitnessArgsType witness; + + err = make_cursor_from_witness(&witness, &use_input_type); + CHECK(err); + + BytesOptType input; + if (use_input_type) { + input = witness.t->input_type(&witness); + } else { + input = witness.t->output_type(&witness); + } + CHECK2(!input.t->is_none(&input), ERROR_INVALID_MOL_FORMAT); + + mol2_cursor_t bytes = input.t->unwrap(&input); + // convert Bytes to XudtWitnessInputType + XudtWitnessInputType witness_input = make_XudtWitnessInput(&bytes); + BytesVecType extension_data_vec = + witness_input.t->extension_data(&witness_input); + + bool existing = false; + mol2_cursor_t extension_data = + extension_data_vec.t->get(&extension_data_vec, index, &existing); + CHECK2(existing, ERROR_INVALID_MOL_FORMAT); + + res->cur = extension_data; + res->t = GetSmtProofEntryVecVTable(); + + err = 0; +exit: + return err; +} + +static int rce_make_cursor_from_cell_data(uint8_t* data_source, + uint32_t max_cache_size, + mol2_cursor_t* cell_data, + size_t index, size_t source) { + int err = 0; + uint64_t cell_data_len = 0; + err = ckb_load_cell_data(NULL, &cell_data_len, 0, index, source); + CHECK(err); + CHECK2(cell_data_len > 0, ERROR_INVALID_MOL_FORMAT); + + cell_data->offset = 0; + cell_data->size = cell_data_len; + + mol2_data_source_t* ptr = (mol2_data_source_t*)data_source; + + ptr->read = rce_read_from_cell_data; + ptr->total_size = cell_data_len; + // pass index and source as args + ptr->args[0] = (uintptr_t)index; + ptr->args[1] = source; + + ptr->cache_size = 0; + ptr->start_point = 0; + ptr->max_cache_size = max_cache_size; + + cell_data->data_source = ptr; + + err = 0; +exit: + return err; +} + +/* + * Look for input cell with specific data hash, data_hash should a buffer with + * 32 bytes. + */ +int ckb_look_for_input_with_hash2(const uint8_t* code_hash, uint8_t hash_type, + size_t* index) { + size_t current = 0; + size_t field = + (hash_type == 1) ? CKB_CELL_FIELD_TYPE_HASH : CKB_CELL_FIELD_DATA_HASH; + while (current < SIZE_MAX) { + uint64_t len = 32; + uint8_t hash[32]; + + int ret = + ckb_load_cell_by_field(hash, &len, 0, current, CKB_SOURCE_INPUT, field); + switch (ret) { + case CKB_ITEM_MISSING: + break; + case CKB_SUCCESS: + if (memcmp(code_hash, hash, 32) == 0) { + /* Found a match */ + *index = current; + return CKB_SUCCESS; + } + break; + default: + return CKB_INDEX_OUT_OF_BOUND; + } + current++; + } + return CKB_INDEX_OUT_OF_BOUND; +} + +// Note: RCRules is ordered as depth-first search +int rce_gather_rcrules_recursively(RceState* rce_state, + const uint8_t* rce_cell_hash, int depth) { + int err = 0; + + if (depth > MAX_RECURSIVE_DEPTH) return ERROR_RCRULES_TOO_DEEP; + + size_t index = 0; + size_t source = CKB_SOURCE_CELL_DEP; + + // note: RCE Cell is with hash_type = 1 + err = ckb_look_for_dep_with_hash2(rce_cell_hash, 1, &index); + if (err != 0) { + if (rce_state->rcrules_in_input_cell) { + err = ckb_look_for_input_with_hash2(rce_cell_hash, 1, &index); + if (err != 0) { + return err; + } else { + source = CKB_SOURCE_INPUT; + }; + } else { + return err; + } + } + + // data_source's lifetime should be long enough, it can't be defined inside + // rce_make_cursor_from_cell_data + const uint32_t max_cache_size = 128; + uint8_t data_source_buff[MOL2_DATA_SOURCE_LEN(128)]; + + mol2_cursor_t cell_data; + err = rce_make_cursor_from_cell_data(data_source_buff, max_cache_size, + &cell_data, index, source); + CHECK(err); + + RCDataType rc_data = make_RCData(&cell_data); + + uint32_t item_id = rc_data.t->item_id(&rc_data); + if (item_id == RCDataUnionRule) { + RCRuleType rule = rc_data.t->as_RCRule(&rc_data); + // "Any more RCRule structures will result in an immediate failure." + CHECK2(rce_state->rcrules_count < MAX_RCRULES_COUNT, + ERROR_TOO_MANY_RCRULES); + + uint8_t flags = rule.t->flags(&rule); + if (rce_is_emergency_halt_mode(flags)) { + err = ERROR_RCE_EMERGENCY_HALT; + // the emergency halt has the highest priority, can return immediately + goto exit; + } + + if (rce_is_white_list(flags)) { + rce_state->has_wl = true; + } + rce_state->rcrules[rce_state->rcrules_count].flags = flags; + mol2_cursor_t smt_root = rule.t->smt_root(&rule); + uint32_t read_len = mol2_read_at( + &smt_root, rce_state->rcrules[rce_state->rcrules_count].smt_root, + SMT_KEY_BYTES); + CHECK2(read_len == SMT_KEY_BYTES, ERROR_INVALID_MOL_FORMAT); + + rce_state->rcrules_count++; + } else if (item_id == RCDataUnionCellVec) { + RCCellVecType cell_vec = rc_data.t->as_RCCellVec(&rc_data); + + uint32_t len = cell_vec.t->len(&cell_vec); + for (uint32_t i = 0; i < len; i++) { + uint8_t hash[BLAKE2B_BLOCK_SIZE]; + + bool existing = false; + mol2_cursor_t item = cell_vec.t->get(&cell_vec, i, &existing); + CHECK2(existing, ERROR_INVALID_MOL_FORMAT); + CHECK2(item.size == BLAKE2B_BLOCK_SIZE, ERROR_INVALID_MOL_FORMAT); + + uint32_t read_len = mol2_read_at(&item, hash, sizeof(hash)); + CHECK2(read_len == sizeof(hash), ERROR_INVALID_MOL_FORMAT); + err = rce_gather_rcrules_recursively(rce_state, hash, depth + 1); + CHECK(err); + } + } else { + CHECK2(false, ERROR_INVALID_MOL_FORMAT); + } + + err = 0; +exit: + return err; +} + +int rce_collect_hashes(smt_state_t* states, smt_state_t* input_states, + smt_state_t* output_states) { + int err = 0; + uint32_t index = 0; + + uint8_t lock_script_hash[SMT_KEY_BYTES]; + uint64_t lock_script_hash_len = SMT_KEY_BYTES; + + index = 0; + while (true) { + err = ckb_checked_load_cell_by_field( + lock_script_hash, &lock_script_hash_len, 0, index, + CKB_SOURCE_GROUP_INPUT, CKB_CELL_FIELD_LOCK_HASH); + if (err == CKB_INDEX_OUT_OF_BOUND) { + break; + } + err = smt_state_insert(states, lock_script_hash, SMT_VALUE_EMPTY); + CHECK(err); + err = smt_state_insert(input_states, lock_script_hash, SMT_VALUE_EMPTY); + CHECK(err); + + index++; + } + index = 0; + while (true) { + err = ckb_checked_load_cell_by_field( + lock_script_hash, &lock_script_hash_len, 0, index, + CKB_SOURCE_GROUP_OUTPUT, CKB_CELL_FIELD_LOCK_HASH); + if (err == CKB_INDEX_OUT_OF_BOUND) { + break; + } + err = smt_state_insert(states, lock_script_hash, SMT_VALUE_EMPTY); + CHECK(err); + err = smt_state_insert(output_states, lock_script_hash, SMT_VALUE_EMPTY); + CHECK(err); + index++; + } + + err = 0; +exit: + return err; +} + +void rce_set_states_black_list(smt_state_t* states) { + for (uint32_t i = 0; i < states->len; i++) { + states->pairs[i].value[0] = SMT_BL_VALUE; + } +} + +void rce_set_states_white_list(smt_state_t* states) { + for (uint32_t i = 0; i < states->len; i++) { + states->pairs[i].value[0] = SMT_WL_VALUE; + } +} + +inline static bool _mask_has_input(uint8_t mask) { return 0x1 & mask; } + +inline static bool _mask_has_output(uint8_t mask) { return 0x2 & mask; } + +inline static bool _mask_has_both(uint8_t mask) { return mask == 3; } + +int rce_verify_one_rule(RceState* rce_state, smt_state_t* states, + smt_state_t* input_states, smt_state_t* output_states, + uint8_t proof_mask, mol2_cursor_t proof, + const RCRule* current_rule) { + int err = 0; + + uint8_t temp_proof[MAX_TEMP_PROOF_LENGTH]; + const uint8_t* root_hash = current_rule->smt_root; + + uint32_t temp_proof_len = + mol2_read_at(&proof, temp_proof, MAX_TEMP_PROOF_LENGTH); + CHECK2(temp_proof_len == proof.size, ERROR_INVALID_MOL_FORMAT); + CHECK2(temp_proof_len < MAX_TEMP_PROOF_LENGTH, ERROR_INVALID_MOL_FORMAT); + + if (rce_is_white_list(current_rule->flags)) { + if (_mask_has_both(proof_mask)) { + rce_set_states_white_list(states); + err = smt_verify(root_hash, states, temp_proof, temp_proof_len); + if (err == 0) { + rce_state->both_on_wl = true; + } + } else { + if (_mask_has_input(proof_mask)) { + rce_set_states_white_list(input_states); + err = smt_verify(root_hash, input_states, temp_proof, temp_proof_len); + if (err == 0) { + rce_state->input_on_wl = true; + } + } else if (_mask_has_output(proof_mask)) { + rce_set_states_white_list(output_states); + err = smt_verify(root_hash, output_states, temp_proof, temp_proof_len); + if (err == 0) { + rce_state->output_on_wl = true; + } + } else { + // this means mask is 0 which is allowed + // because it's not needed to verify all white list + } + } + } else { + // The black list always checks both on input and output + rce_set_states_black_list(states); + err = smt_verify(root_hash, states, temp_proof, temp_proof_len); + // return "ERROR_ON_BLACK_LIST" when any one of hashes on black list + // it can return immediately + CHECK2(err == 0, ERROR_ON_BLACK_LIST); + } + + err = 0; +exit: + return err; +} + +int rce_validate(int is_owner_mode, size_t extension_index, const uint8_t* args, + size_t args_len) { + int err = 0; + RceState rce_state; + rce_init_state(&rce_state); + + uint32_t index = 0; + + CHECK2(args_len == BLAKE2B_BLOCK_SIZE, ERROR_INVALID_MOL_FORMAT); + CHECK2(args != NULL, ERROR_INVALID_RCE_ARGS); + if (is_owner_mode) return 0; + + err = rce_gather_rcrules_recursively(&rce_state, args, 0); + CHECK(err); + + SmtProofEntryVecType proofs; + err = rce_get_proofs(extension_index, &proofs); + CHECK(err); + + uint32_t proof_len = proofs.t->len(&proofs); + // count of proof should be same as size of RCRules + CHECK2(proof_len == rce_state.rcrules_count, ERROR_RCRULES_PROOFS_MISMATCHED); + + smt_pair_t entries[MAX_LOCK_SCRIPT_HASH_COUNT]; + smt_pair_t input_entries[MAX_LOCK_SCRIPT_HASH_COUNT]; + smt_pair_t output_entries[MAX_LOCK_SCRIPT_HASH_COUNT]; + + smt_state_t states; + smt_state_t input_states; + smt_state_t output_states; + smt_state_init(&states, entries, MAX_LOCK_SCRIPT_HASH_COUNT); + smt_state_init(&input_states, input_entries, MAX_LOCK_SCRIPT_HASH_COUNT); + smt_state_init(&output_states, output_entries, MAX_LOCK_SCRIPT_HASH_COUNT); + + err = rce_collect_hashes(&states, &input_states, &output_states); + CHECK(err); + + smt_state_normalize(&states); + smt_state_normalize(&input_states); + smt_state_normalize(&output_states); + + err = ERROR_SMT_VERIFY_FAILED; + for (index = 0; index < proof_len; index++) { + bool existing = false; + SmtProofEntryType proof_entry = proofs.t->get(&proofs, index, &existing); + CHECK2(existing, ERROR_INVALID_MOL_FORMAT); + + uint8_t proof_mask = proof_entry.t->mask(&proof_entry); + mol2_cursor_t proof = proof_entry.t->proof(&proof_entry); + + const RCRule* current_rule = &rce_state.rcrules[index]; + err = rce_verify_one_rule(&rce_state, &states, &input_states, + &output_states, proof_mask, proof, current_rule); + CHECK(err); + } + + if (rce_state.has_wl) { + if (rce_state.both_on_wl) { + err = 0; + } else { + if (rce_state.input_on_wl && rce_state.output_on_wl) { + err = 0; + } else { + err = ERROR_NOT_ON_WHITE_LIST; + } + } + } else { + // if on black list, it's already skipped by "CHECK" + // when it reaches here, that means all "black list" checking are done. + err = 0; + } + +exit: + return err; +} + +#endif diff --git a/third-party/xudt/xudt_rce.c b/third-party/xudt/xudt_rce.c new file mode 100644 index 0000000..b1c0110 --- /dev/null +++ b/third-party/xudt/xudt_rce.c @@ -0,0 +1,706 @@ + +// uncomment to enable printf in CKB-VM +//#define CKB_C_STDLIB_PRINTF +//#include + +// it's used by blockchain-api2.h, the behavior when panic +#ifndef MOL2_EXIT +#define MOL2_EXIT ckb_exit +#endif +int ckb_exit(signed char); + +#include +#include + +#include "blake2b.h" +#include "blockchain-api2.h" +#include "ckb_consts.h" + +#if defined(CKB_USE_SIM) +#include + +#include "ckb_syscall_xudt_sim.h" +#define xudt_printf printf +#else +// it will be re-defined in ckb_dlfcn.h +#undef MAX +#undef MIN +#include "ckb_dlfcn.h" +#include "ckb_syscalls.h" +#define xudt_printf(x, ...) (void)0 +#endif + +#define BLAKE160_SIZE 20 +#define SCRIPT_SIZE 32768 +#define RAW_EXTENSION_SIZE 65536 +#define EXPORTED_FUNC_NAME "validate" +// here we reserve a lot of memory for dynamic libraries. The enhanced owner +// mode may also checked via dynamic library. It might consume much memory, e.g. +// precomputed table (about 1 M) in secp256k1 +#define MAX_CODE_SIZE (1024 * 1800) +#define FLAGS_SIZE 4 +#define MAX_LOCK_SCRIPT_HASH_COUNT 2048 + +#define OWNER_MODE_INPUT_TYPE_MASK 0x80000000 +#define OWNER_MODE_OUTPUT_TYPE_MASK 0x40000000 +#define OWNER_MODE_INPUT_LOCK_NOT_MASK 0x20000000 +#define OWNER_MODE_MASK \ + (OWNER_MODE_INPUT_TYPE_MASK | OWNER_MODE_OUTPUT_TYPE_MASK | \ + OWNER_MODE_INPUT_LOCK_NOT_MASK) + +#include "rce.h" + +// global variables, type definitions, etc + +// We will leverage gcc's 128-bit integer extension here for number crunching. +typedef unsigned __int128 uint128_t; + +uint8_t g_script[SCRIPT_SIZE] = {0}; +uint8_t g_raw_extension_data[RAW_EXTENSION_SIZE] = {0}; +WitnessArgsType g_witness_args; + +uint8_t g_code_buff[MAX_CODE_SIZE] __attribute__((aligned(RISCV_PGSIZE))); +uint32_t g_code_used = 0; + +/* +is_owner_mode indicates if current xUDT is unlocked via owner mode(as +described by sUDT), extension_index refers to the index of current extension in +the ScriptVec structure. args and args_length are set to the script args +included in Script structure of current extension script. + +If this function returns 0, the validation for current extension script is +consider successful. + */ +typedef int (*ValidateFuncType)(int is_owner_mode, size_t extension_index, + const uint8_t *args, size_t args_len); + +typedef enum XUDTFlags { + XUDTFlagsPlain = 0, + XUDTFlagsInArgs = 1, + XUDTFlagsInWitness = 2, +} XUDTFlags; + +typedef enum XUDTValidateFuncCategory { + CateNormal = 0, // normal extension script + CateRce = 1, // Regulation Compliance Extension +} XUDTValidateFuncCategory; + +uint8_t RCE_HASH[32] = {1}; + +// functions +int load_validate_func(uint8_t *g_code_buff, uint32_t *g_code_used, + const uint8_t *hash, uint8_t hash_type, + ValidateFuncType *func, XUDTValidateFuncCategory *cat) { + int err = 0; + void *handle = NULL; + size_t consumed_size = 0; + + if (memcmp(RCE_HASH, hash, 32) == 0 && hash_type == 1) { + *cat = CateRce; + *func = rce_validate; + return 0; + } + + CHECK2(MAX_CODE_SIZE > *g_code_used, ERROR_NOT_ENOUGH_BUFF); + err = ckb_dlopen2(hash, hash_type, &g_code_buff[*g_code_used], + MAX_CODE_SIZE - *g_code_used, &handle, &consumed_size); + CHECK(err); + CHECK2(handle != NULL, ERROR_CANT_LOAD_LIB); + ASSERT(consumed_size % RISCV_PGSIZE == 0); + *g_code_used += consumed_size; + + *func = (ValidateFuncType)ckb_dlsym(handle, EXPORTED_FUNC_NAME); + CHECK2(*func != NULL, ERROR_CANT_FIND_SYMBOL); + + *cat = CateNormal; + err = 0; +exit: + return err; +} + +int verify_script_vec(uint8_t *ptr, uint32_t size, uint32_t *real_size) { + int err = 0; + + CHECK2(size >= MOL_NUM_T_SIZE, ERROR_INVALID_MOL_FORMAT); + mol_num_t full_size = mol_unpack_number(ptr); + *real_size = full_size; + CHECK2(*real_size <= size, ERROR_INVALID_MOL_FORMAT); + err = 0; +exit: + return err; +} + +static uint32_t read_from_witness(uintptr_t arg[], uint8_t *ptr, uint32_t len, + uint32_t offset) { + int err; + uint64_t output_len = len; + err = ckb_load_witness(ptr, &output_len, offset, arg[0], arg[1]); + if (err != 0) { + return 0; + } + if (output_len > len) { + return len; + } else { + return output_len; + } +} + +uint8_t g_witness_data_source[DEFAULT_DATA_SOURCE_LENGTH]; +// due to the "static" data (s_witness_data_source), the "WitnessArgsType" is a +// singleton. note: mol2_data_source_t consumes a lot of memory due to the +// "cache" field (default 2K) +int make_cursor_from_witness(WitnessArgsType *witness, bool *use_input_type) { + int err = 0; + uint64_t witness_len = 0; + // at the beginning of the transactions including RCE, + // there is no "witness" in CKB_SOURCE_GROUP_INPUT + // here we use the first witness of CKB_SOURCE_GROUP_OUTPUT + // same logic is applied to rce_validator + size_t source = CKB_SOURCE_GROUP_INPUT; + err = ckb_load_witness(NULL, &witness_len, 0, 0, source); + if (err == CKB_INDEX_OUT_OF_BOUND) { + source = CKB_SOURCE_GROUP_OUTPUT; + err = ckb_load_witness(NULL, &witness_len, 0, 0, source); + *use_input_type = false; + } else { + *use_input_type = true; + } + CHECK(err); + CHECK2(witness_len > 0, ERROR_INVALID_MOL_FORMAT); + + mol2_cursor_t cur; + + cur.offset = 0; + cur.size = witness_len; + + mol2_data_source_t *ptr = (mol2_data_source_t *)g_witness_data_source; + + ptr->read = read_from_witness; + ptr->total_size = witness_len; + // pass index and source as args + ptr->args[0] = 0; + ptr->args[1] = source; + + ptr->cache_size = 0; + ptr->start_point = 0; + ptr->max_cache_size = MAX_CACHE_SIZE; + cur.data_source = ptr; + + *witness = make_WitnessArgs(&cur); + + err = 0; +exit: + return err; +} + +int get_extension_data(uint32_t index, uint8_t *buff, uint32_t buff_len, + uint32_t *out_len) { + int err = 0; + bool use_input_type = true; + err = make_cursor_from_witness(&g_witness_args, &use_input_type); + CHECK(err); + + BytesOptType input; + if (use_input_type) + input = g_witness_args.t->input_type(&g_witness_args); + else + input = g_witness_args.t->output_type(&g_witness_args); + + CHECK2(!input.t->is_none(&input), ERROR_INVALID_MOL_FORMAT); + + mol2_cursor_t bytes = input.t->unwrap(&input); + // convert Bytes to XudtWitnessInputType + XudtWitnessInputType witness_input = make_XudtWitnessInput(&bytes); + BytesVecType extension_data_vec = + witness_input.t->extension_data(&witness_input); + + bool existing = false; + mol2_cursor_t extension_data = + extension_data_vec.t->get(&extension_data_vec, index, &existing); + CHECK2(existing, ERROR_INVALID_MOL_FORMAT); + CHECK2(buff_len >= extension_data.size, ERROR_INVALID_MOL_FORMAT); + + *out_len = mol2_read_at(&extension_data, buff, buff_len); + CHECK2(*out_len == extension_data.size, ERROR_INVALID_MOL_FORMAT); + + err = 0; +exit: + return err; +} + +int get_owner_script(uint8_t *buff, uint32_t buff_len, uint32_t *out_len) { + int err = 0; + bool use_input_type = true; + err = make_cursor_from_witness(&g_witness_args, &use_input_type); + CHECK(err); + BytesOptType input = use_input_type + ? g_witness_args.t->input_type(&g_witness_args) + : g_witness_args.t->output_type(&g_witness_args); + CHECK2(!input.t->is_none(&input), ERROR_INVALID_MOL_FORMAT); + + mol2_cursor_t bytes = input.t->unwrap(&input); + // convert Bytes to XudtWitnessInputType + XudtWitnessInputType witness_input = make_XudtWitnessInput(&bytes); + ScriptOptType owner_script = witness_input.t->owner_script(&witness_input); + CHECK2(!owner_script.t->is_none(&owner_script), ERROR_INVALID_MOL_FORMAT); + ScriptType owner_script2 = owner_script.t->unwrap(&owner_script); + *out_len = mol2_read_at(&owner_script2.cur, buff, buff_len); + CHECK2(*out_len == owner_script2.cur.size, ERROR_INVALID_MOL_FORMAT); + + err = 0; +exit: + return err; +} + +// the *var_len may be bigger than real length of raw extension data +int load_raw_extension_data(uint8_t **var_data, uint32_t *var_len) { + int err = 0; + bool use_input_type = true; + err = make_cursor_from_witness(&g_witness_args, &use_input_type); + CHECK(err); + + BytesOptType input; + if (use_input_type) { + input = g_witness_args.t->input_type(&g_witness_args); + } else { + input = g_witness_args.t->output_type(&g_witness_args); + } + + CHECK2(!input.t->is_none(&input), ERROR_INVALID_MOL_FORMAT); + + struct mol2_cursor_t bytes = input.t->unwrap(&input); + // convert Bytes to XudtWitnessInputType + XudtWitnessInputType witness_input = make_XudtWitnessInput(&bytes); + ScriptVecOptType script_vec = + witness_input.t->raw_extension_data(&witness_input); + + uint32_t read_len = + mol2_read_at(&script_vec.cur, g_raw_extension_data, RAW_EXTENSION_SIZE); + CHECK2(read_len == script_vec.cur.size, ERROR_INVALID_MOL_FORMAT); + + *var_data = g_raw_extension_data; + *var_len = read_len; + + err = 0; +exit: + return err; +} + +int check_owner_mode(size_t source, size_t field, mol_seg_t args_bytes_seg, + int *owner_mode) { + int err = 0; + size_t i = 0; + uint8_t buffer[BLAKE2B_BLOCK_SIZE]; + + while (1) { + uint64_t len = BLAKE2B_BLOCK_SIZE; + err = ckb_checked_load_cell_by_field(buffer, &len, 0, i, source, field); + if (err == CKB_INDEX_OUT_OF_BOUND) { + err = 0; + break; + } + if (err == CKB_ITEM_MISSING) { + i += 1; + err = 0; + continue; + } + CHECK(err); + if (args_bytes_seg.size >= BLAKE2B_BLOCK_SIZE && + memcmp(buffer, args_bytes_seg.ptr, BLAKE2B_BLOCK_SIZE) == 0) { + *owner_mode = 1; + break; + } + i += 1; + } + +exit: + return err; +} + +int check_enhanced_owner_mode(int *owner_mode) { + int err = 0; + uint8_t owner_script[SCRIPT_SIZE]; + uint32_t owner_script_len = 0; + uint8_t owner_script_hash[BLAKE2B_BLOCK_SIZE] = {0}; + + err = get_owner_script(owner_script, SCRIPT_SIZE, &owner_script_len); + CHECK(err); + + err = blake2b(owner_script_hash, BLAKE2B_BLOCK_SIZE, owner_script, + owner_script_len, NULL, 0); + CHECK2(err == 0, ERROR_BLAKE2B_ERROR); + + // get 32 bytes hash from args and compare it to owner script hash + { + uint64_t len = SCRIPT_SIZE; + int ret = ckb_checked_load_script(g_script, &len, 0); + CHECK(ret); + CHECK2(len <= SCRIPT_SIZE, ERROR_SCRIPT_TOO_LONG); + + mol_seg_t script_seg; + script_seg.ptr = g_script; + script_seg.size = len; + + mol_errno mol_err = MolReader_Script_verify(&script_seg, false); + CHECK2(mol_err == MOL_OK, ERROR_ENCODING); + + mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); + mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); + CHECK2(args_bytes_seg.size >= BLAKE2B_BLOCK_SIZE, ERROR_ARGUMENTS_LEN); + + if (memcmp(owner_script_hash, args_bytes_seg.ptr, BLAKE2B_BLOCK_SIZE) != + 0) { + CHECK2(false, ERROR_HASH_MISMATCHED); + } + } + + // execute owner script + mol_seg_t owner_script_seg = {.ptr = owner_script, .size = owner_script_len}; + mol_errno mol_err = MolReader_Script_verify(&owner_script_seg, false); + CHECK2(mol_err == MOL_OK, ERROR_ENCODING); + mol_seg_t code_hash = MolReader_Script_get_code_hash(&owner_script_seg); + mol_seg_t hash_type = MolReader_Script_get_hash_type(&owner_script_seg); + + mol_seg_t owner_args_seg = MolReader_Script_get_args(&owner_script_seg); + mol_seg_t owner_args_bytes_seg = MolReader_Bytes_raw_bytes(&owner_args_seg); + + ValidateFuncType func = NULL; + XUDTValidateFuncCategory cat = CateNormal; + err = load_validate_func(g_code_buff, &g_code_used, code_hash.ptr, + *(uint8_t *)hash_type.ptr, &func, &cat); + CHECK(err); + + err = func(0, 0, owner_args_bytes_seg.ptr, owner_args_bytes_seg.size); + CHECK(err); + *owner_mode = 1; + +exit: + return err; +} + +// *var_data will point to "Raw Extension Data", which can be in args or witness +// *var_data will refer to a memory location of g_script or g_raw_extension_data +int parse_args(int *owner_mode, XUDTFlags *flags, uint8_t **var_data, + uint32_t *var_len, uint8_t *hashes, uint32_t *hashes_count) { + int err = 0; + bool owner_mode_for_input_type = false; + bool owner_mode_for_output_type = false; + // default is on + bool owner_mode_for_input_lock = true; + + uint64_t len = SCRIPT_SIZE; + int ret = ckb_checked_load_script(g_script, &len, 0); + CHECK(ret); + CHECK2(len <= SCRIPT_SIZE, ERROR_SCRIPT_TOO_LONG); + + mol_seg_t script_seg; + script_seg.ptr = g_script; + script_seg.size = len; + + mol_errno mol_err = MolReader_Script_verify(&script_seg, false); + CHECK2(mol_err == MOL_OK, ERROR_ENCODING); + + mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); + mol_seg_t args_bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); + CHECK2(args_bytes_seg.size >= BLAKE2B_BLOCK_SIZE, ERROR_ARGUMENTS_LEN); + + if (args_bytes_seg.size >= (FLAGS_SIZE + BLAKE2B_BLOCK_SIZE)) { + uint32_t val = *(uint32_t *)(args_bytes_seg.ptr + BLAKE2B_BLOCK_SIZE); + if (val & OWNER_MODE_INPUT_TYPE_MASK) { + owner_mode_for_input_type = true; + } + if (val & OWNER_MODE_OUTPUT_TYPE_MASK) { + owner_mode_for_output_type = true; + } + if (val & OWNER_MODE_INPUT_LOCK_NOT_MASK) { + owner_mode_for_input_lock = false; + } + } + + *hashes_count = 0; + + // collect hashes + size_t i = 0; + while (1) { + uint8_t buffer[BLAKE2B_BLOCK_SIZE]; + uint64_t len2 = BLAKE2B_BLOCK_SIZE; + ret = ckb_checked_load_cell_by_field(buffer, &len2, 0, i, CKB_SOURCE_INPUT, + CKB_CELL_FIELD_LOCK_HASH); + if (ret == CKB_INDEX_OUT_OF_BOUND) { + break; + } + CHECK(ret); + CHECK2(*hashes_count < MAX_LOCK_SCRIPT_HASH_COUNT, ERROR_TOO_MANY_LOCK); + + memcpy(&hashes[(*hashes_count) * BLAKE2B_BLOCK_SIZE], buffer, + BLAKE2B_BLOCK_SIZE); + *hashes_count += 1; + i += 1; + } + + *owner_mode = 0; + + if (owner_mode_for_input_lock && *owner_mode == 0) { + err = check_owner_mode(CKB_SOURCE_INPUT, CKB_CELL_FIELD_LOCK_HASH, + args_bytes_seg, owner_mode); + CHECK(err); + } + + if (owner_mode_for_input_type && *owner_mode == 0) { + err = check_owner_mode(CKB_SOURCE_INPUT, CKB_CELL_FIELD_TYPE_HASH, + args_bytes_seg, owner_mode); + CHECK(err); + } + if (owner_mode_for_output_type && *owner_mode == 0) { + err = check_owner_mode(CKB_SOURCE_OUTPUT, CKB_CELL_FIELD_TYPE_HASH, + args_bytes_seg, owner_mode); + CHECK(err); + } + + // parse xUDT args + if (args_bytes_seg.size < (FLAGS_SIZE + BLAKE2B_BLOCK_SIZE)) { + *var_data = NULL; + *var_len = 0; + *flags = XUDTFlagsPlain; + } else { + uint32_t temp_flags = + (*(uint32_t *)(args_bytes_seg.ptr + BLAKE2B_BLOCK_SIZE)) & + ~OWNER_MODE_MASK; + if (temp_flags == XUDTFlagsPlain) { + *flags = XUDTFlagsPlain; + } else if (temp_flags == XUDTFlagsInArgs) { + uint32_t real_size = 0; + *flags = XUDTFlagsInArgs; + *var_len = args_bytes_seg.size - BLAKE2B_BLOCK_SIZE - FLAGS_SIZE; + *var_data = args_bytes_seg.ptr + BLAKE2B_BLOCK_SIZE + FLAGS_SIZE; + + err = verify_script_vec(*var_data, *var_len, &real_size); + CHECK(err); + // note, it's different than "flag = 2" + CHECK2(real_size == *var_len, ERROR_INVALID_ARGS_FORMAT); + } else if (temp_flags == XUDTFlagsInWitness) { + *flags = XUDTFlagsInWitness; + uint32_t hash_size = + args_bytes_seg.size - BLAKE2B_BLOCK_SIZE - FLAGS_SIZE; + CHECK2(hash_size == BLAKE160_SIZE, ERROR_INVALID_FLAG); + + err = load_raw_extension_data(var_data, var_len); + CHECK(err); + CHECK2(var_len > 0, ERROR_INVALID_MOL_FORMAT); + // verify the hash + uint8_t hash[BLAKE2B_BLOCK_SIZE] = {0}; + uint8_t *blake160_hash = + args_bytes_seg.ptr + BLAKE2B_BLOCK_SIZE + FLAGS_SIZE; + err = blake2b(hash, BLAKE2B_BLOCK_SIZE, *var_data, *var_len, NULL, 0); + CHECK2(err == 0, ERROR_BLAKE2B_ERROR); + CHECK2(memcmp(blake160_hash, hash, BLAKE160_SIZE) == 0, + ERROR_HASH_MISMATCHED); + } else { + CHECK2(false, ERROR_INVALID_FLAG); + } + } + err = 0; +exit: + return err; +} + +// copied from simple_udt.c +int simple_udt(int owner_mode) { + if (owner_mode) + return CKB_SUCCESS; + + int ret = 0; + // When the owner mode is not enabled, however, we will then need to ensure + // the sum of all input tokens is not smaller than the sum of all output + // tokens. First, let's loop through all input cells containing current UDTs, + // and gather the sum of all input tokens. + uint128_t input_amount = 0; + size_t i = 0; + uint64_t len = 0; + while (1) { + uint128_t current_amount = 0; + len = 16; + // The implementation here does not require that the transaction only + // contains UDT cells for the current UDT type. It's perfectly fine to mix + // the cells for multiple different types of UDT together in one + // transaction. But that also means we need a way to tell one UDT type from + // another UDT type. The trick is in the `CKB_SOURCE_GROUP_INPUT` value used + // here. When using it as the source part of the syscall, the syscall would + // only iterate through cells with the same script as the current running + // script. Since different UDT types will naturally have different + // script(the args part will be different), we can be sure here that this + // loop would only iterate through UDTs that are of the same type as the one + // identified by the current running script. + // + // In the case that multiple UDT types are included in the same transaction, + // this simple UDT script will be run multiple times to validate the + // transaction, each time with a different script containing different + // script args, representing different UDT types. + // + // A different trick used here, is that our current implementation assumes + // that the amount of UDT is stored as unsigned 128-bit little endian + // integer in the first 16 bytes of cell data. Since RISC-V also uses little + // endian format, we can just read the first 16 bytes of cell data into + // `current_amount`, which is just an unsigned 128-bit integer in C. The + // memory layout of a C program will ensure that the value is set correctly. + ret = ckb_load_cell_data((uint8_t *)¤t_amount, &len, 0, i, + CKB_SOURCE_GROUP_INPUT); + // When `CKB_INDEX_OUT_OF_BOUND` is reached, we know we have iterated + // through all cells of current type. + if (ret == CKB_INDEX_OUT_OF_BOUND) { + break; + } + if (ret != CKB_SUCCESS) { + return ret; + } + if (len < 16) { + return ERROR_ENCODING; + } + input_amount += current_amount; + // Like any serious smart contract out there, we will need to check for + // overflows. + if (input_amount < current_amount) { + return ERROR_OVERFLOWING; + } + i += 1; + } + + // With the sum of all input UDT tokens gathered, let's now iterate through + // output cells to grab the sum of all output UDT tokens. + uint128_t output_amount = 0; + i = 0; + while (1) { + uint128_t current_amount = 0; + len = 16; + // Similar to the above code piece, we are also looping through output cells + // with the same script as current running script here by using + // `CKB_SOURCE_GROUP_OUTPUT`. + ret = ckb_load_cell_data((uint8_t *)¤t_amount, &len, 0, i, + CKB_SOURCE_GROUP_OUTPUT); + if (ret == CKB_INDEX_OUT_OF_BOUND) { + break; + } + if (ret != CKB_SUCCESS) { + return ret; + } + if (len < 16) { + return ERROR_ENCODING; + } + output_amount += current_amount; + // Like any serious smart contract out there, we will need to check for + // overflows. + if (output_amount < current_amount) { + return ERROR_OVERFLOWING; + } + i += 1; + } + + // When both value are gathered, we can perform the final check here to + // prevent non-authorized token issuance. + if (input_amount < output_amount) { + return ERROR_AMOUNT; + } + return CKB_SUCCESS; +} + +// If the extension script is identical to a lock script of one input cell in +// current transaction, we consider the extension script to be already +// validated, no additional check is needed for current extension +int is_extension_script_validated(mol_seg_t extension_script, + uint8_t *input_lock_script_hash, + uint32_t input_lock_script_hash_count) { + int err = 0; + uint8_t hash[BLAKE2B_BLOCK_SIZE]; + err = blake2b(hash, BLAKE2B_BLOCK_SIZE, extension_script.ptr, + extension_script.size, NULL, 0); + CHECK2(err == 0, ERROR_BLAKE2B_ERROR); + + for (uint32_t i = 0; i < input_lock_script_hash_count; i++) { + if (memcmp(&input_lock_script_hash[i * BLAKE2B_BLOCK_SIZE], hash, + BLAKE2B_BLOCK_SIZE) == 0) { + return 0; + } + } + err = ERROR_NOT_VALIDATED; +exit: + return err; +} + +#ifdef CKB_USE_SIM +int simulator_main() { +#else +int main() { +#endif + int err = 0; + int owner_mode = 0; + uint8_t *raw_extension_data = NULL; + uint32_t raw_extension_len = 0; + XUDTFlags flags = XUDTFlagsPlain; + uint8_t + input_lock_script_hashes[MAX_LOCK_SCRIPT_HASH_COUNT * BLAKE2B_BLOCK_SIZE]; + uint32_t input_lock_script_hash_count = 0; + err = parse_args(&owner_mode, &flags, &raw_extension_data, &raw_extension_len, + input_lock_script_hashes, &input_lock_script_hash_count); + CHECK(err); + CHECK2(owner_mode == 1 || owner_mode == 0, ERROR_INVALID_ARGS_FORMAT); + // check enhanced mode here + if (!owner_mode) { + check_enhanced_owner_mode(&owner_mode); + // don't need to check the return result from this function + // if failed, owner mode is still false + } + + if (flags != XUDTFlagsPlain) { + CHECK2(raw_extension_data != NULL, ERROR_INVALID_ARGS_FORMAT); + CHECK2(raw_extension_len > 0, ERROR_INVALID_ARGS_FORMAT); + } + err = simple_udt(owner_mode); + if (err != 0) { + goto exit; + } + + if (flags == XUDTFlagsPlain) { + err = 0; + goto exit; + } + + mol_seg_t raw_extension_seg = {0}; + raw_extension_seg.ptr = raw_extension_data; + raw_extension_seg.size = raw_extension_len; + CHECK2(MolReader_ScriptVec_verify(&raw_extension_seg, true) == MOL_OK, + ERROR_INVALID_ARGS_FORMAT); + uint32_t size = MolReader_ScriptVec_length(&raw_extension_seg); + for (uint32_t i = 0; i < size; i++) { + ValidateFuncType func; + mol_seg_res_t res = MolReader_ScriptVec_get(&raw_extension_seg, i); + CHECK2(res.errno == 0, ERROR_INVALID_MOL_FORMAT); + CHECK2(MolReader_Script_verify(&res.seg, false) == MOL_OK, + ERROR_INVALID_MOL_FORMAT); + + mol_seg_t code_hash = MolReader_Script_get_code_hash(&res.seg); + mol_seg_t hash_type = MolReader_Script_get_hash_type(&res.seg); + mol_seg_t args = MolReader_Script_get_args(&res.seg); + + uint8_t hash_type2 = *((uint8_t *)hash_type.ptr); + XUDTValidateFuncCategory cat = CateNormal; + err = load_validate_func(g_code_buff, &g_code_used, code_hash.ptr, + hash_type2, &func, &cat); + CHECK(err); + // RCE is with high priority, must be checked + if (cat != CateRce) { + int err2 = is_extension_script_validated( + res.seg, input_lock_script_hashes, input_lock_script_hash_count); + if (err2 == 0) { + continue; + } + } + mol_seg_t args_raw_bytes = MolReader_Bytes_raw_bytes(&args); + + err = func(owner_mode, i, args_raw_bytes.ptr, args_raw_bytes.size); + CHECK(err); + } + + err = 0; +exit: + return err; +} diff --git a/third-party/xudt/xudt_rce.mol b/third-party/xudt/xudt_rce.mol new file mode 100644 index 0000000..9182272 --- /dev/null +++ b/third-party/xudt/xudt_rce.mol @@ -0,0 +1,70 @@ +import blockchain; + +vector ScriptVec