diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc0ed8a5b..53a7913bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,7 +211,8 @@ jobs: - name: Run Merkle tree tests run: | - docker run --rm -t ${{ github.repository_owner }}/machine-emulator:tests test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=/usr/bin/test-merkle-tree-hash + docker run --rm -t ${{ github.repository_owner }}/machine-emulator:tests test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=/usr/bin/test-merkle-tree-hash --hash-tree-target=uarch + docker run --rm -t ${{ github.repository_owner }}/machine-emulator:tests test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=/usr/bin/test-merkle-tree-hash --hash-tree-target=risc0 - name: Run C API tests run: | diff --git a/src/Makefile b/src/Makefile index eff7a414c..0c47593be 100644 --- a/src/Makefile +++ b/src/Makefile @@ -164,6 +164,7 @@ CLANG_TIDY_WARNS=-Wthread-safety -Wglobal-constructors INCS+= \ -I../third-party/llvm-flang-uint128 \ -I../third-party/tiny_sha3 \ + -I../third-party/SHA256 \ -I../third-party/nlohmann-json \ -I../third-party/downloads \ $(BOOST_INC) @@ -216,6 +217,9 @@ endif # The SHA3 is third party library we always want to compile with O3 SHA3_CFLAGS=-O3 +# The SHA256 is third party library we always want to compile with O3 +SHA256_CFLAGS=-O3 + # Optimization flags for the interpreter ifneq (,$(filter yes,$(relwithdebinfo) $(release))) ifneq (,$(findstring gcc,$(CC))) @@ -378,6 +382,7 @@ LIBCARTESI_OBJS:= \ uarch-step.o \ uarch-reset-state.o \ sha3.o \ + sha256.o \ machine-merkle-tree.o \ pristine-merkle-tree.o \ uarch-interpret.o \ @@ -398,6 +403,7 @@ LUACARTESI_OBJS:= \ LIBCARTESI_MERKLE_TREE_OBJS:= \ sha3.o \ + sha256.o \ machine-merkle-tree.o \ back-merkle-tree.o \ pristine-merkle-tree.o \ @@ -551,6 +557,7 @@ jsonrpc-discover.cpp: jsonrpc-discover.json @$(CXX) $(CXXFLAGS) $(LUA_INC) $< -MM -MT $@ -MF $@.d > /dev/null 2>&1 @touch $@ + %.clang-tidy: %.c @$(CLANG_TIDY) --header-filter='$(CLANG_TIDY_HEADER_FILTER)' $(CLANG_TIDY_FLAGS) $< -- $(CFLAGS) $(CLANG_TIDY_WARNS) -DCLANG_TIDY_LINT 2>/dev/null @$(CC) $(CFLAGS) $< -MM -MT $@ -MF $@.d > /dev/null 2>&1 @@ -559,6 +566,12 @@ jsonrpc-discover.cpp: jsonrpc-discover.json sha3.o: ../third-party/tiny_sha3/sha3.c $(CC) $(CFLAGS) $(SHA3_CFLAGS) -c -o $@ $< +sha256.o: ../third-party/SHA256/sha256.c + $(CC) $(CFLAGS) $(SHA256_CFLAGS) -c -o $@ $< + +sha256.o: ../third-party/SHA256/sha256.c + $(CC) $(CFLAGS) $(SHA256_CFLAGS) -c -o $@ $< + uarch-pristine-ram.o: $(UARCH_PRISTINE_RAM_C) $(CC) $(CFLAGS) -c -o $@ $< diff --git a/src/access-log.h b/src/access-log.h index 97d439a77..2c1abb1d7 100644 --- a/src/access-log.h +++ b/src/access-log.h @@ -67,11 +67,8 @@ static inline uint64_t get_word_access_data(const access_data &ad, int offset = /// \brief Records an access to the machine state class access { - using hasher_type = machine_merkle_tree::hasher_type; - public: - using hash_type = machine_merkle_tree::hash_type; - using sibling_hashes_type = std::vector; + using sibling_hashes_type = std::vector; using proof_type = machine_merkle_tree::proof_type; void set_type(access_type type) { @@ -143,38 +140,38 @@ class access { /// \brief Sets hash of data that was written at address after access. /// \param hash Hash of new data at address. - void set_written_hash(const hash_type &hash) { + void set_written_hash(const machine_hash &hash) { m_written_hash = hash; } /// \brief Gets hash of data that was written at address after access. /// \returns Hash of written data at address. - const std::optional &get_written_hash() const { + const std::optional &get_written_hash() const { return m_written_hash; } - std::optional &get_written_hash() { + std::optional &get_written_hash() { return m_written_hash; } /// \brief Sets hash of data that can be read at address before access. /// \param hash Hash of data at address. - void set_read_hash(const hash_type &hash) { + void set_read_hash(const machine_hash &hash) { m_read_hash = hash; } /// \brief Gets hash of data that can be read at address before access. /// \returns Hash of data at address. - const hash_type &get_read_hash() const { + const machine_hash &get_read_hash() const { return m_read_hash; } - hash_type &get_read_hash() { + machine_hash &get_read_hash() { return m_read_hash; } /// \brief Constructs a proof using this access' data and a given root hash. /// \param root_hash Hash to be used as the root of the proof. /// \return The corresponding proof - proof_type make_proof(const hash_type root_hash) const { + proof_type make_proof(const machine_hash root_hash) const { // the access can be of data smaller than the merkle tree word size // however, the proof must be at least as big as the merkle tree word size const int proof_log2_size = std::max(m_log2_size, machine_merkle_tree::get_log2_word_size()); @@ -218,9 +215,9 @@ class access { uint64_t m_address{0}; ///< Address of access int m_log2_size{0}; ///< Log2 of size of access std::optional m_read; ///< Data before access - hash_type m_read_hash{}; ///< Hash of data before access + machine_hash m_read_hash{}; ///< Hash of data before access std::optional m_written; ///< Written data - std::optional m_written_hash; ///< Hash of written data + std::optional m_written_hash; ///< Hash of written data std::optional m_sibling_hashes; ///< Hashes of siblings in path from address to root }; @@ -258,22 +255,27 @@ class access_log { }; private: - std::vector m_accesses; ///< List of all accesses - std::vector m_brackets; ///< Begin/End annotations - std::vector m_notes; ///< Per-access annotations - type m_log_type; ///< Log type + std::vector m_accesses; ///< List of all accesses + std::vector m_brackets; ///< Begin/End annotations + std::vector m_notes; ///< Per-access annotations + type m_log_type; ///< Log type + hash_tree_target m_hash_tree_target{hash_tree_target::uarch}; ///< Hash tree target public: - explicit access_log(type log_type) : m_log_type(log_type) { + explicit access_log(type log_type, hash_tree_target hash_tree_target = hash_tree_target::uarch) : + m_log_type(log_type), + m_hash_tree_target(hash_tree_target) { ; } template - access_log(ACCESSES &&accesses, BRACKETS &&brackets, NOTES &¬es, type log_type) : + access_log(ACCESSES &&accesses, BRACKETS &&brackets, NOTES &¬es, type log_type, + hash_tree_target hash_tree_target) : m_accesses(std::forward(accesses)), m_brackets(std::forward(brackets)), m_notes(std::forward(notes)), - m_log_type(log_type) { + m_log_type(log_type), + m_hash_tree_target(hash_tree_target) { ; } @@ -335,6 +337,10 @@ class access_log { type get_log_type() const { return m_log_type; } + + hash_tree_target get_hash_tree_target() const { + return m_hash_tree_target; + } }; } // namespace cartesi diff --git a/src/back-merkle-tree.cpp b/src/back-merkle-tree.cpp index 027edccb7..0952432c5 100644 --- a/src/back-merkle-tree.cpp +++ b/src/back-merkle-tree.cpp @@ -28,12 +28,13 @@ namespace cartesi { -back_merkle_tree::back_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size) : +back_merkle_tree::back_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher) : m_log2_root_size{log2_root_size}, m_log2_leaf_size{log2_leaf_size}, + m_hasher{hasher}, m_max_leaves{address_type{1} << (log2_root_size - log2_leaf_size)}, m_context(std::max(1, log2_root_size - log2_leaf_size + 1)), - m_pristine_hashes{log2_root_size, log2_word_size} { + m_pristine_hashes{m_hasher, log2_root_size, log2_word_size} { if (log2_root_size < 0) { throw std::out_of_range{"log2_root_size is negative"}; } @@ -54,9 +55,8 @@ back_merkle_tree::back_merkle_tree(int log2_root_size, int log2_leaf_size, int l } } -void back_merkle_tree::push_back(const hash_type &new_leaf_hash) { - hasher_type h; - hash_type right = new_leaf_hash; +void back_merkle_tree::push_back(const machine_hash &new_leaf_hash) { + machine_hash right = new_leaf_hash; if (m_leaf_count >= m_max_leaves) { throw std::out_of_range{"too many leaves"}; } @@ -64,7 +64,7 @@ void back_merkle_tree::push_back(const hash_type &new_leaf_hash) { for (int i = 0; i <= depth; ++i) { if ((m_leaf_count & (address_type{1} << i)) != 0) { const auto &left = m_context[i]; - get_concat_hash(h, left, right, right); + m_hasher.get_concat_hash(left, right, right); } else { m_context[i] = right; break; @@ -74,7 +74,6 @@ void back_merkle_tree::push_back(const hash_type &new_leaf_hash) { } void back_merkle_tree::pad_back(uint64_t new_leaf_count) { - hasher_type h; if (new_leaf_count > m_max_leaves || m_leaf_count + new_leaf_count > m_max_leaves) { throw std::invalid_argument("too many leaves"); } @@ -93,7 +92,7 @@ void back_merkle_tree::pad_back(uint64_t new_leaf_count) { const uint64_t i_span = address_type{1} << i; if ((m_leaf_count & i_span) != 0) { const auto &left = m_context[i]; - get_concat_hash(h, left, right, right); + m_hasher.get_concat_hash(left, right, right); } else { m_context[i] = right; // outer loop continues where we left off @@ -118,8 +117,7 @@ void back_merkle_tree::pad_back(uint64_t new_leaf_count) { } } -back_merkle_tree::hash_type back_merkle_tree::get_root_hash() const { - hasher_type h; +machine_hash back_merkle_tree::get_root_hash() const { assert(m_leaf_count <= m_max_leaves); const int depth = m_log2_root_size - m_log2_leaf_size; if (m_leaf_count < m_max_leaves) { @@ -127,10 +125,10 @@ back_merkle_tree::hash_type back_merkle_tree::get_root_hash() const { for (int i = 0; i < depth; ++i) { if ((m_leaf_count & (address_type{1} << i)) != 0) { const auto &left = m_context[i]; - get_concat_hash(h, left, root, root); + m_hasher.get_concat_hash(left, root, root); } else { const auto &right = m_pristine_hashes.get_hash(m_log2_leaf_size + i); - get_concat_hash(h, root, right, root); + m_hasher.get_concat_hash(root, right, root); } } return root; @@ -143,25 +141,24 @@ back_merkle_tree::proof_type back_merkle_tree::get_next_leaf_proof() const { if (m_leaf_count >= m_max_leaves) { throw std::out_of_range{"tree is full"}; } - hasher_type h; proof_type proof{m_log2_root_size, m_log2_leaf_size}; proof.set_target_address(m_leaf_count << m_log2_leaf_size); proof.set_target_hash(m_pristine_hashes.get_hash(m_log2_leaf_size)); - hash_type hash = m_pristine_hashes.get_hash(m_log2_leaf_size); + machine_hash hash = m_pristine_hashes.get_hash(m_log2_leaf_size); for (int i = 0; i < depth; ++i) { if ((m_leaf_count & (address_type{1} << i)) != 0) { const auto &left = m_context[i]; proof.set_sibling_hash(left, m_log2_leaf_size + i); - get_concat_hash(h, left, hash, hash); + m_hasher.get_concat_hash(left, hash, hash); } else { const auto &right = m_pristine_hashes.get_hash(m_log2_leaf_size + i); proof.set_sibling_hash(right, m_log2_leaf_size + i); - get_concat_hash(h, hash, right, hash); + m_hasher.get_concat_hash(hash, right, hash); } } proof.set_root_hash(hash); #ifndef NDEBUG - if (!proof.verify(h)) { + if (!proof.verify(m_hasher)) { throw std::runtime_error{"produced invalid proof"}; } #endif diff --git a/src/back-merkle-tree.h b/src/back-merkle-tree.h index fd1902b80..8f7c319ee 100644 --- a/src/back-merkle-tree.h +++ b/src/back-merkle-tree.h @@ -39,23 +39,17 @@ namespace cartesi { /// The class only ever stores log(n) hashes (1 for each tree level). class back_merkle_tree { public: - /// \brief Hasher class. - using hasher_type = keccak_256_hasher; - - /// \brief Storage for a hash. - using hash_type = hasher_type::hash_type; - /// \brief Storage for a hash. using address_type = uint64_t; /// \brief Storage for the proof of a word value. - using proof_type = merkle_tree_proof; + using proof_type = merkle_tree_proof; /// \brief Constructor /// \param log2_root_size Log2 of root node /// \param log2_leaf_size Log2 of leaf node /// \param log2_word_size Log2 of word node - back_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size); + back_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher); /// \brief Appends a new hash to the tree /// \param new_leaf_hash Hash of new leaf data @@ -88,7 +82,7 @@ class back_merkle_tree { /// If the bit is not set, we simply store context[i] = right and break /// In other words, we can update the context in /// log time (log2_root_size-log2_leaf_size) - void push_back(const hash_type &new_leaf_hash); + void push_back(const machine_hash &new_leaf_hash); /// \brief Appends a number of padding hashes to the tree /// \param leaf_count Number of padding hashes to append @@ -131,7 +125,7 @@ class back_merkle_tree { /// root = hash(root, pristine[i+log2_leaf_size]) and move up a bit /// (i.e., to grow our subtree, we need to pad it on the right with /// a pristine subtree of the same size) - hash_type get_root_hash() const; + machine_hash get_root_hash() const; /// \brief Returns proof for the next pristine leaf /// \returns Proof for leaf at given index, or throws exception @@ -140,11 +134,12 @@ class back_merkle_tree { proof_type get_next_leaf_proof() const; private: - int m_log2_root_size; ///< Log2 of tree size - int m_log2_leaf_size; ///< Log2 of leaf size + int m_log2_root_size; ///< Log2 of tree size + int m_log2_leaf_size; ///< Log2 of leaf size + mutable i_hasher m_hasher; address_type m_leaf_count{0}; ///< Number of leaves already added address_type m_max_leaves; ///< Maximum number of leaves - std::vector m_context; ///< Hashes of bits set in leaf_count + std::vector m_context; ///< Hashes of bits set in leaf_count pristine_merkle_tree m_pristine_hashes; ///< Hash of pristine subtrees of all sizes }; diff --git a/src/cartesi-machine.lua b/src/cartesi-machine.lua index ff5cfbc66..ce5d6626f 100755 --- a/src/cartesi-machine.lua +++ b/src/cartesi-machine.lua @@ -429,6 +429,12 @@ where options are: --load= load machine previously stored in . + --hash-tree-target= + set the target environment for hash tree verification + possible values are: + "uarch" (default) + "risc0" + --initial-hash print initial state hash before running machine. @@ -604,6 +610,7 @@ local htif_console_getchar = false local htif_yield_automatic = true local htif_yield_manual = true local initial_hash = false +local hash_tree_target = "uarch" local final_hash = false local initial_proof = {} local final_proof = {} @@ -1186,6 +1193,15 @@ local options = { return true end, }, + { + "^%-%-hash%-tree%-target%=(.*)$", + function(o) + if not o or #o < 1 then return false end + assert(o == "uarch" or o == "risc0", "invalid hash tree target " .. o) + hash_tree_target = o + return true + end, + }, { "^(%-%-initial%-proof%=(.+))$", function(all, opts) @@ -1719,8 +1735,10 @@ else uarch = uarch, flash_drive = {}, virtio = virtio, + hash_tree = { + target = hash_tree_target, + }, } - -- show splash on init if init_splash then config.dtb.init = config.dtb.init diff --git a/src/cartesi/util.lua b/src/cartesi/util.lua index f050ad64d..54efcfc72 100644 --- a/src/cartesi/util.lua +++ b/src/cartesi/util.lua @@ -121,6 +121,7 @@ function _M.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle, fina indentout(out, indent + 1, '"init_uarch_cycle": %u,\n', init_uarch_cycle) indentout(out, indent + 1, '"final_mcycle": %u,\n', final_mcycle) indentout(out, indent + 1, '"final_uarch_cycle": %u,\n', final_uarch_cycle) + indentout(out, indent + 1, '"hash_tree_target": %s,\n', log.hash_tree_target) indentout(out, indent + 1, '"accesses": [\n') dump_json_log_accesses(log.accesses, out, indent + 2) indentout(out, indent + 1, "]") diff --git a/src/clua-cartesi.cpp b/src/clua-cartesi.cpp index 9d513edaf..c06bd25ae 100644 --- a/src/clua-cartesi.cpp +++ b/src/clua-cartesi.cpp @@ -29,6 +29,7 @@ #include "machine-c-api.h" #include "machine-c-version.h" #include "riscv-constants.h" +#include "sha-256-hasher.h" #include "uarch-constants.h" #include "uarch-pristine.h" @@ -57,7 +58,47 @@ static const auto gperf_meta = clua_make_luaL_Reg_array({ static int cartesi_mod_keccak(lua_State *L) { using namespace cartesi; keccak_256_hasher h; - keccak_256_hasher::hash_type hash; + machine_hash hash; + if (lua_gettop(L) > 2) { + luaL_argerror(L, 3, "too many arguments"); + } + if (lua_gettop(L) < 1) { + luaL_argerror(L, 1, "too few arguments"); + } + if (lua_isinteger(L, 1) != 0) { + if (lua_gettop(L) > 1) { + luaL_argerror(L, 2, "too many arguments"); + } + uint64_t word = luaL_checkinteger(L, 1); + h.begin(); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + h.add_data(reinterpret_cast(&word), sizeof(word)); + h.end(hash); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + lua_pushlstring(L, reinterpret_cast(hash.data()), hash.size()); + return 1; + } + h.begin(); + size_t len1 = 0; + const char *hash1 = luaL_checklstring(L, 1, &len1); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + h.add_data(reinterpret_cast(hash1), len1); + size_t len2 = 0; + const char *hash2 = luaL_optlstring(L, 2, "", &len2); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + h.add_data(reinterpret_cast(hash2), len2); + h.end(hash); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + lua_pushlstring(L, reinterpret_cast(hash.data()), hash.size()); + return 1; +} + +/// \brief This is the cartesi.keccak() function implementation. +/// \param L Lua state. +static int cartesi_mod_sha256(lua_State *L) { + using namespace cartesi; + sha_256_hasher h; + machine_hash hash; if (lua_gettop(L) > 2) { luaL_argerror(L, 3, "too many arguments"); } @@ -148,6 +189,7 @@ static int cartesi_mod_new(lua_State *L) try { /// \brief Contents of the cartesi module table. static const auto cartesi_mod = clua_make_luaL_Reg_array({ {"keccak", cartesi_mod_keccak}, + {"sha256", cartesi_mod_sha256}, {"tobase64", cartesi_mod_tobase64}, {"frombase64", cartesi_mod_frombase64}, {"tojson", cartesi_mod_tojson}, @@ -192,6 +234,8 @@ CM_API int luaopen_cartesi(lua_State *L) { clua_setintegerfield(L, CM_VERSION_MINOR, "VERSION_MINOR", -1); clua_setintegerfield(L, CM_VERSION_PATCH, "VERSION_PATCH", -1); clua_setintegerfield(L, CM_HASH_SIZE, "HASH_SIZE", -1); + clua_setintegerfield(L, CM_HASH_TREE_TARGET_UARCH, "HASH_TREE_TARGET_UARCH", -1); + clua_setintegerfield(L, CM_HASH_TREE_TARGET_RISC0, "HASH_TREE_TARGET_RISC0", -1); clua_setintegerfield(L, CM_TREE_LOG2_WORD_SIZE, "TREE_LOG2_WORD_SIZE", -1); clua_setintegerfield(L, CM_TREE_LOG2_PAGE_SIZE, "TREE_LOG2_PAGE_SIZE", -1); clua_setintegerfield(L, CM_TREE_LOG2_ROOT_SIZE, "TREE_LOG2_ROOT_SIZE", -1); diff --git a/src/clua-i-virtual-machine.cpp b/src/clua-i-virtual-machine.cpp index 593a9b893..331f98d05 100644 --- a/src/clua-i-virtual-machine.cpp +++ b/src/clua-i-virtual-machine.cpp @@ -166,6 +166,7 @@ cm_reg clua_check_cm_proc_reg(lua_State *L, int idx) try { {"htif_ihalt", CM_REG_HTIF_IHALT}, {"htif_iconsole", CM_REG_HTIF_ICONSOLE}, {"htif_iyield", CM_REG_HTIF_IYIELD}, + {"hash_tree_target", CM_REG_HASH_TREE_TARGET}, {"uarch_x0", CM_REG_UARCH_X0}, {"uarch_x1", CM_REG_UARCH_X1}, {"uarch_x2", CM_REG_UARCH_X2}, diff --git a/src/complete-merkle-tree.cpp b/src/complete-merkle-tree.cpp index af28e3bbb..71d4eeeaf 100644 --- a/src/complete-merkle-tree.cpp +++ b/src/complete-merkle-tree.cpp @@ -28,10 +28,11 @@ namespace cartesi { -complete_merkle_tree::complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size) : +complete_merkle_tree::complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher h) : m_log2_root_size{log2_root_size}, m_log2_leaf_size{log2_leaf_size}, - m_pristine{log2_root_size, log2_word_size}, + m_hasher{h}, + m_pristine{h, log2_root_size, log2_word_size}, m_tree(std::max(0, log2_root_size - log2_leaf_size + 1)) { check_log2_sizes(log2_root_size, log2_leaf_size, log2_word_size); } @@ -61,7 +62,7 @@ complete_merkle_tree::proof_type complete_merkle_tree::get_proof(address_type ad /// \brief Appends a new leaf hash to the tree /// \param hash Hash to append -void complete_merkle_tree::push_back(const hash_type &hash) { +void complete_merkle_tree::push_back(const machine_hash &hash) { auto &leaves = get_level(get_log2_leaf_size()); if (leaves.size() >= address_type{1} << (get_log2_root_size() - get_log2_leaf_size())) { throw std::out_of_range{"tree is full"}; @@ -91,7 +92,7 @@ void complete_merkle_tree::check_log2_sizes(int log2_root_size, int log2_leaf_si } } -const complete_merkle_tree::hash_type &complete_merkle_tree::get_node_hash(address_type address, int log2_size) const { +const machine_hash &complete_merkle_tree::get_node_hash(address_type address, int log2_size) const { const auto &level = get_level(log2_size); address >>= log2_size; if (address >= (address_type{1} << (get_log2_root_size() - log2_size))) { @@ -104,7 +105,6 @@ const complete_merkle_tree::hash_type &complete_merkle_tree::get_node_hash(addre } void complete_merkle_tree::bubble_up() { - hasher_type h; // Go bottom up, updating hashes for (int log2_next_size = get_log2_leaf_size() + 1; log2_next_size <= get_log2_root_size(); ++log2_next_size) { auto log2_prev_size = log2_next_size - 1; @@ -121,11 +121,11 @@ void complete_merkle_tree::bubble_up() { auto last_safe_entry = prev.size() / 2; // Do all entries for which we have two non-pristine children for (; first_entry < last_safe_entry; ++first_entry) { - get_concat_hash(h, prev[2 * first_entry], prev[(2 * first_entry) + 1], next[first_entry]); + m_hasher.get_concat_hash(prev[2 * first_entry], prev[(2 * first_entry) + 1], next[first_entry]); } // Maybe do last odd entry if (prev.size() > 2 * last_safe_entry) { - get_concat_hash(h, prev.back(), m_pristine.get_hash(log2_prev_size), next[last_safe_entry]); + m_hasher.get_concat_hash(prev.back(), m_pristine.get_hash(log2_prev_size), next[last_safe_entry]); } } } diff --git a/src/complete-merkle-tree.h b/src/complete-merkle-tree.h index f56783bc3..5986f0bc7 100644 --- a/src/complete-merkle-tree.h +++ b/src/complete-merkle-tree.h @@ -21,7 +21,7 @@ #include #include -#include "keccak-256-hasher.h" +#include "i-hasher.h" #include "merkle-tree-proof.h" #include "meta.h" #include "pristine-merkle-tree.h" @@ -38,34 +38,29 @@ namespace cartesi { /// The tree is optimized to store only the hashes that are not pristine. class complete_merkle_tree { public: - /// \brief Hasher class. - using hasher_type = keccak_256_hasher; - - /// \brief Storage for a hash. - using hash_type = hasher_type::hash_type; - /// \brief Storage for an address. using address_type = uint64_t; /// \brief Storage for a proof. - using proof_type = merkle_tree_proof; + using proof_type = merkle_tree_proof; /// \brief Storage for a level in the tree. - using level_type = std::vector; + using level_type = std::vector; /// \brief Constructor for pristine tree /// \param log2_root_size Log2 of tree size /// \param log2_leaf_size Log2 of leaf node /// \param log2_word_size Log2 of word - complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size); + complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher h); /// \brief Constructor from non-pristine leaves (assumed flushed left) /// \param log2_root_size Log2 of tree size /// \param log2_leaf_size Log2 of leaf node /// \param log2_word_size Log2 of word + /// \param hasher Hasher template - complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, L &&leaves) : - complete_merkle_tree{log2_root_size, log2_leaf_size, log2_word_size} { + complete_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher, L &&leaves) : + complete_merkle_tree{log2_root_size, log2_leaf_size, log2_word_size, hasher} { static_assert(std::is_same_v::type>, "not a leaves vector"); get_level(get_log2_leaf_size()) = std::forward(leaves); bubble_up(); @@ -73,14 +68,14 @@ class complete_merkle_tree { /// \brief Returns the tree's root hash /// \returns Root hash - hash_type get_root_hash() const { + machine_hash get_root_hash() const { return get_node_hash(0, get_log2_root_size()); } /// \brief Returns the hash of a node at a given address of a given size /// \param address Node address /// \param log2_size Log2 size subintended by node - const hash_type &get_node_hash(address_type address, int log2_size) const; + const machine_hash &get_node_hash(address_type address, int log2_size) const; /// \brief Returns proof for a given node /// \param address Node address @@ -90,7 +85,7 @@ class complete_merkle_tree { /// \brief Appends a new leaf hash to the tree /// \param hash Hash to append - void push_back(const hash_type &hash); + void push_back(const machine_hash &hash); /// \brief Returns number of leaves in tree address_type size() const { @@ -131,6 +126,7 @@ class complete_merkle_tree { int m_log2_root_size; ///< Log2 of tree size int m_log2_leaf_size; ///< Log2 of page size + mutable i_hasher m_hasher; ///< Hash function pristine_merkle_tree m_pristine; ///< Pristine hashes for all levels std::vector m_tree; ///< Merkle tree }; diff --git a/src/full-merkle-tree.cpp b/src/full-merkle-tree.cpp index 5b107d8fc..5788bd926 100644 --- a/src/full-merkle-tree.cpp +++ b/src/full-merkle-tree.cpp @@ -28,26 +28,28 @@ namespace cartesi { -full_merkle_tree::full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size) : +full_merkle_tree::full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher) : m_log2_root_size{log2_root_size}, m_log2_leaf_size{log2_leaf_size}, + m_hasher{hasher}, m_max_leaves{address_type{1} << std::max(0, log2_root_size - log2_leaf_size)} { check_log2_sizes(log2_root_size, log2_leaf_size, log2_word_size); m_tree.resize(2 * m_max_leaves); - init_pristine_subtree(pristine_merkle_tree{log2_root_size, log2_word_size}, 1, log2_root_size); + init_pristine_subtree(pristine_merkle_tree{m_hasher, log2_root_size, log2_word_size}, 1, log2_root_size); } -full_merkle_tree::full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, - const std::vector &leaves) : +full_merkle_tree::full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher, + const std::vector &leaves) : m_log2_root_size(log2_root_size), m_log2_leaf_size(log2_leaf_size), + m_hasher{hasher}, m_max_leaves{address_type{1} << std::max(0, log2_root_size - log2_leaf_size)} { check_log2_sizes(log2_root_size, log2_leaf_size, log2_word_size); if (leaves.size() > m_max_leaves) { throw std::out_of_range{"too many leaves"}; } m_tree.resize(2 * m_max_leaves); - init_tree(pristine_merkle_tree{log2_root_size, log2_word_size}, leaves); + init_tree(pristine_merkle_tree{m_hasher, log2_root_size, log2_word_size}, leaves); } full_merkle_tree::proof_type full_merkle_tree::get_proof(address_type address, int log2_size) const { @@ -67,8 +69,7 @@ full_merkle_tree::proof_type full_merkle_tree::get_proof(address_type address, i proof.set_sibling_hash(get_node_hash(sibling_address, log2_sibling_size), log2_sibling_size); } #ifndef NDEBUG - hasher_type h{}; - if (!proof.verify(h)) { + if (!proof.verify(m_hasher)) { throw std::runtime_error{"produced invalid proof"}; } #endif @@ -104,20 +105,19 @@ void full_merkle_tree::init_pristine_subtree(const pristine_merkle_tree &pristin } } -void full_merkle_tree::init_subtree(hasher_type &h, int index, int log2_size) { +void full_merkle_tree::init_subtree(i_hasher &h, int index, int log2_size) { if (log2_size > get_log2_leaf_size()) { init_subtree(h, left_child_index(index), log2_size - 1); init_subtree(h, right_child_index(index), log2_size - 1); - get_concat_hash(h, m_tree[left_child_index(index)], m_tree[right_child_index(index)], m_tree[index]); + h.get_concat_hash(m_tree[left_child_index(index)], m_tree[right_child_index(index)], m_tree[index]); } } -void full_merkle_tree::init_tree(const pristine_merkle_tree &pristine, const std::vector &leaves) { +void full_merkle_tree::init_tree(const pristine_merkle_tree &pristine, const std::vector &leaves) { std::copy(leaves.begin(), leaves.end(), &m_tree[m_max_leaves]); std::fill_n(&m_tree[m_max_leaves + leaves.size()], m_max_leaves - leaves.size(), pristine.get_hash(get_log2_leaf_size())); - hasher_type h; - init_subtree(h, 1, get_log2_root_size()); + init_subtree(m_hasher, 1, get_log2_root_size()); } full_merkle_tree::address_type full_merkle_tree::get_node_index(address_type address, int log2_size) const { diff --git a/src/full-merkle-tree.h b/src/full-merkle-tree.h index cc3035538..17a06e1c3 100644 --- a/src/full-merkle-tree.h +++ b/src/full-merkle-tree.h @@ -20,7 +20,7 @@ #include #include -#include "keccak-256-hasher.h" +#include "i-hasher.h" #include "merkle-tree-proof.h" #include "pristine-merkle-tree.h" @@ -33,30 +33,25 @@ namespace cartesi { /// \details This class implements a full merkle tree class full_merkle_tree { public: - /// \brief Hasher class. - using hasher_type = keccak_256_hasher; - - /// \brief Storage for a hash. - using hash_type = hasher_type::hash_type; - /// \brief Storage for an address. using address_type = uint64_t; /// \brief Storage for a proof. - using proof_type = merkle_tree_proof; + using proof_type = merkle_tree_proof; /// \brief Constructor for a pristine tree /// \param log2_root_size Log2 of root node /// \param log2_leaf_size Log2 of leaf node /// \param log2_word_size Log2 of word - full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size); + full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher); /// \brief Constructor for list of consecutive leaf hashes /// \param log2_root_size Log2 of root node /// \param log2_leaf_size Log2 of leaf node /// \param log2_word_size Log2 of word /// \param leaves List of leaf hashes - full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, const std::vector &leaves); + full_merkle_tree(int log2_root_size, int log2_leaf_size, int log2_word_size, i_hasher hasher, + const std::vector &leaves); /// \brief Returns log2 of size of tree int get_log2_root_size() const { @@ -70,14 +65,14 @@ class full_merkle_tree { /// \brief Returns the tree's root hash /// \returns Root hash - const hash_type &get_root_hash() const { + const machine_hash &get_root_hash() const { return get_node_hash(0, get_log2_root_size()); } /// \brief Returns the hash of a node at a given address of a given size /// \param address Node address /// \param log2_size Log2 size subintended by node - const hash_type &get_node_hash(address_type address, int log2_size) const { + const machine_hash &get_node_hash(address_type address, int log2_size) const { return m_tree[get_node_index(address, log2_size)]; } @@ -118,24 +113,25 @@ class full_merkle_tree { /// \param log2_size Log2 size of root at index /// \details The nodes corresponding to subtrees of size log2_leaf_size /// are assumed to have already been set prior to calling this function - void init_subtree(hasher_type &h, int index, int log2_size); + void init_subtree(i_hasher &h, int index, int log2_size); /// \brief Initialize tree from a list of consecutive page hashes /// \param leaves List of page hashes /// \details The page hashes in leaves are copied to the appropriate /// subtree nodes, in order, and the rest are filled with pristine /// page hashes - void init_tree(const pristine_merkle_tree &pristine, const std::vector &leaves); + void init_tree(const pristine_merkle_tree &pristine, const std::vector &leaves); /// \brief Returns index of a node in the tree array /// \param address Node address /// \param log2_size address_type get_node_index(address_type address, int log2_size) const; - int m_log2_root_size; ///< Log2 of tree size - int m_log2_leaf_size; ///< Log2 of leaf size - address_type m_max_leaves; ///< Maximum number of leaves - std::vector m_tree; ///< Binary heap with tree node hashes + int m_log2_root_size; ///< Log2 of tree size + int m_log2_leaf_size; ///< Log2 of leaf size + mutable i_hasher m_hasher; ///< Hasher object + address_type m_max_leaves; ///< Maximum number of leaves + std::vector m_tree; ///< Binary heap with tree node hashes }; } // namespace cartesi diff --git a/src/hash-tree-target.h b/src/hash-tree-target.h new file mode 100644 index 000000000..59464a85d --- /dev/null +++ b/src/hash-tree-target.h @@ -0,0 +1,56 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef HASH_TREE_TARGET_H +#define HASH_TREE_TARGET_H + +#include +#include +#include +#include +#include + +namespace cartesi { + +/// \brief Target environment for hash tree verification +enum class hash_tree_target : uint64_t { + uarch, ///< Micro-architecture + risc0, ///< RISC0 +}; + +static inline std::optional parse_hash_tree_target(uint64_t value) { + switch (static_cast(value)) { + case hash_tree_target::uarch: + case hash_tree_target::risc0: + return static_cast(value); + default: + return std::nullopt; + } +} + +static inline std::optional parse_hash_tree_target(const char *name) { + if (strcmp(name, "uarch") == 0) { + return hash_tree_target::uarch; + } + if (strcmp(name, "risc0") == 0) { + return hash_tree_target::risc0; + } + return std::nullopt; +} + +} // namespace cartesi + +#endif // HASH_H diff --git a/src/i-hasher.h b/src/i-hasher.h index 70fc17390..e8fd1f8f1 100644 --- a/src/i-hasher.h +++ b/src/i-hasher.h @@ -18,123 +18,128 @@ #define I_HASHER_H /// \file -/// \brief Hasher interface +/// \brief Hasher class -#include #include #include #include #include +#include +#include "hash-tree-target.h" +#include "keccak-256-hasher.h" +#include "machine-config.h" +#include "machine-hash.h" #include "meta.h" +#include "sha-256-hasher.h" namespace cartesi { -/// \brief Hasher interface. -/// \tparam DERIVED Derived class implementing the interface. (An example of CRTP.) -/// \tparam HASH_SIZE Size of hash. -template -class i_hasher { // CRTP - i_hasher() = default; - friend DERIVED; - - /// \brief Returns object cast as the derived class - DERIVED &derived() { - return *static_cast(this); - } - - /// \brief Returns object cast as the derived class - const DERIVED &derived() const { - return *static_cast(this); +/// \brief Hasher interface +class i_hasher { + hash_tree_target m_hash_tree_target; + std::variant m_hasher; + + /// \brief Constructor + /// \param hash_tree_target Hash tree target. + /// \details The actual hashing algorithm is selected based on the hash tree target. + explicit i_hasher(hash_tree_target hash_tree_target) : m_hash_tree_target(hash_tree_target) { + switch (hash_tree_target) { + case hash_tree_target::uarch: + m_hasher = keccak_256_hasher(); + break; + case hash_tree_target::risc0: + m_hasher = sha_256_hasher(); + break; + } } public: - constexpr static size_t hash_size = HASH_SIZE::value; - - using hash_type = std::array; + /// \brief Returns the hash tree target + hash_tree_target get_hash_tree_target() const { + return m_hash_tree_target; + } + /// \brief Begins the hashing process void begin() { - return derived().do_begin(); + std::visit([](auto &h) { h.begin(); }, m_hasher); } + /// \brief Adds data to the hash + /// \param data Pointer to the data to be added + /// \param length Length of the data to be added void add_data(const unsigned char *data, size_t length) { - return derived().do_add_data(data, length); + std::visit([data, length](auto &h) { h.add_data(data, length); }, m_hasher); } - void end(hash_type &hash) { - return derived().do_end(hash); + /// \brief Finalizes the hash + /// \param hash Receives the resulting hash + void end(machine_hash &hash) { + std::visit([&hash](auto &h) { h.end(hash); }, m_hasher); } -}; -template -using is_an_i_hasher = - std::integral_constant::type>::value>; - -/// \brief Computes the hash of concatenated hashes -/// \tparam H Hasher class -/// \param h Hasher object -/// \param left Left hash to concatenate -/// \param right Right hash to concatenate -/// \param result Receives the hash of the concatenation -template -inline static void get_concat_hash(H &h, const typename H::hash_type &left, const typename H::hash_type &right, - typename H::hash_type &result) { - static_assert(is_an_i_hasher::value, "not an i_hasher"); - h.begin(); - h.add_data(left.data(), static_cast(left.size())); - h.add_data(right.data(), static_cast(right.size())); - h.end(result); -} - -/// \brief Computes the hash of concatenated hashes -/// \tparam H Hasher class -/// \param h Hasher object -/// \param left Left hash to concatenate -/// \param right Right hash to concatenate -/// \return The hash of the concatenation -template -inline static typename H::hash_type get_concat_hash(H &h, const typename H::hash_type &left, - const typename H::hash_type &right) { - static_assert(is_an_i_hasher::value, "not an i_hasher"); - h.begin(); - h.add_data(left.data(), left.size()); - h.add_data(right.data(), right.size()); - typename H::hash_type result; - h.end(result); - return result; -} - -/// \brief Computes a merkle tree hash of a data buffer -/// \tparam H Hasher class -/// \param h Hasher object -/// \param data Data to be hashed -/// \param data_length Length of data -/// \param word_length Length of each word -/// \param result Receives the resulting merkle tree hash -template -inline static void get_merkle_tree_hash(H &h, const unsigned char *data, uint64_t data_length, uint64_t word_length, - typename H::hash_type &result) { - if (data_length > word_length) { - if (data_length & 1) { - throw std::invalid_argument("data_length must be a power of 2 multiple of word_length"); + /// \brief Creates a hasher object + static i_hasher make_uarch() { + return make(hash_tree_target::uarch); + } + + /// \brief Creates a hasher object + static i_hasher make_risc0() { + return make(hash_tree_target::risc0); + } + + /// \brief Creates a hasher object + static i_hasher make(hash_tree_target hash_tree_target) { + switch (hash_tree_target) { + case hash_tree_target::uarch: + return i_hasher(hash_tree_target::uarch); + case hash_tree_target::risc0: + return i_hasher(hash_tree_target::risc0); } - data_length = data_length / 2; - typename H::hash_type left; - get_merkle_tree_hash(h, data, data_length, word_length, left); - get_merkle_tree_hash(h, data + data_length, data_length, word_length, result); - h.begin(); - h.add_data(left.data(), left.size()); - h.add_data(result.data(), result.size()); - h.end(result); - } else { - if (data_length != word_length) { - throw std::invalid_argument("data_length must be a power of 2 multiple of word_length"); + } + + /// \brief Computes the hash of concatenated hashes + /// \param h Hasher object + /// \param left Left hash to concatenate + /// \param right Right hash to concatenate + /// \param result Receives the hash of the concatenation + void get_concat_hash(const machine_hash &left, const machine_hash &right, machine_hash &result) { + begin(); + add_data(left.data(), static_cast(left.size())); + add_data(right.data(), static_cast(right.size())); + end(result); + } + + /// \brief Computes a merkle tree hash of a data buffer + /// \param h Hasher object + /// \param data Data to be hashed + /// \param data_length Length of data + /// \param word_length Length of each word + /// \param result Receives the resulting merkle tree hash + void get_merkle_tree_hash(const unsigned char *data, uint64_t data_length, uint64_t word_length, + machine_hash &result) { + if (data_length > word_length) { + if ((data_length & 1) != 0) { + throw std::invalid_argument("data_length must be a power of 2 multiple of word_length"); + } + data_length = data_length / 2; + machine_hash left; + get_merkle_tree_hash(data, data_length, word_length, left); + get_merkle_tree_hash(data + data_length, data_length, word_length, result); + begin(); + add_data(left.data(), left.size()); + add_data(result.data(), result.size()); + end(result); + } else { + if (data_length != word_length) { + throw std::invalid_argument("data_length must be a power of 2 multiple of word_length"); + } + begin(); + add_data(data, data_length); + end(result); } - h.begin(); - h.add_data(data, data_length); - h.end(result); } -} +}; } // namespace cartesi diff --git a/src/i-virtual-machine.h b/src/i-virtual-machine.h index 2ea1dd3cd..4f5332b67 100644 --- a/src/i-virtual-machine.h +++ b/src/i-virtual-machine.h @@ -44,7 +44,6 @@ namespace cartesi { /// \} class i_virtual_machine { public: - using hash_type = machine_merkle_tree::hash_type; using reg = machine_reg; /// \brief Constructor @@ -104,7 +103,7 @@ class i_virtual_machine { } /// \brief Obtains the root hash of the Merkle tree. - void get_root_hash(hash_type &hash) const { + void get_root_hash(machine_hash &hash) const { do_get_root_hash(hash); } @@ -226,26 +225,26 @@ class i_virtual_machine { } /// \brief Checks the validity of a state transition caused by log_step. - interpreter_break_reason verify_step(const hash_type &root_hash_before, const std::string &log_filename, - uint64_t mcycle_count, const hash_type &root_hash_after) const { + interpreter_break_reason verify_step(const machine_hash &root_hash_before, const std::string &log_filename, + uint64_t mcycle_count, const machine_hash &root_hash_after) const { return do_verify_step(root_hash_before, log_filename, mcycle_count, root_hash_after); } /// \brief Checks the validity of a state transition caused by log_step_uarch. - void verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { + void verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { do_verify_step_uarch(root_hash_before, log, root_hash_after); } /// \brief Checks the validity of a state transition caused by log_reset_uarch. - void verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { + void verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { do_verify_reset_uarch(root_hash_before, log, root_hash_after); } /// \brief Checks the validity of state transitions caused by log_send_cmio_response. void verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after) const { do_verify_send_cmio_response(reason, data, length, root_hash_before, log, root_hash_after); } @@ -264,7 +263,7 @@ class i_virtual_machine { virtual interpreter_break_reason do_log_step(uint64_t mcycle_count, const std::string &filename) = 0; virtual access_log do_log_step_uarch(const access_log::type &log_type) = 0; virtual machine_merkle_tree::proof_type do_get_proof(uint64_t address, int log2_size) const = 0; - virtual void do_get_root_hash(hash_type &hash) const = 0; + virtual void do_get_root_hash(machine_hash &hash) const = 0; virtual bool do_verify_merkle_tree() const = 0; virtual uint64_t do_read_reg(reg r) const = 0; virtual void do_write_reg(reg w, uint64_t val) = 0; @@ -289,14 +288,14 @@ class i_virtual_machine { const access_log::type &log_type) = 0; virtual uint64_t do_get_reg_address(reg r) const = 0; virtual machine_config do_get_default_config() const = 0; - virtual interpreter_break_reason do_verify_step(const hash_type &root_hash_before, const std::string &log_filename, - uint64_t mcycle_count, const hash_type &root_hash_after) const = 0; - virtual void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const = 0; - virtual void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const = 0; + virtual interpreter_break_reason do_verify_step(const machine_hash &root_hash_before, + const std::string &log_filename, uint64_t mcycle_count, const machine_hash &root_hash_after) const = 0; + virtual void do_verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const = 0; + virtual void do_verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const = 0; virtual void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const = 0; + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after) const = 0; virtual bool do_is_jsonrpc_virtual_machine() const { return false; } diff --git a/src/json-util.cpp b/src/json-util.cpp index a80e85e07..6da242d95 100644 --- a/src/json-util.cpp +++ b/src/json-util.cpp @@ -59,7 +59,7 @@ std::string encode_base64(const unsigned char *data, uint64_t length) { return encode_base64(input); } -std::string encode_base64(const machine_merkle_tree::hash_type &hash) { +std::string encode_base64(const machine_hash &hash) { return encode_base64(hash.data(), hash.size()); } @@ -187,6 +187,7 @@ static auto reg_from_name(const std::string &name) { {"htif_ihalt", reg::htif_ihalt}, {"htif_iconsole", reg::htif_iconsole}, {"htif_iyield", reg::htif_iyield}, + {"hash_tree_target", reg::hash_tree_target}, {"uarch_x0", reg::uarch_x0}, {"uarch_x1", reg::uarch_x1}, {"uarch_x2", reg::uarch_x2}, @@ -461,6 +462,8 @@ static auto reg_to_name(machine::reg r) { return "htif_iconsole"; case reg::htif_iyield: return "htif_iyield"; + case reg::hash_tree_target: + return "hash_tree_target"; case reg::uarch_x0: return "uarch_x0"; case reg::uarch_x1: @@ -814,8 +817,7 @@ template void ju_get_opt_field(const nlohmann::json &j, const std:: machine_runtime_config &value, const std::string &path); template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_merkle_tree::proof_type::hash_type &value, - const std::string &path) { +void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_hash &value, const std::string &path) { if (!contains(j, key)) { return; } @@ -831,8 +833,8 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_merkle_tree } template -void ju_get_opt_field(const nlohmann::json &j, const K &key, - std::optional &optional, const std::string &path) { +void ju_get_opt_field(const nlohmann::json &j, const K &key, std::optional &optional, + const std::string &path) { optional = {}; if (!contains(j, key)) { return; @@ -849,11 +851,11 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, std::copy(bin.begin(), bin.end(), optional.value().data()); } -template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, - machine_merkle_tree::proof_type::hash_type &value, const std::string &path); +template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_hash &value, + const std::string &path); -template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_merkle_tree::proof_type::hash_type &value, const std::string &path); +template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, machine_hash &value, + const std::string &path); template void ju_get_opt_field(const nlohmann::json &j, const K &key, @@ -879,10 +881,10 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_merkle_tree::proof_type::address_type target_address = 0; ju_get_field(jk, "target_address"s, target_address, new_path); proof.set_target_address(target_address); - machine_merkle_tree::proof_type::hash_type target_hash; + machine_hash target_hash; ju_get_field(jk, "target_hash"s, target_hash, new_path); proof.set_target_hash(target_hash); - machine_merkle_tree::proof_type::hash_type root_hash; + machine_hash root_hash; ju_get_field(jk, "root_hash"s, root_hash, new_path); proof.set_root_hash(root_hash); if (!contains(jk, "sibling_hashes")) { @@ -895,7 +897,7 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, const auto sibling_hashes_base = path + "sibling_hashes/"; for (int log2_size = proof.get_log2_target_size(), i = 0; log2_size < proof.get_log2_root_size(); ++log2_size, ++i) { - machine_merkle_tree::proof_type::hash_type sibling_hash; + machine_hash sibling_hash; ju_get_field(sh, i, sibling_hash, sibling_hashes_base); proof.set_sibling_hash(sibling_hash, log2_size); } @@ -995,11 +997,11 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, access &access, con uint64_t address = 0; ju_get_field(jk, "address"s, address, new_path); access.set_address(address); - machine_merkle_tree::proof_type::hash_type read_hash; + machine_hash read_hash; ju_get_field(jk, "read_hash", read_hash, new_path); access.set_read_hash(read_hash); - not_default_constructible written_hash; + not_default_constructible written_hash; ju_get_opt_field(jk, "written_hash", written_hash, new_path); if (written_hash.has_value()) { access.set_written_hash(written_hash.value()); @@ -1125,6 +1127,8 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, not_default_constru if (!log_type.has_value()) { throw std::logic_error("log_type conversion bug"); } + hash_tree_target target{hash_tree_target::uarch}; + ju_get_opt_field(jk, "hash_tree_target"s, target, new_path); std::vector accesses; ju_get_vector_like_field(jk, "accesses"s, accesses, new_path); for (unsigned i = 0; i < accesses.size(); ++i) { @@ -1149,7 +1153,7 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, not_default_constru } } } - optional.emplace(std::move(accesses), std::move(brackets), std::move(notes), log_type.value()); + optional.emplace(std::move(accesses), std::move(brackets), std::move(notes), log_type.value(), target); } template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, @@ -1526,6 +1530,44 @@ template void ju_get_opt_field(const nlohmann::json &j, const uint64_t template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, cmio_config &value, const std::string &path); +template +void ju_get_opt_field(const nlohmann::json &j, const K &key, hash_tree_target &value, const std::string &path) { + if (!contains(j, key)) { + return; + } + std::string temp{}; + ju_get_field(j, key, temp, path); + auto maybe_hash_tree_target = parse_hash_tree_target(temp.c_str()); + if (maybe_hash_tree_target.has_value()) { + value = maybe_hash_tree_target.value(); + return; + } + + throw std::invalid_argument("field \""s + path + to_string(key) + "\" has invalid value"); +} + +template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, hash_tree_target &value, + const std::string &base); + +template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, hash_tree_target &value, + const std::string &base); + +template +void ju_get_opt_field(const nlohmann::json &j, const K &key, hash_tree_config &value, const std::string &path) { + if (!contains(j, key)) { + return; + } + const auto &jconfig = j[key]; + const auto new_path = path + to_string(key) + "/"; + ju_get_field(jconfig, "target"s, value.target, new_path); +} + +template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, hash_tree_config &value, + const std::string &path); + +template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, hash_tree_config &value, + const std::string &path); + template void ju_get_opt_field(const nlohmann::json &j, const K &key, uarch_processor_config &value, const std::string &path) { if (!contains(j, key)) { @@ -1627,6 +1669,7 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_config &val ju_get_opt_field(config, "htif"s, value.htif, new_path); ju_get_opt_field(config, "uarch"s, value.uarch, new_path); ju_get_opt_field(config, "cmio"s, value.cmio, new_path); + ju_get_opt_field(config, "hash_tree"s, value.hash_tree, new_path); } template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_config &value, @@ -1687,14 +1730,14 @@ void to_json(nlohmann::json &j, const machine::reg ®) { j = reg_to_name(reg); } -void to_json(nlohmann::json &j, const machine_merkle_tree::hash_type &h) { +void to_json(nlohmann::json &j, const machine_hash &h) { j = encode_base64(h); } -void to_json(nlohmann::json &j, const std::vector &hs) { +void to_json(nlohmann::json &j, const std::vector &hs) { j = nlohmann::json::array(); std::transform(hs.cbegin(), hs.cend(), std::back_inserter(j), - [](const machine_merkle_tree::hash_type &h) -> nlohmann::json { return h; }); + [](const machine_hash &h) -> nlohmann::json { return h; }); } void to_json(nlohmann::json &j, const machine_merkle_tree::proof_type &p) { @@ -1764,7 +1807,9 @@ void to_json(nlohmann::json &j, const access_log::type &log_type) { } void to_json(nlohmann::json &j, const access_log &log) { - j = nlohmann::json{{"log_type", log.get_log_type()}, {"accesses", log.get_accesses()}}; + j = nlohmann::json{{"log_type", log.get_log_type()}, {"hash_tree_target", log.get_hash_tree_target()}, + {"accesses", log.get_accesses()}}; + if (log.get_log_type().has_annotations()) { j["notes"] = log.get_notes(); j["brackets"] = log.get_brackets(); @@ -1955,6 +2000,23 @@ void to_json(nlohmann::json &j, const uarch_config &config) { }; } +void to_json(nlohmann::json &j, const hash_tree_target &target) { + switch (target) { + case hash_tree_target::uarch: + j = "uarch"; + break; + case hash_tree_target::risc0: + j = "risc0"; + break; + } +} + +void to_json(nlohmann::json &j, const hash_tree_config &config) { + j = nlohmann::json{ + {"target", config.target}, + }; +} + void to_json(nlohmann::json &j, const machine_config &config) { j = nlohmann::json{ {"processor", config.processor}, @@ -1968,6 +2030,7 @@ void to_json(nlohmann::json &j, const machine_config &config) { {"htif", config.htif}, {"uarch", config.uarch}, {"cmio", config.cmio}, + {"hash_tree", config.hash_tree}, }; } diff --git a/src/json-util.h b/src/json-util.h index 8a67c377e..481cc8cd5 100644 --- a/src/json-util.h +++ b/src/json-util.h @@ -204,8 +204,7 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_runtime_con /// \param value Object to store value /// \param path Path to j template -void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_merkle_tree::proof_type::hash_type &value, - const std::string &path = "params/"); +void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_hash &value, const std::string &path = "params/"); /// \brief Attempts to load a hash from a field in a JSON object /// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) @@ -214,8 +213,28 @@ void ju_get_opt_field(const nlohmann::json &j, const K &key, machine_merkle_tree /// \param value Object to store value /// \param path Path to j template -void ju_get_opt_field(const nlohmann::json &j, const K &key, - std::optional &optional, const std::string &path = "params/"); +void ju_get_opt_field(const nlohmann::json &j, const K &key, std::optional &optional, + const std::string &path = "params/"); + +/// \brief Attempts to load a hash_tree_config object from a field in a JSON object +/// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) +/// \param j JSON object to load from +/// \param key Key to load value from +/// \param value Object to store value +/// \param path Path to j +template +void ju_get_opt_field(const nlohmann::json &j, const K &key, hash_tree_target &value, + const std::string &path = "params/"); + +/// \brief Attempts to load a hash_tree_config object from a field in a JSON object +/// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) +/// \param j JSON object to load from +/// \param key Key to load value from +/// \param value Object to store value +/// \param path Path to j +template +void ju_get_opt_field(const nlohmann::json &j, const K &key, hash_tree_config &value, + const std::string &path = "params/"); /// \brief Attempts to load an Merkle tree proof object from a field in a JSON object /// \tparam K Key type (explicit extern declarations for uint64_t and std::string are provided) @@ -587,7 +606,7 @@ std::string encode_base64(const unsigned char *data, uint64_t length); /// \brief Encodes a hash as a base64 string /// \param hash Hash to encode /// \returns Encoded data -std::string encode_base64(const machine_merkle_tree::hash_type &hash); +std::string encode_base64(const machine_hash &hash); /// \brief Encodes an access_data object as a base64 string /// \param data Access data to encode @@ -596,8 +615,10 @@ std::string encode_base64(const access_data &data); // Automatic conversion functions from Cartesi types to nlohmann::json void to_json(nlohmann::json &j, const access_log::type &log_type); -void to_json(nlohmann::json &j, const machine_merkle_tree::hash_type &h); -void to_json(nlohmann::json &j, const std::vector &hs); +void to_json(nlohmann::json &j, const machine_hash &h); +void to_json(nlohmann::json &j, const hash_tree_target &target); +void to_json(nlohmann::json &j, const hash_tree_config &config); +void to_json(nlohmann::json &j, const std::vector &hs); void to_json(nlohmann::json &j, const machine_merkle_tree::proof_type &p); void to_json(nlohmann::json &j, const access &a); void to_json(nlohmann::json &j, const bracket_note &b); @@ -675,10 +696,18 @@ extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &k const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, machine_runtime_config &value, const std::string &base = "params/"); -extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, - machine_merkle_tree::proof_type::hash_type &value, const std::string &base = "params/"); -extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, - machine_merkle_tree::proof_type::hash_type &value, const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, machine_hash &value, + const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, machine_hash &value, + const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, hash_tree_target &value, + const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, + hash_tree_target &value, const std::string &base = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, hash_tree_config &value, + const std::string &path = "params/"); +extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, + hash_tree_config &value, const std::string &path = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const uint64_t &key, not_default_constructible &value, const std::string &base = "params/"); extern template void ju_get_opt_field(const nlohmann::json &j, const std::string &key, diff --git a/src/jsonrpc-discover.json b/src/jsonrpc-discover.json index 24a90b15e..70274da7d 100644 --- a/src/jsonrpc-discover.json +++ b/src/jsonrpc-discover.json @@ -1627,6 +1627,15 @@ "$ref": "#/components/schemas/VirtIODeviceConfig" } }, + "HashTreeConfigs": { + "title": "HashTreeConfigs", + "type": "object", + "properties": { + "target": { + "type": "string" + } + } + }, "MachineConfig": { "title": "MachineConfig", "type": "object", @@ -1663,6 +1672,9 @@ }, "virtio": { "$ref": "#/components/schemas/VirtIOConfigs" + }, + "hash_tree": { + "$ref": "#/components/schemas/HashTreeConfigs" } }, "required": ["ram"] diff --git a/src/jsonrpc-remote-machine.cpp b/src/jsonrpc-remote-machine.cpp index 99473ff60..6572d2b07 100644 --- a/src/jsonrpc-remote-machine.cpp +++ b/src/jsonrpc-remote-machine.cpp @@ -1054,8 +1054,7 @@ static json jsonrpc_machine_log_reset_uarch_handler(const json &j, const std::sh static json jsonrpc_machine_verify_step_handler(const json &j, const std::shared_ptr &session) { (void) session; static const char *param_name[] = {"root_hash_before", "filename", "mcycle_count", "root_hash_after"}; - auto args = parse_args(j, param_name); + auto args = parse_args(j, param_name); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) auto reason = cartesi::machine::verify_step(std::get<0>(args), std::get<1>(args), std::get<2>(args), std::get<3>(args)); @@ -1069,9 +1068,8 @@ static json jsonrpc_machine_verify_step_handler(const json &j, const std::shared static json jsonrpc_machine_verify_step_uarch_handler(const json &j, const std::shared_ptr & /*session*/) { static const char *param_name[] = {"root_hash_before", "log", "root_hash_after"}; - auto args = - parse_args, - cartesi::machine_merkle_tree::hash_type>(j, param_name); + auto args = parse_args, + cartesi::machine_hash>(j, param_name); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) cartesi::machine::verify_step_uarch(std::get<0>(args), std::get<1>(args).value(), std::get<2>(args)); return jsonrpc_response_ok(j); @@ -1084,9 +1082,8 @@ static json jsonrpc_machine_verify_step_uarch_handler(const json &j, static json jsonrpc_machine_verify_reset_uarch_handler(const json &j, const std::shared_ptr & /*session*/) { static const char *param_name[] = {"root_hash_before", "log", "root_hash_after"}; - auto args = - parse_args, - cartesi::machine_merkle_tree::hash_type>(j, param_name); + auto args = parse_args, + cartesi::machine_hash>(j, param_name); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) cartesi::machine::verify_reset_uarch(std::get<0>(args), std::get<1>(args).value(), std::get<2>(args)); return jsonrpc_response_ok(j); @@ -1130,7 +1127,7 @@ static json jsonrpc_machine_get_root_hash_handler(const json &j, const std::shar return jsonrpc_response_invalid_request(j, "no machine"); } jsonrpc_check_no_params(j); - cartesi::machine_merkle_tree::hash_type hash; + cartesi::machine_hash hash; session->handler->machine->get_root_hash(hash); return jsonrpc_response_ok(j, cartesi::encode_base64(hash)); } @@ -1411,9 +1408,8 @@ static json jsonrpc_machine_log_send_cmio_response_handler(const json &j, static json jsonrpc_machine_verify_send_cmio_response_handler(const json &j, const std::shared_ptr & /*session*/) { static const char *param_name[] = {"reason", "data", "root_hash_before", "log", "root_hash_after"}; - auto args = parse_args, cartesi::machine_merkle_tree::hash_type>(j, - param_name); + auto args = parse_args, cartesi::machine_hash>(j, param_name); auto bin = cartesi::decode_base64(std::get<1>(args)); // NOLINTBEGIN(bugprone-unchecked-optional-access) diff --git a/src/jsonrpc-virtual-machine.cpp b/src/jsonrpc-virtual-machine.cpp index 1ba209e08..2472d2407 100644 --- a/src/jsonrpc-virtual-machine.cpp +++ b/src/jsonrpc-virtual-machine.cpp @@ -73,7 +73,6 @@ using namespace std::string_literals; using json = nlohmann::json; -using hash_type = cartesi::machine_merkle_tree::hash_type; namespace beast = boost::beast; // from namespace http = beast::http; // from @@ -734,7 +733,7 @@ access_log jsonrpc_virtual_machine::do_log_reset_uarch(const access_log::type &l return std::move(result).value(); } -void jsonrpc_virtual_machine::do_get_root_hash(hash_type &hash) const { +void jsonrpc_virtual_machine::do_get_root_hash(machine_hash &hash) const { request("machine.get_root_hash", std::tie(), hash); } @@ -825,8 +824,8 @@ machine_config jsonrpc_virtual_machine::do_get_default_config() const { return result; } -interpreter_break_reason jsonrpc_virtual_machine::do_verify_step(const hash_type &root_hash_before, - const std::string &log_filename, uint64_t mcycle_count, const hash_type &root_hash_after) const { +interpreter_break_reason jsonrpc_virtual_machine::do_verify_step(const machine_hash &root_hash_before, + const std::string &log_filename, uint64_t mcycle_count, const machine_hash &root_hash_after) const { interpreter_break_reason result = interpreter_break_reason::failed; auto b64_root_hash_before = encode_base64(root_hash_before); auto b64_root_hash_after = encode_base64(root_hash_after); @@ -835,16 +834,16 @@ interpreter_break_reason jsonrpc_virtual_machine::do_verify_step(const hash_type return result; } -void jsonrpc_virtual_machine::do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { +void jsonrpc_virtual_machine::do_verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { bool result = false; auto b64_root_hash_before = encode_base64(root_hash_before); auto b64_root_hash_after = encode_base64(root_hash_after); request("machine.verify_step_uarch", std::tie(b64_root_hash_before, log, b64_root_hash_after), result); } -void jsonrpc_virtual_machine::do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { +void jsonrpc_virtual_machine::do_verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { bool result = false; auto b64_root_hash_before = encode_base64(root_hash_before); auto b64_root_hash_after = encode_base64(root_hash_after); @@ -852,7 +851,7 @@ void jsonrpc_virtual_machine::do_verify_reset_uarch(const hash_type &root_hash_b } void jsonrpc_virtual_machine::do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after) const { bool result = false; std::string b64_data = cartesi::encode_base64(data, length); auto b64_root_hash_before = encode_base64(root_hash_before); diff --git a/src/jsonrpc-virtual-machine.h b/src/jsonrpc-virtual-machine.h index eae517539..a90356a60 100644 --- a/src/jsonrpc-virtual-machine.h +++ b/src/jsonrpc-virtual-machine.h @@ -120,7 +120,7 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { uint64_t do_translate_virtual_address(uint64_t vaddr) override; void do_reset_uarch() override; access_log do_log_reset_uarch(const access_log::type &log_type) override; - void do_get_root_hash(hash_type &hash) const override; + void do_get_root_hash(machine_hash &hash) const override; machine_merkle_tree::proof_type do_get_proof(uint64_t address, int log2_size) const override; void do_replace_memory_range(const memory_range_config &new_range) override; access_log do_log_step_uarch(const access_log::type &log_type) override; @@ -137,14 +137,15 @@ class jsonrpc_virtual_machine final : public i_virtual_machine { const access_log::type &log_type) override; uint64_t do_get_reg_address(reg r) const override; machine_config do_get_default_config() const override; - interpreter_break_reason do_verify_step(const hash_type &root_hash_before, const std::string &log_filename, - uint64_t mcycle_count, const hash_type &root_hash_after) const override; - void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const override; - void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const override; + interpreter_break_reason do_verify_step(const machine_hash &root_hash_before, const std::string &log_filename, + uint64_t mcycle_count, const machine_hash &root_hash_after) const override; + void do_verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; + void do_verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const override; + const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; bool do_is_jsonrpc_virtual_machine() const override; void check_server_version(std::chrono::time_point timeout_at) const; diff --git a/src/keccak-256-hasher.h b/src/keccak-256-hasher.h index a649d8d29..de0687f85 100644 --- a/src/keccak-256-hasher.h +++ b/src/keccak-256-hasher.h @@ -21,7 +21,7 @@ #include #include -#include "i-hasher.h" +#include "machine-hash.h" extern "C" { #include "sha3.h" @@ -37,24 +37,22 @@ struct keccak_instance final { int pt; }; -class keccak_256_hasher final : public i_hasher> { +class keccak_256_hasher final { sha3_ctx_t m_ctx{}; - friend i_hasher>; - - void do_begin() { +public: + void begin() { sha3_init(&m_ctx, 32, 0x01); } - void do_add_data(const unsigned char *data, size_t length) { + void add_data(const unsigned char *data, size_t length) { sha3_update(&m_ctx, data, length); } - void do_end(hash_type &hash) { + void end(machine_hash &hash) { sha3_final(hash.data(), &m_ctx); } -public: /// \brief Default constructor keccak_256_hasher() = default; @@ -62,13 +60,13 @@ class keccak_256_hasher final : public i_hasher(cpp_m); } -static cartesi::machine_merkle_tree::hash_type convert_from_c(const cm_hash *c_hash) { +static cartesi::machine_hash convert_from_c(const cm_hash *c_hash) { if (c_hash == nullptr) { throw std::invalid_argument("invalid hash"); } - cartesi::machine_merkle_tree::hash_type cpp_hash; // In emulator this is std::array; + cartesi::machine_hash cpp_hash; // In emulator this is std::array; memcpy(cpp_hash.data(), c_hash, sizeof(cm_hash)); return cpp_hash; } @@ -666,8 +668,8 @@ cm_error cm_verify_step(const cm_machine *m, const cm_hash *root_hash_before, co if (log_filename == nullptr) { throw std::invalid_argument("invalid log_filename"); } - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); + const cartesi::machine_hash cpp_root_hash_before = convert_from_c(root_hash_before); + const cartesi::machine_hash cpp_root_hash_after = convert_from_c(root_hash_after); cartesi::interpreter_break_reason status{}; if (m != nullptr) { const auto *cpp_m = convert_from_c(m); @@ -693,8 +695,8 @@ cm_error cm_verify_step_uarch(const cm_machine *m, const cm_hash *root_hash_befo } const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); + const cartesi::machine_hash cpp_root_hash_before = convert_from_c(root_hash_before); + const cartesi::machine_hash cpp_root_hash_after = convert_from_c(root_hash_after); if (m != nullptr) { const auto *cpp_m = convert_from_c(m); cpp_m->verify_step_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); @@ -713,8 +715,8 @@ cm_error cm_verify_reset_uarch(const cm_machine *m, const cm_hash *root_hash_bef } const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); + const cartesi::machine_hash cpp_root_hash_before = convert_from_c(root_hash_before); + const cartesi::machine_hash cpp_root_hash_after = convert_from_c(root_hash_after); if (m != nullptr) { const auto *cpp_m = convert_from_c(m); cpp_m->verify_reset_uarch(cpp_root_hash_before, cpp_log, cpp_root_hash_after); @@ -746,7 +748,7 @@ cm_error cm_get_root_hash(const cm_machine *m, cm_hash *hash) try { throw std::invalid_argument("invalid hash output"); } const auto *cpp_m = convert_from_c(m); - cartesi::machine_merkle_tree::hash_type cpp_hash; + cartesi::machine_hash cpp_hash; cpp_m->get_root_hash(cpp_hash); memcpy(hash, static_cast(cpp_hash.data()), sizeof(cm_hash)); return cm_result_success(); @@ -1079,8 +1081,8 @@ cm_error cm_verify_send_cmio_response(const cm_machine *m, uint16_t reason, cons } const auto cpp_log = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) cartesi::from_json>(log).value(); - const cartesi::machine::hash_type cpp_root_hash_before = convert_from_c(root_hash_before); - const cartesi::machine::hash_type cpp_root_hash_after = convert_from_c(root_hash_after); + const cartesi::machine_hash cpp_root_hash_before = convert_from_c(root_hash_before); + const cartesi::machine_hash cpp_root_hash_after = convert_from_c(root_hash_after); if (m != nullptr) { const auto *cpp_m = convert_from_c(m); cpp_m->verify_send_cmio_response(reason, data, length, cpp_root_hash_before, cpp_log, cpp_root_hash_after); diff --git a/src/machine-c-api.h b/src/machine-c-api.h index ae3e523ae..c2ca52fd5 100644 --- a/src/machine-c-api.h +++ b/src/machine-c-api.h @@ -42,6 +42,12 @@ extern "C" { // API enums and structures // ----------------------------------------------------------------------------- +/// \brief Constants. +typedef enum cm_hash_tree_target { + CM_HASH_TREE_TARGET_UARCH = 0, + CM_HASH_TREE_TARGET_RISC0 = 1, +} cm_hash_tree_target; + /// \brief Constants. typedef enum cm_constant { CM_HASH_SIZE = 32, @@ -240,6 +246,7 @@ typedef enum cm_reg { CM_REG_HTIF_IHALT, CM_REG_HTIF_ICONSOLE, CM_REG_HTIF_IYIELD, + CM_REG_HASH_TREE_TARGET, // Microarchitecture registers CM_REG_UARCH_X0, CM_REG_UARCH_X1, diff --git a/src/machine-config.h b/src/machine-config.h index 30beab497..4b60bb7a9 100644 --- a/src/machine-config.h +++ b/src/machine-config.h @@ -24,6 +24,7 @@ #include +#include "hash-tree-target.h" #include "riscv-constants.h" #include "uarch-config.h" @@ -184,6 +185,11 @@ struct cmio_config final { cmio_buffer_config tx_buffer; ///< TX buffer configuration }; +/// \brief Hash tree configuration +struct hash_tree_config final { + hash_tree_target target{hash_tree_target::uarch}; ///< Target to hash +}; + /// \brief Machine state configuration struct machine_config final { processor_config processor{}; ///< Processor state @@ -197,6 +203,7 @@ struct machine_config final { virtio_configs virtio; ///< VirtIO devices state uarch_config uarch{}; ///< microarchitecture configuration cmio_config cmio{}; ///< Cmio state + hash_tree_config hash_tree; ///< Hash tree state /// \brief Get the name where config will be stored in a directory static std::string get_config_filename(const std::string &dir); diff --git a/src/machine-hash.h b/src/machine-hash.h new file mode 100644 index 000000000..cce0871f8 --- /dev/null +++ b/src/machine-hash.h @@ -0,0 +1,34 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef MACHINE_HASH_H +#define MACHINE_HASH_H + +#include +#include +#include + +namespace cartesi { + +/// \brief Size of hash in bytes. +constexpr size_t machine_hash_size = 32; + +/// \brief Hash type. +using machine_hash = std::array; + +} // namespace cartesi + +#endif // HASH_H diff --git a/src/machine-merkle-tree.cpp b/src/machine-merkle-tree.cpp index a0c23bc57..8d9118ad1 100644 --- a/src/machine-merkle-tree.cpp +++ b/src/machine-merkle-tree.cpp @@ -32,10 +32,19 @@ namespace cartesi { // Initialize static pristine hashes -const cartesi::pristine_merkle_tree &machine_merkle_tree::pristine_hashes() { - static const cartesi::pristine_merkle_tree tree{machine_merkle_tree::get_log2_root_size(), - machine_merkle_tree::get_log2_word_size()}; - return tree; +const cartesi::pristine_merkle_tree &machine_merkle_tree::pristine_hashes(hash_tree_target hash_tree_target) { + static const cartesi::pristine_merkle_tree uarch_tree{i_hasher::make_uarch(), + machine_merkle_tree::get_log2_root_size(), machine_merkle_tree::get_log2_word_size()}; + + static const cartesi::pristine_merkle_tree risc0_tree{i_hasher::make_risc0(), + machine_merkle_tree::get_log2_root_size(), machine_merkle_tree::get_log2_word_size()}; + + switch (hash_tree_target) { + case hash_tree_target::uarch: + return uarch_tree; + case hash_tree_target::risc0: + return risc0_tree; + } } constexpr machine_merkle_tree::address_type machine_merkle_tree::get_page_index(address_type address) { @@ -108,16 +117,19 @@ machine_merkle_tree::tree_node *machine_merkle_tree::new_page_node(address_type return node; } -void machine_merkle_tree::get_page_node_hash(hasher_type &h, const unsigned char *start, int log2_size, - hash_type &hash) const { +void machine_merkle_tree::get_page_node_hash(i_hasher &h, const unsigned char *start, int log2_size, + machine_hash &hash) const { + if (h.get_hash_tree_target() != m_hash_tree_target) { + throw std::runtime_error{"hash tree target mismatch"}; + } if (log2_size > get_log2_word_size()) { - hash_type child0; - hash_type child1; + machine_hash child0; + machine_hash child1; --log2_size; const address_type size = UINT64_C(1) << log2_size; get_page_node_hash(h, start, log2_size, child0); get_page_node_hash(h, start + size, log2_size, child1); - get_concat_hash(h, child0, child1, hash); + h.get_concat_hash(child0, child1, hash); } else { h.begin(); h.add_data(start, get_word_size()); @@ -125,7 +137,7 @@ void machine_merkle_tree::get_page_node_hash(hasher_type &h, const unsigned char } } -void machine_merkle_tree::get_page_node_hash(hasher_type &h, const unsigned char *page_data, hash_type &hash) const { +void machine_merkle_tree::get_page_node_hash(i_hasher &h, const unsigned char *page_data, machine_hash &hash) const { if (page_data != nullptr) { get_page_node_hash(h, page_data, get_log2_page_size(), hash); } else { @@ -133,7 +145,7 @@ void machine_merkle_tree::get_page_node_hash(hasher_type &h, const unsigned char } } -void machine_merkle_tree::get_page_node_hash(address_type page_index, hash_type &hash) const { +void machine_merkle_tree::get_page_node_hash(address_type page_index, machine_hash &hash) const { assert(page_index == get_page_index(page_index)); tree_node *node = get_page_node(page_index); if (node == nullptr) { @@ -143,17 +155,16 @@ void machine_merkle_tree::get_page_node_hash(address_type page_index, hash_type } } -const machine_merkle_tree::hash_type &machine_merkle_tree::get_child_hash(int child_log2_size, const tree_node *node, - int bit) { +const machine_hash &machine_merkle_tree::get_child_hash(int child_log2_size, const tree_node *node, int bit) const { const tree_node *child = node->child[bit]; return (child != nullptr) ? child->hash : get_pristine_hash(child_log2_size); } -void machine_merkle_tree::update_inner_node_hash(hasher_type &h, int log2_size, tree_node *node) { - get_concat_hash(h, get_child_hash(log2_size - 1, node, 0), get_child_hash(log2_size - 1, node, 1), node->hash); +void machine_merkle_tree::update_inner_node_hash(i_hasher &h, int log2_size, tree_node *node) { + h.get_concat_hash(get_child_hash(log2_size - 1, node, 0), get_child_hash(log2_size - 1, node, 1), node->hash); } -void machine_merkle_tree::dump_hash(const hash_type &hash) { +void machine_merkle_tree::dump_hash(const machine_hash &hash) { auto f = std::cerr.flags(); for (const auto &b : hash) { std::cerr << std::hex << std::setfill('0') << std::setw(2) << static_cast(b); @@ -162,7 +173,7 @@ void machine_merkle_tree::dump_hash(const hash_type &hash) { std::cerr.flags(f); } -const machine_merkle_tree::hash_type &machine_merkle_tree::get_pristine_hash(int log2_size) { +const machine_hash &machine_merkle_tree::get_pristine_hash(int log2_size) const { return pristine_hashes().get_hash(log2_size); } @@ -200,16 +211,16 @@ void machine_merkle_tree::destroy_merkle_tree() { memset(&m_root_storage, 0, sizeof(m_root_storage)); } -void machine_merkle_tree::get_inside_page_sibling_hashes(hasher_type &h, address_type address, int log2_size, - hash_type &hash, const unsigned char *curr_data, int log2_curr_size, hash_type &curr_hash, int parent_diverged, - int curr_diverged, proof_type &proof) const { +void machine_merkle_tree::get_inside_page_sibling_hashes(i_hasher &h, address_type address, int log2_size, + machine_hash &hash, const unsigned char *curr_data, int log2_curr_size, machine_hash &curr_hash, + int parent_diverged, int curr_diverged, proof_type &proof) const { // If node currently being visited is larger than a // word, invoke recursively if (log2_curr_size > get_log2_word_size()) { const int log2_child_size = log2_curr_size - 1; const address_type child_size = UINT64_C(1) << log2_child_size; - hash_type first_hash; - hash_type second_hash; + machine_hash first_hash; + machine_hash second_hash; const int child_bit = static_cast((address & child_size) != 0); get_inside_page_sibling_hashes(h, address, log2_size, hash, curr_data, log2_child_size, first_hash, static_cast((parent_diverged != 0) || (curr_diverged) != 0), static_cast(child_bit != 0), proof); @@ -217,7 +228,7 @@ void machine_merkle_tree::get_inside_page_sibling_hashes(hasher_type &h, address second_hash, static_cast((parent_diverged != 0) || (curr_diverged) != 0), static_cast(child_bit != 1), proof); // Compute curr_hash from hashes of its children - get_concat_hash(h, first_hash, second_hash, curr_hash); + h.get_concat_hash(first_hash, second_hash, curr_hash); // Otherwise directly compute hash of word } else { h.begin(); @@ -238,9 +249,9 @@ void machine_merkle_tree::get_inside_page_sibling_hashes(hasher_type &h, address } } -void machine_merkle_tree::get_inside_page_sibling_hashes(address_type address, int log2_size, hash_type &hash, - const unsigned char *page_data, hash_type &page_hash, proof_type &proof) const { - hasher_type h; +void machine_merkle_tree::get_inside_page_sibling_hashes(address_type address, int log2_size, machine_hash &hash, + const unsigned char *page_data, machine_hash &page_hash, proof_type &proof) const { + auto h = make_hasher(); get_inside_page_sibling_hashes(h, address, log2_size, hash, page_data, get_log2_page_size(), page_hash, 0 /* parent hasn't diverted */, 0 /* curr node hasn't diverged */, proof); } @@ -254,7 +265,7 @@ bool machine_merkle_tree::begin_update() { return true; } -bool machine_merkle_tree::update_page_node_hash(address_type page_index, const hash_type &hash) { +bool machine_merkle_tree::update_page_node_hash(address_type page_index, const machine_hash &hash) { assert(get_page_index(page_index) == page_index); tree_node *node = get_page_node(page_index); // If there is no page node for this page index, allocate a fresh one @@ -275,7 +286,11 @@ bool machine_merkle_tree::update_page_node_hash(address_type page_index, const h return true; } -bool machine_merkle_tree::end_update(hasher_type &h) { +bool machine_merkle_tree::end_update(i_hasher &h) { + if (h.get_hash_tree_target() != m_hash_tree_target) { + throw std::runtime_error{"hash tree target mismatch"}; + } + // Now go over the queue of inner nodes updating their hashes and // enqueueing their parents until the queue is empty while (!m_merkle_update_fifo.empty()) { @@ -293,7 +308,10 @@ bool machine_merkle_tree::end_update(hasher_type &h) { return true; } -machine_merkle_tree::machine_merkle_tree() : m_root_storage{}, m_root{&m_root_storage} { +machine_merkle_tree::machine_merkle_tree(hash_tree_target hash_tree_target) : + m_hash_tree_target(hash_tree_target), + m_root_storage{}, + m_root{&m_root_storage} { m_root->hash = get_pristine_hash(get_log2_root_size()); #ifdef MERKLE_DUMP_STATS m_num_nodes = 0; @@ -312,16 +330,16 @@ machine_merkle_tree::~machine_merkle_tree() { #endif } -void machine_merkle_tree::get_root_hash(hash_type &hash) const { +void machine_merkle_tree::get_root_hash(machine_hash &hash) const { hash = m_root->hash; } bool machine_merkle_tree::verify_tree() const { - hasher_type h; + auto h = make_hasher(); return verify_tree(h, m_root, get_log2_root_size()); } -bool machine_merkle_tree::verify_tree(hasher_type &h, tree_node *node, int log2_size) const { +bool machine_merkle_tree::verify_tree(i_hasher &h, tree_node *node, int log2_size) const { // pristine node is always correct if (node == nullptr) { return true; @@ -334,8 +352,8 @@ bool machine_merkle_tree::verify_tree(hasher_type &h, tree_node *node, int log2_ if (!first_ok || !second_ok) { return false; } - hash_type hash; - get_concat_hash(h, get_child_hash(child_log2_size, node, 0), get_child_hash(child_log2_size, node, 1), hash); + machine_hash hash; + h.get_concat_hash(get_child_hash(child_log2_size, node, 0), get_child_hash(child_log2_size, node, 1), hash); return hash == node->hash; // Assume page nodes are correct } @@ -386,7 +404,7 @@ machine_merkle_tree::proof_type machine_merkle_tree::get_proof(address_type targ // We hit a page node along the path to the target node } else if (log2_node_size == get_log2_page_size()) { assert(node); - hash_type page_hash; + machine_hash page_hash; // If target node is smaller than page size if (log2_target_size < get_log2_page_size()) { // If we were given the page data, compute from it @@ -423,7 +441,7 @@ machine_merkle_tree::proof_type machine_merkle_tree::get_proof(address_type targ proof.set_root_hash(m_root->hash); // NOLINT: m_root can't be nullptr #ifndef NDEBUG // Return proof only if it passes verification - hasher_type h; + auto h = make_hasher(); if (!proof.verify(h)) { throw std::runtime_error{"proof failed verification"}; } @@ -431,8 +449,7 @@ machine_merkle_tree::proof_type machine_merkle_tree::get_proof(address_type targ return proof; } -machine_merkle_tree::hash_type machine_merkle_tree::get_node_hash(address_type target_address, - int log2_target_size) const { +machine_hash machine_merkle_tree::get_node_hash(address_type target_address, int log2_target_size) const { if (log2_target_size > get_log2_root_size() || log2_target_size < get_log2_word_size()) { throw std::runtime_error{"log2_target_size is out of bounds"}; } @@ -458,7 +475,7 @@ machine_merkle_tree::hash_type machine_merkle_tree::get_node_hash(address_type t return node->hash; } -std::ostream &operator<<(std::ostream &out, const machine_merkle_tree::hash_type &hash) { +std::ostream &operator<<(std::ostream &out, const machine_hash &hash) { auto f = out.flags(); for (const unsigned b : hash) { out << std::hex << std::setfill('0') << std::setw(2) << b; diff --git a/src/machine-merkle-tree.h b/src/machine-merkle-tree.h index 71293fed5..5c8aa1fb8 100644 --- a/src/machine-merkle-tree.h +++ b/src/machine-merkle-tree.h @@ -28,7 +28,7 @@ #include #include -#include "keccak-256-hasher.h" +#include "i-hasher.h" #include "merkle-tree-proof.h" #include "pristine-merkle-tree.h" @@ -111,26 +111,22 @@ class machine_merkle_tree final { return m_word_size; } - /// \brief Hasher class. - using hasher_type = keccak_256_hasher; - - /// \brief Storage for a hash. - using hash_type = hasher_type::hash_type; - /// \brief Storage for the proof of a word value. - using proof_type = merkle_tree_proof; + using proof_type = merkle_tree_proof; /// \brief Storage for the hashes of the siblings of all nodes along /// the path from the root to target node. using siblings_type = proof_type::sibling_hashes_type; private: + const hash_tree_target m_hash_tree_target; ///< Hash tree target. + /// \brief Merkle tree node structure. /// \details A node is known to be an inner-node or a page-node implicitly /// based on its height in the tree. //??D This is assumed to be a POD type in the implementation struct tree_node { - hash_type hash; ///< Hash of subintended data. + machine_hash hash; ///< Hash of subintended data. tree_node *parent; ///< Pointer to parent node (nullptr for root). std::array child; ///< Children nodes. uint64_t mark; ///< Helper for traversal algorithms. @@ -181,11 +177,11 @@ class machine_merkle_tree final { /// \param h Hasher object. /// \param log2_size log2 of size subintended by node. /// \param node Node to be updated. - static void update_inner_node_hash(hasher_type &h, int log2_size, tree_node *node); + void update_inner_node_hash(i_hasher &h, int log2_size, tree_node *node); /// \brief Dumps a hash to std::cerr. /// \param hash Hash to be dumped. - static void dump_hash(const hash_type &hash); + static void dump_hash(const machine_hash &hash); /// \brief Returns the hash for a child of a given node. /// \param child_log2_size log2_size of child node. @@ -193,7 +189,7 @@ class machine_merkle_tree final { /// \param bit Bit corresponding to child_log2_size in child node address. /// \return Reference to child hash. If child pointer is null, /// returns a pristine hash. - static const hash_type &get_child_hash(int child_log2_size, const tree_node *node, int bit); + const machine_hash &get_child_hash(int child_log2_size, const tree_node *node, int bit) const; /// \brief Dumps tree rooted at node to std::cerr. /// \param node Root of subtree. @@ -217,7 +213,7 @@ class machine_merkle_tree final { /// \param node Root of subtree. /// \param log2_size log2 of size subintended by \p node. /// \returns True if tree is consistent, false otherwise. - bool verify_tree(hasher_type &h, tree_node *node, int log2_size) const; + bool verify_tree(i_hasher &h, tree_node *node, int log2_size) const; /// \brief Computes the page index for a memory address. /// \param address Memory address. @@ -240,7 +236,7 @@ class machine_merkle_tree final { /// \param start Start of contiguous memory subintended by node. /// \param log2_size log2 of size subintended by node. /// \param hash Receives the hash. - void get_page_node_hash(hasher_type &h, const unsigned char *start, int log2_size, hash_type &hash) const; + void get_page_node_hash(i_hasher &h, const unsigned char *start, int log2_size, machine_hash &hash) const; /// \brief Gets the sibling hashes along the path from /// the node currently being visited and a target node. @@ -259,8 +255,8 @@ class machine_merkle_tree final { /// \param curr_diverged True if node currently being visited is /// itself not in path from root to target node. /// \param proof Proof to receive sibling hashes. - void get_inside_page_sibling_hashes(hasher_type &h, address_type address, int log2_size, hash_type &hash, - const unsigned char *curr_data, int log2_curr_size, hash_type &curr_hash, int parent_diverged, + void get_inside_page_sibling_hashes(i_hasher &h, address_type address, int log2_size, machine_hash &hash, + const unsigned char *curr_data, int log2_curr_size, machine_hash &curr_hash, int parent_diverged, int curr_diverged, proof_type &proof) const; /// \brief Gets the sibling hashes along the path from a @@ -271,13 +267,17 @@ class machine_merkle_tree final { /// \param page_data Pointer to start of contiguous page data. /// \param page_hash Receives the hash for the page. /// \param proof Proof to receive sibling hashes. - void get_inside_page_sibling_hashes(address_type address, int log2_size, hash_type &hash, - const unsigned char *page_data, hash_type &page_hash, proof_type &proof) const; + void get_inside_page_sibling_hashes(address_type address, int log2_size, machine_hash &hash, + const unsigned char *page_data, machine_hash &page_hash, proof_type &proof) const; // Precomputed hashes of spans of zero bytes with // increasing power-of-two sizes, from 2^LOG2_WORD_SIZE // to 2^LOG2_ROOT_SIZE bytes. - static const pristine_merkle_tree &pristine_hashes(); + static const pristine_merkle_tree &pristine_hashes(hash_tree_target hash_tree_target); + + const pristine_merkle_tree &pristine_hashes() const { + return pristine_hashes(m_hash_tree_target); + } public: /// \brief Verifies the entire Merkle tree. @@ -286,7 +286,7 @@ class machine_merkle_tree final { /// \brief Default constructor. /// \details Initializes memory to zero. - machine_merkle_tree(); + explicit machine_merkle_tree(hash_tree_target hash_tree_target); /// \brief No copy constructor machine_merkle_tree(const machine_merkle_tree &) = delete; @@ -301,9 +301,13 @@ class machine_merkle_tree final { /// \details Releases all used memory ~machine_merkle_tree(); + i_hasher make_hasher() const { + return i_hasher::make(m_hash_tree_target); + } + /// \brief Returns the root hash. /// \param hash Receives the hash. - void get_root_hash(hash_type &hash) const; + void get_root_hash(machine_hash &hash) const; /// \brief Start tree update. /// \returns True. @@ -317,14 +321,14 @@ class machine_merkle_tree final { /// \returns True if succeeded, false otherwise. /// \details This method is not thread safe, so be careful when using /// parallelization to compute Merkle trees - bool update_page_node_hash(address_type page_index, const hash_type &hash); + bool update_page_node_hash(address_type page_index, const machine_hash &hash); /// \brief End tree update. /// \param h Hasher object. /// \returns True if succeeded, false otherwise. /// \details This method is not thread safe, so be careful when using /// parallelization to compute Merkle trees - bool end_update(hasher_type &h); + bool end_update(i_hasher &h); /// \brief Returns the proof for a node in the tree. /// \param target_address Address of target node. Must be aligned @@ -342,26 +346,26 @@ class machine_merkle_tree final { /// \param h Hasher object. /// \param page_data Pointer to start of contiguous page data. /// \param hash Receives the hash. - void get_page_node_hash(hasher_type &h, const unsigned char *page_data, hash_type &hash) const; + void get_page_node_hash(i_hasher &h, const unsigned char *page_data, machine_hash &hash) const; /// \brief Gets currently stored hash for page node. /// \param page_index Page index for node. /// \param hash Receives the hash. - void get_page_node_hash(address_type page_index, hash_type &hash) const; + void get_page_node_hash(address_type page_index, machine_hash &hash) const; /// \brief Get the hash of a node in the Merkle tree. /// \param target_address Address of target node. /// \param log2_target_size log2 of the node size. /// \return Hash of the node. - hash_type get_node_hash(address_type target_address, int log2_target_size) const; + machine_hash get_node_hash(address_type target_address, int log2_target_size) const; /// \brief Returns the hash for a log2_size pristine node. /// \param log2_size log2 of size subintended by node. /// \return Reference to precomputed hash. - static const hash_type &get_pristine_hash(int log2_size); + const machine_hash &get_pristine_hash(int log2_size) const; }; -std::ostream &operator<<(std::ostream &out, const machine_merkle_tree::hash_type &hash); +std::ostream &operator<<(std::ostream &out, const machine_hash &hash); } // namespace cartesi diff --git a/src/machine-reg.h b/src/machine-reg.h index 0fc8e77bf..2e455d9b1 100644 --- a/src/machine-reg.h +++ b/src/machine-reg.h @@ -135,8 +135,9 @@ enum class machine_reg : uint64_t { htif_ihalt = PMA_SHADOW_STATE_START + offsetof(shadow_state, htif_ihalt), htif_iconsole = PMA_SHADOW_STATE_START + offsetof(shadow_state, htif_iconsole), htif_iyield = PMA_SHADOW_STATE_START + offsetof(shadow_state, htif_iyield), + hash_tree_target = PMA_SHADOW_STATE_START + offsetof(shadow_state, hash_tree_target), first_ = x0, - last_ = htif_iyield, + last_ = hash_tree_target, uarch_halt_flag = PMA_SHADOW_UARCH_STATE_START + offsetof(shadow_uarch_state, halt_flag), uarch_cycle = PMA_SHADOW_UARCH_STATE_START + offsetof(shadow_uarch_state, cycle), diff --git a/src/machine-state.h b/src/machine-state.h index e13edea6d..d756ec15c 100644 --- a/src/machine-state.h +++ b/src/machine-state.h @@ -29,6 +29,7 @@ #include +#include "hash-tree-target.h" #include "pma-constants.h" #include "pma.h" #include "riscv-constants.h" @@ -85,6 +86,12 @@ struct machine_state { uint64_t senvcfg{}; ///< CSR senvcfg. // Cartesi-specific state + + // Hash tree state + struct { + hash_tree_target target{}; //< Hash tree target. + } hash_tree; + uint64_t ilrsc{}; ///< For LR/SC instructions (Cartesi-specific). uint64_t icycleinstret{}; ///< CSR icycleinstret (Cartesi-specific). struct { diff --git a/src/machine.cpp b/src/machine.cpp index 46ff2acd8..816a5a4fa 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -267,8 +267,11 @@ static void init_tlb_entry(machine &m, uint64_t eidx) { tlbce.pma_index = TLB_INVALID_PMA; } -machine::machine(const machine_config &c, const machine_runtime_config &r) : m_c{c}, m_uarch{c.uarch}, m_r{r} { - +machine::machine(const machine_config &c, const machine_runtime_config &r) : + m_t{c.hash_tree.target}, + m_c{c}, + m_uarch{c.uarch}, + m_r{r} { if (m_c.processor.marchid == UINT64_C(-1)) { m_c.processor.marchid = MARCHID_INIT; } @@ -337,6 +340,7 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : m_c write_reg(reg::iflags_Y, m_c.processor.iflags_Y); write_reg(reg::iflags_H, m_c.processor.iflags_H); write_reg(reg::iunrep, m_c.processor.iunrep); + write_reg(reg::hash_tree_target, static_cast(m_c.hash_tree.target)); // Register RAM if (m_c.ram.image_filename.empty()) { @@ -558,7 +562,7 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : m_c os_disable_sigpipe(); } -static void load_hash(const std::string &dir, machine::hash_type &h) { +static void load_hash(const std::string &dir, machine_hash &h) { auto name = dir + "/hash"; auto fp = unique_fopen(name.c_str(), "rb"); if (fread(h.data(), 1, h.size(), fp.get()) != h.size()) { @@ -570,8 +574,8 @@ machine::machine(const std::string &dir, const machine_runtime_config &r) : mach if (r.skip_root_hash_check) { return; } - hash_type hstored; - hash_type hrestored; + machine_hash hstored; + machine_hash hrestored; load_hash(dir, hstored); if (!update_merkle_tree()) { throw std::runtime_error{"error updating Merkle tree"}; @@ -817,7 +821,7 @@ void machine::store_pmas(const machine_config &c, const std::string &dir) const } } -static void store_hash(const machine::hash_type &h, const std::string &dir) { +static void store_hash(const machine_hash &h, const std::string &dir) { auto name = dir + "/hash"; auto fp = unique_fopen(name.c_str(), "wb"); if (fwrite(h.data(), 1, h.size(), fp.get()) != h.size()) { @@ -833,7 +837,7 @@ void machine::store(const std::string &dir) const { if (!update_merkle_tree()) { throw std::runtime_error{"error updating Merkle tree"}; } - hash_type h; + machine_hash h; m_t.get_root_hash(h); store_hash(h, dir); } @@ -1109,6 +1113,8 @@ uint64_t machine::read_reg(reg r) const { return m_s.htif.iconsole; case reg::htif_iyield: return m_s.htif.iyield; + case reg::hash_tree_target: + return static_cast(m_s.hash_tree.target); case reg::uarch_x0: return m_uarch.get_state().x[0]; case reg::uarch_x1: @@ -1517,6 +1523,14 @@ void machine::write_reg(reg w, uint64_t value) { case reg::htif_iyield: m_s.htif.iyield = value; break; + case reg::hash_tree_target: { + auto maybe_hash_tree_target = parse_hash_tree_target(value); + if (!maybe_hash_tree_target) { + throw std::invalid_argument{"invalid hash tree target"}; + } + m_s.hash_tree.target = *maybe_hash_tree_target; + } break; + case reg::uarch_x0: throw std::invalid_argument{"register is read-only"}; case reg::uarch_x1: @@ -1682,7 +1696,7 @@ void machine::mark_write_tlb_dirty_pages() const { bool machine::verify_dirty_page_maps() const { static_assert(PMA_PAGE_SIZE == machine_merkle_tree::get_page_size(), "PMA and machine_merkle_tree page sizes must match"); - machine_merkle_tree::hasher_type h; + auto h = m_t.make_hasher(); auto scratch = unique_calloc(PMA_PAGE_SIZE, std::nothrow_t{}); if (!scratch) { return false; @@ -1699,8 +1713,8 @@ bool machine::verify_dirty_page_maps() const { if (pma.get_istart_M()) { const unsigned char *page_data = nullptr; peek(pma, *this, page_start_in_range, &page_data, scratch.get()); - hash_type stored; - hash_type real; + machine_hash stored; + machine_hash real; m_t.get_page_node_hash(page_address, stored); m_t.get_page_node_hash(h, page_data, real); const bool marked_dirty = pma.is_page_marked_dirty(page_start_in_range); @@ -1733,7 +1747,7 @@ static uint64_t get_task_concurrency(uint64_t value) { } bool machine::update_merkle_tree() const { - machine_merkle_tree::hasher_type gh; + auto gh = m_t.make_hasher(); static_assert(PMA_PAGE_SIZE == machine_merkle_tree::get_page_size(), "PMA and machine_merkle_tree page sizes must match"); // Go over the write TLB and mark as dirty all pages currently there @@ -1752,7 +1766,7 @@ bool machine::update_merkle_tree() const { if (!scratch) { return false; } - machine_merkle_tree::hasher_type h; + auto h = m_t.make_hasher(); // Thread j is responsible for page i if i % n == j. for (uint64_t i = j; i < pages_in_range; i += n) { const uint64_t page_start_in_range = i * PMA_PAGE_SIZE; @@ -1773,11 +1787,11 @@ bool machine::update_merkle_tree() const { // safe, so we protect it with a mutex const parallel_for_mutex_guard lock(mutex); if (!m_t.update_page_node_hash(page_address, - machine_merkle_tree::get_pristine_hash(machine_merkle_tree::get_log2_page_size()))) { + m_t.get_pristine_hash(machine_merkle_tree::get_log2_page_size()))) { return false; } } else { - hash_type hash; + machine_hash hash; m_t.get_page_node_hash(h, page_data, hash); { // The update_page_node_hash function in the machine_merkle_tree is not thread @@ -1811,7 +1825,7 @@ bool machine::update_merkle_tree_page(uint64_t address) { address &= ~(PMA_PAGE_SIZE - 1); pma_entry &pma = find_pma_entry(m_merkle_pmas, address, sizeof(uint64_t)); const uint64_t page_start_in_range = address - pma.get_start(); - machine_merkle_tree::hasher_type h; + auto h = m_t.make_hasher(); auto scratch = unique_calloc(PMA_PAGE_SIZE, std::nothrow_t{}); if (!scratch) { return false; @@ -1825,7 +1839,7 @@ bool machine::update_merkle_tree_page(uint64_t address) { } if (page_data != nullptr) { const uint64_t page_address = pma.get_start() + page_start_in_range; - hash_type hash; + machine_hash hash; m_t.get_page_node_hash(h, page_data, hash); if (!m_t.update_page_node_hash(page_address, hash)) { m_t.end_update(h); @@ -1840,7 +1854,7 @@ const boost::container::static_vector &machine::get_pmas() c return m_s.pmas; } -void machine::get_root_hash(hash_type &hash) const { +void machine::get_root_hash(machine_hash &hash) const { if (read_reg(reg::iunrep) != 0) { throw std::runtime_error("cannot compute root hash of unreproducible machines"); } @@ -1850,12 +1864,12 @@ void machine::get_root_hash(hash_type &hash) const { m_t.get_root_hash(hash); } -machine::hash_type machine::get_merkle_tree_node_hash(uint64_t address, int log2_size, +machine_hash machine::get_merkle_tree_node_hash(uint64_t address, int log2_size, skip_merkle_tree_update_t /* unused */) const { return m_t.get_node_hash(address, log2_size); } -machine::hash_type machine::get_merkle_tree_node_hash(uint64_t address, int log2_size) const { +machine_hash machine::get_merkle_tree_node_hash(uint64_t address, int log2_size) const { if (!update_merkle_tree()) { throw std::runtime_error{"error updating Merkle tree"}; } @@ -2100,16 +2114,17 @@ void machine::send_cmio_response(uint16_t reason, const unsigned char *data, uin access_log machine::log_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, const access_log::type &log_type) { - hash_type root_hash_before; + machine_hash root_hash_before; get_root_hash(root_hash_before); - access_log log(log_type); + auto hasher = m_t.make_hasher(); + access_log log(log_type, hasher.get_hash_tree_target()); // Call send_cmio_response with the recording state accessor - record_send_cmio_state_access a(*this, log); + record_send_cmio_state_access a(*this, log, hasher); a.push_bracket(bracket_type::begin, "send cmio response"); cartesi::send_cmio_response(a, reason, data, length); a.push_bracket(bracket_type::end, "send cmio response"); // Verify access log before returning - hash_type root_hash_after; + machine_hash root_hash_after; update_merkle_tree(); get_root_hash(root_hash_after); verify_send_cmio_response(reason, data, length, root_hash_before, log, root_hash_after); @@ -2117,7 +2132,7 @@ access_log machine::log_send_cmio_response(uint16_t reason, const unsigned char } void machine::verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) { + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after) { // There must be at least one access in log if (log.get_accesses().empty()) { throw std::invalid_argument{"too few accesses in log"}; @@ -2129,7 +2144,7 @@ void machine::verify_send_cmio_response(uint16_t reason, const unsigned char *da a.finish(); // Make sure the access log ends at the same root hash as the state - hash_type obtained_root_hash; + machine_hash obtained_root_hash; a.get_root_hash(obtained_root_hash); if (obtained_root_hash != root_hash_after) { throw std::invalid_argument{"mismatch in root hash after replay"}; @@ -2137,12 +2152,18 @@ void machine::verify_send_cmio_response(uint16_t reason, const unsigned char *da } void machine::reset_uarch() { + if (m_s.hash_tree.target != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree target is not uarch"); + } uarch_state_access a(m_uarch.get_state(), get_state()); uarch_reset_state(a); } access_log machine::log_reset_uarch(const access_log::type &log_type) { - hash_type root_hash_before; + if (m_s.hash_tree.target != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree target is not uarch"); + } + machine_hash root_hash_before; get_root_hash(root_hash_before); // Call uarch_reset_state with a uarch_record_state_access object uarch_record_state_access a(m_uarch.get_state(), *this, log_type); @@ -2150,15 +2171,18 @@ access_log machine::log_reset_uarch(const access_log::type &log_type) { uarch_reset_state(a); a.push_bracket(bracket_type::end, "reset uarch state"); // Verify access log before returning - hash_type root_hash_after; + machine_hash root_hash_after; update_merkle_tree(); get_root_hash(root_hash_after); verify_reset_uarch(root_hash_before, *a.get_log(), root_hash_after); return std::move(*a.get_log()); } -void machine::verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) { +void machine::verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) { + if (log.get_hash_tree_target() != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree_target is not uarch"); + } // There must be at least one access in log if (log.get_accesses().empty()) { throw std::invalid_argument{"too few accesses in log"}; @@ -2168,7 +2192,7 @@ void machine::verify_reset_uarch(const hash_type &root_hash_before, const access uarch_reset_state(a); a.finish(); // Make sure the access log ends at the same root hash as the state - hash_type obtained_root_hash; + machine_hash obtained_root_hash; a.get_root_hash(obtained_root_hash); if (obtained_root_hash != root_hash_after) { throw std::invalid_argument{"mismatch in root hash after replay"}; @@ -2179,10 +2203,13 @@ void machine::verify_reset_uarch(const hash_type &root_hash_before, const access extern template UArchStepStatus uarch_step(uarch_record_state_access &a); access_log machine::log_step_uarch(const access_log::type &log_type) { + if (m_s.hash_tree.target != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree target is not uarch"); + } if (m_uarch.get_state().ram.get_istart_E()) { throw std::runtime_error("microarchitecture RAM is not present"); } - hash_type root_hash_before; + machine_hash root_hash_before; get_root_hash(root_hash_before); // Call interpret with a logged state access object uarch_record_state_access a(m_uarch.get_state(), *this, log_type); @@ -2190,7 +2217,7 @@ access_log machine::log_step_uarch(const access_log::type &log_type) { uarch_step(a); a.push_bracket(bracket_type::end, "step"); // Verify access log before returning - hash_type root_hash_after; + machine_hash root_hash_after; get_root_hash(root_hash_after); verify_step_uarch(root_hash_before, *a.get_log(), root_hash_after); return std::move(*a.get_log()); @@ -2199,8 +2226,11 @@ access_log machine::log_step_uarch(const access_log::type &log_type) { // Declaration of explicit instantiation in module uarch-step.cpp extern template UArchStepStatus uarch_step(uarch_replay_state_access &a); -void machine::verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) { +void machine::verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) { + if (log.get_hash_tree_target() != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree target is not uarch"); + } // There must be at least one access in log if (log.get_accesses().empty()) { throw std::invalid_argument{"too few accesses in log"}; @@ -2210,7 +2240,7 @@ void machine::verify_step_uarch(const hash_type &root_hash_before, const access_ uarch_step(a); a.finish(); // Make sure the access log ends at the same root hash as the state - hash_type obtained_root_hash; + machine_hash obtained_root_hash; a.get_root_hash(obtained_root_hash); if (obtained_root_hash != root_hash_after) { throw std::invalid_argument{"mismatch in root hash after replay"}; @@ -2223,6 +2253,9 @@ machine_config machine::get_default_config() { // NOLINTNEXTLINE(readability-convert-member-functions-to-static) uarch_interpreter_break_reason machine::run_uarch(uint64_t uarch_cycle_end) { + if (m_s.hash_tree.target != hash_tree_target::uarch) { + throw std::runtime_error("operation not supported: hash_tree target is not uarch"); + } if (read_reg(reg::iunrep) != 0) { throw std::runtime_error("microarchitecture cannot be used with unreproducible machines"); } @@ -2237,15 +2270,17 @@ interpreter_break_reason machine::log_step(uint64_t mcycle_count, const std::str if (!update_merkle_tree()) { throw std::runtime_error{"error updating Merkle tree"}; } - // Ensure that the microarchitecture is reset - auto current_uarch_state_hash = - get_merkle_tree_node_hash(PMA_SHADOW_UARCH_STATE_START, UARCH_STATE_LOG2_SIZE, skip_merkle_tree_update); - if (current_uarch_state_hash != get_uarch_pristine_state_hash()) { - throw std::runtime_error{"microarchitecture is not reset"}; + if (m_s.hash_tree.target == hash_tree_target::uarch) { + // Ensure that the microarchitecture is reset + auto current_uarch_state_hash = + get_merkle_tree_node_hash(PMA_SHADOW_UARCH_STATE_START, UARCH_STATE_LOG2_SIZE, skip_merkle_tree_update); + if (current_uarch_state_hash != get_uarch_pristine_state_hash()) { + throw std::runtime_error{"microarchitecture is not reset"}; + } } - hash_type root_hash_before; + machine_hash root_hash_before; get_root_hash(root_hash_before); - record_step_state_access::context context(filename); + record_step_state_access::context context(filename, m_s.hash_tree.target); record_step_state_access a(context, *this); uint64_t mcycle_end{}; if (__builtin_add_overflow(a.read_mcycle(), mcycle_count, &mcycle_end)) { @@ -2253,14 +2288,14 @@ interpreter_break_reason machine::log_step(uint64_t mcycle_count, const std::str } auto break_reason = interpret(a, mcycle_end); a.finish(); - hash_type root_hash_after; + machine_hash root_hash_after; get_root_hash(root_hash_after); verify_step(root_hash_before, filename, mcycle_count, root_hash_after); return break_reason; } -interpreter_break_reason machine::verify_step(const hash_type &root_hash_before, const std::string &filename, - uint64_t mcycle_count, const hash_type &root_hash_after) { +interpreter_break_reason machine::verify_step(const machine_hash &root_hash_before, const std::string &filename, + uint64_t mcycle_count, const machine_hash &root_hash_after) { auto data_length = os_get_file_length(filename.c_str(), "step log file"); auto *data = os_map_file(filename.c_str(), data_length, false /* not shared */); replay_step_state_access::context context; diff --git a/src/machine.h b/src/machine.h index c7bb5f705..e32bac262 100644 --- a/src/machine.h +++ b/src/machine.h @@ -127,7 +127,6 @@ class machine final { public: /// \brief Type of hash - using hash_type = machine_merkle_tree::hash_type; using reg = machine_reg; @@ -174,8 +173,8 @@ class machine final { /// \param log_filename Name of the file containing the log. /// \param mcycle_count Number of mcycles the machine was run for. /// \param root_hash_after Hash of the state after the step. - static interpreter_break_reason verify_step(const hash_type &root_hash_before, const std::string &log_filename, - uint64_t mcycle_count, const hash_type &root_hash_after); + static interpreter_break_reason verify_step(const machine_hash &root_hash_before, const std::string &log_filename, + uint64_t mcycle_count, const machine_hash &root_hash_after); /// \brief Runs the machine in the microarchitecture until the mcycles advances by one unit or the micro cycle /// counter (uarch_cycle) reaches uarch_cycle_end @@ -200,15 +199,15 @@ class machine final { /// \param root_hash_before State hash before step. /// \param log Step state access log. /// \param root_hash_after State hash after step. - static void verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after); + static void verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after); /// \brief Checks the validity of a state transition caused by log_reset_uarch. /// \param root_hash_before State hash before uarch reset /// \param log Step state access log. /// \param root_hash_after State hash after uarch reset. - static void verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after); + static void verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after); /// \brief Returns copy of default machine config static machine_config get_default_config(); @@ -291,19 +290,19 @@ class machine final { /// \brief Obtains the root hash of the Merkle tree. /// \param hash Receives the hash. - void get_root_hash(hash_type &hash) const; + void get_root_hash(machine_hash &hash) const; /// \brief Obtains the hash of a node in the Merkle tree. /// \param address Address of target node. Must be aligned to a 2log2_size boundary. /// \param log2_size log2 of size subintended by target node. /// \returns The hash of the target node. - hash_type get_merkle_tree_node_hash(uint64_t address, int log2_size) const; + machine_hash get_merkle_tree_node_hash(uint64_t address, int log2_size) const; /// \brief Obtains the hash of a node in the Merkle tree without making any modifications to the tree. /// \param address Address of target node. Must be aligned to a 2log2_size boundary. /// \param log2_size log2 of size subintended by target node. /// \returns The hash of the target node. - hash_type get_merkle_tree_node_hash(uint64_t address, int log2_size, skip_merkle_tree_update_t /*unused*/) const; + machine_hash get_merkle_tree_node_hash(uint64_t address, int log2_size, skip_merkle_tree_update_t /*unused*/) const; /// \brief Verifies integrity of Merkle tree. /// \returns True if tree is self-consistent, false otherwise. @@ -447,7 +446,7 @@ class machine final { /// \param log Log containing the state accesses performed by the load operation /// \param root_hash_after State hash after response was sent. static void verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after); + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after); }; } // namespace cartesi diff --git a/src/merkle-tree-hash.cpp b/src/merkle-tree-hash.cpp index 3531de036..4e428fd0f 100644 --- a/src/merkle-tree-hash.cpp +++ b/src/merkle-tree-hash.cpp @@ -30,8 +30,10 @@ #include "unique-c-ptr.h" using namespace cartesi; -using hasher_type = keccak_256_hasher; -using hash_type = hasher_type::hash_type; + +static i_hasher make_hasher() { + return i_hasher::make_uarch(); +} /// \brief Checks if string matches prefix and captures remaninder /// \param pre Prefix to match in str. @@ -68,7 +70,7 @@ static bool intval(const char *pre, const char *str, int *val) { /// \brief Prints hash in hex to file /// \param hash Hash to be printed. /// \param f File to print to -static void print_hash(const hash_type &hash, FILE *f) { +static void print_hash(const machine_hash &hash, FILE *f) { for (auto b : hash) { std::ignore = fprintf(f, "%02x", static_cast(b)); } @@ -80,12 +82,12 @@ static void print_hash(const hash_type &hash, FILE *f) { /// \brief Reads a hash in hex from file /// \param f File to read from /// \returns Hash if successful, nothing otherwise -static std::optional read_hash(FILE *f) { +static std::optional read_hash(FILE *f) { std::array hex_hash{}; if (fread(hex_hash.data(), 1, hex_hash.size(), f) != hex_hash.size()) { return {}; } - hash_type h; + machine_hash h; for (size_t i = 0; i < hasher_type::hash_size; ++i) { std::array hex_c{hex_hash[2 * i], hex_hash[2 * i + 1], '\0'}; unsigned c = 0; @@ -116,7 +118,7 @@ __attribute__((format(printf, 1, 2))) static void error(const char *fmt, ...) { /// \param word Pointer to word data. Must contain 2^log2_word_size bytes /// \param log2_word_size Log2 of word size /// \param hash Receives the word hash -static void get_word_hash(hasher_type &h, const unsigned char *word, int log2_word_size, hash_type &hash) { +static void get_word_hash(i_hasher &h, const unsigned char *word, int log2_word_size, machine_hash &hash) { h.begin(); h.add_data(word, 1 << log2_word_size); h.end(hash); @@ -129,32 +131,21 @@ static void get_word_hash(hasher_type &h, const unsigned char *word, int log2_wo /// \param log2_leaf_size Log2 of leaf size /// \param log2_word_size Log2 of word size /// \returns Merkle hash of leaf data -static hash_type get_leaf_hash(hasher_type &h, const unsigned char *leaf_data, int log2_leaf_size, int log2_word_size) { +static machine_hash get_leaf_hash(i_hasher h, const unsigned char *leaf_data, int log2_leaf_size, int log2_word_size) { assert(log2_word_size >= 1); assert(log2_leaf_size >= log2_word_size); if (log2_leaf_size > log2_word_size) { - hash_type left = get_leaf_hash(h, leaf_data, log2_leaf_size - 1, log2_word_size); - const hash_type right = + machine_hash left = get_leaf_hash(h, leaf_data, log2_leaf_size - 1, log2_word_size); + const machine_hash right = get_leaf_hash(h, leaf_data + (1 << (log2_leaf_size - 1)), log2_leaf_size - 1, log2_word_size); - get_concat_hash(h, left, right, left); + h.get_concat_hash(left, right, left); return left; } - hash_type leaf; + machine_hash leaf; get_word_hash(h, leaf_data, log2_word_size, leaf); return leaf; } -/// \brief Computes the Merkle hash of a leaf of data -/// \param leaf_data Pointer to buffer containing leaf data with -/// at least 2^log2_leaf_size bytes -/// \param log2_leaf_size Log2 of leaf size -/// \param log2_word_size Log2 of word size -/// \returns Merkle hash of leaf data -static hash_type get_leaf_hash(const unsigned char *leaf_data, int log2_leaf_size, int log2_word_size) { - hasher_type h; - return get_leaf_hash(h, leaf_data, log2_leaf_size, log2_word_size); -} - /// \brief Prints help message static void help(const char *name) { std::ignore = fprintf(stderr, R"(Usage: @@ -198,7 +189,7 @@ The hash function used is Keccak-256. exit(0); } -int main(int argc, char *argv[]) { +int main(int argc, char *argv[]) try { const char *input_name = nullptr; int log2_word_size = 3; int log2_leaf_size = 12; @@ -246,7 +237,7 @@ int main(int argc, char *argv[]) { return 1; } - back_merkle_tree back_tree{log2_root_size, log2_leaf_size, log2_word_size}; + back_merkle_tree back_tree{log2_root_size, log2_leaf_size, log2_word_size, make_hasher()}; const uint64_t max_leaves = UINT64_C(1) << (log2_root_size - log2_leaf_size); uint64_t leaf_count = 0; @@ -266,7 +257,7 @@ int main(int argc, char *argv[]) { // Pad leaf with zeros if file ended before next leaf boundary memset(leaf_buf.get() + got, 0, leaf_size - got); // Compute leaf hash - auto leaf_hash = get_leaf_hash(leaf_buf.get(), log2_leaf_size, log2_word_size); + auto leaf_hash = get_leaf_hash(make_hasher(), leaf_buf.get(), log2_leaf_size, log2_word_size); // Add leaf to incremental tree back_tree.push_back(leaf_hash); // Compare the root hash for the incremental tree and the @@ -275,4 +266,10 @@ int main(int argc, char *argv[]) { } print_hash(back_tree.get_root_hash(), stdout); return 0; +} catch (std::exception &e) { + std::cerr << "Caught exception: " << e.what() << '\n'; + return 1; +} catch (...) { + std::cerr << "Caught unknown exception\n"; + return 1; } diff --git a/src/merkle-tree-proof.h b/src/merkle-tree-proof.h index 14de54041..7f8fe89b0 100644 --- a/src/merkle-tree-proof.h +++ b/src/merkle-tree-proof.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -38,12 +39,9 @@ namespace cartesi { /// \} /// \tparam HASH_TYPE the type that holds a hash /// \tparam ADDRESS_TYPE the type that holds an address -template class merkle_tree_proof final { public: - using hash_type = HASH_TYPE; - - using address_type = ADDRESS_TYPE; + using address_type = uint64_t; /// \brief Constructs a merkle_tree_proof object and allocates /// room for the sibling hashes @@ -70,7 +68,7 @@ class merkle_tree_proof final { /// \brief Storage for the hashes of the siblings of all nodes along /// the path from the root node to the target node. - using sibling_hashes_type = std::vector; + using sibling_hashes_type = std::vector; /// \brief Gets log2 of size subintended by entire tree. /// \returns log2 of size subintended by entire tree. @@ -101,45 +99,45 @@ class merkle_tree_proof final { /// \brief Set hash of target node /// \param hash New hash. - void set_target_hash(const hash_type &hash) { + void set_target_hash(const machine_hash &hash) { m_target_hash = hash; } /// \brief Gets hash of target node /// \return Reference to hash. - const hash_type &get_target_hash() const { + const machine_hash &get_target_hash() const { return m_target_hash; } - hash_type &get_target_hash() { + machine_hash &get_target_hash() { return m_target_hash; } /// \brief Set hash of root node /// \param hash New hash. - void set_root_hash(const hash_type &hash) { + void set_root_hash(const machine_hash &hash) { m_root_hash = hash; } /// \brief Gets hash of root node /// \return Reference to hash. - const hash_type &get_root_hash() const { + const machine_hash &get_root_hash() const { return m_root_hash; } - hash_type &get_root_hash() { + machine_hash &get_root_hash() { return m_root_hash; } /// \brief Get hash corresponding to log2_size from the list of siblings. /// \param log2_size log2 of size subintended by hash. /// \return Reference to hash inside list of siblings. - const hash_type &get_sibling_hash(int log2_size) const { + const machine_hash &get_sibling_hash(int log2_size) const { return m_sibling_hashes[log2_size_to_index(log2_size)]; } /// \brief Modify hash corresponding to log2_size in the list of siblings. /// \param hash New hash. /// \param log2_size log2 of size subintended by hash. - void set_sibling_hash(const hash_type &hash, int log2_size) { + void set_sibling_hash(const machine_hash &hash, int log2_size) { m_sibling_hashes[log2_size_to_index(log2_size)] = hash; } @@ -171,7 +169,7 @@ class merkle_tree_proof final { } /// \brief Checks if two Merkle proofs are different - bool operator!=(const merkle_tree_proof &other) const { + bool operator!=(const merkle_tree_proof &other) const { return !(operator==(other)); } @@ -179,8 +177,7 @@ class merkle_tree_proof final { ///< \tparam HASHER_TYPE Hasher class to use ///< \param h Hasher object to use ///< \return True if proof is valid, false otherwise - template - bool verify(HASHER_TYPE &h) const { + bool verify(i_hasher &h) const { return bubble_up(h, get_target_hash()) == get_root_hash(); } @@ -189,29 +186,23 @@ class merkle_tree_proof final { ///< \param h Hasher object to use ///< \param new_target_hash New target hash to replace ///< \return New root hash - template - hash_type bubble_up(HASHER_TYPE &h, const hash_type &new_target_hash) const { - static_assert(is_an_i_hasher::value, "not an i_hasher"); - static_assert(std::is_same_v::type::hash_type, hash_type>, - "incompatible hash types"); - hash_type hash = new_target_hash; + machine_hash bubble_up(i_hasher &h, const machine_hash &new_target_hash) const { + machine_hash hash = new_target_hash; for (int log2_size = get_log2_target_size(); log2_size < get_log2_root_size(); ++log2_size) { - const int bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; + const auto bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; if (bit) { - get_concat_hash(h, get_sibling_hash(log2_size), hash, hash); + h.get_concat_hash(get_sibling_hash(log2_size), hash, hash); } else { - get_concat_hash(h, hash, get_sibling_hash(log2_size), hash); + h.get_concat_hash(hash, get_sibling_hash(log2_size), hash); } } return hash; } - template - merkle_tree_proof slice(HASHER_TYPE &h, int new_log2_root_size, - int new_log2_target_size) const { - static_assert(is_an_i_hasher::value, "not an i_hasher"); - static_assert(std::is_same_v::type::hash_type, hash_type>, - "incompatible hash types"); + merkle_tree_proof slice(i_hasher &h, int new_log2_root_size, int new_log2_target_size) const { + //// static_assert(is_an_i_hasher::value, "not an hasher"); + //// static_assert(std::is_same_v::type::machine_hash, machine_hash>, + //// "incompatible hash types"); if (new_log2_root_size <= 0) { throw std::out_of_range{"log2_root_size is not positive"}; } @@ -227,24 +218,24 @@ class merkle_tree_proof final { if (new_log2_target_size < get_log2_target_size()) { throw std::out_of_range{"log2_target_size is too small"}; } - merkle_tree_proof sliced(new_log2_root_size, new_log2_target_size); - hash_type hash = get_target_hash(); + merkle_tree_proof sliced(new_log2_root_size, new_log2_target_size); + machine_hash hash = get_target_hash(); for (int log2_size = get_log2_target_size(); log2_size < new_log2_target_size; ++log2_size) { - int bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; + const auto bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; if (bit) { - get_concat_hash(h, get_sibling_hash(log2_size), hash, hash); + h.get_concat_hash(get_sibling_hash(log2_size), hash, hash); } else { - get_concat_hash(h, hash, get_sibling_hash(log2_size), hash); + h.get_concat_hash(hash, get_sibling_hash(log2_size), hash); } } sliced.set_target_hash(hash); for (int log2_size = new_log2_target_size; log2_size < new_log2_root_size; ++log2_size) { - int bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; - const hash_type &sibling_hash = get_sibling_hash(log2_size); + const auto bit = (get_target_address() & (static_cast(1) << log2_size)) != 0; + const machine_hash &sibling_hash = get_sibling_hash(log2_size); if (bit) { - get_concat_hash(h, sibling_hash, hash, hash); + h.get_concat_hash(sibling_hash, hash, hash); } else { - get_concat_hash(h, hash, sibling_hash, hash); + h.get_concat_hash(hash, sibling_hash, hash); } sliced.set_sibling_hash(sibling_hash, log2_size); } @@ -269,9 +260,9 @@ class merkle_tree_proof final { address_type m_target_address{0}; ///< Address of target node int m_log2_target_size{0}; ///< log2 of size subintended by target node - hash_type m_target_hash{}; ///< Hash of target node + machine_hash m_target_hash{}; ///< Hash of target node int m_log2_root_size{0}; ///< log2 of size subintended by tree - hash_type m_root_hash{}; ///< Hash of root node + machine_hash m_root_hash{}; ///< Hash of root node sibling_hashes_type m_sibling_hashes; ///< Hashes of siblings in path from target to root }; diff --git a/src/pristine-merkle-tree.cpp b/src/pristine-merkle-tree.cpp index 08b65b823..efaed703b 100644 --- a/src/pristine-merkle-tree.cpp +++ b/src/pristine-merkle-tree.cpp @@ -29,7 +29,8 @@ namespace cartesi { -pristine_merkle_tree::pristine_merkle_tree(int log2_root_size, int log2_word_size) : +pristine_merkle_tree::pristine_merkle_tree(i_hasher hasher, int log2_root_size, int log2_word_size) : + m_hasher(hasher), m_log2_root_size{log2_root_size}, m_log2_word_size{log2_word_size}, m_hashes(std::max(0, log2_root_size - log2_word_size + 1)) { @@ -44,16 +45,16 @@ pristine_merkle_tree::pristine_merkle_tree(int log2_root_size, int log2_word_siz } std::vector word(1 << log2_word_size, 0); assert(word.size() == (UINT64_C(1) << log2_word_size)); - hasher_type h; + auto &h = m_hasher; h.begin(); h.add_data(word.data(), word.size()); h.end(m_hashes[0]); for (unsigned i = 1; i < m_hashes.size(); ++i) { - get_concat_hash(h, m_hashes[i - 1], m_hashes[i - 1], m_hashes[i]); + h.get_concat_hash(m_hashes[i - 1], m_hashes[i - 1], m_hashes[i]); } } -const pristine_merkle_tree::hash_type &pristine_merkle_tree::get_hash(int log2_size) const { +const machine_hash &pristine_merkle_tree::get_hash(int log2_size) const { if (log2_size < m_log2_word_size || log2_size > m_log2_root_size) { throw std::out_of_range{"log2_size is out of range"}; } diff --git a/src/pristine-merkle-tree.h b/src/pristine-merkle-tree.h index d3230f600..8565a6bf8 100644 --- a/src/pristine-merkle-tree.h +++ b/src/pristine-merkle-tree.h @@ -20,7 +20,7 @@ #include #include -#include "keccak-256-hasher.h" +#include "i-hasher.h" /// \file /// \brief Pristine Merkle tree interface. @@ -29,30 +29,26 @@ namespace cartesi { /// \brief Hashes of pristine subtrees for a range of sizes class pristine_merkle_tree { -public: - /// \brief Hasher class. - using hasher_type = keccak_256_hasher; - - /// \brief Storage for a hash. - using hash_type = hasher_type::hash_type; + i_hasher m_hasher; +public: using address_type = uint64_t; /// \brief Constructor /// \param log2_root_size Log2 of root node /// \param log2_word_size Log2 of word - pristine_merkle_tree(int log2_root_size, int log2_word_size); + pristine_merkle_tree(i_hasher hasher, int log2_root_size, int log2_word_size); /// \brief Returns hash of pristine subtree /// \param log2_size Log2 of subtree size. Must be between /// log2_word_size (inclusive) and log2_root_size (inclusive) passed /// to constructor. - const hash_type &get_hash(int log2_size) const; + const machine_hash &get_hash(int log2_size) const; private: - int m_log2_root_size; ///< Log2 of tree size - int m_log2_word_size; ///< Log2 of word size - std::vector m_hashes; ///< Vector with hashes + int m_log2_root_size; ///< Log2 of tree size + int m_log2_word_size; ///< Log2 of word size + std::vector m_hashes; ///< Vector with hashes }; } // namespace cartesi diff --git a/src/record-send-cmio-state-access.h b/src/record-send-cmio-state-access.h index c7f66cd76..3a934be9b 100644 --- a/src/record-send-cmio-state-access.h +++ b/src/record-send-cmio-state-access.h @@ -42,23 +42,20 @@ namespace cartesi { /// \details This records all state accesses that happen during the execution of /// a machine::send_cmio_response() function call class record_send_cmio_state_access : public i_state_access { - using hasher_type = machine_merkle_tree::hasher_type; - using hash_type = machine_merkle_tree::hash_type; // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) - machine &m_m; ///< Associated machine - access_log &m_log; ///< Pointer to access log + machine &m_m; ///< Associated machine + access_log &m_log; ///< Pointer to access log + mutable i_hasher m_hasher; ///< Hasher used to compute hashes // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) - static void get_hash(const access_data &data, hash_type &hash) { - hasher_type hasher; - get_merkle_tree_hash(hasher, data.data(), data.size(), machine_merkle_tree::get_word_size(), hash); - } - public: /// \brief Constructor from machine state. /// \param m Reference to machine state. /// \param log Reference to access log. - explicit record_send_cmio_state_access(machine &m, access_log &log) : m_m(m), m_log(log) { + explicit record_send_cmio_state_access(machine &m, access_log &log, i_hasher hasher) : + m_m(m), + m_log(log), + m_hasher{hasher} { ; } @@ -238,9 +235,8 @@ class record_send_cmio_state_access : public i_state_access; using pages_type = std::map; - using hash_type = machine_merkle_tree::hash_type; - using sibling_hashes_type = std::vector; + using sibling_hashes_type = std::vector; using page_indices_type = std::vector; struct context { /// \brief Constructor of record step state access context /// \param filename where to save the log - explicit context(std::string filename) : filename(std::move(filename)) { + /// \param hash_tree_target hash tree target + explicit context(std::string filename, hash_tree_target target) : + filename(std::move(filename)), + target{target} { ; } std::string filename; ///< where to save the log + hash_tree_target target; ///< saved in the log file so the replay can recreate a compatible hasher mutable pages_type touched_pages; ///< copy of all pages touched during execution }; @@ -81,11 +84,16 @@ class record_step_state_access : public i_state_access(m_context.target); + if (fwrite(&hash_tree_target, sizeof(hash_tree_target), 1, fp.get()) != 1) { + throw std::runtime_error("Could not write hash tree target to log file"); + } if (fwrite(&page_count, sizeof(page_count), 1, fp.get()) != 1) { throw std::runtime_error("Could not write page count to log file"); } @@ -97,7 +105,7 @@ class record_step_state_access : public i_state_access { public: using tree_type = machine_merkle_tree; - using hash_type = tree_type::hash_type; - using hasher_type = tree_type::hasher_type; using proof_type = tree_type::proof_type; struct context { /// \brief Constructor replay_send_cmio_state_access context /// \param log Access log to be replayed /// \param initial_hash Initial root hash - context(const access_log &log, machine_merkle_tree::hash_type initial_hash) : + context(const access_log &log, machine_hash initial_hash) : accesses(log.get_accesses()), - root_hash(initial_hash) { + root_hash(initial_hash), + target(log.get_hash_tree_target()) { ; } const std::vector &accesses; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) ///< Index of next access to ne consumed unsigned int next_access{}; ///< Root hash before next access - machine_merkle_tree::hash_type root_hash; - ///< Hasher needed to verify proofs - machine_merkle_tree::hasher_type hasher; + machine_hash root_hash; + ///< hash tree target used to create hashers + hash_tree_target target; }; private: @@ -79,7 +78,7 @@ class replay_send_cmio_state_access : public i_state_access(write_length, std::nothrow_t{}); if (!scratch) { throw std::runtime_error("Could not allocate scratch memory"); @@ -323,8 +323,7 @@ class replay_send_cmio_state_access : public i_state_access data_length) { memset(scratch.get() + data_length, 0, write_length - data_length); } - get_merkle_tree_hash(hasher, scratch.get(), write_length, machine_merkle_tree::get_word_size(), - computed_data_hash); + h.get_merkle_tree_hash(scratch.get(), write_length, machine_merkle_tree::get_word_size(), computed_data_hash); // check if logged written hash matches the computed data hash if (written_hash != computed_data_hash) { throw std::invalid_argument{"logged written hash of " + text + @@ -332,8 +331,8 @@ class replay_send_cmio_state_access : public i_state_access), - "hash_type size mismatch"); +static_assert(sizeof(cartesi::machine_hash) == sizeof(std::remove_pointer_t), + "machine_hash size mismatch"); -extern "C" void interop_merkle_tree_hash(const unsigned char *data, size_t size, interop_hash_type hash) { - machine_merkle_tree::hasher_type hasher{}; - get_merkle_tree_hash(hasher, data, size, machine_merkle_tree::get_word_size(), +extern "C" void interop_merkle_tree_hash(int hash_tree_target, const unsigned char *data, size_t size, + interop_hash_type hash) { + auto hasher = make_hasher(hash_tree_target); + hasher.get_merkle_tree_hash(data, size, machine_merkle_tree::get_word_size(), // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - *reinterpret_cast(hash)); + *reinterpret_cast(hash)); } -extern "C" void interop_concat_hash(interop_const_hash_type left, interop_const_hash_type right, +extern "C" void interop_concat_hash(int hash_tree_target, interop_const_hash_type left, interop_const_hash_type right, interop_hash_type result) { - machine_merkle_tree::hasher_type hasher{}; + auto hasher = make_hasher(hash_tree_target); // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) - get_concat_hash(hasher, *reinterpret_cast(left), - *reinterpret_cast(right), - *reinterpret_cast(result)); + hasher.get_concat_hash(*reinterpret_cast(left), + *reinterpret_cast(right), *reinterpret_cast(result)); // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } diff --git a/src/replay-step-state-access-interop.h b/src/replay-step-state-access-interop.h index b8e4d3428..b3b314289 100644 --- a/src/replay-step-state-access-interop.h +++ b/src/replay-step-state-access-interop.h @@ -32,9 +32,10 @@ NO_RETURN inline void interop_throw_runtime_error(const char *msg) { throw std::runtime_error(msg); } -extern "C" void interop_merkle_tree_hash(const unsigned char *data, size_t size, interop_hash_type hash); +extern "C" void interop_merkle_tree_hash(int hash_tree_target, const unsigned char *data, size_t size, + interop_hash_type hash); -extern "C" void interop_concat_hash(interop_const_hash_type left, interop_const_hash_type right, +extern "C" void interop_concat_hash(int hash_tree_target, interop_const_hash_type left, interop_const_hash_type right, interop_hash_type result); #endif diff --git a/src/replay-step-state-access.h b/src/replay-step-state-access.h index b8d61d3d2..582ace624 100644 --- a/src/replay-step-state-access.h +++ b/src/replay-step-state-access.h @@ -201,20 +201,20 @@ class replay_step_state_access : public i_state_access; - static_assert(sizeof(hash_type) == interop_machine_hash_byte_size); + static_assert(sizeof(machine_hash) == interop_machine_hash_byte_size); struct PACKED page_type { address_type index; data_type data; - hash_type hash; + machine_hash hash; }; struct context { + hash_tree_target target{hash_tree_target::uarch}; uint64_t page_count{0}; ///< Number of pages in the step log page_type *pages{nullptr}; ///< Array of page data uint64_t sibling_count{0}; ///< Number of sibling hashes in the step log - hash_type *sibling_hashes{nullptr}; ///< Array of sibling hashes + machine_hash *sibling_hashes{nullptr}; ///< Array of sibling hashes std::array, PMA_MAX> pmas{}; ///< Array of PMA entries }; @@ -229,9 +229,10 @@ class replay_step_state_access : public i_state_access(log_image + first_siblng_offset); + m_context.sibling_hashes = reinterpret_cast(log_image + first_siblng_offset); // ensure that we read exactly the expected log size if (end_offset != log_size) { @@ -281,7 +295,7 @@ class replay_step_state_access : public i_state_access 0 && m_context.pages[i - 1].index >= m_context.pages[i].index) { interop_throw_runtime_error("invalid log format: page index is not in increasing order"); @@ -305,7 +319,7 @@ class replay_step_state_access : public i_state_access(); @@ -453,9 +467,9 @@ class replay_step_state_access : public i_state_access(m_context.target), m_context.pages[i].data, PMA_PAGE_SIZE, // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(&m_context.pages[i].hash)); } @@ -479,7 +493,7 @@ class replay_step_state_access : public i_state_access(&left), reinterpret_cast(&right), - reinterpret_cast(&hash)); + machine_hash hash{}; + interop_concat_hash(static_cast(m_context.target), reinterpret_cast(&left), + reinterpret_cast(&right), reinterpret_cast(&hash)); return hash; } if (m_context.pages[next_page].index == page_index) { diff --git a/src/sha-256-hasher.h b/src/sha-256-hasher.h new file mode 100644 index 000000000..a8199b742 --- /dev/null +++ b/src/sha-256-hasher.h @@ -0,0 +1,64 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef SHA256_HASHER_H +#define SHA256_HASHER_H + +#include + +#include "machine-hash.h" + +extern "C" { +#include "sha256.h" +} + +namespace cartesi { + +class sha_256_hasher final { + sha256_context m_ctx{}; + +public: + void begin() { + sha256_init(&m_ctx); + } + + void add_data(const unsigned char *data, size_t length) { + sha256_hash(&m_ctx, data, length); + } + + void end(machine_hash &hash) { + sha256_done(&m_ctx, hash.data()); + } + + /// \brief Default constructor + sha_256_hasher() = default; + + /// \brief Default destructor + ~sha_256_hasher() = default; + + /// \brief No copy constructor + sha_256_hasher(const sha_256_hasher &) = default; + /// \brief No move constructor + sha_256_hasher(sha_256_hasher &&) = default; + /// \brief No copy assignment + sha_256_hasher &operator=(const sha_256_hasher &) = default; + /// \brief No move assignment + sha_256_hasher &operator=(sha_256_hasher &&) = default; +}; + +} // namespace cartesi + +#endif diff --git a/src/shadow-state-factory.cpp b/src/shadow-state-factory.cpp index d055a63da..9a42f1068 100644 --- a/src/shadow-state-factory.cpp +++ b/src/shadow-state-factory.cpp @@ -97,6 +97,7 @@ static bool shadow_state_peek(const pma_entry & /*pma*/, const machine &m, uint6 s->htif_ihalt = m.read_reg(machine_reg::htif_ihalt); s->htif_iconsole = m.read_reg(machine_reg::htif_iconsole); s->htif_iyield = m.read_reg(machine_reg::htif_iyield); + s->hash_tree_target = m.read_reg(machine_reg::hash_tree_target); *page_data = scratch; return true; } diff --git a/src/shadow-state.h b/src/shadow-state.h index fd35759f1..91adb34d2 100644 --- a/src/shadow-state.h +++ b/src/shadow-state.h @@ -76,6 +76,7 @@ struct PACKED shadow_state { uint64_t htif_ihalt; uint64_t htif_iconsole; uint64_t htif_iyield; + uint64_t hash_tree_target; }; /// \brief Global instance of the processor shadow device driver. diff --git a/src/uarch-pristine-state-hash.cpp b/src/uarch-pristine-state-hash.cpp index 3f97941cf..771d866ce 100644 --- a/src/uarch-pristine-state-hash.cpp +++ b/src/uarch-pristine-state-hash.cpp @@ -23,16 +23,16 @@ namespace cartesi { -static machine_merkle_tree::hash_type make_uarch_pristine_state_hash() noexcept { - machine_merkle_tree::hash_type h; +static machine_hash make_uarch_pristine_state_hash() noexcept { + machine_hash h; for (std::size_t i = 0; i < h.size(); ++i) { h[i] = uarch_pristine_hash[i]; } return h; } -const machine_merkle_tree::hash_type &get_uarch_pristine_state_hash() { - static const machine_merkle_tree::hash_type uarch_pristine_state_hash = make_uarch_pristine_state_hash(); +const machine_hash &get_uarch_pristine_state_hash() { + static const machine_hash uarch_pristine_state_hash = make_uarch_pristine_state_hash(); return uarch_pristine_state_hash; } diff --git a/src/uarch-pristine-state-hash.h b/src/uarch-pristine-state-hash.h index 618ee65e6..ea41d94d1 100644 --- a/src/uarch-pristine-state-hash.h +++ b/src/uarch-pristine-state-hash.h @@ -23,7 +23,7 @@ namespace cartesi { /// \brief Gets the hash of the pristine uarch state. /// \details This hash is computed at compile time by the program compute-uarch-pristine-hash.cpp -const machine_merkle_tree::hash_type &get_uarch_pristine_state_hash(); +const machine_hash &get_uarch_pristine_state_hash(); } // namespace cartesi diff --git a/src/uarch-record-state-access.h b/src/uarch-record-state-access.h index f73c313f2..fc4a501c4 100644 --- a/src/uarch-record-state-access.h +++ b/src/uarch-record-state-access.h @@ -49,8 +49,6 @@ namespace cartesi { /// \details The uarch_record_state_access logs all access to the machine state. class uarch_record_state_access : public i_uarch_state_access { - using hasher_type = machine_merkle_tree::hasher_type; - using hash_type = machine_merkle_tree::hash_type; // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) uarch_state &m_us; @@ -85,9 +83,9 @@ class uarch_record_state_access : public i_uarch_state_access { using tree_type = machine_merkle_tree; - using hash_type = tree_type::hash_type; - using hasher_type = tree_type::hasher_type; using proof_type = tree_type::proof_type; ///< Access log generated by step @@ -53,17 +51,18 @@ class uarch_replay_state_access : public i_uarch_state_access= m_accesses.size()) { throw std::invalid_argument{"too few accesses in log"}; @@ -345,7 +344,7 @@ class uarch_replay_state_access : public i_uarch_state_accessget_proof(address, log2_size); } -void virtual_machine::do_get_root_hash(hash_type &hash) const { +void virtual_machine::do_get_root_hash(machine_hash &hash) const { get_machine()->get_root_hash(hash); } @@ -183,23 +183,23 @@ machine_config virtual_machine::do_get_default_config() const { return machine::get_default_config(); } -interpreter_break_reason virtual_machine::do_verify_step(const hash_type &root_hash_before, - const std::string &log_filename, uint64_t mcycle_count, const hash_type &root_hash_after) const { +interpreter_break_reason virtual_machine::do_verify_step(const machine_hash &root_hash_before, + const std::string &log_filename, uint64_t mcycle_count, const machine_hash &root_hash_after) const { return machine::verify_step(root_hash_before, log_filename, mcycle_count, root_hash_after); } -void virtual_machine::do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { +void virtual_machine::do_verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { machine::verify_step_uarch(root_hash_before, log, root_hash_after); } -void virtual_machine::do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const { +void virtual_machine::do_verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const { machine::verify_reset_uarch(root_hash_before, log, root_hash_after); } void virtual_machine::do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const { + const machine_hash &root_hash_before, const access_log &log, const machine_hash &root_hash_after) const { machine::verify_send_cmio_response(reason, data, length, root_hash_before, log, root_hash_after); } diff --git a/src/virtual-machine.h b/src/virtual-machine.h index 64e9340c7..5e20f45a9 100644 --- a/src/virtual-machine.h +++ b/src/virtual-machine.h @@ -53,7 +53,7 @@ class virtual_machine : public i_virtual_machine { void do_store(const std::string &directory) const override; access_log do_log_step_uarch(const access_log::type &log_type) override; machine_merkle_tree::proof_type do_get_proof(uint64_t address, int log2_size) const override; - void do_get_root_hash(hash_type &hash) const override; + void do_get_root_hash(machine_hash &hash) const override; bool do_verify_merkle_tree() const override; uint64_t do_read_reg(reg r) const override; void do_write_reg(reg w, uint64_t val) override; @@ -78,14 +78,15 @@ class virtual_machine : public i_virtual_machine { const access_log::type &log_type) override; uint64_t do_get_reg_address(reg r) const override; machine_config do_get_default_config() const override; - interpreter_break_reason do_verify_step(const hash_type &root_hash_before, const std::string &log_filename, - uint64_t mcycle_count, const hash_type &root_hash_after) const override; - void do_verify_step_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const override; - void do_verify_reset_uarch(const hash_type &root_hash_before, const access_log &log, - const hash_type &root_hash_after) const override; + interpreter_break_reason do_verify_step(const machine_hash &root_hash_before, const std::string &log_filename, + uint64_t mcycle_count, const machine_hash &root_hash_after) const override; + void do_verify_step_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; + void do_verify_reset_uarch(const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; void do_verify_send_cmio_response(uint16_t reason, const unsigned char *data, uint64_t length, - const hash_type &root_hash_before, const access_log &log, const hash_type &root_hash_after) const override; + const machine_hash &root_hash_before, const access_log &log, + const machine_hash &root_hash_after) const override; machine *get_machine(); const machine *get_machine() const; diff --git a/tests/Makefile b/tests/Makefile index e54068cdd..efc30a945 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -200,7 +200,8 @@ test-uarch-interpreter: $(MAKE) -C ../third-party/riscv-arch-test verify XLEN=64 RISCV_TARGET=cartesi RISCV_ISA=rv64i RISCV_DEVICE=I WORK=$(BUILDDIR)/uarch-riscv-arch-test RUN_ARCH_TEST='$(RUN_ARCH_TEST)' test-hash: - $(LD_PRELOAD_PREFIX) ./build/misc/test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=build/misc/test-merkle-tree-hash + $(LD_PRELOAD_PREFIX) ./build/misc/test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=build/misc/test-merkle-tree-hash --hash-tree-target=uarch + $(LD_PRELOAD_PREFIX) ./build/misc/test-merkle-tree-hash --log2-root-size=30 --log2-leaf-size=12 --input=build/misc/test-merkle-tree-hash --hash-tree-target=risc0 test-jsonrpc: ./scripts/test-jsonrpc-server.sh ../src/cartesi-jsonrpc-machine '$(LUA) ../src/cartesi-machine.lua' '$(LUA) ./lua/cartesi-machine-tests.lua' '$(LUA)' diff --git a/tests/lua/cartesi/tests/util.lua b/tests/lua/cartesi/tests/util.lua index c98e13138..c33a74724 100644 --- a/tests/lua/cartesi/tests/util.lua +++ b/tests/lua/cartesi/tests/util.lua @@ -32,16 +32,29 @@ local test_util = { tests_uarch_path = adjust_path(assert(os.getenv("CARTESI_TESTS_UARCH_PATH"))), } -local zero_keccak_hash_table = { - "", - "", -} - -do - local hash = cartesi.keccak(string.rep("\0", 1 << WORD_LOG2_SIZE)) +local function compute_zero_hash_table(hash_fn) + local zero_hash_table = { + "", + "", + } + local hash = hash_fn(string.rep("\0", 1 << WORD_LOG2_SIZE)) for i = WORD_LOG2_SIZE, ROOT_LOG2_SIZE - 1 do - zero_keccak_hash_table[i] = hash - hash = cartesi.keccak(hash, hash) + zero_hash_table[i] = hash + hash = hash_fn(hash, hash) + end + return zero_hash_table +end + +local zero_keccak_hash_table = compute_zero_hash_table(cartesi.keccak) +local zero_sha256_hash_table = compute_zero_hash_table(cartesi.sha256) + +local function get_zero_hash_table(hasher_fn) + if hasher_fn == cartesi.keccak then + return zero_keccak_hash_table + elseif hasher_fn == cartesi.sha256 then + return zero_sha256_hash_table + else + assert(false, "Invalid hash function") end end @@ -93,7 +106,7 @@ function back_merkle_tree_meta.__index:push_back(new_leaf_hash) for i = 0, depth do if self.m_leaf_count & (0x01 << i) ~= 0x0 then local left = self.m_context[i] - right = cartesi.keccak(left, right) + right = self.hash_fn(left, right) else self.m_context[i] = right break @@ -118,12 +131,12 @@ function back_merkle_tree_meta.__index:pad_back(new_leaf_count) -- is our smallest tree at depth j? if (self.m_leaf_count & j_span) ~= 0x0 then -- if so, we can add 2^j pristine leaves directly - local right = zero_keccak_hash_table[self.m_log2_leaf_size + j] + local right = self.zero_hash_table[self.m_log2_leaf_size + j] for i = j, depth do local i_span = 0x1 << i if (self.m_leaf_count & i_span) ~= 0x0 then local left = self.m_context[i] - right = cartesi.keccak(left, right) + right = self.hash_fn(left, right) else self.m_context[i] = right -- next outer loop starts again from where inner loop left off @@ -141,7 +154,7 @@ function back_merkle_tree_meta.__index:pad_back(new_leaf_count) for i = 0, depth do local i_span = 0x1 << i if (new_leaf_count & i_span) ~= 0x0 then - self.m_context[i] = zero_keccak_hash_table[self.m_log2_leaf_size + i] + self.m_context[i] = self.zero_hash_table[self.m_log2_leaf_size + i] new_leaf_count = new_leaf_count - i_span self.m_leaf_count = self.m_leaf_count + i_span end @@ -152,14 +165,14 @@ function back_merkle_tree_meta.__index:get_root_hash() assert(self.m_leaf_count <= self.m_max_leaves, "too many leaves") local depth = self.m_log2_root_size - self.m_log2_leaf_size if self.m_leaf_count < self.m_max_leaves then - local root = zero_keccak_hash_table[self.m_log2_leaf_size] + local root = self.zero_hash_table[self.m_log2_leaf_size] for i = 0, depth - 1 do if (self.m_leaf_count & (0x01 << i)) ~= 0 then local left = self.m_context[i] - root = cartesi.keccak(left, root) + root = self.hash_fn(left, root) else - local right = zero_keccak_hash_table[self.m_log2_leaf_size + i] - root = cartesi.keccak(root, right) + local right = self.zero_hash_table[self.m_log2_leaf_size + i] + root = self.hash_fn(root, right) end end return root @@ -168,8 +181,10 @@ function back_merkle_tree_meta.__index:get_root_hash() end end -function test_util.new_back_merkle_tree(log2_root_size, log2_leaf_size) +function test_util.new_back_merkle_tree(log2_root_size, log2_leaf_size, hash_fn) local self = {} + self.hash_fn = hash_fn + self.zero_hash_table = get_zero_hash_table(hash_fn) self.m_context = {} self.m_log2_leaf_size = log2_leaf_size self.m_log2_root_size = log2_root_size @@ -206,7 +221,22 @@ function test_util.split_string(inputstr, sep) return t end -function test_util.check_proof(proof) +function test_util.get_machine_hash_tree_hash_fn(machine) + assert(machine, "machine is nil") + local config = machine:get_initial_config() + + local hash_tree_target = config.hash_tree.target + if hash_tree_target == "uarch" then + return cartesi.keccak + elseif hash_tree_target == "risc0" then + return cartesi.sha256 + else + assert(false, "Invalid hash tree target: " .. tostring(hash_tree_target)) + end +end + +function test_util.check_proof(proof, hash_fn) + assert(hash_fn, "hash_fn is nil") local hash = proof.target_hash for log2_size = proof.log2_target_size, proof.log2_root_size - 1 do local bit = (proof.target_address & (1 << log2_size)) ~= 0 @@ -216,7 +246,7 @@ function test_util.check_proof(proof) else first, second = hash, proof.sibling_hashes[log2_size - proof.log2_target_size + 1] end - hash = cartesi.keccak(first, second) + hash = hash_fn(first, second) end return hash == proof.root_hash end @@ -231,16 +261,17 @@ function test_util.load_file(filename) return data end -local function merkle_hash(data, start, log2_size) +local function merkle_hash(data, start, log2_size, hash_fn) + local zero_hash_table = get_zero_hash_table(hash_fn) if log2_size == PAGE_LOG2_SIZE and data:sub(start + 1, start + PAGE_SIZE) == ZERO_PAGE then - return zero_keccak_hash_table[PAGE_LOG2_SIZE] + return zero_hash_table[PAGE_LOG2_SIZE] elseif log2_size > WORD_LOG2_SIZE then local child_log2_size = log2_size - 1 - local left = merkle_hash(data, start, child_log2_size) - local right = merkle_hash(data, start + (1 << child_log2_size), child_log2_size) - return cartesi.keccak(left, right) + local left = merkle_hash(data, start, child_log2_size, hash_fn) + local right = merkle_hash(data, start + (1 << child_log2_size), child_log2_size, hash_fn) + return hash_fn(left, right) else - return cartesi.keccak(data:sub(start + 1, start + (1 << WORD_LOG2_SIZE))) + return hash_fn(data:sub(start + 1, start + (1 << WORD_LOG2_SIZE)), nil) end end @@ -248,14 +279,14 @@ test_util.merkle_hash = merkle_hash -- Take data from dumped memory files -- and calculate root hash of the machine -function test_util.calculate_emulator_hash(machine) - local tree = test_util.new_back_merkle_tree(64, PAGE_LOG2_SIZE) +function test_util.calculate_emulator_hash(machine, hash_fn) + local tree = test_util.new_back_merkle_tree(64, PAGE_LOG2_SIZE, hash_fn) local last = 0 for _, v in ipairs(machine:get_memory_ranges()) do tree:pad_back((v.start - last) >> PAGE_LOG2_SIZE) local finish = v.start + v.length for j = v.start, finish - 1, PAGE_SIZE do - local page_hash = merkle_hash(machine:read_memory(j, PAGE_SIZE), 0, PAGE_LOG2_SIZE) + local page_hash = merkle_hash(machine:read_memory(j, PAGE_SIZE), 0, PAGE_LOG2_SIZE, hash_fn) tree:push_back(page_hash) end last = finish @@ -264,12 +295,12 @@ function test_util.calculate_emulator_hash(machine) end -- Read memory from given machine and calculate uarch state hash -function test_util.calculate_uarch_state_hash(machine) +function test_util.calculate_uarch_state_hash(machine, hash_fn) local shadow_data = machine:read_memory(cartesi.UARCH_SHADOW_START_ADDRESS, cartesi.UARCH_SHADOW_LENGTH) local ram_data = machine:read_memory(cartesi.UARCH_RAM_START_ADDRESS, cartesi.UARCH_RAM_LENGTH) - local tree = test_util.new_back_merkle_tree(cartesi.UARCH_STATE_LOG2_SIZE, PAGE_LOG2_SIZE) + local tree = test_util.new_back_merkle_tree(cartesi.UARCH_STATE_LOG2_SIZE, PAGE_LOG2_SIZE, hash_fn) for j = 0, #shadow_data - 1, PAGE_SIZE do - local page_hash = merkle_hash(shadow_data, j, PAGE_LOG2_SIZE) + local page_hash = merkle_hash(shadow_data, j, PAGE_LOG2_SIZE, hash_fn) tree:push_back(page_hash) end -- pad the region between the end of shadow data and start of ram @@ -277,7 +308,7 @@ function test_util.calculate_uarch_state_hash(machine) (cartesi.UARCH_RAM_START_ADDRESS - cartesi.UARCH_SHADOW_START_ADDRESS - #shadow_data) >> PAGE_LOG2_SIZE ) for j = 0, #ram_data - 1, PAGE_SIZE do - local page_hash = merkle_hash(ram_data, j, PAGE_LOG2_SIZE) + local page_hash = merkle_hash(ram_data, j, PAGE_LOG2_SIZE, hash_fn) tree:push_back(page_hash) end return tree:get_root_hash() diff --git a/tests/lua/machine-bind.lua b/tests/lua/machine-bind.lua index c4259c1f2..a0e7078d0 100755 --- a/tests/lua/machine-bind.lua +++ b/tests/lua/machine-bind.lua @@ -190,6 +190,7 @@ local cpu_reg_addr = { htif_ihalt = 824, htif_iconsole = 832, htif_iyield = 840, + hash_tree_target = 848, } for i = 0, 31 do cpu_reg_addr["x" .. i] = i * 8 @@ -235,6 +236,7 @@ local function get_cpu_reg_test_values() fcsr = 0x61, ilrsc = 0x2e0, iunrep = 0x0, + hash_tree_target = cartesi.HASH_TREE_TARGET_UARCH, } for i = 0, 31 do reg_values["x" .. i] = i * 8 @@ -260,6 +262,7 @@ local function build_machine_config(config_options) -- Create new machine local initial_reg_values = get_cpu_reg_test_values() local config = { + hash_tree = config_options.hash_tree or nil, processor = config_options.processor or initial_reg_values, ram = { length = 1 << 20 }, htif = config_options.htif or nil, @@ -299,6 +302,65 @@ local do_test = test_util.make_do_test(build_machine, machine_type) print("Testing machine bindings for type " .. machine_type) +print("\n\nDifferent hash tree hash targets") + +do_test("Hash tree target should be keccak by default", function(machine) + assert(machine:get_initial_config().hash_tree.target == "uarch", "hash tree target should be uarch") +end) + +test_util.make_do_test(build_machine, machine_type, { hash_tree = { target = "uarch" } })( + "Hash tree hash tree target should be keccak for uarch target", + function(machine) + assert(machine:get_initial_config().hash_tree.target == "uarch", "hash tree target should be uarch") + assert( + machine:read_reg("hash_tree_target") == cartesi.HASH_TREE_TARGET_UARCH, + "hash tree target should be uarch" + ) + assert( + machine:read_word(machine:get_reg_address("hash_tree_target")) == cartesi.HASH_TREE_TARGET_UARCH, + "hash tree target should be uarch" + ) + local root_hash = machine:get_root_hash() + local keccak_calculated = test_util.calculate_emulator_hash(machine, cartesi.keccak) + assert(root_hash == keccak_calculated, "initial root hash does not match") + local sha256_calculated = test_util.calculate_emulator_hash(machine, cartesi.sha256) + assert(root_hash ~= sha256_calculated, "initial root hash should not match sha256") + end +) + +test_util.make_do_test(build_machine, machine_type, { hash_tree = { target = "risc0" } })( + "Hash tree hash tree target should be sha256 for riscv target", + function(machine) + assert(machine:get_initial_config().hash_tree.target == "risc0", "hash tree target should be risc0") + assert( + machine:read_reg("hash_tree_target") == cartesi.HASH_TREE_TARGET_RISC0, + "hash tree target should be HASH_TREE_TARGET_RISC0" + ) + assert( + machine:read_word(machine:get_reg_address("hash_tree_target")) == cartesi.HASH_TREE_TARGET_RISC0, + "hash tree target should be HASH_TREE_TARGET_RISC0" + ) + local root_hash = machine:get_root_hash() + local sha256_calculated = test_util.calculate_emulator_hash(machine, cartesi.sha256) + assert(root_hash == sha256_calculated, "initial root hash does not match") + local keccak_calculated = test_util.calculate_emulator_hash(machine, cartesi.keccak) + assert(root_hash ~= keccak_calculated, "initial root hash should not match keccak") + end +) + +test_util.make_do_test(function() end, machine_type, {})( + "Fails to construct machine of unsupported hash tree target", + function() + local success, err = pcall(function() + build_machine(machine_type, { + hash_tree = { target = "invalid" }, + }) + end) + assert(success == false) + assert(err and err:match('field "value/hash_tree/target" has invalid value')) + end +) + print("\n\ntesting machine initial flags") do_test("machine should not have halt and yield initial flags set", function(machine) -- Check machine is not halted @@ -322,6 +384,7 @@ end) print("\n\ntesting merkle tree get_proof for values for registers") do_test("should provide proof for values in registers", function(machine) + local hash_fn = test_util.get_machine_hash_tree_hash_fn(machine) local initial_reg_values = get_cpu_reg_test_values() initial_reg_values.mvendorid = nil initial_reg_values.marchid = nil @@ -331,7 +394,7 @@ do_test("should provide proof for values in registers", function(machine) for _, v in pairs(initial_reg_values) do for el = cartesi.TREE_LOG2_WORD_SIZE, cartesi.TREE_LOG2_ROOT_SIZE - 1 do local a = test_util.align(v, el) - assert(test_util.check_proof(assert(machine:get_proof(a, el), "no proof")), "proof failed") + assert(test_util.check_proof(assert(machine:get_proof(a, el), "no proof"), hash_fn), "proof failed") end end end) @@ -388,6 +451,8 @@ local function test_config(config) for i = 1, 31 do assert(type(config.processor["x" .. i]) == "number", "x" .. i .. " is not a number") end + local hash_tree = config.hash_tree + assert(hash_tree.target == nil or type(hash_tree.target) == "string", "invalid hash_tree.target") local htif = config.htif for _, field in ipairs({ "console_getchar", "yield_manual", "yield_automatic" }) do assert(htif[field] == nil or type(htif[field]) == "boolean", "invalid htif." .. field) @@ -432,11 +497,12 @@ end) print("\n\n test calculation of initial root hash") do_test("should return expected value", function(machine) + local hash_fn = test_util.get_machine_hash_tree_hash_fn(machine) -- Get starting root hash local root_hash = machine:get_root_hash() print("Root hash: ", test_util.tohex(root_hash)) - local calculated_root_hash = test_util.calculate_emulator_hash(machine) + local calculated_root_hash = test_util.calculate_emulator_hash(machine, hash_fn) assert(root_hash == calculated_root_hash, "initial root hash does not match") end) @@ -680,6 +746,7 @@ do_test("Step log must contain conssitent data hashes", function(machine) local log = machine:log_step_uarch() local final_hash = machine:get_root_hash() machine:verify_step_uarch(initial_hash, log, final_hash) + assert(log.hash_tree_target == "uarch", "log hash_tree_target should be uarch") local read_access = log.accesses[1] assert(read_access.type == "read") local read_hash = read_access.read_hash @@ -704,6 +771,19 @@ do_test("Step log must contain conssitent data hashes", function(machine) assert(err:match("logged written data of uarch.cycle does not hash to the logged written hash at 8th access")) end) +do_test("Can't verify uarch step for non-uarch target", function(machine) + local initial_hash = machine:get_root_hash() + local log = machine:log_step_uarch() + assert(log.hash_tree_target == "uarch", "log hash_tree_target should be uarch") + local final_hash = machine:get_root_hash() + machine:verify_step_uarch(initial_hash, log, final_hash) + -- it should refuse to verify the step with a different target + log.hash_tree_target = "risc0" + local ok, err = pcall(machine.verify_step_uarch, machine, initial_hash, log, final_hash) + assert(not ok, "verify_step_uarch should fail") + assert(err:match("operation not supported: hash_tree target is not uarch")) +end) + do_test("step when uarch cycle is max", function(machine) machine:write_reg("uarch_cycle", MAX_UARCH_CYCLE) assert(machine:read_reg("uarch_cycle") == MAX_UARCH_CYCLE) @@ -829,6 +909,7 @@ for i = 0, 31 do end local function test_reset_uarch(machine, with_log, with_annotations) + local hash_fn = test_util.get_machine_hash_tree_hash_fn(machine) -- assert initial fixture state assert(machine:read_reg("uarch_halt_flag") ~= 0) assert(machine:read_reg("uarch_cycle") == 1) @@ -841,7 +922,7 @@ local function test_reset_uarch(machine, with_log, with_annotations) machine:write_memory(cartesi.UARCH_RAM_START_ADDRESS, gibberish, #gibberish) assert(machine:read_memory(cartesi.UARCH_RAM_START_ADDRESS, #gibberish) == gibberish) -- assert uarch state hash is not pristine - local uarch_state_hash = test_util.calculate_uarch_state_hash(machine) + local uarch_state_hash = test_util.calculate_uarch_state_hash(machine, hash_fn) assert(uarch_state_hash ~= cartesi.UARCH_PRISTINE_STATE_HASH) -- reset uarch state if with_log then @@ -869,7 +950,7 @@ local function test_reset_uarch(machine, with_log, with_annotations) -- assert that gibberish was removed from uarch ram assert(machine:read_memory(cartesi.UARCH_RAM_START_ADDRESS, #gibberish) ~= gibberish) -- compute current uarch state hash - uarch_state_hash = test_util.calculate_uarch_state_hash(machine) + uarch_state_hash = test_util.calculate_uarch_state_hash(machine, hash_fn) -- assert computed and pristine hash match assert(uarch_state_hash == cartesi.UARCH_PRISTINE_STATE_HASH) end @@ -908,6 +989,22 @@ test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_c end ) +test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_config })( + "Should refuse to verify reset_uarch with non-uarch target", + function(machine) + local initial_hash = machine:get_root_hash() + local log = machine:log_reset_uarch(cartesi.ACCESS_LOG_TYPE_ANNOTATIONS) + assert(log.hash_tree_target == "uarch", "log hash_tree_target should be uarch") + local final_hash = machine:get_root_hash() + -- verify happy path + machine:verify_reset_uarch(initial_hash, log, final_hash) + -- it should fail if it is not uarch + log.hash_tree_target = "risc0" + local _, err = pcall(machine.verify_reset_uarch, machine, final_hash, log, final_hash) + assert(err:match("operation not supported: hash_tree_target is not uarch")) + end +) + test_util.make_do_test(build_machine, machine_type, { uarch = test_reset_uarch_config })( "Testing verify_reset_uarch", function(machine) @@ -1088,6 +1185,26 @@ do_test("Test unhappy paths of verify_step_uarch", function(machine) end) end) +test_util.make_do_test(build_machine, machine_type, { hash_tree = { target = "risc0" } })( + "Uarch operations should fail if hash tree target is not uarch", + function(machine) + assert(machine:get_initial_config().hash_tree.target == "risc0", "hash tree target should be risc0") + -- The machine is configured for risc0, therefore: + -- run_uarch should fail + local success, err = pcall(machine.run_uarch, machine, 1) + assert(success == false and err:match("operation not supported: hash_tree target is not uarch")) + -- reset_uarch should fail + success, err = pcall(machine.reset_uarch, machine) + assert(success == false and err:match("operation not supported: hash_tree target is not uarch")) + -- log_reset_uarch should fail + success, err = pcall(machine.log_reset_uarch, machine) + assert(success == false and err:match("operation not supported: hash_tree target is not uarch")) + --log_uarch step should fail + success, err = pcall(machine.log_step_uarch, machine) + assert(success == false and err:match("operation not supported: hash_tree target is not uarch")) + end +) + print("\n\n testing unsupported uarch instructions ") local uarch_illegal_insn_program = { @@ -1175,13 +1292,11 @@ local function assert_access(accesses, index, expected_key_and_values) end end -local function test_send_cmio_input_with_different_arguments() +local function test_send_cmio_input_with_different_arguments(hash_tree_target) local data = string.rep("a", 1 << cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE) local reason = 1 local max_rx_buffer_len = 1 << cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE - local data_hash = test_util.merkle_hash(data, 0, cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE) local all_zeros = string.rep("\0", max_rx_buffer_len) - local all_zeros_hash = test_util.merkle_hash(all_zeros, 0, cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE) -- prepares and asserts the state before send_cmio_response is called local function assert_before_cmio_response_sent(machine) machine:write_reg("iflags_Y", 1) @@ -1198,20 +1313,30 @@ local function test_send_cmio_input_with_different_arguments() local expected_fromhost = ((reason & 0xffff) << 32) | (#data & 0xffffffff) assert(machine:read_reg("htif_fromhost") == expected_fromhost) end - do_test("send_cmio_response happy path", function(machine) - assert_before_cmio_response_sent(machine) - machine:send_cmio_response(reason, data) - assert_after_cmio_response_sent(machine) - end) + + test_util.make_do_test(build_machine, machine_type, { hash_tree = { target = hash_tree_target }, uarch = {} })( + "send_cmio_response happy path ", + function(machine) + assert_before_cmio_response_sent(machine) + machine:send_cmio_response(reason, data) + assert_after_cmio_response_sent(machine) + end + ) for _, large_data in ipairs({ false, true }) do local annotations = true - do_test( + test_util.make_do_test(build_machine, machine_type, { hash_tree = { target = hash_tree_target }, uarch = {} })( string.format( - "log_send_cmio_response happy path with annotations=%s, large_data=%s", + "log_send_cmio_response with annotations=%s, large_data=%s, hash_tree_target=%s", annotations, - large_data + large_data, + hash_tree_target ), function(machine) + local hash_fn = test_util.get_machine_hash_tree_hash_fn(machine) + local data_hash = test_util.merkle_hash(data, 0, cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE, hash_fn) + local all_zeros_hash = + test_util.merkle_hash(all_zeros, 0, cartesi.PMA_CMIO_RX_BUFFER_LOG2_SIZE, hash_fn) + local log_type = (annotations and cartesi.ACCESS_LOG_TYPE_ANNOTATIONS or 0) | (large_data and cartesi.ACCESS_LOG_TYPE_LARGE_DATA or 0) assert_before_cmio_response_sent(machine) @@ -1253,7 +1378,8 @@ local function test_send_cmio_input_with_different_arguments() end end -test_send_cmio_input_with_different_arguments() +test_send_cmio_input_with_different_arguments("uarch") +test_send_cmio_input_with_different_arguments("risc0") do_test("Dump of log produced by send_cmio_response should match", function(machine) machine:write_reg("iflags_Y", 1) @@ -1502,8 +1628,9 @@ end) -- helper function to load a step log file into a table local function read_step_log_file(filename) local file = assert(io.open(filename, "rb")) + local hash_tree_target = string.unpack(">(proof_str) .value(); auto proof_root_hash = proof.get_root_hash(); - auto verification = calculate_proof_root_hash(proof); + auto h = make_hasher(_machine); + auto verification = calculate_proof_root_hash(h, proof); BOOST_CHECK_EQUAL_COLLECTIONS(verification.begin(), verification.end(), proof_root_hash.begin(), proof_root_hash.end()); verification = calculate_emulator_hash(_machine); @@ -1526,7 +1527,8 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(machine_verify_merkle_tree_proof_updates_test, or cartesi::from_json>(proof_str) .value(); auto proof_root_hash = proof.get_root_hash(); - auto verification = calculate_proof_root_hash(proof); + auto h = make_hasher(_machine); + auto verification = calculate_proof_root_hash(h, proof); BOOST_CHECK_EQUAL_COLLECTIONS(verification.begin(), verification.end(), proof_root_hash.begin(), proof_root_hash.end()); verification = calculate_emulator_hash(_machine); @@ -1543,7 +1545,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(machine_verify_merkle_tree_proof_updates_test, or proof = cartesi::from_json>(proof_str) .value(); proof_root_hash = proof.get_root_hash(); - verification = calculate_proof_root_hash(proof); + verification = calculate_proof_root_hash(h, proof); BOOST_CHECK_EQUAL_COLLECTIONS(verification.begin(), verification.end(), proof_root_hash.begin(), proof_root_hash.end()); verification = calculate_emulator_hash(_machine); diff --git a/tests/misc/test-merkle-tree-hash.cpp b/tests/misc/test-merkle-tree-hash.cpp index dab8986e4..92ddf8177 100644 --- a/tests/misc/test-merkle-tree-hash.cpp +++ b/tests/misc/test-merkle-tree-hash.cpp @@ -35,10 +35,9 @@ #include using namespace cartesi; -using hasher_type = keccak_256_hasher; -using hash_type = hasher_type::hash_type; namespace { + /// \brief Checks if string matches prefix and captures remaninder /// \param pre Prefix to match in str. /// \param str Input string @@ -74,7 +73,7 @@ bool intval(const char *pre, const char *str, int *val) { /// \brief Prints hash in hex to file /// \param hash Hash to be printed. /// \param f File to print to -void print_hash(const hash_type &hash, FILE *f) { +void print_hash(const machine_hash &hash, FILE *f) { for (auto b : hash) { std::ignore = fprintf(f, "%02x", static_cast(b)); } @@ -86,12 +85,12 @@ void print_hash(const hash_type &hash, FILE *f) { /// \brief Reads a hash in hex from file /// \param f File to read from /// \returns Hash if successful, nothing otherwise -static std::optional read_hash(FILE *f) { +static std::optional read_hash(FILE *f) { std::array hex_hash{}; if (fread(hex_hash.data(), 1, hex_hash.size(), f) != hex_hash.size()) { return {}; } - hash_type h; + machine_hash h; for (size_t i = 0; i < hasher_type::hash_size; ++i) { std::array hex_c = {hex_hash[2 * i], hex_hash[2 * i + 1], '\0'}; unsigned c = 0; @@ -122,7 +121,7 @@ __attribute__((format(printf, 1, 2))) void error(const char *fmt, ...) { /// \param leaf Pointer to leaf data. Must contain 2^log2_word_size bytes /// \param log2_word Log2 of word size /// \param hash Receives the leaf hash -void get_word_hash(hasher_type &h, const unsigned char *word, int log2_word_size, hash_type &hash) { +void get_word_hash(i_hasher &h, const unsigned char *word, int log2_word_size, machine_hash &hash) { h.begin(); h.add_data(word, 1 << log2_word_size); h.end(hash); @@ -135,37 +134,27 @@ void get_word_hash(hasher_type &h, const unsigned char *word, int log2_word_size /// at least 2^log2_leaf_size bytes /// \param log2_leaf_size Log2 of leaf size /// \returns Merkle hash of leaf data -hash_type get_leaf_hash(hasher_type &h, int log2_word_size, const unsigned char *leaf_data, int log2_leaf_size) { +machine_hash get_leaf_hash(i_hasher &h, int log2_word_size, const unsigned char *leaf_data, int log2_leaf_size) { assert(log2_leaf_size >= log2_word_size); if (log2_leaf_size > log2_word_size) { - hash_type left = get_leaf_hash(h, log2_word_size, leaf_data, log2_leaf_size - 1); - const hash_type right = + machine_hash left = get_leaf_hash(h, log2_word_size, leaf_data, log2_leaf_size - 1); + const machine_hash right = get_leaf_hash(h, log2_word_size, leaf_data + (1 << (log2_leaf_size - 1)), log2_leaf_size - 1); - get_concat_hash(h, left, right, left); + h.get_concat_hash(left, right, left); return left; } - hash_type leaf; + machine_hash leaf; get_word_hash(h, leaf_data, log2_word_size, leaf); return leaf; } -/// \brief Computes the Merkle hash of a leaf of data -/// \param log2_word_size Log2 of word size -/// \param leaf_data Pointer to buffer containing leaf data with -/// at least 2^log2_leaf_size bytes -/// \param log2_leaf_size Log2 of leaf size -/// \returns Merkle hash of leaf data -hash_type get_leaf_hash(int log2_word_size, const unsigned char *leaf_data, int log2_leaf_size) { - hasher_type h; - return get_leaf_hash(h, log2_word_size, leaf_data, log2_leaf_size); -} - /// \brief Prints help message void help(const char *name) { std::ignore = fprintf(stderr, "Usage:\n %s [--input=] " "[--log2-word-size=] [--log2-leaf-size=

] " - "[--log2-root-size=]\n", + "[--log2-root-size=]\n" + "[--hash-tree-target=(uarch|risc0)>]\n", name); exit(0); } @@ -173,6 +162,7 @@ void help(const char *name) { int main(int argc, char *argv[]) try { const char *input_name = nullptr; + const char *hash_tree_target_name = "uarch"; int log2_word_size = 3; int log2_leaf_size = 12; int log2_root_size = 30; @@ -185,7 +175,8 @@ int main(int argc, char *argv[]) try { } if (stringval("--input=", argv[i], &input_name) || intval("--log2-word-size=", argv[i], &log2_word_size) || intval("--log2-leaf-size=", argv[i], &log2_leaf_size) || - intval("--log2-root-size=", argv[i], &log2_root_size)) { + intval("--log2-root-size=", argv[i], &log2_root_size) || + stringval("--hash-tree-target=", argv[i], &hash_tree_target_name)) { ; } else { error("unrecognized option '%s'\n", argv[i]); @@ -198,6 +189,13 @@ int main(int argc, char *argv[]) try { log2_root_size); return 1; } + auto maybe_hash_tree_target = parse_hash_tree_target(hash_tree_target_name); + if (!maybe_hash_tree_target) { + error("invalid hash tree target '%s'\n", hash_tree_target_name); + return 1; + } + auto h = i_hasher::make(*maybe_hash_tree_target); + // Read from stdin if no input name was given auto input_file = unique_file_ptr{stdin}; if (input_name != nullptr) { @@ -217,12 +215,12 @@ int main(int argc, char *argv[]) try { } std::cerr << "instantiating back tree\n"; - back_merkle_tree back_tree{log2_root_size, log2_leaf_size, log2_word_size}; + back_merkle_tree back_tree{log2_root_size, log2_leaf_size, log2_word_size, h}; std::cerr << "instantiating complete tree\n"; - complete_merkle_tree complete_tree{log2_root_size, log2_leaf_size, log2_word_size}; + complete_merkle_tree complete_tree{log2_root_size, log2_leaf_size, log2_word_size, h}; - std::vector leaf_hashes; + std::vector leaf_hashes; const uint64_t max_leaves = UINT64_C(1) << (log2_root_size - log2_leaf_size); // NOLINT(misc-include-cleaner) uint64_t leaf_count = 0; @@ -238,7 +236,6 @@ int main(int argc, char *argv[]) try { // the root hash in log time keeping only constant size state. // 4) The complete_merkle_tree can receive leaf hashes and maintain // only the part of the tree that is not pristine - hasher_type h; while (true) { auto got = fread(leaf_buf.get(), 1, leaf_size, input_file.get()); if (got == 0) { @@ -254,8 +251,7 @@ int main(int argc, char *argv[]) try { } // Pad leaf with zeros if file ended before next leaf boundary memset(leaf_buf.get() + got, 0, leaf_size - got); - // Compute leaf hash - auto leaf_hash = get_leaf_hash(log2_word_size, leaf_buf.get(), log2_leaf_size); + auto leaf_hash = get_leaf_hash(h, log2_word_size, leaf_buf.get(), log2_leaf_size); // Add to array of leaf hashes leaf_hashes.push_back(leaf_hash); // Print leaf hash @@ -265,7 +261,7 @@ int main(int argc, char *argv[]) try { // Add new leaf to back tree back_tree.push_back(leaf_hash); // Build full tree from array of leaf hashes - const full_merkle_tree tree_from_scratch(log2_root_size, log2_leaf_size, log2_word_size, leaf_hashes); + const full_merkle_tree tree_from_scratch(log2_root_size, log2_leaf_size, log2_word_size, h, leaf_hashes); // Compare the root hash for the back tree and the tree // from scratch if (back_tree.get_root_hash() != tree_from_scratch.get_root_hash()) { diff --git a/tests/misc/test-utils.h b/tests/misc/test-utils.h index 660ada53e..98dec0051 100644 --- a/tests/misc/test-utils.h +++ b/tests/misc/test-utils.h @@ -18,11 +18,11 @@ #include "json-util.h" #include -#include +#include #include #include -using hash_type = cartesi::keccak_256_hasher::hash_type; +using machine_hash = cartesi::machine_hash; // Calculate root hash for data buffer of log2_size namespace detail { @@ -31,14 +31,14 @@ constexpr int WORD_LOG2_SIZE = 5; constexpr int MERKLE_PAGE_LOG2_SIZE = 12; constexpr int MERKLE_PAGE_SIZE = (UINT64_C(1) << MERKLE_PAGE_LOG2_SIZE); -static hash_type merkle_hash(cartesi::keccak_256_hasher &h, const std::string_view &data, int log2_size) { - hash_type result; +static machine_hash merkle_hash(cartesi::i_hasher &h, const std::string_view &data, int log2_size) { + machine_hash result; if (log2_size > WORD_LOG2_SIZE) { --log2_size; auto half_size = data.size() / 2; auto left = merkle_hash(h, std::string_view{data.data(), half_size}, log2_size); auto right = merkle_hash(h, std::string_view{data.data() + half_size, half_size}, log2_size); - get_concat_hash(h, left, right, result); + h.get_concat_hash(left, right, result); } else { h.begin(); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) @@ -50,7 +50,18 @@ static hash_type merkle_hash(cartesi::keccak_256_hasher &h, const std::string_vi } // namespace detail -static hash_type merkle_hash(const std::string_view &data, int log2_size) { +// \brief Creates a hasher object compatible with the one used by the machine's config +static cartesi::i_hasher make_hasher(cm_machine *machine) { + const char *cfg_jsonstr{}; + cm_error error_code = cm_get_initial_config(machine, &cfg_jsonstr); + if (error_code != 0) { + throw std::runtime_error{cm_get_last_error_message()}; + } + const auto cfg = cartesi::from_json(cfg_jsonstr); + return cartesi::i_hasher::make(cfg.hash_tree.target); +} + +static machine_hash merkle_hash(cartesi::i_hasher &h, const std::string_view &data, int log2_size) { if (log2_size > 63) { throw std::domain_error("log2_size is too large"); } @@ -60,19 +71,18 @@ static hash_type merkle_hash(const std::string_view &data, int log2_size) { if ((UINT64_C(1) << log2_size) != data.size()) { throw std::invalid_argument("log2_size does not match data size"); } - cartesi::keccak_256_hasher h; return detail::merkle_hash(h, data, log2_size); } -static hash_type calculate_proof_root_hash(const cartesi::machine_merkle_tree::proof_type &proof) { - hash_type hash; +static machine_hash calculate_proof_root_hash(cartesi::i_hasher &h, + const cartesi::machine_merkle_tree::proof_type &proof) { + machine_hash hash; memcpy(hash.data(), proof.get_target_hash().data(), sizeof(cm_hash)); for (int log2_size = static_cast(proof.get_log2_target_size()); log2_size < static_cast(proof.get_log2_root_size()); ++log2_size) { - cartesi::keccak_256_hasher h; auto bit = (proof.get_target_address() & (UINT64_C(1) << log2_size)); - hash_type first; - hash_type second; + machine_hash first; + machine_hash second; if (bit) { memcpy(first.data(), proof.get_sibling_hashes()[log2_size - proof.get_log2_target_size()].data(), sizeof(cm_hash)); @@ -82,13 +92,14 @@ static hash_type calculate_proof_root_hash(const cartesi::machine_merkle_tree::p memcpy(second.data(), proof.get_sibling_hashes()[log2_size - proof.get_log2_target_size()].data(), sizeof(cm_hash)); } - get_concat_hash(h, first, second, hash); + h.get_concat_hash(first, second, hash); } return hash; } -static hash_type calculate_emulator_hash(cm_machine *machine) { - cartesi::back_merkle_tree tree(CM_TREE_LOG2_ROOT_SIZE, CM_TREE_LOG2_PAGE_SIZE, CM_TREE_LOG2_WORD_SIZE); +static machine_hash calculate_emulator_hash(cm_machine *machine) { + auto h = make_hasher(machine); + cartesi::back_merkle_tree tree(CM_TREE_LOG2_ROOT_SIZE, CM_TREE_LOG2_PAGE_SIZE, CM_TREE_LOG2_WORD_SIZE, h); std::string page; page.resize(detail::MERKLE_PAGE_SIZE); const char *ranges_jsonstr{}; @@ -105,7 +116,7 @@ static hash_type calculate_emulator_hash(cm_machine *machine) { if (cm_read_memory(machine, s, reinterpret_cast(page.data()), page.size()) != 0) { throw std::runtime_error{cm_get_last_error_message()}; } - auto page_hash = merkle_hash(page, detail::MERKLE_PAGE_LOG2_SIZE); + auto page_hash = merkle_hash(h, page, detail::MERKLE_PAGE_LOG2_SIZE); tree.push_back(page_hash); } last = end; diff --git a/third-party/SHA256/LICENSE b/third-party/SHA256/LICENSE new file mode 100644 index 000000000..701cbba11 --- /dev/null +++ b/third-party/SHA256/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2010,2014 Literatecode, http://www.literatecode.com +Copyright (c) 2022 Ilia Levin (ilia@levin.sg) + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party/SHA256/sha256.c b/third-party/SHA256/sha256.c new file mode 100644 index 000000000..9f7bf2fcf --- /dev/null +++ b/third-party/SHA256/sha256.c @@ -0,0 +1,328 @@ +//usr/bin/env clang -Ofast -Wall -Wextra -pedantic ${0} -o ${0%%.c*} $* ;exit $? +// +// SHA-256 implementation, Mark 2 +// +// Copyright (c) 2010,2014 Literatecode, http://www.literatecode.com +// Copyright (c) 2022 Ilia Levin (ilia@levin.sg) +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include "sha256.h" + +#ifndef _cbmc_ +#define __CPROVER_assume(...) do {} while(0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define FN_ static inline __attribute__((const)) + +static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + + +// ----------------------------------------------------------------------------- +FN_ uint8_t _shb(uint32_t x, uint32_t n) +{ + return ((x >> (n & 31)) & 0xff); +} // _shb + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _shw(uint32_t x, uint32_t n) +{ + return ((x << (n & 31)) & 0xffffffff); +} // _shw + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _r(uint32_t x, uint8_t n) +{ + return ((x >> n) | _shw(x, 32 - n)); +} // _r + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _Ch(uint32_t x, uint32_t y, uint32_t z) +{ + return ((x & y) ^ ((~x) & z)); +} // _Ch + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _Ma(uint32_t x, uint32_t y, uint32_t z) +{ + return ((x & y) ^ (x & z) ^ (y & z)); +} // _Ma + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _S0(uint32_t x) +{ + return (_r(x, 2) ^ _r(x, 13) ^ _r(x, 22)); +} // _S0 + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _S1(uint32_t x) +{ + return (_r(x, 6) ^ _r(x, 11) ^ _r(x, 25)); +} // _S1 + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _G0(uint32_t x) +{ + return (_r(x, 7) ^ _r(x, 18) ^ (x >> 3)); +} // _G0 + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _G1(uint32_t x) +{ + return (_r(x, 17) ^ _r(x, 19) ^ (x >> 10)); +} // _G1 + + +// ----------------------------------------------------------------------------- +FN_ uint32_t _word(uint8_t *c) +{ + return (_shw(c[0], 24) | _shw(c[1], 16) | _shw(c[2], 8) | (c[3])); +} // _word + + +// ----------------------------------------------------------------------------- +static void _addbits(sha256_context *ctx, uint32_t n) +{ + __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx)); + + if (ctx->bits[0] > (0xffffffff - n)) { + ctx->bits[1] = (ctx->bits[1] + 1) & 0xFFFFFFFF; + } + ctx->bits[0] = (ctx->bits[0] + n) & 0xFFFFFFFF; +} // _addbits + + +// ----------------------------------------------------------------------------- +static void _hash(sha256_context *ctx) +{ + __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx)); + + register uint32_t a, b, c, d, e, f, g, h; + uint32_t t[2]; + + a = ctx->hash[0]; + b = ctx->hash[1]; + c = ctx->hash[2]; + d = ctx->hash[3]; + e = ctx->hash[4]; + f = ctx->hash[5]; + g = ctx->hash[6]; + h = ctx->hash[7]; + + for (uint32_t i = 0; i < 64; i++) { + if (i < 16) { + ctx->W[i] = _word(&ctx->buf[_shw(i, 2)]); + } else { + ctx->W[i] = _G1(ctx->W[i - 2]) + ctx->W[i - 7] + + _G0(ctx->W[i - 15]) + ctx->W[i - 16]; + } + + t[0] = h + _S1(e) + _Ch(e, f, g) + K[i] + ctx->W[i]; + t[1] = _S0(a) + _Ma(a, b, c); + h = g; + g = f; + f = e; + e = d + t[0]; + d = c; + c = b; + b = a; + a = t[0] + t[1]; + } + + ctx->hash[0] += a; + ctx->hash[1] += b; + ctx->hash[2] += c; + ctx->hash[3] += d; + ctx->hash[4] += e; + ctx->hash[5] += f; + ctx->hash[6] += g; + ctx->hash[7] += h; +} // _hash + + +// ----------------------------------------------------------------------------- +void sha256_init(sha256_context *ctx) +{ + if (ctx != NULL) { + ctx->bits[0] = ctx->bits[1] = ctx->len = 0; + ctx->hash[0] = 0x6a09e667; + ctx->hash[1] = 0xbb67ae85; + ctx->hash[2] = 0x3c6ef372; + ctx->hash[3] = 0xa54ff53a; + ctx->hash[4] = 0x510e527f; + ctx->hash[5] = 0x9b05688c; + ctx->hash[6] = 0x1f83d9ab; + ctx->hash[7] = 0x5be0cd19; + } +} // sha256_init + + +// ----------------------------------------------------------------------------- +void sha256_hash(sha256_context *ctx, const void *data, size_t len) +{ + const uint8_t *bytes = (const uint8_t *)data; + + if ((ctx != NULL) && (bytes != NULL) && (ctx->len < sizeof(ctx->buf))) { + __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(bytes)); + __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx)); + for (size_t i = 0; i < len; i++) { + ctx->buf[ctx->len++] = bytes[i]; + if (ctx->len == sizeof(ctx->buf)) { + _hash(ctx); + _addbits(ctx, sizeof(ctx->buf) * 8); + ctx->len = 0; + } + } + } +} // sha256_hash + + +// ----------------------------------------------------------------------------- +void sha256_done(sha256_context *ctx, uint8_t *hash) +{ + register uint32_t i, j; + + if (ctx != NULL) { + j = ctx->len % sizeof(ctx->buf); + ctx->buf[j] = 0x80; + for (i = j + 1; i < sizeof(ctx->buf); i++) { + ctx->buf[i] = 0x00; + } + + if (ctx->len > 55) { + _hash(ctx); + for (j = 0; j < sizeof(ctx->buf); j++) { + ctx->buf[j] = 0x00; + } + } + + _addbits(ctx, ctx->len * 8); + ctx->buf[63] = _shb(ctx->bits[0], 0); + ctx->buf[62] = _shb(ctx->bits[0], 8); + ctx->buf[61] = _shb(ctx->bits[0], 16); + ctx->buf[60] = _shb(ctx->bits[0], 24); + ctx->buf[59] = _shb(ctx->bits[1], 0); + ctx->buf[58] = _shb(ctx->bits[1], 8); + ctx->buf[57] = _shb(ctx->bits[1], 16); + ctx->buf[56] = _shb(ctx->bits[1], 24); + _hash(ctx); + + if (hash != NULL) { + for (i = 0, j = 24; i < 4; i++, j -= 8) { + hash[i + 0] = _shb(ctx->hash[0], j); + hash[i + 4] = _shb(ctx->hash[1], j); + hash[i + 8] = _shb(ctx->hash[2], j); + hash[i + 12] = _shb(ctx->hash[3], j); + hash[i + 16] = _shb(ctx->hash[4], j); + hash[i + 20] = _shb(ctx->hash[5], j); + hash[i + 24] = _shb(ctx->hash[6], j); + hash[i + 28] = _shb(ctx->hash[7], j); + } + } + } +} // sha256_done + + +// ----------------------------------------------------------------------------- +void sha256(const void *data, size_t len, uint8_t *hash) +{ + sha256_context ctx; + + sha256_init(&ctx); + sha256_hash(&ctx, data, len); + sha256_done(&ctx, hash); +} // sha256 + + +#if 0 +#pragma mark - Self Test +#endif +#ifdef SHA256_SELF_TEST__ +#include +#include + +int main(void) +{ + char *buf[] = { + "", + "e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855", + + "abc", + "ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad", + + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1", + + "The quick brown fox jumps over the lazy dog", + "d7a8fbb3 07d78094 69ca9abc b0082e4f 8d5651e4 6d3cdb76 2d02d0bf 37c9e592", + + "The quick brown fox jumps over the lazy cog", // avalanche effect test + "e4c4d8f3 bf76b692 de791a17 3e053211 50f7a345 b46484fe 427f6acc 7ecc81be", + + "bhn5bjmoniertqea40wro2upyflkydsibsk8ylkmgbvwi420t44cq034eou1szc1k0mk46oeb7ktzmlxqkbte2sy", + "9085df2f 02e0cc45 5928d0f5 1b27b4bf 1d9cd260 a66ed1fd a11b0a3f f5756d99" + }; + const size_t tests_total = sizeof(buf) / sizeof(buf[0]); + uint8_t hash[SHA256_SIZE_BYTES]; + + if (0 != (tests_total % 2)) { + return printf("invalid tests\n"); + } + + for (size_t i = 0; i < tests_total; i += 2) { + sha256(buf[i], strlen(buf[i]), hash); + printf("input = '%s'\ndigest: %s\nresult: ", buf[i], buf[i + 1]); + for (size_t j = 0; j < SHA256_SIZE_BYTES; j++) { + printf("%02x%s", hash[j], ((j % 4) == 3) ? " " : ""); + } + printf("\n\n"); + } + + return 0; +} // main + +#endif // def SHA256_SELF_TEST__ + +#ifdef __cplusplus +} +#endif diff --git a/third-party/SHA256/sha256.h b/third-party/SHA256/sha256.h new file mode 100644 index 000000000..6df116bc3 --- /dev/null +++ b/third-party/SHA256/sha256.h @@ -0,0 +1,52 @@ +// +// SHA-256 implementation, Mark 2 +// +// Copyright (c) 2010,2014 Literatecode, http://www.literatecode.com +// Copyright (c) 2022 Ilia Levin (ilia@levin.sg) +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#ifndef SHA256_H_ +#define SHA256_H_ + +#include +#include + +#define SHA256_SIZE_BYTES (32) + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct { + uint8_t buf[64]; + uint32_t hash[8]; + uint32_t bits[2]; + uint32_t len; + uint32_t rfu__; + uint32_t W[64]; +} sha256_context; + +void sha256_init(sha256_context *ctx); +void sha256_hash(sha256_context *ctx, const void *data, size_t len); +void sha256_done(sha256_context *ctx, uint8_t *hash); + +void sha256(const void *data, size_t len, uint8_t *hash); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/uarch/Makefile b/uarch/Makefile index 392642d7c..8708f7dfc 100644 --- a/uarch/Makefile +++ b/uarch/Makefile @@ -19,7 +19,7 @@ else HOST_CXX := g++ endif -HOST_CFLAGS := -I$(THIRD_PARTY_DIR)/tiny_sha3 -I$(EMULATOR_SRC_DIR) +HOST_CFLAGS := -I$(THIRD_PARTY_DIR)/tiny_sha3 -I$(THIRD_PARTY_DIR)/SHA256 -I$(EMULATOR_SRC_DIR) CC := $(TOOLCHAIN_PREFIX)gcc LD := $(TOOLCHAIN_PREFIX)ld @@ -81,6 +81,7 @@ COMPUTE_UARCH_CPP_SOURCES=\ $(EMULATOR_SRC_DIR)/full-merkle-tree.cpp COMPUTE_UARCH_C_SOURCES=\ $(THIRD_PARTY_DIR)/tiny_sha3/sha3.c \ + $(THIRD_PARTY_DIR)/SHA256/sha256.c \ uarch-pristine-ram.c UARCH_OBJS = $(patsubst %.c,%.uarch_c.o,$(patsubst %.cpp,%.uarch_cpp.o,$(UARCH_SOURCES))) diff --git a/uarch/compute-uarch-pristine-hash.cpp b/uarch/compute-uarch-pristine-hash.cpp index c68f46184..12ed009f9 100644 --- a/uarch/compute-uarch-pristine-hash.cpp +++ b/uarch/compute-uarch-pristine-hash.cpp @@ -31,8 +31,6 @@ using namespace cartesi; using tree_type = machine_merkle_tree; -using hash_type = tree_type::hash_type; -using hashertype = tree_type::hasher_type; using proof_type = tree_type::proof_type; static_assert(PMA_PAGE_SIZE == tree_type::get_page_size(), "PMA and machine_merkle_tree page sizes must match"); @@ -55,9 +53,9 @@ Computes the hash of the pristine uarch state. } int main(int argc, char *argv[]) try { - tree_type tree{}; - hashertype hasher{}; - hash_type hash{}; + tree_type tree{hash_tree_target::uarch}; + auto hasher = tree.make_hasher(); + machine_hash hash{}; // Process command line arguments for (int i = 1; i < argc; ++i) {