diff --git a/Makefile b/Makefile index 1c46576..f07853b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CC := $(TARGET)-gcc LD := $(TARGET)-gcc OBJCOPY := $(TARGET)-objcopy CFLAGS := -fPIC -O3 -fno-builtin-printf -fno-builtin-memcmp -nostdinc -nostdlib -nostartfiles -fvisibility=hidden -fdata-sections -ffunction-sections -I deps/secp256k1/src -I deps/secp256k1 -I deps/ckb-c-std-lib -I deps/ckb-c-std-lib/libc -I deps/ckb-c-std-lib/molecule -I c -I build -Wall -Werror -Wno-nonnull -Wno-nonnull-compare -Wno-unused-function -g -LDFLAGS := -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections +LDFLAGS := -nostdlib -nostartfiles -fno-builtin -Wl,-static -Wl,--gc-sections SECP256K1_SRC_20210801 := deps/secp256k1-20210801/src/ecmult_static_pre_context.h @@ -54,7 +54,8 @@ ${PROTOCOL_SCHEMA}: curl -L -o $@ ${PROTOCOL_URL} ALL_C_SOURCE := $(wildcard c/omni_lock.c c/omni_lock_acp.h c/omni_lock_time_lock.h \ - tests/omni_lock/omni_lock_sim.c tests/omni_lock/ckb_syscall_omni_lock_sim.h tests/omni_lock/omni_lock_supply.h) + tests/omni_lock/omni_lock_sim.c tests/omni_lock/ckb_syscall_omni_lock_sim.h tests/omni_lock/omni_lock_supply.h\ + c/blake2b_decl_only.h c/cobuild.h c/cobuild.c) fmt: docker run --rm -v `pwd`:/code ${CLANG_FORMAT_DOCKER} bash -c "cd code && clang-format -i -style=Google $(ALL_C_SOURCE)" @@ -76,17 +77,23 @@ omni_lock_mol: ${MOLC} --language - --schema-file c/omni_lock.mol --format json > build/omni_lock_mol2.json moleculec-c2 --input build/omni_lock_mol2.json | clang-format -style=Google > c/omni_lock_mol2.h -build/omni_lock: c/omni_lock.c c/omni_lock_supply.h c/omni_lock_acp.h c/secp256k1_lock.h build/secp256k1_data_info_20210801.h $(SECP256K1_SRC_20210801) c/ckb_identity.h - $(CC) $(OMNI_LOCK_CFLAGS) $(LDFLAGS) -o $@ $< +build/cobuild.o: c/cobuild.c c/cobuild.h + $(CC) -c $(OMNI_LOCK_CFLAGS) -o $@ $< + +build/omni_lock.o: c/omni_lock.c c/omni_lock_supply.h c/omni_lock_acp.h c/secp256k1_lock.h build/secp256k1_data_info_20210801.h $(SECP256K1_SRC_20210801) c/ckb_identity.h + $(CC) -c $(OMNI_LOCK_CFLAGS) -o $@ $< + +build/omni_lock: build/omni_lock.o build/cobuild.o + $(CC) $(LDFLAGS) -o $@ $^ cp $@ $@.debug $(OBJCOPY) --strip-debug --strip-all $@ - clean: rm -rf build/secp256k1_data_info_20210801.h build/dump_secp256k1_data_20210801 rm -rf build/secp256k1_data_20210801 rm -rf build/*.debug rm -f build/omni_lock + rm -f build/*.o cd deps/secp256k1-20210801 && [ -f "Makefile" ] && make clean install-tools: diff --git a/c/blake2b_decl_only.h b/c/blake2b_decl_only.h new file mode 100644 index 0000000..eb069e3 --- /dev/null +++ b/c/blake2b_decl_only.h @@ -0,0 +1,61 @@ + +#ifndef __BLAKE2B_DECL_ONLY_H__ +#define __BLAKE2B_DECL_ONLY_H__ + +#include +#include + +#define BLAKE2_PACKED(x) x __attribute__((packed)) + +enum blake2b_constant { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 +}; +BLAKE2_PACKED(struct blake2b_param__ { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint32_t node_offset; /* 12 */ + uint32_t xof_length; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ +}); + +typedef struct blake2b_param__ blake2b_param; + +typedef struct blake2b_state__ { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + size_t buflen; + size_t outlen; + uint8_t last_node; +} blake2b_state; + +/* Streaming API */ +int ckb_blake2b_init(blake2b_state *S, size_t outlen); +int blake2b_init(blake2b_state *S, size_t outlen); +int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, + size_t keylen); +int blake2b_update(blake2b_state *S, const void *in, size_t inlen); +int blake2b_final(blake2b_state *S, void *out, size_t outlen); +/* Simple API */ +int blake2b(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen); + +/* This is simply an alias for blake2b */ +int blake2(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen); + +int blake2b_init_param(blake2b_state *S, const blake2b_param *P); + +#endif diff --git a/c/cobuild.c b/c/cobuild.c new file mode 100644 index 0000000..5af180d --- /dev/null +++ b/c/cobuild.c @@ -0,0 +1,227 @@ +// clang-format off +#define CKB_DECLARATION_ONLY +#include +#include +#include +#include +#include "cobuild.h" + +#define MOLECULEC_C2_DECLARATION_ONLY +#define MOLECULEC2_VERSION 6001 +#define MOLECULE2_API_VERSION_MIN 5000 +#include "molecule2_reader.h" + +#include "blake2b_decl_only.h" +#include "ckb_consts.h" +#include "ckb_syscall_apis.h" +// clang-format on + +#define CHECK2(cond, code) \ + do { \ + if (!(cond)) { \ + printf("error at %s:%d, error code %d", __FILE__, __LINE__, code); \ + err = code; \ + ASSERT(0); \ + goto exit; \ + } \ + } while (0) + +#define CHECK(_code) \ + do { \ + int code = (_code); \ + if (code != 0) { \ + printf("error at %s:%d, error code %d", __FILE__, __LINE__, code); \ + err = code; \ + ASSERT(0); \ + goto exit; \ + } \ + } while (0) + +enum CobuildErrorCode { + // cobuild error code is from 110 + ERROR_GENERAL = 110, + ERROR_HASH, + ERROR_NONEMPTY_WITNESS, +}; + +const char *PERSONAL_SIGHASH_ALL = "ckb-tcob-sighash"; +const char *PERSONAL_SIGHASH_ALL_ONLY = "ckb-tcob-sgohash"; +const char *PERSONAL_OTX = "ckb-tcob-otxhash"; + +static void store32(void *dst, uint32_t w) { + uint8_t *p = (uint8_t *)dst; + p[0] = (uint8_t)(w >> 0); + p[1] = (uint8_t)(w >> 8); + p[2] = (uint8_t)(w >> 16); + p[3] = (uint8_t)(w >> 24); +} + +int ckb_blake2b_init_personal(blake2b_state *S, size_t outlen, + const char *personal) { + blake2b_param P[1]; + + if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32(&P->leaf_length, 0); + store32(&P->node_offset, 0); + store32(&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + memset(P->reserved, 0, sizeof(P->reserved)); + memset(P->salt, 0, sizeof(P->salt)); + memset(P->personal, 0, sizeof(P->personal)); + for (int i = 0; i < BLAKE2B_PERSONALBYTES; ++i) { + (P->personal)[i] = personal[i]; + } + return blake2b_init_param(S, P); +} + +int new_sighash_all_blake2b(blake2b_state *S) { + return ckb_blake2b_init_personal(S, 32, PERSONAL_SIGHASH_ALL); +} + +int new_sighash_all_only_blake2b(blake2b_state *S) { + return ckb_blake2b_init_personal(S, 32, PERSONAL_SIGHASH_ALL_ONLY); +} + +int new_otx_blake2b(blake2b_state *S) { + return ckb_blake2b_init_personal(S, 32, PERSONAL_OTX); +} + +typedef int (*load_function_t)(void *, uint64_t *, size_t, size_t, size_t); +#define BATCH_SIZE 1024 + +int streaming_hash(blake2b_state *ctx, size_t start, size_t total_len, + size_t index, size_t source, load_function_t f) { + int err = ERROR_GENERAL; + uint8_t temp[BATCH_SIZE]; + uint64_t len = BATCH_SIZE; + size_t remaining_len = total_len; + + int ret = f(temp, &len, start, index, source); + CHECK(ret); + + uint64_t offset = (len > BATCH_SIZE) ? BATCH_SIZE : len; + if (remaining_len >= offset) { + blake2b_update(ctx, temp, offset); + remaining_len -= offset; + } else { + blake2b_update(ctx, temp, remaining_len); + return CKB_SUCCESS; + } + + while (offset < len) { + uint64_t current_len = BATCH_SIZE; + ret = f(temp, ¤t_len, start + offset, index, source); + CHECK(err); + uint64_t current_read = + (current_len > BATCH_SIZE) ? BATCH_SIZE : current_len; + if (remaining_len >= current_read) { + blake2b_update(ctx, temp, current_read); + } else { + blake2b_update(ctx, temp, remaining_len); + return CKB_SUCCESS; + } + offset += current_read; + } + +exit: + return err; +} + +int ckb_hash_cell_data(blake2b_state *ctx, size_t index) { + size_t source = CKB_SOURCE_INPUT; + uint64_t cell_len = 0; + int err = ckb_load_cell_data(0, &cell_len, 0, index, source); + if (err) return err; + uint32_t cell_len2 = (uint32_t)cell_len; + // cobuild requires length = 4 + blake2b_update(ctx, &cell_len2, sizeof(cell_len2)); + return streaming_hash(ctx, 0, cell_len, index, source, ckb_load_cell_data); +} + +int ckb_hash_witness(blake2b_state *ctx, size_t index) { + size_t source = CKB_SOURCE_INPUT; + uint64_t witness_len = 0; + int err = ckb_load_witness(0, &witness_len, 0, index, source); + if (err) return err; + // cobuild requires length = 4 + uint32_t witness_len2 = (uint32_t)witness_len; + blake2b_update(ctx, &witness_len2, sizeof(witness_len2)); + return streaming_hash(ctx, 0, witness_len, index, source, ckb_load_witness); +} + +// for lock script with message, the other witness in script group except first +// one should be empty +int ckb_check_others_in_group() { + int err = ERROR_GENERAL; + for (size_t index = 1;; index++) { + uint64_t witness_len = 0; + err = ckb_load_witness(0, &witness_len, 0, index, CKB_SOURCE_GROUP_INPUT); + if (err == CKB_INDEX_OUT_OF_BOUND) { + err = CKB_SUCCESS; + break; + } + CHECK(err); + CHECK2(witness_len > 0, ERROR_NONEMPTY_WITNESS); + } + +exit: + return err; +} + +int ckb_fetch_message(bool *has_message, size_t *index, size_t *start, + size_t *len) { + return 0; +} + +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 (uint32_t)output_len; + } +} + +static uint8_t g_witness_data_source[DEFAULT_DATA_SOURCE_LENGTH]; +static int make_cobuild_witness(mol2_cursor_t *witness) { + int err = 0; + uint64_t witness_len = 0; + size_t source = CKB_SOURCE_GROUP_INPUT; + err = ckb_load_witness(NULL, &witness_len, 0, 0, source); + if (err != 0) { + return err; + } + mol2_cursor_t cur; + + cur.offset = 0; + cur.size = (uint32_t)witness_len; + + mol2_data_source_t *ptr = (mol2_data_source_t *)g_witness_data_source; + + ptr->read = read_from_witness; + ptr->total_size = (uint32_t)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 = cur; + + return 0; +} diff --git a/c/cobuild.h b/c/cobuild.h new file mode 100644 index 0000000..d65bb45 --- /dev/null +++ b/c/cobuild.h @@ -0,0 +1,30 @@ +#ifndef __COBUILD_H__ +#define __COBUILD_H__ + +#include +#include +#include + +#include "blake2b_decl_only.h" + +// return a blake2b instance with personalization for SighashAll +int new_sighash_all_blake2b(blake2b_state *S); + +// return a blake2b instance with personalization for SighashAllOnly +int new_sighash_all_only_blake2b(blake2b_state *S); + +// return a blake2b instance with personalization for OTX +int new_otx_blake2b(blake2b_state *S); + +// hash whole cell data with source = INPUT +int ckb_hash_cell_data(blake2b_state *ctx, size_t index); +// hash whole witness with source = INPUT +int ckb_hash_witness(blake2b_state *ctx, size_t index); +int ckb_check_others_in_group(); + +// fetch message from input witness. *has_message = false if it doesn't exist. +// when *has_message = true, *start and *len together specify the slice in +// witness with *index. +int ckb_fetch_message(bool *has_message, size_t *index, size_t *start, + size_t *len); +#endif diff --git a/tests/omni_lock_rust/Cargo.lock b/tests/omni_lock_rust/Cargo.lock index 00d109c..f74a536 100644 --- a/tests/omni_lock_rust/Cargo.lock +++ b/tests/omni_lock_rust/Cargo.lock @@ -868,7 +868,6 @@ dependencies = [ "faster-hex 0.3.1", "gdb-remote-protocol", "hex", - "includedir_codegen", "lazy_static", "openssl", "rand 0.6.5", diff --git a/tests/omni_lock_rust/Cargo.toml b/tests/omni_lock_rust/Cargo.toml index 48ca362..102b8aa 100644 --- a/tests/omni_lock_rust/Cargo.toml +++ b/tests/omni_lock_rust/Cargo.toml @@ -33,8 +33,4 @@ ckb-vm = { version = "=0.20.0-rc2", features = ["detect-asm"] } ripemd = "0.1.3" sha2 = "0.10.6" hex = "0.4.3" - -[build-dependencies] -includedir_codegen = "0.5.0" -blake2b-rs = "0.1.5" faster-hex = "0.3" diff --git a/tests/omni_lock_rust/build.rs b/tests/omni_lock_rust/build.rs deleted file mode 100644 index e6f0946..0000000 --- a/tests/omni_lock_rust/build.rs +++ /dev/null @@ -1,82 +0,0 @@ -pub use blake2b_rs::{Blake2b, Blake2bBuilder}; -use includedir_codegen::Compression; - -use std::{ - env, - fs::File, - io::{BufWriter, Read, Write}, - path::Path, -}; - -const PATH_PREFIX: &str = "../../build/"; -const BUF_SIZE: usize = 8 * 1024; -const CKB_HASH_PERSONALIZATION: &[u8] = b"ckb-default-hash"; - -const BINARIES: &[(&str, &str)] = &[ - ( - "omni_lock", - "d83e7ec3ed31b24f7627af8d05d8da7a87fdf1afa120ef75a5c487464e8472aa", - ), -]; - -fn main() { - let mut bundled = includedir_codegen::start("BUNDLED_CELL"); - - let out_path = Path::new(&env::var("OUT_DIR").unwrap()).join("code_hashes.rs"); - let mut out_file = BufWriter::new(File::create(&out_path).expect("create code_hashes.rs")); - - let mut errors = Vec::new(); - - for (name, expected_hash) in BINARIES { - let path = format!("{}{}", PATH_PREFIX, name); - - let mut buf = [0u8; BUF_SIZE]; - bundled - .add_file(&path, Compression::Gzip) - .expect("add files to resource bundle"); - - // build hash - let mut blake2b = new_blake2b(); - let mut fd = File::open(&path).expect("open file"); - loop { - let read_bytes = fd.read(&mut buf).expect("read file"); - if read_bytes > 0 { - blake2b.update(&buf[..read_bytes]); - } else { - break; - } - } - - let mut hash = [0u8; 32]; - blake2b.finalize(&mut hash); - - let actual_hash = faster_hex::hex_string(&hash).unwrap(); - if expected_hash != &actual_hash { - errors.push((name, expected_hash, actual_hash)); - continue; - } - - write!( - &mut out_file, - "pub const {}: [u8; 32] = {:?};\n", - format!("CODE_HASH_{}", name.to_uppercase()), - hash - ) - .expect("write to code_hashes.rs"); - } - - if !errors.is_empty() { - for (name, expected, actual) in errors.into_iter() { - eprintln!("{}: expect {}, actual {}", name, expected, actual); - } - panic!("not all hashes are right"); - } - - bundled.build("bundled.rs").expect("build resource bundle"); -} - -pub fn new_blake2b() -> Blake2b { - Blake2bBuilder::new(32) - .personal(CKB_HASH_PERSONALIZATION) - .build() -} diff --git a/tests/omni_lock_rust/tests/test_omni_lock.rs b/tests/omni_lock_rust/tests/test_omni_lock.rs index d420bfb..451c956 100644 --- a/tests/omni_lock_rust/tests/test_omni_lock.rs +++ b/tests/omni_lock_rust/tests/test_omni_lock.rs @@ -3,6 +3,10 @@ mod misc; +use std::fs::File; +use std::io::Read; + +use blake2b_rs::{Blake2b, Blake2bBuilder}; use ckb_chain_spec::consensus::ConsensusBuilder; use ckb_crypto::secp::Generator; use ckb_error::assert_error_eq; @@ -730,3 +734,32 @@ fn test_eth_displaying_unlock() { let verify_result = verifier.verify(MAX_CYCLES); verify_result.expect("pass verification"); } + +// this test can fail during development +#[test] +fn test_binary_unchanged() { + let mut buf = [0u8; 8 * 1024]; + // build hash + let mut blake2b = Blake2bBuilder::new(32) + .personal(b"ckb-default-hash") + .build(); + + let mut fd = File::open("../../build/omni_lock").expect("open file"); + loop { + let read_bytes = fd.read(&mut buf).expect("read file"); + if read_bytes > 0 { + blake2b.update(&buf[..read_bytes]); + } else { + break; + } + } + + let mut hash = [0u8; 32]; + blake2b.finalize(&mut hash); + + let actual_hash = faster_hex::hex_string(&hash).unwrap(); + assert_eq!( + "d83e7ec3ed31b24f7627af8d05d8da7a87fdf1afa120ef75a5c487464e8472aa", + &actual_hash + ); +}