Skip to content

Add configurable hash tree target support #327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
13 changes: 13 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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 \
Expand All @@ -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 \
Expand Down Expand Up @@ -551,6 +557,7 @@ jsonrpc-discover.cpp: jsonrpc-discover.json
@$(CXX) $(CXXFLAGS) $(LUA_INC) $< -MM -MT $@ -MF [email protected] > /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 [email protected] > /dev/null 2>&1
Expand All @@ -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 $@ $<

Expand Down
46 changes: 26 additions & 20 deletions src/access-log.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<hash_type>;
using sibling_hashes_type = std::vector<machine_hash>;
using proof_type = machine_merkle_tree::proof_type;

void set_type(access_type type) {
Expand Down Expand Up @@ -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<hash_type> &get_written_hash() const {
const std::optional<machine_hash> &get_written_hash() const {
return m_written_hash;
}
std::optional<hash_type> &get_written_hash() {
std::optional<machine_hash> &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());
Expand Down Expand Up @@ -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<access_data> 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<access_data> m_written; ///< Written data
std::optional<hash_type> m_written_hash; ///< Hash of written data
std::optional<machine_hash> m_written_hash; ///< Hash of written data
std::optional<sibling_hashes_type> m_sibling_hashes; ///< Hashes of siblings in path from address to root
};

Expand Down Expand Up @@ -258,22 +255,27 @@ class access_log {
};

private:
std::vector<access> m_accesses; ///< List of all accesses
std::vector<bracket_note> m_brackets; ///< Begin/End annotations
std::vector<std::string> m_notes; ///< Per-access annotations
type m_log_type; ///< Log type
std::vector<access> m_accesses; ///< List of all accesses
std::vector<bracket_note> m_brackets; ///< Begin/End annotations
std::vector<std::string> 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 <typename ACCESSES, typename BRACKETS, typename NOTES>
access_log(ACCESSES &&accesses, BRACKETS &&brackets, NOTES &&notes, type log_type) :
access_log(ACCESSES &&accesses, BRACKETS &&brackets, NOTES &&notes, type log_type,
hash_tree_target hash_tree_target) :
m_accesses(std::forward<ACCESSES>(accesses)),
m_brackets(std::forward<BRACKETS>(brackets)),
m_notes(std::forward<NOTES>(notes)),
m_log_type(log_type) {
m_log_type(log_type),
m_hash_tree_target(hash_tree_target) {
;
}

Expand Down Expand Up @@ -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
Expand Down
31 changes: 14 additions & 17 deletions src/back-merkle-tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
}
Expand All @@ -54,17 +55,16 @@ 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"};
}
const int depth = m_log2_root_size - 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];
get_concat_hash(h, left, right, right);
m_hasher.get_concat_hash(left, right, right);
} else {
m_context[i] = right;
break;
Expand All @@ -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");
}
Expand All @@ -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
Expand All @@ -118,19 +117,18 @@ 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) {
auto root = 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];
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;
Expand All @@ -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
Expand Down
21 changes: 8 additions & 13 deletions src/back-merkle-tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<hash_type, address_type>;
using proof_type = merkle_tree_proof;

/// \brief Constructor
/// \param log2_root_size Log<sub>2</sub> of root node
/// \param log2_leaf_size Log<sub>2</sub> of leaf node
/// \param log2_word_size Log<sub>2</sub> 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -140,11 +134,12 @@ class back_merkle_tree {
proof_type get_next_leaf_proof() const;

private:
int m_log2_root_size; ///< Log<sub>2</sub> of tree size
int m_log2_leaf_size; ///< Log<sub>2</sub> of leaf size
int m_log2_root_size; ///< Log<sub>2</sub> of tree size
int m_log2_leaf_size; ///< Log<sub>2</sub> 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<hash_type> m_context; ///< Hashes of bits set in leaf_count
std::vector<machine_hash> m_context; ///< Hashes of bits set in leaf_count
pristine_merkle_tree m_pristine_hashes; ///< Hash of pristine subtrees of all sizes
};

Expand Down
20 changes: 19 additions & 1 deletion src/cartesi-machine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ where options are:
--load=<directory>
load machine previously stored in <directory>.

--hash-tree-target=<target>
set the target environment for hash tree verification
possible values are:
"uarch" (default)
"risc0"

--initial-hash
print initial state hash before running machine.

Expand Down Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading