Add tests for various Merkle-tree related functionalities
What changed, and why it matters
This commit only adds new automated unit tests for Merkle-tree helper functions in the Ledger Bitcoin app. It does not change any production firmware code, so it cannot by itself introduce a security vulnerability or fix one. The tests exercise functions that verify Merkle proofs, retrieve leaf data, and stream key-value map entries.
No security action required. Treat as normal quality/test-coverage improvement. Reviewers may optionally confirm the tests pass in CI and that the mock dispatcher behavior accurately reflects the real protocol.
Security signals we found
No production code changes
Only unit-test files and CMake build definitions added
Tests validate expected failure modes (wrong root, out-of-bounds index, unknown hash, buffer too small, unsorted tree)
Evidence from the diff
The diff is a pure test addition: 2,806 lines across nine new cmocka-based unit-test files plus CMake wiring. It covers call_check_merkle_tree_sorted_with_callback, call_get_merkle_leaf_element, call_get_merkle_leaf_hash, call_get_merkle_leaf_index, call_get_merkle_preimage, call_stream_merkle_leaf_element, call_stream_merkleized_map_value, and call_stream_preimage. No source files under src/ are modified. The tests use the existing mock dispatcher and mock hash implementations to simulate host-side Merkle proof responses.
Changed components
unit-tests/CMakeLists.txtunit-tests/test_check_merkle_tree_sorted.cunit-tests/test_get_merkle_leaf_element.cunit-tests/test_get_merkle_leaf_hash.cunit-tests/test_get_merkle_leaf_index.cunit-tests/test_get_merkle_preimage.cunit-tests/test_stream_merkle_leaf_element.cunit-tests/test_stream_merkleized_map_value.cunit-tests/test_stream_preimage.cInspect captured patch +2806 / −0
diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt
index ea30f0e..1ad143f 100644
--- a/unit-tests/CMakeLists.txt
+++ b/unit-tests/CMakeLists.txt
@@ -56,6 +56,14 @@ add_executable(test_wallet test_wallet.c)
add_executable(test_get_preimage test_get_preimage.c)
add_executable(test_extract_bip32_derivation test_extract_bip32_derivation.c)
add_executable(test_psbt_parse test_psbt_parse.c)
+add_executable(test_check_merkle_tree_sorted test_check_merkle_tree_sorted.c)
+add_executable(test_get_merkle_leaf_element test_get_merkle_leaf_element.c)
+add_executable(test_get_merkle_preimage test_get_merkle_preimage.c)
+add_executable(test_get_merkle_leaf_hash test_get_merkle_leaf_hash.c)
+add_executable(test_get_merkle_leaf_index test_get_merkle_leaf_index.c)
+add_executable(test_stream_preimage test_stream_preimage.c)
+add_executable(test_stream_merkle_leaf_element test_stream_merkle_leaf_element.c)
+add_executable(test_stream_merkleized_map_value test_stream_merkleized_map_value.c)
# add_executable(test_crypto test_crypto.c)
@@ -74,10 +82,15 @@ add_library(buffer_ext SHARED ../src/common/buffer_ext.c)
add_library(display_utils SHARED ../src/ui/display_utils.c)
add_library(extract_bip32_derivation SHARED ../src/handler/sign_psbt/extract_bip32_derivation.c)
add_library(get_merkle_leaf_hash SHARED ../src/handler/lib/get_merkle_leaf_hash.c)
+add_library(get_merkle_leaf_element SHARED ../src/handler/lib/get_merkle_leaf_element.c)
+add_library(get_merkle_preimage SHARED ../src/handler/lib/get_merkle_preimage.c)
add_library(get_preimage SHARED ../src/handler/lib/get_preimage.c)
+add_library(check_merkle_tree_sorted SHARED ../src/handler/lib/check_merkle_tree_sorted.c)
+add_library(get_merkle_leaf_index SHARED ../src/handler/lib/get_merkle_leaf_index.c)
add_library(merkle SHARED ../src/common/merkle.c)
add_library(stream_merkle_leaf_element SHARED ../src/handler/lib/stream_merkle_leaf_element.c)
add_library(stream_preimage SHARED ../src/handler/lib/stream_preimage.c)
+add_library(stream_merkleized_map_value SHARED ../src/handler/lib/stream_merkleized_map_value.c)
add_library(parser SHARED ../src/common/parser_ext.c)
add_library(read SHARED $ENV{BOLOS_SDK}/lib_standard_app/read.c)
add_library(script SHARED ../src/common/script.c)
@@ -94,8 +107,22 @@ target_include_directories(get_preimage PRIVATE ../src/handler ../src/handler/li
target_include_directories(mock_dispatcher PRIVATE ../src/handler ../src/handler/lib ../src/common)
target_include_directories(stream_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
target_include_directories(stream_preimage PRIVATE ../src/handler ../src/handler/lib)
+target_include_directories(stream_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
target_include_directories(test_extract_bip32_derivation PRIVATE ../src/handler ../src/handler/lib ../src/handler/sign_psbt ../src/common)
+target_include_directories(get_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(get_merkle_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(check_merkle_tree_sorted PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(get_merkle_leaf_index PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_link_libraries(get_merkle_leaf_index PUBLIC get_merkle_leaf_hash)
target_include_directories(test_get_preimage PRIVATE ../src/handler ../src/handler/lib)
+target_include_directories(test_get_merkle_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_get_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_check_merkle_tree_sorted PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_get_merkle_leaf_hash PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_get_merkle_leaf_index PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_stream_preimage PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_stream_merkle_leaf_element PRIVATE ../src/handler ../src/handler/lib ../src/common)
+target_include_directories(test_stream_merkleized_map_value PRIVATE ../src/handler ../src/handler/lib ../src/common)
# Mock libraries
target_link_libraries(crypto_mocks PUBLIC sha256)
@@ -110,8 +137,16 @@ target_link_libraries(test_parser PUBLIC cmocka gcov parser buffer buffer_ext va
target_link_libraries(test_script PUBLIC cmocka gcov script buffer varint read write bip32)
target_link_libraries(test_wallet PUBLIC cmocka gcov wallet script buffer buffer_ext varint read write bip32 base58 crypto_mocks)
target_link_libraries(test_get_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_preimage)
+target_link_libraries(test_get_merkle_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_preimage)
target_link_libraries(test_extract_bip32_derivation PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle extract_bip32_derivation stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage psbt_parse)
target_link_libraries(test_psbt_parse PUBLIC cmocka gcov psbt_parse)
+target_link_libraries(test_check_merkle_tree_sorted PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle check_merkle_tree_sorted get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage psbt_parse)
+target_link_libraries(test_get_merkle_leaf_element PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_element get_merkle_leaf_hash get_merkle_preimage stream_preimage psbt_parse)
+target_link_libraries(test_get_merkle_leaf_hash PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_hash stream_preimage psbt_parse)
+target_link_libraries(test_get_merkle_leaf_index PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle get_merkle_leaf_hash get_merkle_leaf_index stream_preimage psbt_parse)
+target_link_libraries(test_stream_preimage PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_preimage psbt_parse)
+target_link_libraries(test_stream_merkle_leaf_element PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_merkle_leaf_element get_merkle_leaf_hash stream_preimage psbt_parse)
+target_link_libraries(test_stream_merkleized_map_value PUBLIC cmocka gcov mock_dispatcher cx_hash_mock sha256 buffer buffer_ext varint read write bip32 merkle stream_merkleized_map_value stream_merkle_leaf_element get_merkle_leaf_hash get_merkle_leaf_index stream_preimage psbt_parse)
# target_link_libraries(test_crypto PUBLIC cmocka gcov crypto)
add_test(test_bitvector test_bitvector)
@@ -123,5 +158,13 @@ add_test(test_wallet test_wallet)
add_test(test_get_preimage test_get_preimage)
add_test(test_extract_bip32_derivation test_extract_bip32_derivation)
add_test(test_psbt_parse test_psbt_parse)
+add_test(test_check_merkle_tree_sorted test_check_merkle_tree_sorted)
+add_test(test_get_merkle_leaf_element test_get_merkle_leaf_element)
+add_test(test_get_merkle_preimage test_get_merkle_preimage)
+add_test(test_get_merkle_leaf_hash test_get_merkle_leaf_hash)
+add_test(test_get_merkle_leaf_index test_get_merkle_leaf_index)
+add_test(test_stream_preimage test_stream_preimage)
+add_test(test_stream_merkle_leaf_element test_stream_merkle_leaf_element)
+add_test(test_stream_merkleized_map_value test_stream_merkleized_map_value)
# add_test(test_crypto test_crypto)
diff --git a/unit-tests/test_check_merkle_tree_sorted.c b/unit-tests/test_check_merkle_tree_sorted.c
new file mode 100644
index 0000000..5173df6
--- /dev/null
+++ b/unit-tests/test_check_merkle_tree_sorted.c
@@ -0,0 +1,469 @@
+/**
+ * Unit tests for call_check_merkle_tree_sorted_with_callback using the mock dispatcher.
+ *
+ * Tests verify that the function correctly:
+ * - Accepts elements in strict lexicographic order.
+ * - Rejects elements that are not in strict lexicographic order.
+ * - Invokes the callback once per element in order.
+ * - Handles edge cases (single element, empty tree, duplicate elements).
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/check_merkle_tree_sorted.h"
+
+/* ---------- Callback tracking ---------- */
+
+#define MAX_CALLBACK_CALLS 64
+
+typedef struct {
+ size_t n_calls;
+ int indices[MAX_CALLBACK_CALLS];
+ uint8_t elements[MAX_CALLBACK_CALLS][256];
+ size_t element_lens[MAX_CALLBACK_CALLS];
+} callback_tracker_t;
+
+static void tracking_callback(dispatcher_context_t *dc,
+ void *state,
+ const merkleized_map_commitment_t *map_commitment,
+ int index,
+ buffer_t *buf) {
+ (void) dc;
+ (void) map_commitment;
+
+ callback_tracker_t *tracker = (callback_tracker_t *) state;
+ assert_true(tracker->n_calls < MAX_CALLBACK_CALLS);
+
+ size_t i = tracker->n_calls++;
+ tracker->indices[i] = index;
+ size_t len = buf->size - buf->offset;
+ assert_true(len <= 256);
+ memcpy(tracker->elements[i], buf->ptr + buf->offset, len);
+ tracker->element_lens[i] = len;
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: three elements in strict lexicographic order.
+ */
+static void test_sorted_three_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Elements in sorted order: "aaa" < "bbb" < "ccc" */
+ const uint8_t *elems[] = {(const uint8_t *) "aaa",
+ (const uint8_t *) "bbb",
+ (const uint8_t *) "ccc"};
+ size_t lens[] = {3, 3, 3};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 3);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ callback_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 3,
+ tracking_callback,
+ NULL);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 3);
+
+ /* Verify callback received elements in order */
+ assert_int_equal(tracker.indices[0], 0);
+ assert_int_equal(tracker.indices[1], 1);
+ assert_int_equal(tracker.indices[2], 2);
+ assert_memory_equal(tracker.elements[0], "aaa", 3);
+ assert_memory_equal(tracker.elements[1], "bbb", 3);
+ assert_memory_equal(tracker.elements[2], "ccc", 3);
+}
+
+/**
+ * Happy path: single element tree is always sorted.
+ */
+static void test_sorted_single_element(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "hello"};
+ size_t lens[] = {5};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 1);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ callback_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 1,
+ tracking_callback,
+ NULL);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 1);
+ assert_memory_equal(tracker.elements[0], "hello", 5);
+}
+
+/**
+ * Happy path: NULL callback (no callback invoked, just order checking).
+ */
+static void test_sorted_null_callback(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "aa", (const uint8_t *) "bb"};
+ size_t lens[] = {2, 2};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 2);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ NULL,
+ root,
+ 2,
+ NULL,
+ NULL);
+ assert_int_equal(result, 0);
+}
+
+/**
+ * Happy path: empty tree (size=0) should succeed immediately.
+ */
+static void test_sorted_empty_tree(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t root[32] = {0};
+
+ callback_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 0,
+ tracking_callback,
+ NULL);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 0);
+}
+
+/**
+ * Error: elements NOT in sorted order (descending).
+ * The tree is built with the elements in descending order, so
+ * call_check_merkle_tree_sorted_with_callback should detect the unsorted order.
+ */
+static void test_unsorted_descending(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Elements in reverse order: "ccc" > "bbb" > "aaa" */
+ const uint8_t *elems[] = {(const uint8_t *) "ccc",
+ (const uint8_t *) "bbb",
+ (const uint8_t *) "aaa"};
+ size_t lens[] = {3, 3, 3};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 3);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ NULL,
+ root,
+ 3,
+ NULL,
+ NULL);
+ assert_true(result < 0);
+}
+
+/**
+ * Error: duplicate elements (equal keys are not strictly sorted).
+ */
+static void test_unsorted_duplicates(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "aaa",
+ (const uint8_t *) "aaa",
+ (const uint8_t *) "bbb"};
+ size_t lens[] = {3, 3, 3};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 3);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ NULL,
+ root,
+ 3,
+ NULL,
+ NULL);
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: elements of different lengths, sorted lexicographically.
+ * "a" < "aa" < "b" in lexicographic order (shorter prefix comes first).
+ */
+static void test_sorted_different_lengths(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "a",
+ (const uint8_t *) "aa",
+ (const uint8_t *) "b"};
+ size_t lens[] = {1, 2, 1};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 3);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ callback_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 3,
+ tracking_callback,
+ NULL);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 3);
+ assert_int_equal(tracker.element_lens[0], 1);
+ assert_int_equal(tracker.element_lens[1], 2);
+ assert_int_equal(tracker.element_lens[2], 1);
+}
+
+/**
+ * Error: unsorted with prefix relationship.
+ * "aa" before "a" — the longer prefix comes first, violating order.
+ */
+static void test_unsorted_prefix(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "aa",
+ (const uint8_t *) "a"};
+ size_t lens[] = {2, 1};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 2);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ NULL,
+ root,
+ 2,
+ NULL,
+ NULL);
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: many elements in sorted order.
+ * Verifies correctness with a larger tree that exercises multiple levels of merkle proofs.
+ */
+static void test_sorted_many_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Generate 10 sorted elements: "\x00", "\x01", ..., "\x09" */
+ uint8_t raw[10][1];
+ const uint8_t *elems[10];
+ size_t lens[10];
+
+ for (int i = 0; i < 10; i++) {
+ raw[i][0] = (uint8_t) i;
+ elems[i] = raw[i];
+ lens[i] = 1;
+ }
+
+ mock_dispatcher_add_list(&mock, elems, lens, 10);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ callback_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 10,
+ tracking_callback,
+ NULL);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 10);
+
+ for (int i = 0; i < 10; i++) {
+ assert_int_equal(tracker.indices[i], i);
+ assert_int_equal(tracker.element_lens[i], 1);
+ assert_int_equal(tracker.elements[i][0], (uint8_t) i);
+ }
+}
+
+/**
+ * Error: tree with size mismatch (wrong size passed to function).
+ * call_get_merkle_leaf_element should fail because the mock tree has a
+ * different number of elements.
+ */
+static void test_wrong_tree_size(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "a", (const uint8_t *) "b"};
+ size_t lens[] = {1, 1};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 2);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ /* Pass size=5 when actual tree has 2 elements */
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ NULL,
+ root,
+ 5,
+ NULL,
+ NULL);
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: two elements, with the map_commitment parameter passed through.
+ * Verifies that the callback receives the map_commitment pointer.
+ */
+typedef struct {
+ size_t n_calls;
+ const merkleized_map_commitment_t *received_commitment;
+} commitment_tracker_t;
+
+static void commitment_tracking_callback(dispatcher_context_t *dc,
+ void *state,
+ const merkleized_map_commitment_t *map_commitment,
+ int index,
+ buffer_t *buf) {
+ (void) dc;
+ (void) index;
+ (void) buf;
+
+ commitment_tracker_t *tracker = (commitment_tracker_t *) state;
+ tracker->n_calls++;
+ tracker->received_commitment = map_commitment;
+}
+
+static void test_map_commitment_passed(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "x", (const uint8_t *) "y"};
+ size_t lens[] = {1, 1};
+
+ mock_dispatcher_add_list(&mock, elems, lens, 2);
+ uint8_t root[32];
+ memcpy(root, mock.trees[mock.n_trees - 1].root, 32);
+
+ merkleized_map_commitment_t dummy_commitment;
+ memset(&dummy_commitment, 0xAB, sizeof(dummy_commitment));
+
+ commitment_tracker_t tracker;
+ memset(&tracker, 0, sizeof(tracker));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_check_merkle_tree_sorted_with_callback(dc,
+ &tracker,
+ root,
+ 2,
+ commitment_tracking_callback,
+ &dummy_commitment);
+ assert_int_equal(result, 0);
+ assert_int_equal(tracker.n_calls, 2);
+ assert_ptr_equal(tracker.received_commitment, &dummy_commitment);
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_sorted_three_elements),
+ cmocka_unit_test(test_sorted_single_element),
+ cmocka_unit_test(test_sorted_null_callback),
+ cmocka_unit_test(test_sorted_empty_tree),
+ cmocka_unit_test(test_unsorted_descending),
+ cmocka_unit_test(test_unsorted_duplicates),
+ cmocka_unit_test(test_sorted_different_lengths),
+ cmocka_unit_test(test_unsorted_prefix),
+ cmocka_unit_test(test_sorted_many_elements),
+ cmocka_unit_test(test_wrong_tree_size),
+ cmocka_unit_test(test_map_commitment_passed),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_get_merkle_leaf_element.c b/unit-tests/test_get_merkle_leaf_element.c
new file mode 100644
index 0000000..c268079
--- /dev/null
+++ b/unit-tests/test_get_merkle_leaf_element.c
@@ -0,0 +1,301 @@
+/**
+ * Unit tests for call_get_merkle_leaf_element using the mock dispatcher.
+ *
+ * Tests verify that the function correctly retrieves the preimage of a
+ * leaf in a Merkle tree identified by root and tree_size, combining
+ * call_get_merkle_leaf_hash and call_get_merkle_preimage internally.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/get_merkle_leaf_element.h"
+
+/* ---------- Helpers ---------- */
+
+/**
+ * Build a Merkle tree from the given elements and copy the root hash.
+ */
+static void build_tree(mock_dispatcher_t *mock,
+ const uint8_t *const *elems,
+ const size_t *lens,
+ size_t n,
+ uint8_t root_out[32]) {
+ mock_dispatcher_add_list(mock, elems, lens, n);
+ memcpy(root_out, mock->trees[mock->n_trees - 1].root, 32);
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: single element tree, retrieve the only leaf.
+ */
+static void test_get_leaf_element_single(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xCA, 0xFE};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t out[256];
+ memset(out, 0xAA, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_element(dc, root, 1, 0, out, sizeof(out));
+
+ assert_int_equal(result, (int) sizeof(elem));
+ assert_memory_equal(out, elem, sizeof(elem));
+}
+
+/**
+ * Happy path: three-element tree, retrieve each leaf by index.
+ */
+static void test_get_leaf_element_three_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "alpha",
+ (const uint8_t *) "beta",
+ (const uint8_t *) "gamma"};
+ size_t lens[] = {5, 4, 5};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 3, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 3; i++) {
+ uint8_t out[256];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_element(dc, root, 3, (uint32_t) i, out, sizeof(out));
+
+ assert_int_equal(result, (int) lens[i]);
+ assert_memory_equal(out, elems[i], lens[i]);
+ }
+}
+
+/**
+ * Happy path: power-of-two number of elements (4 elements, balanced tree).
+ */
+static void test_get_leaf_element_four_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0x00, 0x01, 0x02};
+ uint8_t e1[] = {0x10, 0x11};
+ uint8_t e2[] = {0x20};
+ uint8_t e3[] = {0x30, 0x31, 0x32, 0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 4, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 4; i++) {
+ uint8_t out[256];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_element(dc, root, 4, (uint32_t) i, out, sizeof(out));
+
+ assert_int_equal(result, (int) lens[i]);
+ assert_memory_equal(out, elems[i], lens[i]);
+ }
+}
+
+/**
+ * Edge case: leaf element of exactly 1 byte.
+ */
+static void test_get_leaf_element_one_byte(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0x42};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {1};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t out[64];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_element(dc, root, 1, 0, out, sizeof(out));
+
+ assert_int_equal(result, 1);
+ assert_int_equal(out[0], 0x42);
+}
+
+/**
+ * Error: output buffer too small for the leaf element.
+ */
+static void test_get_leaf_element_buffer_too_small(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t elem[100];
+ for (size_t i = 0; i < sizeof(elem); i++) {
+ elem[i] = (uint8_t) i;
+ }
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t out[10]; /* Too small */
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_element(dc, root, 1, 0, out, sizeof(out));
+
+ assert_true(result < 0);
+}
+
+/**
+ * Error: wrong Merkle root (no matching tree).
+ */
+static void test_get_leaf_element_wrong_root(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xAB, 0xCD};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ /* Corrupt the root */
+ uint8_t bad_root[32];
+ memset(bad_root, 0xFF, 32);
+
+ uint8_t out[256];
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_element(dc, bad_root, 1, 0, out, sizeof(out));
+
+ assert_true(result < 0);
+}
+
+/**
+ * Error: leaf index out of bounds (>= tree_size).
+ */
+static void test_get_leaf_element_index_out_of_bounds(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "x", (const uint8_t *) "y"};
+ size_t lens[] = {1, 1};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 2, root);
+
+ uint8_t out[256];
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_element(dc, root, 2, 5, out, sizeof(out));
+
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: larger tree (8 elements) to exercise deeper proof paths.
+ */
+static void test_get_leaf_element_eight_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t data[8][16];
+ const uint8_t *elems[8];
+ size_t lens[8];
+
+ for (size_t i = 0; i < 8; i++) {
+ for (size_t j = 0; j < 16; j++) {
+ data[i][j] = (uint8_t) (i * 16 + j);
+ }
+ elems[i] = data[i];
+ lens[i] = 16;
+ }
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 8, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 8; i++) {
+ uint8_t out[256];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_element(dc, root, 8, (uint32_t) i, out, sizeof(out));
+
+ assert_int_equal(result, 16);
+ assert_memory_equal(out, data[i], 16);
+ }
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_get_leaf_element_single),
+ cmocka_unit_test(test_get_leaf_element_three_elements),
+ cmocka_unit_test(test_get_leaf_element_four_elements),
+ cmocka_unit_test(test_get_leaf_element_one_byte),
+ cmocka_unit_test(test_get_leaf_element_buffer_too_small),
+ cmocka_unit_test(test_get_leaf_element_wrong_root),
+ cmocka_unit_test(test_get_leaf_element_index_out_of_bounds),
+ cmocka_unit_test(test_get_leaf_element_eight_elements),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_get_merkle_leaf_hash.c b/unit-tests/test_get_merkle_leaf_hash.c
new file mode 100644
index 0000000..074ef9c
--- /dev/null
+++ b/unit-tests/test_get_merkle_leaf_hash.c
@@ -0,0 +1,348 @@
+/**
+ * Unit tests for call_get_merkle_leaf_hash using the mock dispatcher.
+ *
+ * Tests verify that the function correctly retrieves a leaf hash from a
+ * Merkle tree and validates the proof against the root.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/get_merkle_leaf_hash.h"
+
+/* ---------- Helpers ---------- */
+
+static void compute_sha256(const uint8_t *data, size_t len, uint8_t out[32]) {
+ calc_sha_256(out, data, len);
+}
+
+/**
+ * Build a Merkle tree from the given elements and copy the root hash.
+ */
+static void build_tree(mock_dispatcher_t *mock,
+ const uint8_t *const *elems,
+ const size_t *lens,
+ size_t n,
+ uint8_t root_out[32]) {
+ mock_dispatcher_add_list(mock, elems, lens, n);
+ memcpy(root_out, mock->trees[mock->n_trees - 1].root, 32);
+}
+
+/**
+ * Compute the expected leaf hash: SHA256(0x00 || element).
+ */
+static void compute_leaf_hash(const uint8_t *elem, size_t len, uint8_t out[32]) {
+ uint8_t buf[257];
+ buf[0] = 0x00;
+ memcpy(buf + 1, elem, len);
+ compute_sha256(buf, 1 + len, out);
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: single element tree, retrieve the only leaf hash.
+ */
+static void test_get_leaf_hash_single(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xCA, 0xFE};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elem, sizeof(elem), expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_hash(dc, root, 1, 0, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+}
+
+/**
+ * Happy path: three-element tree, retrieve each leaf hash by index.
+ */
+static void test_get_leaf_hash_three_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "alpha",
+ (const uint8_t *) "beta",
+ (const uint8_t *) "gamma"};
+ size_t lens[] = {5, 4, 5};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 3, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 3; i++) {
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elems[i], lens[i], expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_hash(dc, root, 3, (uint32_t) i, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+ }
+}
+
+/**
+ * Happy path: power-of-two number of elements (4 elements, balanced tree).
+ */
+static void test_get_leaf_hash_four_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0x00, 0x01, 0x02};
+ uint8_t e1[] = {0x10, 0x11};
+ uint8_t e2[] = {0x20};
+ uint8_t e3[] = {0x30, 0x31, 0x32, 0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 4, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 4; i++) {
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elems[i], lens[i], expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_hash(dc, root, 4, (uint32_t) i, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+ }
+}
+
+/**
+ * Happy path: larger unbalanced tree (5 elements).
+ */
+static void test_get_leaf_hash_five_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0xAA};
+ uint8_t e1[] = {0xBB, 0xCC};
+ uint8_t e2[] = {0xDD, 0xEE, 0xFF};
+ uint8_t e3[] = {0x11, 0x22};
+ uint8_t e4[] = {0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3, e4};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3), sizeof(e4)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 5, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 5; i++) {
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elems[i], lens[i], expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_hash(dc, root, 5, (uint32_t) i, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+ }
+}
+
+/**
+ * Happy path: 8-element balanced tree (depth 3).
+ */
+static void test_get_leaf_hash_eight_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t data[8][4];
+ const uint8_t *elems[8];
+ size_t lens[8];
+
+ for (size_t i = 0; i < 8; i++) {
+ for (size_t j = 0; j < 4; j++) {
+ data[i][j] = (uint8_t) ((i * 4 + j) ^ 0x5A);
+ }
+ elems[i] = data[i];
+ lens[i] = 4;
+ }
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 8, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 8; i++) {
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elems[i], lens[i], expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_hash(dc, root, 8, (uint32_t) i, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+ }
+}
+
+/**
+ * Edge case: leaf element of exactly 1 byte.
+ */
+static void test_get_leaf_hash_one_byte_element(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0x42};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {1};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elem, 1, expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_hash(dc, root, 1, 0, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+}
+
+/**
+ * Error: wrong Merkle root (no matching tree registered).
+ */
+static void test_get_leaf_hash_wrong_root(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xDE, 0xAD};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ /* Corrupt the root */
+ uint8_t bad_root[32];
+ memset(bad_root, 0xFF, 32);
+
+ uint8_t out[32];
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_hash(dc, bad_root, 1, 0, out);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: two-element tree, verify both leaves.
+ */
+static void test_get_leaf_hash_two_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t e0[] = {0x01, 0x02, 0x03};
+ const uint8_t e1[] = {0x04, 0x05};
+ const uint8_t *elems[] = {e0, e1};
+ size_t lens[] = {sizeof(e0), sizeof(e1)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 2, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 2; i++) {
+ uint8_t expected_hash[32];
+ compute_leaf_hash(elems[i], lens[i], expected_hash);
+
+ uint8_t out[32];
+ memset(out, 0, sizeof(out));
+
+ int result = call_get_merkle_leaf_hash(dc, root, 2, (uint32_t) i, out);
+
+ assert_int_equal(result, 0);
+ assert_memory_equal(out, expected_hash, 32);
+ }
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_get_leaf_hash_single),
+ cmocka_unit_test(test_get_leaf_hash_three_elements),
+ cmocka_unit_test(test_get_leaf_hash_four_elements),
+ cmocka_unit_test(test_get_leaf_hash_five_elements),
+ cmocka_unit_test(test_get_leaf_hash_eight_elements),
+ cmocka_unit_test(test_get_leaf_hash_one_byte_element),
+ cmocka_unit_test(test_get_leaf_hash_wrong_root),
+ cmocka_unit_test(test_get_leaf_hash_two_elements),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_get_merkle_leaf_index.c b/unit-tests/test_get_merkle_leaf_index.c
new file mode 100644
index 0000000..6e96c37
--- /dev/null
+++ b/unit-tests/test_get_merkle_leaf_index.c
@@ -0,0 +1,324 @@
+/**
+ * Unit tests for call_get_merkle_leaf_index using the mock dispatcher.
+ *
+ * Tests verify that the function correctly finds the index of a leaf
+ * by its hash in a Merkle tree, and validates the result by fetching
+ * the leaf hash at the returned index and comparing.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/get_merkle_leaf_index.h"
+
+/* ---------- Helpers ---------- */
+
+static void compute_sha256(const uint8_t *data, size_t len, uint8_t out[32]) {
+ calc_sha_256(out, data, len);
+}
+
+/**
+ * Build a Merkle tree from the given elements and copy the root hash.
+ */
+static void build_tree(mock_dispatcher_t *mock,
+ const uint8_t *const *elems,
+ const size_t *lens,
+ size_t n,
+ uint8_t root_out[32]) {
+ mock_dispatcher_add_list(mock, elems, lens, n);
+ memcpy(root_out, mock->trees[mock->n_trees - 1].root, 32);
+}
+
+/**
+ * Compute the expected leaf hash: SHA256(0x00 || element).
+ */
+static void compute_leaf_hash(const uint8_t *elem, size_t len, uint8_t out[32]) {
+ uint8_t buf[257];
+ buf[0] = 0x00;
+ memcpy(buf + 1, elem, len);
+ compute_sha256(buf, 1 + len, out);
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: single element tree, find the only leaf by its hash.
+ */
+static void test_get_leaf_index_single(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xCA, 0xFE};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elem, sizeof(elem), leaf_hash);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_index(dc, 1, root, leaf_hash);
+
+ assert_int_equal(result, 0);
+}
+
+/**
+ * Happy path: three-element tree, find each leaf index.
+ */
+static void test_get_leaf_index_three_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "alpha",
+ (const uint8_t *) "beta",
+ (const uint8_t *) "gamma"};
+ size_t lens[] = {5, 4, 5};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 3, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 3; i++) {
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elems[i], lens[i], leaf_hash);
+
+ int result = call_get_merkle_leaf_index(dc, 3, root, leaf_hash);
+
+ assert_int_equal(result, (int) i);
+ }
+}
+
+/**
+ * Happy path: power-of-two number of elements (4 elements, balanced tree).
+ */
+static void test_get_leaf_index_four_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0x00, 0x01, 0x02};
+ uint8_t e1[] = {0x10, 0x11};
+ uint8_t e2[] = {0x20};
+ uint8_t e3[] = {0x30, 0x31, 0x32, 0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 4, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 4; i++) {
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elems[i], lens[i], leaf_hash);
+
+ int result = call_get_merkle_leaf_index(dc, 4, root, leaf_hash);
+
+ assert_int_equal(result, (int) i);
+ }
+}
+
+/**
+ * Happy path: 5-element unbalanced tree.
+ */
+static void test_get_leaf_index_five_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0xAA};
+ uint8_t e1[] = {0xBB, 0xCC};
+ uint8_t e2[] = {0xDD, 0xEE, 0xFF};
+ uint8_t e3[] = {0x11, 0x22};
+ uint8_t e4[] = {0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3, e4};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3), sizeof(e4)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 5, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 5; i++) {
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elems[i], lens[i], leaf_hash);
+
+ int result = call_get_merkle_leaf_index(dc, 5, root, leaf_hash);
+
+ assert_int_equal(result, (int) i);
+ }
+}
+
+/**
+ * Error: unknown leaf hash (not in the tree).
+ */
+static void test_get_leaf_index_unknown_hash(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xCA, 0xFE};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ /* Use a leaf hash that doesn't exist in the tree */
+ uint8_t fake_hash[32];
+ memset(fake_hash, 0xDE, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_index(dc, 1, root, fake_hash);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Error: wrong Merkle root (no matching tree registered).
+ */
+static void test_get_leaf_index_wrong_root(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xDE, 0xAD};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elem, sizeof(elem), leaf_hash);
+
+ /* Corrupt the root */
+ uint8_t bad_root[32];
+ memset(bad_root, 0xFF, 32);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_leaf_index(dc, 1, bad_root, leaf_hash);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: two-element tree, find both leaves.
+ */
+static void test_get_leaf_index_two_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t e0[] = {0x01, 0x02, 0x03};
+ const uint8_t e1[] = {0x04, 0x05};
+ const uint8_t *elems[] = {e0, e1};
+ size_t lens[] = {sizeof(e0), sizeof(e1)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 2, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 2; i++) {
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elems[i], lens[i], leaf_hash);
+
+ int result = call_get_merkle_leaf_index(dc, 2, root, leaf_hash);
+
+ assert_int_equal(result, (int) i);
+ }
+}
+
+/**
+ * Happy path: 8-element balanced tree (depth 3).
+ */
+static void test_get_leaf_index_eight_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t data[8][4];
+ const uint8_t *elems[8];
+ size_t lens[8];
+
+ for (size_t i = 0; i < 8; i++) {
+ for (size_t j = 0; j < 4; j++) {
+ data[i][j] = (uint8_t) ((i * 4 + j) ^ 0x5A);
+ }
+ elems[i] = data[i];
+ lens[i] = 4;
+ }
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 8, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 8; i++) {
+ uint8_t leaf_hash[32];
+ compute_leaf_hash(elems[i], lens[i], leaf_hash);
+
+ int result = call_get_merkle_leaf_index(dc, 8, root, leaf_hash);
+
+ assert_int_equal(result, (int) i);
+ }
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_get_leaf_index_single),
+ cmocka_unit_test(test_get_leaf_index_three_elements),
+ cmocka_unit_test(test_get_leaf_index_four_elements),
+ cmocka_unit_test(test_get_leaf_index_five_elements),
+ cmocka_unit_test(test_get_leaf_index_unknown_hash),
+ cmocka_unit_test(test_get_leaf_index_wrong_root),
+ cmocka_unit_test(test_get_leaf_index_two_elements),
+ cmocka_unit_test(test_get_leaf_index_eight_elements),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_get_merkle_preimage.c b/unit-tests/test_get_merkle_preimage.c
new file mode 100644
index 0000000..074c603
--- /dev/null
+++ b/unit-tests/test_get_merkle_preimage.c
@@ -0,0 +1,304 @@
+/**
+ * Unit tests for call_get_merkle_preimage using the mock dispatcher.
+ *
+ * call_get_merkle_preimage behaves like call_get_preimage, but strips the
+ * leading 0x00 Merkle leaf prefix byte from the output. The hash is
+ * verified over the full preimage (including the prefix).
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/get_merkle_preimage.h"
+
+/* ---------- Helpers ---------- */
+
+static void compute_sha256(const uint8_t *data, size_t len, uint8_t out[32]) {
+ calc_sha_256(out, data, len);
+}
+
+/**
+ * Register a Merkle leaf preimage: the stored preimage is (0x00 || element),
+ * and the hash is SHA256(0x00 || element).
+ * Fills `hash_out` with the hash if non-NULL.
+ */
+static void add_merkle_preimage(mock_dispatcher_t *mock,
+ const uint8_t *element,
+ size_t element_len,
+ uint8_t hash_out[32]) {
+ uint8_t prefixed[257];
+ prefixed[0] = 0x00;
+ memcpy(prefixed + 1, element, element_len);
+
+ mock_dispatcher_add_preimage(mock, prefixed, 1 + element_len);
+
+ if (hash_out) {
+ compute_sha256(prefixed, 1 + element_len, hash_out);
+ }
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: small element (fits entirely in the first response, no
+ * GET_MORE_ELEMENTS needed).
+ */
+static void test_get_merkle_preimage_small(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[50];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i & 0xFF);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[256];
+ memset(out, 0xAA, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ /* Returns element length (preimage_len - 1, stripping 0x00 prefix) */
+ assert_int_equal(result, (int) sizeof(element));
+ assert_memory_equal(out, element, sizeof(element));
+}
+
+/**
+ * Happy path: large element that requires GET_MORE_ELEMENTS to transfer
+ * all the bytes.
+ */
+static void test_get_merkle_preimage_large(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Element of 250 bytes; with the 0x00 prefix the preimage is 251 bytes,
+ * which might not fully fit in the first response chunk. */
+ uint8_t element[250];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) ((i * 7 + 13) & 0xFF);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[512];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_memory_equal(out, element, sizeof(element));
+}
+
+/**
+ * Error: requesting preimage of an unknown hash should return a negative value.
+ */
+static void test_get_merkle_preimage_unknown_hash(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Don't register any preimage; just call with a random hash */
+ uint8_t hash[32] = {0xDE, 0xAD, 0xBE, 0xEF};
+ uint8_t out[256];
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ /* process_interruption returns -1 → call_get_merkle_preimage returns -1 */
+ assert_true(result < 0);
+}
+
+/**
+ * Error: output buffer too small for the element (preimage_len - 1 > out_ptr_len).
+ * call_get_merkle_preimage should return -4.
+ */
+static void test_get_merkle_preimage_buffer_too_small(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[100];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) i;
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[50]; /* Too small for 100-byte element */
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, -4);
+}
+
+/**
+ * Edge case: minimal element of exactly 1 byte.
+ * Preimage is (0x00 || 0x42) = 2 bytes, output should be just 0x42.
+ */
+static void test_get_merkle_preimage_one_byte(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[1] = {0x42};
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, 1, hash);
+
+ uint8_t out[64];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, 1);
+ assert_int_equal(out[0], 0x42);
+}
+
+/**
+ * Edge case: element that, with the 0x00 prefix, exactly fills the max
+ * first-response payload (no GET_MORE_ELEMENTS needed).
+ *
+ * For a preimage of length L, the varint encoding takes 1 byte if L < 253.
+ * Max payload = 255 - varint_len(1) - partial_data_len_byte(1) = 253.
+ * So a preimage of 253 bytes fits in one chunk → element of 252 bytes.
+ */
+static void test_get_merkle_preimage_exact_fit(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[252];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i ^ 0xA5);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[512];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_memory_equal(out, element, sizeof(element));
+}
+
+/**
+ * Edge case: element whose preimage (0x00 || element) is one byte over the
+ * exact-fit boundary, so a few bytes go through GET_MORE_ELEMENTS.
+ *
+ * For preimage length 254: varint takes 3 bytes (>= 253),
+ * max_payload = 255 - 3 - 1 = 251. So 3 bytes spill to GET_MORE_ELEMENTS.
+ * Element length = 253.
+ */
+static void test_get_merkle_preimage_one_byte_overflow(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[253];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i * 3);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[512];
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_memory_equal(out, element, sizeof(element));
+}
+
+/**
+ * Edge case: output buffer exactly matches element length (no spare room).
+ */
+static void test_get_merkle_preimage_exact_buffer(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[64];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i + 0x10);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ uint8_t out[64]; /* Exactly the element size */
+ memset(out, 0, sizeof(out));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_get_merkle_preimage(dc, hash, out, sizeof(out));
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_memory_equal(out, element, sizeof(element));
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_get_merkle_preimage_small),
+ cmocka_unit_test(test_get_merkle_preimage_large),
+ cmocka_unit_test(test_get_merkle_preimage_unknown_hash),
+ cmocka_unit_test(test_get_merkle_preimage_buffer_too_small),
+ cmocka_unit_test(test_get_merkle_preimage_one_byte),
+ cmocka_unit_test(test_get_merkle_preimage_exact_fit),
+ cmocka_unit_test(test_get_merkle_preimage_one_byte_overflow),
+ cmocka_unit_test(test_get_merkle_preimage_exact_buffer),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_stream_merkle_leaf_element.c b/unit-tests/test_stream_merkle_leaf_element.c
new file mode 100644
index 0000000..e7b987a
--- /dev/null
+++ b/unit-tests/test_stream_merkle_leaf_element.c
@@ -0,0 +1,373 @@
+/**
+ * Unit tests for call_stream_merkle_leaf_element using the mock dispatcher.
+ *
+ * call_stream_merkle_leaf_element is the streaming counterpart of
+ * call_get_merkle_leaf_element: instead of writing to an output buffer it
+ * streams data via callbacks.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/stream_merkle_leaf_element.h"
+
+/* ---------- Helpers ---------- */
+
+/**
+ * Build a Merkle tree from the given elements and copy the root hash.
+ */
+static void build_tree(mock_dispatcher_t *mock,
+ const uint8_t *const *elems,
+ const size_t *lens,
+ size_t n,
+ uint8_t root_out[32]) {
+ mock_dispatcher_add_list(mock, elems, lens, n);
+ memcpy(root_out, mock->trees[mock->n_trees - 1].root, 32);
+}
+
+/* Accumulator for streaming callbacks */
+typedef struct {
+ uint8_t buf[1024];
+ size_t offset;
+ size_t total_len;
+ bool len_called;
+} stream_accumulator_t;
+
+static void acc_len_callback(size_t len, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ acc->total_len = len;
+ acc->len_called = true;
+}
+
+static void acc_data_callback(buffer_t *data, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ size_t n = data->size - data->offset;
+ memcpy(acc->buf + acc->offset, data->ptr + data->offset, n);
+ acc->offset += n;
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: single element tree, retrieve the only leaf.
+ */
+static void test_stream_leaf_element_single(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xCA, 0xFE};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result =
+ call_stream_merkle_leaf_element(dc, root, 1, 0, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(elem));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(elem));
+ assert_int_equal(acc.offset, sizeof(elem));
+ assert_memory_equal(acc.buf, elem, sizeof(elem));
+}
+
+/**
+ * Happy path: three-element tree, retrieve each leaf by index.
+ */
+static void test_stream_leaf_element_three_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "alpha",
+ (const uint8_t *) "beta",
+ (const uint8_t *) "gamma"};
+ size_t lens[] = {5, 4, 5};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 3, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 3; i++) {
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ int result = call_stream_merkle_leaf_element(dc,
+ root,
+ 3,
+ (uint32_t) i,
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) lens[i]);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, lens[i]);
+ assert_int_equal(acc.offset, lens[i]);
+ assert_memory_equal(acc.buf, elems[i], lens[i]);
+ }
+}
+
+/**
+ * Happy path: power-of-two number of elements (4 elements, balanced tree).
+ */
+static void test_stream_leaf_element_four_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t e0[] = {0x00, 0x01, 0x02};
+ uint8_t e1[] = {0x10, 0x11};
+ uint8_t e2[] = {0x20};
+ uint8_t e3[] = {0x30, 0x31, 0x32, 0x33};
+
+ const uint8_t *elems[] = {e0, e1, e2, e3};
+ size_t lens[] = {sizeof(e0), sizeof(e1), sizeof(e2), sizeof(e3)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 4, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 4; i++) {
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ int result = call_stream_merkle_leaf_element(dc,
+ root,
+ 4,
+ (uint32_t) i,
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) lens[i]);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, lens[i]);
+ assert_int_equal(acc.offset, lens[i]);
+ assert_memory_equal(acc.buf, elems[i], lens[i]);
+ }
+}
+
+/**
+ * Edge case: leaf element of exactly 1 byte.
+ */
+static void test_stream_leaf_element_one_byte(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0x42};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {1};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result =
+ call_stream_merkle_leaf_element(dc, root, 1, 0, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, 1);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, 1);
+ assert_int_equal(acc.offset, 1);
+ assert_int_equal(acc.buf[0], 0x42);
+}
+
+/**
+ * Error: wrong Merkle root (no matching tree).
+ */
+static void test_stream_leaf_element_wrong_root(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xAB, 0xCD};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ /* Corrupt the root */
+ uint8_t bad_root[32];
+ memset(bad_root, 0xFF, 32);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkle_leaf_element(dc,
+ bad_root,
+ 1,
+ 0,
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Error: leaf index out of bounds (>= tree_size).
+ */
+static void test_stream_leaf_element_index_out_of_bounds(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t *elems[] = {(const uint8_t *) "x", (const uint8_t *) "y"};
+ size_t lens[] = {1, 1};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 2, root);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkle_leaf_element(dc,
+ root,
+ 2,
+ 5,
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Happy path: larger tree (8 elements) to exercise deeper proof paths.
+ */
+static void test_stream_leaf_element_eight_elements(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t data[8][16];
+ const uint8_t *elems[8];
+ size_t lens[8];
+
+ for (size_t i = 0; i < 8; i++) {
+ for (size_t j = 0; j < 16; j++) {
+ data[i][j] = (uint8_t) (i * 16 + j);
+ }
+ elems[i] = data[i];
+ lens[i] = 16;
+ }
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 8, root);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ for (size_t i = 0; i < 8; i++) {
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ int result = call_stream_merkle_leaf_element(dc,
+ root,
+ 8,
+ (uint32_t) i,
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, 16);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, 16);
+ assert_int_equal(acc.offset, 16);
+ assert_memory_equal(acc.buf, data[i], 16);
+ }
+}
+
+/**
+ * Happy path: NULL len_callback should work (len_callback is optional).
+ */
+static void test_stream_leaf_element_null_len_callback(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t elem[] = {0xDE, 0xAD, 0xBE, 0xEF};
+ const uint8_t *elems[] = {elem};
+ size_t lens[] = {sizeof(elem)};
+
+ uint8_t root[32];
+ build_tree(&mock, elems, lens, 1, root);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result =
+ call_stream_merkle_leaf_element(dc, root, 1, 0, NULL, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(elem));
+ assert_false(acc.len_called);
+ assert_int_equal(acc.offset, sizeof(elem));
+ assert_memory_equal(acc.buf, elem, sizeof(elem));
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stream_leaf_element_single),
+ cmocka_unit_test(test_stream_leaf_element_three_elements),
+ cmocka_unit_test(test_stream_leaf_element_four_elements),
+ cmocka_unit_test(test_stream_leaf_element_one_byte),
+ cmocka_unit_test(test_stream_leaf_element_wrong_root),
+ cmocka_unit_test(test_stream_leaf_element_index_out_of_bounds),
+ cmocka_unit_test(test_stream_leaf_element_eight_elements),
+ cmocka_unit_test(test_stream_leaf_element_null_len_callback),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_stream_merkleized_map_value.c b/unit-tests/test_stream_merkleized_map_value.c
new file mode 100644
index 0000000..2e73603
--- /dev/null
+++ b/unit-tests/test_stream_merkleized_map_value.c
@@ -0,0 +1,332 @@
+/**
+ * Unit tests for call_stream_merkleized_map_value using the mock dispatcher.
+ *
+ * call_stream_merkleized_map_value looks up a key in a merkleized key-value
+ * map (by finding its index via Merkle leaf index), then streams the
+ * corresponding value via callbacks.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/stream_merkleized_map_value.h"
+
+/* ---------- Helpers ---------- */
+
+/* Accumulator for streaming callbacks */
+typedef struct {
+ uint8_t buf[1024];
+ size_t offset;
+ size_t total_len;
+ bool len_called;
+} stream_accumulator_t;
+
+static void acc_len_callback(size_t len, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ acc->total_len = len;
+ acc->len_called = true;
+}
+
+static void acc_data_callback(buffer_t *data, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ size_t n = data->size - data->offset;
+ memcpy(acc->buf + acc->offset, data->ptr + data->offset, n);
+ acc->offset += n;
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: single key-value pair, look up by key and stream the value.
+ */
+static void test_stream_map_value_single(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t key[] = {0x01, 0x02};
+ const uint8_t value[] = {0xAA, 0xBB, 0xCC};
+
+ const uint8_t *keys[] = {key};
+ const size_t key_lens[] = {sizeof(key)};
+ const uint8_t *values[] = {value};
+ const size_t value_lens[] = {sizeof(value)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 1, &commitment);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ key,
+ sizeof(key),
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) sizeof(value));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(value));
+ assert_int_equal(acc.offset, sizeof(value));
+ assert_memory_equal(acc.buf, value, sizeof(value));
+}
+
+/**
+ * Happy path: three key-value pairs, look up each by key.
+ * Keys must be in sorted order for the Merkle map to work.
+ */
+static void test_stream_map_value_three_pairs(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Keys are sorted lexicographically by mock_dispatcher_add_map */
+ const uint8_t k0[] = {0x01};
+ const uint8_t k1[] = {0x02};
+ const uint8_t k2[] = {0x03};
+ const uint8_t v0[] = {0x10, 0x11, 0x12};
+ const uint8_t v1[] = {0x20, 0x21};
+ const uint8_t v2[] = {0x30, 0x31, 0x32, 0x33};
+
+ const uint8_t *keys[] = {k0, k1, k2};
+ const size_t key_lens[] = {sizeof(k0), sizeof(k1), sizeof(k2)};
+ const uint8_t *values[] = {v0, v1, v2};
+ const size_t value_lens[] = {sizeof(v0), sizeof(v1), sizeof(v2)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 3, &commitment);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ /* Look up each key-value pair */
+ const uint8_t *test_keys[] = {k0, k1, k2};
+ const size_t test_key_lens[] = {sizeof(k0), sizeof(k1), sizeof(k2)};
+ const uint8_t *expected_values[] = {v0, v1, v2};
+ const size_t expected_value_lens[] = {sizeof(v0), sizeof(v1), sizeof(v2)};
+
+ for (size_t i = 0; i < 3; i++) {
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ test_keys[i],
+ test_key_lens[i],
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) expected_value_lens[i]);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, expected_value_lens[i]);
+ assert_int_equal(acc.offset, expected_value_lens[i]);
+ assert_memory_equal(acc.buf, expected_values[i], expected_value_lens[i]);
+ }
+}
+
+/**
+ * Error: key not found in the map.
+ */
+static void test_stream_map_value_key_not_found(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t key[] = {0x01};
+ const uint8_t value[] = {0xAA};
+
+ const uint8_t *keys[] = {key};
+ const size_t key_lens[] = {sizeof(key)};
+ const uint8_t *values[] = {value};
+ const size_t value_lens[] = {sizeof(value)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 1, &commitment);
+
+ /* Look up a key that doesn't exist */
+ const uint8_t missing_key[] = {0xFF};
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ missing_key,
+ sizeof(missing_key),
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Edge case: value of exactly 1 byte.
+ */
+static void test_stream_map_value_one_byte_value(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t key[] = {0x42};
+ const uint8_t value[] = {0x99};
+
+ const uint8_t *keys[] = {key};
+ const size_t key_lens[] = {sizeof(key)};
+ const uint8_t *values[] = {value};
+ const size_t value_lens[] = {sizeof(value)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 1, &commitment);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ key,
+ sizeof(key),
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, 1);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, 1);
+ assert_int_equal(acc.offset, 1);
+ assert_int_equal(acc.buf[0], 0x99);
+}
+
+/**
+ * Happy path: NULL len_callback should work (len_callback is optional).
+ */
+static void test_stream_map_value_null_len_callback(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ const uint8_t key[] = {0x05};
+ const uint8_t value[] = {0x10, 0x20, 0x30};
+
+ const uint8_t *keys[] = {key};
+ const size_t key_lens[] = {sizeof(key)};
+ const uint8_t *values[] = {value};
+ const size_t value_lens[] = {sizeof(value)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 1, &commitment);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ key,
+ sizeof(key),
+ NULL,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) sizeof(value));
+ assert_false(acc.len_called);
+ assert_int_equal(acc.offset, sizeof(value));
+ assert_memory_equal(acc.buf, value, sizeof(value));
+}
+
+/**
+ * Happy path: keys provided in unsorted order (mock_dispatcher_add_map sorts them).
+ */
+static void test_stream_map_value_unsorted_keys(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Provide keys in reverse order; add_map will sort them */
+ const uint8_t k0[] = {0x03};
+ const uint8_t k1[] = {0x01};
+ const uint8_t k2[] = {0x02};
+ const uint8_t v0[] = {0x30};
+ const uint8_t v1[] = {0x10};
+ const uint8_t v2[] = {0x20};
+
+ const uint8_t *keys[] = {k0, k1, k2};
+ const size_t key_lens[] = {sizeof(k0), sizeof(k1), sizeof(k2)};
+ const uint8_t *values[] = {v0, v1, v2};
+ const size_t value_lens[] = {sizeof(v0), sizeof(v1), sizeof(v2)};
+
+ merkleized_map_commitment_t commitment;
+ mock_dispatcher_add_map(&mock, keys, key_lens, values, value_lens, 3, &commitment);
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+
+ /* Look up each original key and verify it retrieves the correct value */
+ for (size_t i = 0; i < 3; i++) {
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ int result = call_stream_merkleized_map_value(dc,
+ &commitment,
+ keys[i],
+ key_lens[i],
+ acc_len_callback,
+ acc_data_callback,
+ &acc);
+
+ assert_int_equal(result, (int) value_lens[i]);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, value_lens[i]);
+ assert_int_equal(acc.offset, value_lens[i]);
+ assert_memory_equal(acc.buf, values[i], value_lens[i]);
+ }
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stream_map_value_single),
+ cmocka_unit_test(test_stream_map_value_three_pairs),
+ cmocka_unit_test(test_stream_map_value_key_not_found),
+ cmocka_unit_test(test_stream_map_value_one_byte_value),
+ cmocka_unit_test(test_stream_map_value_null_len_callback),
+ cmocka_unit_test(test_stream_map_value_unsorted_keys),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/unit-tests/test_stream_preimage.c b/unit-tests/test_stream_preimage.c
new file mode 100644
index 0000000..7dc9600
--- /dev/null
+++ b/unit-tests/test_stream_preimage.c
@@ -0,0 +1,312 @@
+/**
+ * Unit tests for call_stream_preimage using the mock dispatcher.
+ *
+ * call_stream_preimage behaves like call_get_merkle_preimage, but instead of
+ * writing to an output buffer it streams data via callbacks. The len_callback
+ * is invoked once with the total element length (excluding the 0x00 prefix),
+ * then the data callback is invoked one or more times with buffer_t chunks.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <cmocka.h>
+
+/* SDK mock stubs */
+unsigned int pic(unsigned int linked_address) {
+ return linked_address;
+}
+#undef PIC
+#define PIC(x) (x)
+
+#include "mock_dispatcher.h"
+#include "cx_hash_mock.h"
+#include "sha-256.h"
+
+#include "handler/lib/stream_preimage.h"
+
+/* ---------- Helpers ---------- */
+
+static void compute_sha256(const uint8_t *data, size_t len, uint8_t out[32]) {
+ calc_sha_256(out, data, len);
+}
+
+/**
+ * Register a Merkle leaf preimage: the stored preimage is (0x00 || element),
+ * and the hash is SHA256(0x00 || element).
+ * Fills `hash_out` with the hash if non-NULL.
+ */
+static void add_merkle_preimage(mock_dispatcher_t *mock,
+ const uint8_t *element,
+ size_t element_len,
+ uint8_t hash_out[32]) {
+ uint8_t prefixed[512];
+ prefixed[0] = 0x00;
+ memcpy(prefixed + 1, element, element_len);
+
+ mock_dispatcher_add_preimage(mock, prefixed, 1 + element_len);
+
+ if (hash_out) {
+ compute_sha256(prefixed, 1 + element_len, hash_out);
+ }
+}
+
+/* Accumulator for streaming callbacks */
+typedef struct {
+ uint8_t buf[1024];
+ size_t offset;
+ size_t total_len; /* set by len_callback */
+ bool len_called;
+} stream_accumulator_t;
+
+static void acc_len_callback(size_t len, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ acc->total_len = len;
+ acc->len_called = true;
+}
+
+static void acc_data_callback(buffer_t *data, void *state) {
+ stream_accumulator_t *acc = (stream_accumulator_t *) state;
+ size_t n = data->size - data->offset;
+ memcpy(acc->buf + acc->offset, data->ptr + data->offset, n);
+ acc->offset += n;
+}
+
+/* ---------- Test cases ---------- */
+
+/**
+ * Happy path: small element (fits entirely in the first response, no
+ * GET_MORE_ELEMENTS needed).
+ */
+static void test_stream_preimage_small(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[50];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i & 0xFF);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(element));
+ assert_int_equal(acc.offset, sizeof(element));
+ assert_memory_equal(acc.buf, element, sizeof(element));
+}
+
+/**
+ * Happy path: large element that requires GET_MORE_ELEMENTS to transfer
+ * all the bytes.
+ */
+static void test_stream_preimage_large(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[300];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) ((i * 7 + 13) & 0xFF);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(element));
+ assert_int_equal(acc.offset, sizeof(element));
+ assert_memory_equal(acc.buf, element, sizeof(element));
+}
+
+/**
+ * Error: requesting preimage of an unknown hash should return a negative value.
+ */
+static void test_stream_preimage_unknown_hash(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t hash[32] = {0xDE, 0xAD, 0xBE, 0xEF};
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_true(result < 0);
+}
+
+/**
+ * Edge case: minimal element of exactly 1 byte.
+ * Preimage is (0x00 || 0x42) = 2 bytes, streamed output should be just 0x42.
+ */
+static void test_stream_preimage_one_byte(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[1] = {0x42};
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, 1, hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, 1);
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, 1);
+ assert_int_equal(acc.offset, 1);
+ assert_int_equal(acc.buf[0], 0x42);
+}
+
+/**
+ * Happy path: NULL len_callback should work (len_callback is optional).
+ */
+static void test_stream_preimage_null_len_callback(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[30];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i * 3);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, NULL, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_false(acc.len_called);
+ assert_int_equal(acc.offset, sizeof(element));
+ assert_memory_equal(acc.buf, element, sizeof(element));
+}
+
+/**
+ * Edge case: element that, with the 0x00 prefix, exactly fills the max
+ * first-response payload (no GET_MORE_ELEMENTS needed).
+ *
+ * For a preimage of length L, the varint encoding takes 1 byte if L < 253.
+ * Max payload = 255 - varint_len(1) - partial_data_len_byte(1) = 253.
+ * A preimage of 253 bytes means element of 252 bytes (253 - 1 for 0x00 prefix).
+ */
+static void test_stream_preimage_exact_fit(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ uint8_t element[252];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i ^ 0xA5);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(element));
+ assert_int_equal(acc.offset, sizeof(element));
+ assert_memory_equal(acc.buf, element, sizeof(element));
+}
+
+/**
+ * Edge case: element one byte over the exact-fit boundary, so a few bytes
+ * go through GET_MORE_ELEMENTS.
+ */
+static void test_stream_preimage_one_byte_overflow(void **state) {
+ (void) state;
+
+ static mock_dispatcher_t mock;
+ mock_dispatcher_init(&mock);
+ mock_dispatcher_reset_hash_pool();
+
+ /* Preimage length = 254 (element 253 + prefix 1).
+ * Varint for 254 takes 3 bytes, so max_payload = 255 - 3 - 1 = 251.
+ * 254 - 251 = 3 bytes via GET_MORE_ELEMENTS.
+ */
+ uint8_t element[253];
+ for (size_t i = 0; i < sizeof(element); i++) {
+ element[i] = (uint8_t) (i * 3);
+ }
+
+ uint8_t hash[32];
+ add_merkle_preimage(&mock, element, sizeof(element), hash);
+
+ stream_accumulator_t acc;
+ memset(&acc, 0, sizeof(acc));
+
+ dispatcher_context_t *dc = mock_dispatcher_get_dc(&mock);
+ int result = call_stream_preimage(dc, hash, acc_len_callback, acc_data_callback, &acc);
+
+ assert_int_equal(result, (int) sizeof(element));
+ assert_true(acc.len_called);
+ assert_int_equal(acc.total_len, sizeof(element));
+ assert_int_equal(acc.offset, sizeof(element));
+ assert_memory_equal(acc.buf, element, sizeof(element));
+}
+
+/* ---------- Main ---------- */
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_stream_preimage_small),
+ cmocka_unit_test(test_stream_preimage_large),
+ cmocka_unit_test(test_stream_preimage_unknown_hash),
+ cmocka_unit_test(test_stream_preimage_one_byte),
+ cmocka_unit_test(test_stream_preimage_null_len_callback),
+ cmocka_unit_test(test_stream_preimage_exact_fit),
+ cmocka_unit_test(test_stream_preimage_one_byte_overflow),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.