diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba79348..8916c87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,7 @@ jobs: build-essential \ cmake \ pkg-config \ - libssl-dev \ - libcppunit-dev + libssl-dev - name: Configure (${{ matrix.name }}) run: | @@ -50,7 +49,7 @@ jobs: run: | ctest --test-dir build-${{ matrix.name }} \ --output-on-failure \ - -R 'test_kryzhovnik|test_kryzhovnik_wrapper|test_kryzhovnik_api|test_merkletree|test_adapters_smoke|test_adapters_batch' + -R 'test_kryzhovnik|test_kryzhovnik_wrapper|test_kryzhovnik_api|test_adapters_smoke|test_adapters_batch|test_streebog|test_merkle|test_batch_signing' - name: Benchmark smoke (${{ matrix.name }}) run: | diff --git a/.gitmodules b/.gitmodules index a2f523b..d006613 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,6 +12,3 @@ path = third_party/kryzhovnik url = https://github.com/cherninkiy/kryzhovnik-wrapper-tc26 branch = main -[submodule "third_party/iaik_merkle_tree"] - path = third_party/iaik_merkle_tree - url = https://github.com/IAIK/merkle-tree.git diff --git a/README.md b/README.md index d0d23c6..a65a40e 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ The goal is to measure the speedup of signing a batch of messages with a single ## Features -- (planned) Generic Merkle tree implementation (wrapper around [IAIK/merkle-tree](https://github.com/IAIK/merkle-tree)) -- (planned) Hash abstraction layer (supports **Streebog** via Hypericum implementation, SHA-256 for fallback/testing) +- Generic Merkle tree implementation integrated in `src/merkle` +- Batch signing core integrated in `src/batch_signing*` +- Batch signature format: `root_signature + list_of_proofs` (with metadata framing for serialization) +- Hash abstraction layer (supports **Streebog** and test hash backends) - Signature abstraction layer for Russian PQC: - [Shipovnik](https://github.com/QAPP-tech/shipovnik_tc26) - [Hypericum](https://github.com/QAPP-tech/hypericum_tc26) @@ -59,9 +61,14 @@ batch-pqc/ │ ├── shipovnik/ → https://github.com/cherninkiy/shipovnik-wrapper-tc26 │ ├── hypericum/ → https://github.com/cherninkiy/hypericum-wrapper-tc26 │ ├── kryzhovnik/ → https://github.com/cherninkiy/kryzhovnik-wrapper-tc26 -│ └── iaik_merkle_tree/ → https://github.com/IAIK/merkle-tree ├── src/ │ ├── signature/ # signature provider adapters +│ ├── batch_bench.h # unified adapter contract (bb_status, bb_algorithm) +│ ├── merkle/ # Merkle tree implementation +│ ├── batch_signing.h # batch signing API +│ ├── batch_signing.c # batch signing implementation +│ ├── batch_adapters.h # callbacks to connect batch layer with bb_algorithm API +│ ├── batch_adapters.c # adapter callback implementation │ └── utils/ # timers, message generators ├── bench/ # benchmarking executables ├── tests/ # unit tests (CTest) @@ -76,7 +83,7 @@ batch-pqc/ - Linux x86_64 (tested on Ubuntu 22.04) - CMake 3.14+, GCC/Clang with C11 support -- OpenSSL (for SHA-256 fallback, optional) +- OpenSSL development package (`libssl-dev`) for Kryzhovnik build ### Quick Start @@ -142,8 +149,8 @@ After running benchmarks, the `results/` directory will contain: - [x] Adapters for Shipovnik / Hypericum / Kryzhovnik with unified `bb_status` - [x] Detached/status-return integration APIs wired through wrapper submodules - [x] Sequential benchmark `bench_seq` with warmup and corrected signature-size metric -- [x] Test coverage: `test_adapters_smoke`, `test_adapters_batch`, `test_kryzhovnik*`, `test_merkletree` -- [ ] Merkle-based batch signer/verifier implementation +- [x] Merkle-based batch signer/verifier implementation +- [x] Test coverage: `test_adapters_smoke`, `test_adapters_batch`, `test_kryzhovnik*`, `test_merkle`, `test_batch_signing`, `test_streebog` - [ ] Sequential vs real batch signing benchmark comparison - [ ] Final report (PDF) @@ -164,12 +171,10 @@ This project is licensed under the **MIT License** – see [LICENSE](LICENSE) fi Third-party components have their own licenses: -- `iaik_merkle_tree` – public domain / BSD-like - PQC implementations – please refer to each submodule's license ## Acknowledgments -- [IAIK/merkle-tree](https://github.com/IAIK/merkle-tree) – Merkle tree implementation (public domain / BSD‑like) - [QAPP-tech](https://github.com/QAPP-tech) – Shipovnik and Hypericum (including Streebog hash) PQC signatures - [Elena Kirshanova](https://github.com/ElenaKirshanova) – original Kryzhovnik implementation: [pqc_LWR_signature](https://github.com/ElenaKirshanova/pqc_LWR_signature) - A compatibility fork is used in this project to integrate Kryzhovnik with Hypericum and Shipovnik diff --git a/README_ru.md b/README_ru.md index b099526..93ea471 100644 --- a/README_ru.md +++ b/README_ru.md @@ -25,8 +25,10 @@ ## Возможности -- (планируется) Реализация дерева Меркла (обёртка над [IAIK/merkle-tree](https://github.com/IAIK/merkle-tree)) -- (планируется) Слой абстракции хеш-функций (поддерживает **Стрибог** через реализацию Hypericum, SHA-256 для тестов) +- Реализация дерева Меркла в `src/merkle` +- Ядро пакетной подписи в `src/batch_signing*` +- Формат пакетной подписи: `подпись_корня + список_доказательств` (с метаданными для сериализации) +- Слой абстракции хеш-функций (поддерживает **Стрибог** и тестовые hash-backend'ы) - Слой абстракции подписей для российских PQC: - [Шиповник](https://github.com/QAPP-tech/shipovnik_tc26) - [Гиперикум](https://github.com/QAPP-tech/hypericum_tc26) @@ -63,9 +65,14 @@ batch-pqc/ │ ├── shipovnik/ → https://github.com/cherninkiy/shipovnik-wrapper-tc26 │ ├── hypericum/ → https://github.com/cherninkiy/hypericum-wrapper-tc26 │ ├── kryzhovnik/ → https://github.com/cherninkiy/kryzhovnik-wrapper-tc26 -│ └── iaik_merkle_tree/ → https://github.com/IAIK/merkle-tree ├── src/ │ ├── signature/ # адаптеры для алгоритмов подписи +│ ├── batch_bench.h # единый контракт адаптеров (bb_status, bb_algorithm) +│ ├── merkle/ # реализация дерева Меркла +│ ├── batch_signing.h # API пакетной подписи +│ ├── batch_signing.c # реализация пакетной подписи +│ ├── batch_adapters.h # callback-интерфейс для интеграции с bb_algorithm API +│ ├── batch_adapters.c # реализация callback-адаптеров │ └── utils/ # таймеры, генераторы сообщений ├── bench/ # исполняемые файлы бенчмарков ├── tests/ # модульные тесты (CTest) @@ -80,7 +87,7 @@ batch-pqc/ - Linux x86_64 (тестировалось на Ubuntu 22.04) - CMake 3.14+, GCC/Clang с поддержкой C11 -- OpenSSL (для SHA-256, опционально) +- OpenSSL development package (`libssl-dev`) для сборки Kryzhovnik ### Быстрый старт @@ -147,8 +154,8 @@ cmake --build build --parallel - [x] Адаптеры для Шиповника / Гиперикума / Крыжовника с единым `bb_status` - [x] Подключены detached/status-return API через wrapper-сабмодули - [x] Последовательный бенчмарк `bench_seq` с warmup и исправленной метрикой размера подписей -- [x] Покрытие тестами: `test_adapters_smoke`, `test_adapters_batch`, `test_kryzhovnik*`, `test_merkletree` -- [ ] Реализация пакетной подписи/верификации на основе дерева Меркла +- [x] Реализация пакетной подписи/верификации на основе дерева Меркла +- [x] Покрытие тестами: `test_adapters_smoke`, `test_adapters_batch`, `test_kryzhovnik*`, `test_merkle`, `test_batch_signing`, `test_streebog` - [ ] Сравнение бенчмарков: последовательная vs реальная пакетная подпись - [ ] Финальный отчёт (PDF) @@ -170,12 +177,10 @@ cmake --build build --parallel Сторонние компоненты имеют свои лицензии: -- `iaik_merkle_tree` – public domain / BSD-like - Реализации PQC – смотрите лицензии в каждом субмодуле ## Благодарности -- [IAIK/merkle-tree](https://github.com/IAIK/merkle-tree) – реализация дерева Меркла (public domain / BSD‑like) - [QAPP-tech](https://github.com/QAPP-tech) – подписи Шиповник и Гиперикум (включая хеш Стрибог) - [Elena Kirshanova](https://github.com/ElenaKirshanova) – оригинальная реализация Крыжовника: [pqc_LWR_signature](https://github.com/ElenaKirshanova/pqc_LWR_signature) - В этом проекте используется форк совместимости для интеграции Крыжовника с Гиперикум и Шиповник diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e42da2f..99b111f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,3 +22,32 @@ target_link_libraries(batch_bench_adapters ) target_compile_features(batch_bench_adapters PRIVATE c_std_99) + +add_library(batch_signing_core STATIC + merkle/merkle.c + batch_signing.c +) + +target_include_directories(batch_signing_core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_features(batch_signing_core PRIVATE c_std_99) + +add_library(batch_adapters STATIC + batch_adapters.c +) + +target_include_directories(batch_adapters + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(batch_adapters + PUBLIC + batch_signing_core + batch_bench_adapters +) + +target_compile_features(batch_adapters PRIVATE c_std_99) diff --git a/src/batch_adapters.c b/src/batch_adapters.c new file mode 100644 index 0000000..b481344 --- /dev/null +++ b/src/batch_adapters.c @@ -0,0 +1,72 @@ +#include "batch_adapters.h" + +int batch_bb_adapter_init(batch_bb_adapter_ctx* ctx, + const bb_algorithm* algorithm, + const uint8_t* sk, + size_t sk_len, + const uint8_t* pk, + size_t pk_len) { + if (ctx == NULL || algorithm == NULL || algorithm->sign == NULL || algorithm->verify == NULL) { + return -1; + } + if (sk == NULL || pk == NULL) { + return -1; + } + + ctx->algorithm = algorithm; + ctx->sk = sk; + ctx->sk_len = sk_len; + ctx->pk = pk; + ctx->pk_len = pk_len; + return 0; +} + +size_t batch_bb_signature_capacity(const batch_bb_adapter_ctx* ctx) { + if (ctx == NULL || ctx->algorithm == NULL) { + return 0; + } + return ctx->algorithm->signature_bytes; +} + +int batch_bb_sign_callback(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + uint8_t* sig, + size_t sig_capacity, + size_t* sig_len) { + batch_bb_adapter_ctx* ctx = (batch_bb_adapter_ctx*)user_ctx; + bb_status status; + + if (ctx == NULL || ctx->algorithm == NULL || ctx->algorithm->sign == NULL || + msg == NULL || sig == NULL || sig_len == NULL) { + return 0; + } + + status = ctx->algorithm->sign(ctx->sk, + ctx->sk_len, + ctx->pk, + ctx->pk_len, + msg, + msg_len, + sig, + sig_capacity, + sig_len); + return status == BB_OK ? 1 : 0; +} + +int batch_bb_verify_callback(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + const uint8_t* sig, + size_t sig_len) { + batch_bb_adapter_ctx* ctx = (batch_bb_adapter_ctx*)user_ctx; + bb_status status; + + if (ctx == NULL || ctx->algorithm == NULL || ctx->algorithm->verify == NULL || + msg == NULL || sig == NULL) { + return 0; + } + + status = ctx->algorithm->verify(ctx->pk, ctx->pk_len, msg, msg_len, sig, sig_len); + return status == BB_OK ? 1 : 0; +} diff --git a/src/batch_adapters.h b/src/batch_adapters.h new file mode 100644 index 0000000..3d585b1 --- /dev/null +++ b/src/batch_adapters.h @@ -0,0 +1,40 @@ +#ifndef BATCH_ADAPTERS_H +#define BATCH_ADAPTERS_H + +#include "batch_bench.h" +#include "batch_signing.h" + +#include +#include + +typedef struct batch_bb_adapter_ctx { + const bb_algorithm* algorithm; + const uint8_t* sk; + size_t sk_len; + const uint8_t* pk; + size_t pk_len; +} batch_bb_adapter_ctx; + +int batch_bb_adapter_init(batch_bb_adapter_ctx* ctx, + const bb_algorithm* algorithm, + const uint8_t* sk, + size_t sk_len, + const uint8_t* pk, + size_t pk_len); + +size_t batch_bb_signature_capacity(const batch_bb_adapter_ctx* ctx); + +int batch_bb_sign_callback(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + uint8_t* sig, + size_t sig_capacity, + size_t* sig_len); + +int batch_bb_verify_callback(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + const uint8_t* sig, + size_t sig_len); + +#endif diff --git a/src/batch_signing.c b/src/batch_signing.c new file mode 100644 index 0000000..8793151 --- /dev/null +++ b/src/batch_signing.c @@ -0,0 +1,685 @@ +#include "batch_signing.h" +#include "merkle/merkle.h" + +#include +#include + +#define BATCH_SIGNATURE_VERSION 1u + +static int checked_add_size(size_t lhs, size_t rhs, size_t* out) { + if (rhs > SIZE_MAX - lhs) { + return -1; + } + + *out = lhs + rhs; + return 0; +} + +static int checked_mul_size(size_t lhs, size_t rhs, size_t* out) { + if (lhs != 0 && rhs > SIZE_MAX / lhs) { + return -1; + } + + *out = lhs * rhs; + return 0; +} + +struct batch_signer { + size_t hash_size; + size_t root_sig_capacity; + batch_hash_fn hash; + batch_sign_fn sign; + void* user_ctx; + + uint8_t** messages; + size_t* msg_lens; + size_t num_msgs; + size_t capacity; +}; + +struct batch_signature { + size_t hash_size; + + uint8_t* root_hash; + uint8_t* root_signature; + size_t root_sig_len; + + uint8_t*** proofs; + size_t* proof_lens; + size_t num_proofs; +}; + +/* Encode/decode uint32_t in little-endian byte order for a stable wire format. */ +static int write_u32(uint8_t** ptr, const uint8_t* end, uint32_t value) { + if ((size_t)(end - *ptr) < sizeof(uint32_t)) { + return -1; + } + (*ptr)[0] = (uint8_t)(value); + (*ptr)[1] = (uint8_t)(value >> 8); + (*ptr)[2] = (uint8_t)(value >> 16); + (*ptr)[3] = (uint8_t)(value >> 24); + *ptr += sizeof(uint32_t); + return 0; +} + +static int read_u32(const uint8_t** ptr, const uint8_t* end, uint32_t* out) { + if ((size_t)(end - *ptr) < sizeof(uint32_t)) { + return -1; + } + *out = (uint32_t)((*ptr)[0]) + | (uint32_t)((*ptr)[1]) << 8 + | (uint32_t)((*ptr)[2]) << 16 + | (uint32_t)((*ptr)[3]) << 24; + *ptr += sizeof(uint32_t); + return 0; +} + +static int batch_signer_grow(batch_signer_t* ctx, size_t new_capacity) { + if (new_capacity <= ctx->capacity) { + return 0; + } + + size_t old_capacity = ctx->capacity; + size_t cap = old_capacity ? old_capacity : 16; + while (cap < new_capacity) { + if (cap > SIZE_MAX / 2) { + return -1; + } + cap *= 2; + } + + size_t msg_bytes = 0; + size_t len_bytes = 0; + if (checked_mul_size(cap, sizeof(uint8_t*), &msg_bytes) != 0 || + checked_mul_size(cap, sizeof(size_t), &len_bytes) != 0) { + return -1; + } + + uint8_t** new_msgs = (uint8_t**)malloc(msg_bytes); + if (new_msgs == NULL) { + return -1; + } + + size_t* new_lens = (size_t*)malloc(len_bytes); + if (new_lens == NULL) { + free(new_msgs); + return -1; + } + + if (old_capacity > 0) { + memcpy(new_msgs, ctx->messages, old_capacity * sizeof(uint8_t*)); + memcpy(new_lens, ctx->msg_lens, old_capacity * sizeof(size_t)); + } + if (cap > old_capacity) { + memset(new_msgs + old_capacity, 0, (cap - old_capacity) * sizeof(uint8_t*)); + memset(new_lens + old_capacity, 0, (cap - old_capacity) * sizeof(size_t)); + } + + free(ctx->messages); + free(ctx->msg_lens); + ctx->messages = new_msgs; + ctx->msg_lens = new_lens; + ctx->capacity = cap; + return 0; +} + +batch_signer_t* batch_signer_create(size_t hash_size, + size_t root_sig_capacity, + batch_hash_fn hash, + batch_sign_fn sign, + void* user_ctx) { + if (hash_size == 0 || root_sig_capacity == 0 || hash == NULL || sign == NULL) { + return NULL; + } + + batch_signer_t* ctx = (batch_signer_t*)calloc(1, sizeof(batch_signer_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->hash_size = hash_size; + ctx->root_sig_capacity = root_sig_capacity; + ctx->hash = hash; + ctx->sign = sign; + ctx->user_ctx = user_ctx; + ctx->capacity = 16; + + ctx->messages = (uint8_t**)malloc(ctx->capacity * sizeof(uint8_t*)); + ctx->msg_lens = (size_t*)malloc(ctx->capacity * sizeof(size_t)); + + if (ctx->messages == NULL || ctx->msg_lens == NULL) { + free(ctx->messages); + free(ctx->msg_lens); + free(ctx); + return NULL; + } + + memset(ctx->messages, 0, ctx->capacity * sizeof(uint8_t*)); + memset(ctx->msg_lens, 0, ctx->capacity * sizeof(size_t)); + + return ctx; +} + +void batch_signer_free(batch_signer_t* ctx) { + if (ctx == NULL) { + return; + } + + batch_signer_reset(ctx); + free(ctx->messages); + free(ctx->msg_lens); + free(ctx); +} + +int batch_signer_add_message(batch_signer_t* ctx, const uint8_t* msg, size_t msg_len) { + if (ctx == NULL || msg == NULL) { + return -1; + } + + if (ctx->num_msgs >= ctx->capacity) { + if (batch_signer_grow(ctx, ctx->num_msgs + 1) != 0) { + return -1; + } + } + + uint8_t* copy = NULL; + if (msg_len > 0) { + copy = (uint8_t*)malloc(msg_len); + if (copy == NULL) { + return -1; + } + memcpy(copy, msg, msg_len); + } + + ctx->messages[ctx->num_msgs] = copy; + ctx->msg_lens[ctx->num_msgs] = msg_len; + ctx->num_msgs++; + + return 0; +} + +void batch_signer_reset(batch_signer_t* ctx) { + if (ctx == NULL) { + return; + } + + for (size_t i = 0; i < ctx->num_msgs; ++i) { + free(ctx->messages[i]); + ctx->messages[i] = NULL; + } + ctx->num_msgs = 0; +} + +size_t batch_signer_count(const batch_signer_t* ctx) { + if (ctx == NULL) { + return 0; + } + return ctx->num_msgs; +} + +batch_signature_t* batch_signer_sign(batch_signer_t* ctx) { + if (ctx == NULL || ctx->num_msgs == 0) { + return NULL; + } + + uint8_t** leaf_hashes = (uint8_t**)malloc(ctx->num_msgs * sizeof(uint8_t*)); + if (leaf_hashes == NULL) { + return NULL; + } + + for (size_t i = 0; i < ctx->num_msgs; ++i) { + leaf_hashes[i] = (uint8_t*)malloc(ctx->hash_size); + if (leaf_hashes[i] == NULL) { + for (size_t j = 0; j < i; ++j) free(leaf_hashes[j]); + free(leaf_hashes); + return NULL; + } + ctx->hash(ctx->messages[i], ctx->msg_lens[i], leaf_hashes[i]); + } + + merkle_tree_t* tree = merkle_tree_create(ctx->num_msgs, ctx->hash_size, ctx->hash); + if (tree == NULL) { + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + for (size_t i = 0; i < ctx->num_msgs; ++i) { + if (merkle_tree_set_leaf(tree, i, leaf_hashes[i]) != 0) { + merkle_tree_destroy(tree); + for (size_t j = 0; j < ctx->num_msgs; ++j) free(leaf_hashes[j]); + free(leaf_hashes); + return NULL; + } + } + if (merkle_tree_build(tree) != 0) { + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + const uint8_t* root_hash = merkle_tree_root(tree); + if (root_hash == NULL) { + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + size_t root_sig_len = 0; + uint8_t* root_sig = (uint8_t*)malloc(ctx->root_sig_capacity); + if (root_sig == NULL) { + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + if (!ctx->sign(ctx->user_ctx, + root_hash, + ctx->hash_size, + root_sig, + ctx->root_sig_capacity, + &root_sig_len) || + root_sig_len == 0 || + root_sig_len > ctx->root_sig_capacity) { + free(root_sig); + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + size_t num_proofs = ctx->num_msgs; + uint8_t*** proofs = (uint8_t***)malloc(num_proofs * sizeof(uint8_t**)); + size_t* proof_lens = (size_t*)malloc(num_proofs * sizeof(size_t)); + + if (proofs == NULL || proof_lens == NULL) { + free(root_sig); + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + free(proofs); + free(proof_lens); + return NULL; + } + + for (size_t i = 0; i < num_proofs; ++i) { + proofs[i] = merkle_tree_get_proof(tree, i, &proof_lens[i]); + /* For a single-slot tree an empty proof (NULL, len=0) is valid. + For any larger tree NULL signals an allocation failure. */ + if (proofs[i] == NULL && merkle_tree_capacity(tree) > 1u) { + for (size_t j = 0; j < i; ++j) merkle_tree_free_proof(proofs[j], proof_lens[j]); + free(proofs); + free(proof_lens); + free(root_sig); + merkle_tree_destroy(tree); + for (size_t j = 0; j < ctx->num_msgs; ++j) free(leaf_hashes[j]); + free(leaf_hashes); + return NULL; + } + } + + batch_signature_t* sig = (batch_signature_t*)malloc(sizeof(batch_signature_t)); + if (sig == NULL) { + for (size_t i = 0; i < num_proofs; ++i) merkle_tree_free_proof(proofs[i], proof_lens[i]); + free(proofs); + free(proof_lens); + free(root_sig); + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + + sig->hash_size = ctx->hash_size; + sig->root_signature = root_sig; + sig->root_sig_len = root_sig_len; + sig->proofs = proofs; + sig->proof_lens = proof_lens; + sig->num_proofs = num_proofs; + + sig->root_hash = (uint8_t*)malloc(ctx->hash_size); + if (sig->root_hash == NULL) { + for (size_t i = 0; i < num_proofs; ++i) merkle_tree_free_proof(proofs[i], proof_lens[i]); + free(proofs); + free(proof_lens); + free(root_sig); + free(sig); + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + return NULL; + } + memcpy(sig->root_hash, root_hash, ctx->hash_size); + + merkle_tree_destroy(tree); + for (size_t i = 0; i < ctx->num_msgs; ++i) free(leaf_hashes[i]); + free(leaf_hashes); + + return sig; +} + +int batch_signature_serialize(const batch_signature_t* sig, uint8_t* out, size_t* out_len) { + if (sig == NULL || out_len == NULL || sig->root_hash == NULL || sig->root_signature == NULL) { + return -1; + } + if (sig->hash_size > UINT32_MAX || + sig->num_proofs > UINT32_MAX || + sig->root_sig_len > UINT32_MAX) { + return -1; + } + + size_t needed = 0; + if (checked_add_size(needed, sizeof(uint32_t) * 5u, &needed) != 0 || + checked_add_size(needed, sig->hash_size, &needed) != 0 || + checked_add_size(needed, sig->root_sig_len, &needed) != 0) { + return -1; + } + for (size_t i = 0; i < sig->num_proofs; ++i) { + size_t proof_bytes = 0; + + if (sig->proof_lens[i] > UINT32_MAX || + (sig->proof_lens[i] > 0 && sig->proofs[i] == NULL) || + checked_mul_size(sig->proof_lens[i], sig->hash_size, &proof_bytes) != 0 || + checked_add_size(needed, sizeof(uint32_t), &needed) != 0 || + checked_add_size(needed, proof_bytes, &needed) != 0) { + return -1; + } + } + + if (out == NULL) { + *out_len = needed; + return 0; + } + + if (*out_len < needed) { + return -1; + } + + uint8_t* ptr = out; + const uint8_t* end = out + needed; + + if (write_u32(&ptr, end, BATCH_SIGNATURE_VERSION) != 0 || + write_u32(&ptr, end, (uint32_t)sig->hash_size) != 0 || + write_u32(&ptr, end, (uint32_t)sig->num_proofs) != 0 || + write_u32(&ptr, end, (uint32_t)sig->hash_size) != 0) { + return -1; + } + + memcpy(ptr, sig->root_hash, sig->hash_size); + ptr += sig->hash_size; + + if (write_u32(&ptr, end, (uint32_t)sig->root_sig_len) != 0) { + return -1; + } + memcpy(ptr, sig->root_signature, sig->root_sig_len); + ptr += sig->root_sig_len; + + for (size_t i = 0; i < sig->num_proofs; ++i) { + if (write_u32(&ptr, end, (uint32_t)sig->proof_lens[i]) != 0) { + return -1; + } + for (size_t j = 0; j < sig->proof_lens[i]; ++j) { + memcpy(ptr, sig->proofs[i][j], sig->hash_size); + ptr += sig->hash_size; + } + } + + *out_len = needed; + return 0; +} + +batch_signature_t* batch_signature_deserialize(const uint8_t* data, size_t data_len, + size_t hash_size) { + if (data == NULL || hash_size == 0) { + return NULL; + } + + const uint8_t* ptr = data; + const uint8_t* end = data + data_len; + uint32_t version = 0; + uint32_t read_hash_size = 0; + uint32_t num_proofs = 0; + uint32_t root_hash_len = 0; + uint32_t root_sig_len = 0; + + if (read_u32(&ptr, end, &version) != 0 || + read_u32(&ptr, end, &read_hash_size) != 0 || + read_u32(&ptr, end, &num_proofs) != 0 || + read_u32(&ptr, end, &root_hash_len) != 0) { + return NULL; + } + if (version != BATCH_SIGNATURE_VERSION || + read_hash_size != (uint32_t)hash_size || + root_hash_len != (uint32_t)hash_size || + (size_t)(end - ptr) < hash_size) { + return NULL; + } + if (num_proofs > (uint32_t)((size_t)(end - ptr) / sizeof(uint32_t))) { + return NULL; + } + + batch_signature_t* sig = (batch_signature_t*)malloc(sizeof(batch_signature_t)); + if (sig == NULL) { + return NULL; + } + + sig->hash_size = hash_size; + sig->num_proofs = num_proofs; + + sig->root_hash = (uint8_t*)malloc(hash_size); + if (sig->root_hash == NULL) { + free(sig); + return NULL; + } + + memcpy(sig->root_hash, ptr, hash_size); + ptr += hash_size; + + if (read_u32(&ptr, end, &root_sig_len) != 0 || (size_t)(end - ptr) < root_sig_len) { + free(sig->root_hash); + free(sig); + return NULL; + } + + sig->root_sig_len = root_sig_len; + sig->root_signature = NULL; + if (root_sig_len > 0) { + sig->root_signature = (uint8_t*)malloc(root_sig_len); + } + if (root_sig_len > 0 && sig->root_signature == NULL) { + free(sig->root_hash); + free(sig); + return NULL; + } + if (root_sig_len > 0) { + memcpy(sig->root_signature, ptr, root_sig_len); + } + ptr += root_sig_len; + + sig->proofs = NULL; + sig->proof_lens = NULL; + if (num_proofs > 0) { + size_t proofs_bytes = 0; + size_t proof_lens_bytes = 0; + + if (checked_mul_size(num_proofs, sizeof(uint8_t**), &proofs_bytes) != 0 || + checked_mul_size(num_proofs, sizeof(size_t), &proof_lens_bytes) != 0) { + free(sig->root_hash); + free(sig->root_signature); + free(sig); + return NULL; + } + + sig->proofs = (uint8_t***)calloc(1, proofs_bytes); + sig->proof_lens = (size_t*)calloc(1, proof_lens_bytes); + } + + if (num_proofs > 0 && (sig->proofs == NULL || sig->proof_lens == NULL)) { + free(sig->root_hash); + free(sig->root_signature); + free(sig->proofs); + free(sig->proof_lens); + free(sig); + return NULL; + } + + for (uint32_t i = 0; i < num_proofs; ++i) { + uint32_t proof_len = 0; + if (read_u32(&ptr, end, &proof_len) != 0) { + for (uint32_t j = 0; j < i; ++j) merkle_tree_free_proof(sig->proofs[j], sig->proof_lens[j]); + free(sig->proofs); + free(sig->proof_lens); + free(sig->root_hash); + free(sig->root_signature); + free(sig); + return NULL; + } + + sig->proof_lens[i] = proof_len; + + size_t proof_bytes = 0; + size_t proof_ptr_bytes = 0; + if (checked_mul_size((size_t)proof_len, hash_size, &proof_bytes) != 0 || + checked_mul_size((size_t)proof_len, sizeof(uint8_t*), &proof_ptr_bytes) != 0 || + (size_t)(end - ptr) < proof_bytes) { + for (uint32_t j = 0; j < i; ++j) merkle_tree_free_proof(sig->proofs[j], sig->proof_lens[j]); + free(sig->proofs); + free(sig->proof_lens); + free(sig->root_hash); + free(sig->root_signature); + free(sig); + return NULL; + } + + sig->proofs[i] = NULL; + if (proof_len > 0) { + sig->proofs[i] = (uint8_t**)malloc(proof_ptr_bytes); + } + if (proof_len > 0 && sig->proofs[i] == NULL) { + for (uint32_t j = 0; j < i; ++j) merkle_tree_free_proof(sig->proofs[j], sig->proof_lens[j]); + free(sig->proofs); + free(sig->proof_lens); + free(sig->root_hash); + free(sig->root_signature); + free(sig); + return NULL; + } + + for (uint32_t j = 0; j < proof_len; ++j) { + sig->proofs[i][j] = (uint8_t*)malloc(hash_size); + if (sig->proofs[i][j] == NULL) { + for (uint32_t k = 0; k < j; ++k) free(sig->proofs[i][k]); + free(sig->proofs[i]); + for (uint32_t k = 0; k < i; ++k) merkle_tree_free_proof(sig->proofs[k], sig->proof_lens[k]); + free(sig->proofs); + free(sig->proof_lens); + free(sig->root_hash); + free(sig->root_signature); + free(sig); + return NULL; + } + memcpy(sig->proofs[i][j], ptr, hash_size); + ptr += hash_size; + } + } + + if (ptr != end) { + batch_signature_free(sig); + return NULL; + } + + return sig; +} + +void batch_signature_free(batch_signature_t* sig) { + if (sig == NULL) { + return; + } + + free(sig->root_hash); + free(sig->root_signature); + + for (size_t i = 0; i < sig->num_proofs; ++i) { + merkle_tree_free_proof(sig->proofs[i], sig->proof_lens[i]); + } + free(sig->proofs); + free(sig->proof_lens); + free(sig); +} + +int batch_verify(const uint8_t* const* messages, + const size_t* msg_lens, + size_t num_msgs, + const batch_signature_t* sig, + size_t hash_size, + batch_hash_fn hash, + batch_verify_fn verify, + void* user_ctx) { + if (messages == NULL || msg_lens == NULL || sig == NULL || hash == NULL || verify == NULL) { + return 0; + } + + if (sig->root_hash == NULL || sig->root_signature == NULL) { + return 0; + } + if (num_msgs != sig->num_proofs || hash_size != sig->hash_size) { + return 0; + } + + for (size_t i = 0; i < num_msgs; ++i) { + uint8_t* leaf_hash = (uint8_t*)malloc(hash_size); + if (messages[i] == NULL || leaf_hash == NULL) { + free(leaf_hash); + return 0; + } + + hash(messages[i], msg_lens[i], leaf_hash); + + if (!merkle_tree_verify_proof(leaf_hash, + sig->proofs[i], + sig->proof_lens[i], + i, + sig->root_hash, + hash_size, + hash)) { + free(leaf_hash); + return 0; + } + free(leaf_hash); + } + + if (!verify(user_ctx, + sig->root_hash, + hash_size, + sig->root_signature, + sig->root_sig_len)) { + return 0; + } + + return 1; +} + +const uint8_t* batch_signature_root_hash(const batch_signature_t* sig) { + if (sig == NULL) { + return NULL; + } + return sig->root_hash; +} + +const uint8_t* batch_signature_root_signature(const batch_signature_t* sig, size_t* out_len) { + if (sig == NULL || out_len == NULL) { + return NULL; + } + *out_len = sig->root_sig_len; + return sig->root_signature; +} + +size_t batch_signature_num_proofs(const batch_signature_t* sig) { + if (sig == NULL) { + return 0; + } + return sig->num_proofs; +} \ No newline at end of file diff --git a/src/batch_signing.h b/src/batch_signing.h new file mode 100644 index 0000000..e0e7a14 --- /dev/null +++ b/src/batch_signing.h @@ -0,0 +1,79 @@ +#ifndef BATCH_SIGNING_H +#define BATCH_SIGNING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct batch_signer batch_signer_t; +typedef struct batch_signature batch_signature_t; + +/* Hash callback: out = hash(in, in_len), out length is hash_size bytes. */ +typedef void (*batch_hash_fn)(const uint8_t* in, size_t in_len, uint8_t* out); + +/* + * Signature callback. + * Returns 1 on success, 0 on failure. + */ +typedef int (*batch_sign_fn)(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + uint8_t* sig, + size_t sig_capacity, + size_t* sig_len); + +/* Verification callback: returns 1 if signature is valid, 0 otherwise. */ +typedef int (*batch_verify_fn)(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + const uint8_t* sig, + size_t sig_len); + +/* Create signer context. root_sig_capacity is the max root-signature size. */ +batch_signer_t* batch_signer_create(size_t hash_size, + size_t root_sig_capacity, + batch_hash_fn hash, + batch_sign_fn sign, + void* user_ctx); + +void batch_signer_free(batch_signer_t* ctx); +int batch_signer_add_message(batch_signer_t* ctx, const uint8_t* msg, size_t msg_len); +void batch_signer_reset(batch_signer_t* ctx); +size_t batch_signer_count(const batch_signer_t* ctx); +batch_signature_t* batch_signer_sign(batch_signer_t* ctx); + +/* + * Serialize to binary format: + * [u32 version=1][u32 hash_size][u32 num_proofs] + * [u32 root_hash_len][root_hash] + * [u32 root_sig_len][root_signature] + * repeated num_proofs times: [u32 proof_len][proof_hash_0..proof_hash_n-1] + */ +int batch_signature_serialize(const batch_signature_t* sig, uint8_t* out, size_t* out_len); + +batch_signature_t* batch_signature_deserialize(const uint8_t* data, size_t data_len, + size_t hash_size); + +void batch_signature_free(batch_signature_t* sig); + +int batch_verify(const uint8_t* const* messages, + const size_t* msg_lens, + size_t num_msgs, + const batch_signature_t* sig, + size_t hash_size, + batch_hash_fn hash, + batch_verify_fn verify, + void* user_ctx); + +const uint8_t* batch_signature_root_hash(const batch_signature_t* sig); +const uint8_t* batch_signature_root_signature(const batch_signature_t* sig, size_t* out_len); +size_t batch_signature_num_proofs(const batch_signature_t* sig); + +#ifdef __cplusplus +} +#endif + +#endif /* BATCH_SIGNING_H */ \ No newline at end of file diff --git a/src/merkle/merkle.c b/src/merkle/merkle.c new file mode 100644 index 0000000..4dd1086 --- /dev/null +++ b/src/merkle/merkle.c @@ -0,0 +1,232 @@ +#include "merkle.h" +#include +#include + +struct merkle_tree { + size_t num_leaves; + size_t leaf_capacity; + size_t hash_size; + uint8_t* tree; + merkle_hash_func hash; +}; + +static int checked_mul_size(size_t lhs, size_t rhs, size_t* out) { + if (lhs != 0 && rhs > SIZE_MAX / lhs) { + return -1; + } + + *out = lhs * rhs; + return 0; +} + +/* Return next power of two that is >= n. */ +static int next_power_of_two(size_t n, size_t* out) { + if (n == 0) { + *out = 1; + return 0; + } + + size_t p = 1; + while (p < n) { + if (p > SIZE_MAX / 2) { + return -1; + } + p <<= 1; + } + + *out = p; + return 0; +} + +merkle_tree_t* merkle_tree_create(size_t max_leaves, size_t hash_size, merkle_hash_func hash) { + if (max_leaves == 0 || hash_size == 0 || hash == NULL) { + return NULL; + } + + size_t cap = 0; + size_t tree_nodes = 0; + size_t tree_size = 0; + if (next_power_of_two(max_leaves, &cap) != 0 || + checked_mul_size(2, cap, &tree_nodes) != 0 || + checked_mul_size(tree_nodes, hash_size, &tree_size) != 0) { + return NULL; + } + + uint8_t* tree = (uint8_t*)calloc(1, tree_size); + if (tree == NULL) { + return NULL; + } + + merkle_tree_t* mt = (merkle_tree_t*)malloc(sizeof(merkle_tree_t)); + if (mt == NULL) { + free(tree); + return NULL; + } + + mt->num_leaves = 0; + mt->leaf_capacity = cap; + mt->hash_size = hash_size; + mt->tree = tree; + mt->hash = hash; + + return mt; +} + +void merkle_tree_destroy(merkle_tree_t* mt) { + if (mt) { + free(mt->tree); + free(mt); + } +} + +int merkle_tree_set_leaf(merkle_tree_t* mt, size_t idx, const uint8_t* hash) { + if (mt == NULL || idx >= mt->leaf_capacity || hash == NULL) { + return -1; + } + + size_t pos = mt->leaf_capacity + idx; + memcpy(mt->tree + pos * mt->hash_size, hash, mt->hash_size); + + if (idx + 1 > mt->num_leaves) { + mt->num_leaves = idx + 1; + } + + return 0; +} + +int merkle_tree_build(merkle_tree_t* mt) { + if (mt == NULL || mt->num_leaves == 0) { + return -1; + } + + size_t buf_len = 2 * mt->hash_size; + uint8_t* buf = (uint8_t*)malloc(buf_len); + if (buf == NULL) { + return -1; + } + + /* Build tree bottom-up. Unset leaves remain zero-initialized by calloc. */ + for (size_t i = mt->leaf_capacity - 1; i > 0; --i) { + uint8_t* left = mt->tree + (2 * i) * mt->hash_size; + uint8_t* right = mt->tree + (2 * i + 1) * mt->hash_size; + uint8_t* out = mt->tree + i * mt->hash_size; + + memcpy(buf, left, mt->hash_size); + memcpy(buf + mt->hash_size, right, mt->hash_size); + mt->hash(buf, buf_len, out); + } + + free(buf); + return 0; +} + +const uint8_t* merkle_tree_root(merkle_tree_t* mt) { + if (mt == NULL || mt->num_leaves == 0) { + return NULL; + } + + return mt->tree + mt->hash_size; +} + +size_t merkle_tree_leaf_count(merkle_tree_t* mt) { + return mt ? mt->num_leaves : 0; +} + +size_t merkle_tree_capacity(merkle_tree_t* mt) { + return mt ? mt->leaf_capacity : 0; +} + +uint8_t** merkle_tree_get_proof(merkle_tree_t* mt, size_t leaf_idx, size_t* out_len) { + if (mt == NULL || leaf_idx >= mt->num_leaves || out_len == NULL) { + return NULL; + } + + size_t idx = mt->leaf_capacity + leaf_idx; + size_t proof_capacity = 0; + size_t proof_size = 0; + uint8_t** proof = NULL; + + while (idx > 1) { + size_t sibling = idx ^ 1; + + if (proof_size >= proof_capacity) { + proof_capacity = proof_capacity ? proof_capacity * 2 : 8; + uint8_t** new_proof = (uint8_t**)realloc(proof, proof_capacity * sizeof(uint8_t*)); + if (new_proof == NULL) { + merkle_tree_free_proof(proof, proof_size); + *out_len = 0; + return NULL; + } + proof = new_proof; + } + + uint8_t* sibling_hash = (uint8_t*)malloc(mt->hash_size); + if (sibling_hash == NULL) { + merkle_tree_free_proof(proof, proof_size); + *out_len = 0; + return NULL; + } + + memcpy(sibling_hash, mt->tree + sibling * mt->hash_size, mt->hash_size); + proof[proof_size++] = sibling_hash; + idx >>= 1; + } + + *out_len = proof_size; + return proof; +} + +void merkle_tree_free_proof(uint8_t** proof, size_t len) { + if (proof) { + for (size_t i = 0; i < len; ++i) { + free(proof[i]); + } + free(proof); + } +} + +int merkle_tree_verify_proof(const uint8_t* leaf_hash, + uint8_t** proof, size_t proof_len, + size_t leaf_idx, + const uint8_t* root_hash, + size_t hash_size, + merkle_hash_func hash) { + if (leaf_hash == NULL || root_hash == NULL || hash == NULL || hash_size == 0) { + return 0; + } + if (proof_len > 0 && proof == NULL) { + return 0; + } + + uint8_t* current = (uint8_t*)malloc(hash_size); + uint8_t* buf = (uint8_t*)malloc(2 * hash_size); + if (current == NULL || buf == NULL) { + free(current); + free(buf); + return 0; + } + + memcpy(current, leaf_hash, hash_size); + + for (size_t i = 0; i < proof_len; ++i) { + uint8_t* left; + uint8_t* right; + + if ((leaf_idx >> i) & 1) { + left = proof[i]; + right = current; + } else { + left = current; + right = proof[i]; + } + + memcpy(buf, left, hash_size); + memcpy(buf + hash_size, right, hash_size); + hash(buf, 2 * hash_size, current); + } + + int result = (memcmp(current, root_hash, hash_size) == 0); + free(current); + free(buf); + return result; +} \ No newline at end of file diff --git a/src/merkle/merkle.h b/src/merkle/merkle.h new file mode 100644 index 0000000..362cfe8 --- /dev/null +++ b/src/merkle/merkle.h @@ -0,0 +1,64 @@ +#ifndef MERKLE_H +#define MERKLE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct merkle_tree merkle_tree_t; + +typedef void (*merkle_hash_func)(const uint8_t* in, size_t in_len, uint8_t* out); + +/* Create a tree for max_leaves leaves (padded to power-of-two capacity). */ +merkle_tree_t* merkle_tree_create(size_t max_leaves, size_t hash_size, merkle_hash_func hash); + +/* Destroy a tree and release owned memory. */ +void merkle_tree_destroy(merkle_tree_t* tree); + +/* Set one leaf hash (copies hash_size bytes). */ +int merkle_tree_set_leaf(merkle_tree_t* tree, size_t idx, const uint8_t* hash); + +/* Build all internal nodes. Must be called after leaves are set. */ +int merkle_tree_build(merkle_tree_t* tree); + +/* Return root pointer (hash_size bytes), owned by tree. */ +const uint8_t* merkle_tree_root(merkle_tree_t* tree); + +/* Return effective number of leaves that were set. */ +size_t merkle_tree_leaf_count(merkle_tree_t* tree); + +/* Return tree capacity (next power of two). */ +size_t merkle_tree_capacity(merkle_tree_t* tree); + +/* + * Get proof path for leaf_idx. + * Returns an array of sibling-hash pointers and sets *out_len. + * For a single-leaf tree (*out_len == 0) returns NULL; this is a valid empty + * proof, not an error. For all larger trees, a NULL return means failure. + * Caller must release a non-NULL return with merkle_tree_free_proof(). + */ +uint8_t** merkle_tree_get_proof(merkle_tree_t* tree, size_t leaf_idx, size_t* out_len); + +/* Release proof returned by merkle_tree_get_proof(). */ +void merkle_tree_free_proof(uint8_t** proof, size_t len); + +/* + * Verify proof path without tree object. + * Returns 1 on success, 0 on mismatch or invalid input. + * For proof_len == 0 (single-leaf tree), proof may be NULL. + */ +int merkle_tree_verify_proof(const uint8_t* leaf_hash, + uint8_t** proof, size_t proof_len, + size_t leaf_idx, + const uint8_t* root_hash, + size_t hash_size, + merkle_hash_func hash); + +#ifdef __cplusplus +} +#endif + +#endif // MERKLE_H \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index eacdc28..e8a9efa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(test_adapters_smoke adapters_smoke.c) +add_executable(test_adapters_smoke test_adapters_smoke.c) target_link_libraries(test_adapters_smoke PRIVATE batch_bench_adapters) @@ -13,3 +13,27 @@ target_link_libraries(test_adapters_batch PRIVATE batch_bench_adapters) target_compile_features(test_adapters_batch PRIVATE c_std_99) add_test(NAME test_adapters_batch COMMAND test_adapters_batch) + +add_executable(test_merkle test_merkle.c) + +target_link_libraries(test_merkle PRIVATE batch_signing_core) + +target_compile_features(test_merkle PRIVATE c_std_99) + +add_test(NAME test_merkle COMMAND test_merkle) + +add_executable(test_batch_signing test_batch_signing.c) + +target_link_libraries(test_batch_signing PRIVATE batch_adapters) + +target_compile_features(test_batch_signing PRIVATE c_std_99) + +add_test(NAME test_batch_signing COMMAND test_batch_signing) + +add_executable(test_streebog test_streebog.c) + +target_link_libraries(test_streebog PRIVATE streebog) + +target_compile_features(test_streebog PRIVATE c_std_99) + +add_test(NAME test_streebog COMMAND test_streebog) diff --git a/tests/adapters_smoke.c b/tests/test_adapters_smoke.c similarity index 100% rename from tests/adapters_smoke.c rename to tests/test_adapters_smoke.c diff --git a/tests/test_batch_signing.c b/tests/test_batch_signing.c new file mode 100644 index 0000000..f6e2b2d --- /dev/null +++ b/tests/test_batch_signing.c @@ -0,0 +1,317 @@ +#include "batch_bench.h" +#include "batch_signing.h" +#include "batch_adapters.h" + +#include +#include +#include +#include + +#define HASH_SIZE_32 32u +#define MSG_COUNT 4u + +typedef struct fake_sig_ctx { + uint8_t key[HASH_SIZE_32]; +} fake_sig_ctx; + +static void test_hash32(const uint8_t* in, size_t in_len, uint8_t* out) { + size_t i; + + memset(out, 0, HASH_SIZE_32); + for (i = 0; i < in_len; ++i) { + out[i % HASH_SIZE_32] ^= (uint8_t)(in[i] + (uint8_t)(i * 17u)); + } + for (i = 0; i < HASH_SIZE_32; ++i) { + out[i] ^= (uint8_t)(0xA5u + (uint8_t)i); + } +} + +static int fake_sign_cb(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + uint8_t* sig, + size_t sig_capacity, + size_t* sig_len) { + fake_sig_ctx* ctx = (fake_sig_ctx*)user_ctx; + size_t i; + + if (ctx == NULL || msg == NULL || sig == NULL || sig_len == NULL) { + return 0; + } + if (msg_len != HASH_SIZE_32 || sig_capacity < HASH_SIZE_32) { + return 0; + } + + for (i = 0; i < HASH_SIZE_32; ++i) { + sig[i] = msg[i] ^ ctx->key[i]; + } + *sig_len = HASH_SIZE_32; + return 1; +} + +static int fake_verify_cb(void* user_ctx, + const uint8_t* msg, + size_t msg_len, + const uint8_t* sig, + size_t sig_len) { + fake_sig_ctx* ctx = (fake_sig_ctx*)user_ctx; + size_t i; + + if (ctx == NULL || msg == NULL || sig == NULL) { + return 0; + } + if (msg_len != HASH_SIZE_32 || sig_len != HASH_SIZE_32) { + return 0; + } + + for (i = 0; i < HASH_SIZE_32; ++i) { + if ((uint8_t)(sig[i] ^ ctx->key[i]) != msg[i]) { + return 0; + } + } + return 1; +} + +static int test_roundtrip_and_tamper(void) { + static const uint8_t m0[] = "alpha"; + static const uint8_t m1[] = "beta"; + static const uint8_t m2[] = "gamma"; + + const uint8_t* messages[3] = {m0, m1, m2}; + size_t lens[3] = {sizeof(m0) - 1u, sizeof(m1) - 1u, sizeof(m2) - 1u}; + fake_sig_ctx ctx; + batch_signer_t* signer; + batch_signature_t* sig = NULL; + batch_signature_t* decoded = NULL; + uint8_t* blob = NULL; + size_t blob_len = 0; + int ok = 0; + size_t i; + + for (i = 0; i < HASH_SIZE_32; ++i) { + ctx.key[i] = (uint8_t)(0x10u + i); + } + + signer = batch_signer_create(HASH_SIZE_32, + HASH_SIZE_32, + test_hash32, + fake_sign_cb, + &ctx); + if (signer == NULL) { + return 1; + } + + for (i = 0; i < 3u; ++i) { + if (batch_signer_add_message(signer, messages[i], lens[i]) != 0) { + batch_signer_free(signer); + return 1; + } + } + + sig = batch_signer_sign(signer); + if (sig == NULL) { + batch_signer_free(signer); + return 1; + } + + if (!batch_verify(messages, lens, 3u, sig, HASH_SIZE_32, test_hash32, fake_verify_cb, &ctx)) { + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + if (batch_signature_serialize(sig, NULL, &blob_len) != 0 || blob_len == 0) { + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + blob = (uint8_t*)malloc(blob_len); + if (blob == NULL) { + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + if (batch_signature_serialize(sig, blob, &blob_len) != 0) { + free(blob); + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + decoded = batch_signature_deserialize(blob, blob_len, HASH_SIZE_32); + free(blob); + if (decoded == NULL) { + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + ok = batch_verify(messages, lens, 3u, decoded, HASH_SIZE_32, test_hash32, fake_verify_cb, &ctx); + if (!ok) { + batch_signature_free(decoded); + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + + { + size_t root_sig_len = 0; + const uint8_t* root_sig = batch_signature_root_signature(decoded, &root_sig_len); + if (root_sig == NULL || root_sig_len == 0) { + batch_signature_free(decoded); + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + } + + /* Tamper: flip a byte in the blob; verify or deserialize must reject it. */ + { + uint8_t* tbuf = NULL; + size_t tlen = 0; + int tamper_ok = 0; + + if (batch_signature_serialize(sig, NULL, &tlen) == 0 && tlen > 16u) { + tbuf = (uint8_t*)malloc(tlen); + if (tbuf != NULL && batch_signature_serialize(sig, tbuf, &tlen) == 0) { + batch_signature_t* bad; + tbuf[tlen / 2u] ^= 0xFFu; + bad = batch_signature_deserialize(tbuf, tlen, HASH_SIZE_32); + if (bad != NULL) { + tamper_ok = !batch_verify(messages, lens, 3u, bad, + HASH_SIZE_32, test_hash32, + fake_verify_cb, &ctx); + batch_signature_free(bad); + } else { + tamper_ok = 1; + } + } + free(tbuf); + } + + if (!tamper_ok) { + batch_signature_free(decoded); + batch_signature_free(sig); + batch_signer_free(signer); + return 1; + } + } + + batch_signature_free(decoded); + batch_signature_free(sig); + batch_signer_free(signer); + return 0; +} + +static int test_adapter_integration(void) { + const bb_algorithm* algo = bb_find_algorithm("hypericum"); + uint8_t* sk = NULL; + uint8_t* pk = NULL; + size_t sk_len = 0; + size_t pk_len = 0; + uint8_t messages[MSG_COUNT][64]; + const uint8_t* msg_ptrs[MSG_COUNT]; + size_t msg_lens[MSG_COUNT]; + batch_bb_adapter_ctx adapter_ctx; + batch_signer_t* signer = NULL; + batch_signature_t* sig = NULL; + size_t i; + + if (algo == NULL) { + return 1; + } + + sk = (uint8_t*)malloc(algo->secret_key_bytes); + pk = (uint8_t*)malloc(algo->public_key_bytes); + if (sk == NULL || pk == NULL) { + free(sk); + free(pk); + return 1; + } + + if (algo->keygen(sk, + algo->secret_key_bytes, + &sk_len, + pk, + algo->public_key_bytes, + &pk_len) != BB_OK) { + free(sk); + free(pk); + return 1; + } + + if (batch_bb_adapter_init(&adapter_ctx, algo, sk, sk_len, pk, pk_len) != 0) { + free(sk); + free(pk); + return 1; + } + + signer = batch_signer_create(HASH_SIZE_32, + batch_bb_signature_capacity(&adapter_ctx), + test_hash32, + batch_bb_sign_callback, + &adapter_ctx); + if (signer == NULL) { + free(sk); + free(pk); + return 1; + } + + for (i = 0; i < MSG_COUNT; ++i) { + size_t j; + for (j = 0; j < sizeof(messages[i]); ++j) { + messages[i][j] = (uint8_t)(i + j + 1u); + } + msg_ptrs[i] = messages[i]; + msg_lens[i] = sizeof(messages[i]); + if (batch_signer_add_message(signer, messages[i], sizeof(messages[i])) != 0) { + batch_signer_free(signer); + free(sk); + free(pk); + return 1; + } + } + + sig = batch_signer_sign(signer); + if (sig == NULL) { + batch_signer_free(signer); + free(sk); + free(pk); + return 1; + } + + if (!batch_verify(msg_ptrs, + msg_lens, + MSG_COUNT, + sig, + HASH_SIZE_32, + test_hash32, + batch_bb_verify_callback, + &adapter_ctx)) { + batch_signature_free(sig); + batch_signer_free(signer); + free(sk); + free(pk); + return 1; + } + + batch_signature_free(sig); + batch_signer_free(signer); + free(sk); + free(pk); + return 0; +} + +int main(void) { + if (test_roundtrip_and_tamper() != 0) { + return 1; + } + if (test_adapter_integration() != 0) { + return 1; + } + + printf("test_batch_signing: ok\n"); + return 0; +} diff --git a/tests/test_merkle.c b/tests/test_merkle.c new file mode 100644 index 0000000..cfd16b6 --- /dev/null +++ b/tests/test_merkle.c @@ -0,0 +1,169 @@ +#include "merkle/merkle.h" + +#include +#include +#include +#include + +static void test_hash32(const uint8_t* in, size_t in_len, uint8_t* out) { + size_t i; + + memset(out, 0, 32); + for (i = 0; i < in_len; ++i) { + out[i % 32] ^= (uint8_t)(in[i] + (uint8_t)(i * 13u)); + } + for (i = 0; i < 32; ++i) { + out[i] ^= (uint8_t)(0x55u + i); + } +} + +static int test_single_leaf_proof(void) { + uint8_t leaf[32] = {0}; + const uint8_t* root; + size_t proof_len = 0; + uint8_t** proof = NULL; + merkle_tree_t* tree = merkle_tree_create(1, 32, test_hash32); + + if (tree == NULL) { + return 1; + } + + leaf[0] = 0x11u; + if (merkle_tree_set_leaf(tree, 0, leaf) != 0 || merkle_tree_build(tree) != 0) { + merkle_tree_destroy(tree); + return 1; + } + + root = merkle_tree_root(tree); + if (root == NULL) { + merkle_tree_destroy(tree); + return 1; + } + + proof = merkle_tree_get_proof(tree, 0, &proof_len); + if (proof_len != 0) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + if (!merkle_tree_verify_proof(leaf, proof, proof_len, 0, root, 32, test_hash32)) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + merkle_tree_destroy(tree); + return 0; +} + +static int test_even_leaf_proof_and_tamper(void) { + merkle_tree_t* tree = merkle_tree_create(2, 32, test_hash32); + uint8_t leaf0[32] = {0}; + uint8_t leaf1[32] = {0}; + size_t proof_len = 0; + uint8_t** proof = NULL; + const uint8_t* root; + + if (tree == NULL) { + return 1; + } + + leaf0[0] = 0x01u; + leaf1[0] = 0x02u; + if (merkle_tree_set_leaf(tree, 0, leaf0) != 0 || + merkle_tree_set_leaf(tree, 1, leaf1) != 0 || + merkle_tree_build(tree) != 0) { + merkle_tree_destroy(tree); + return 1; + } + + root = merkle_tree_root(tree); + proof = merkle_tree_get_proof(tree, 0, &proof_len); + if (proof == NULL || proof_len != 1) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + if (!merkle_tree_verify_proof(leaf0, proof, proof_len, 0, root, 32, test_hash32)) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + proof[0][0] ^= 0x80u; + if (merkle_tree_verify_proof(leaf0, proof, proof_len, 0, root, 32, test_hash32)) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 0; +} + +static int test_odd_leaf_padding(void) { + merkle_tree_t* tree = merkle_tree_create(3, 32, test_hash32); + uint8_t leaf0[32] = {0}; + uint8_t leaf1[32] = {0}; + uint8_t leaf2[32] = {0}; + size_t proof_len = 0; + uint8_t** proof = NULL; + const uint8_t* root; + + if (tree == NULL) { + return 1; + } + + leaf0[0] = 0x21u; + leaf1[0] = 0x22u; + leaf2[0] = 0x23u; + + if (merkle_tree_set_leaf(tree, 0, leaf0) != 0 || + merkle_tree_set_leaf(tree, 1, leaf1) != 0 || + merkle_tree_set_leaf(tree, 2, leaf2) != 0 || + merkle_tree_build(tree) != 0) { + merkle_tree_destroy(tree); + return 1; + } + + if (merkle_tree_capacity(tree) != 4u || merkle_tree_leaf_count(tree) != 3u) { + merkle_tree_destroy(tree); + return 1; + } + + root = merkle_tree_root(tree); + proof = merkle_tree_get_proof(tree, 1u, &proof_len); + if (proof == NULL || proof_len != 2u) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + if (!merkle_tree_verify_proof(leaf1, proof, proof_len, 1u, root, 32, test_hash32)) { + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 1; + } + + merkle_tree_free_proof(proof, proof_len); + merkle_tree_destroy(tree); + return 0; +} + +int main(void) { + if (test_single_leaf_proof() != 0) { + return 1; + } + if (test_even_leaf_proof_and_tamper() != 0) { + return 1; + } + if (test_odd_leaf_padding() != 0) { + return 1; + } + + printf("test_merkle: ok\n"); + return 0; +} \ No newline at end of file diff --git a/tests/test_streebog.c b/tests/test_streebog.c new file mode 100644 index 0000000..a5397f2 --- /dev/null +++ b/tests/test_streebog.c @@ -0,0 +1,83 @@ +#include "gost3411-2012-core.h" + +#include +#include +#include + +static int digest_is_all_zero(const uint8_t* digest, size_t len) { + size_t i; + + for (i = 0; i < len; ++i) { + if (digest[i] != 0u) { + return 0; + } + } + return 1; +} + +static int hash_once(const uint8_t* msg, + size_t msg_len, + uint8_t* out, + unsigned int digest_size_bits) { + GOST34112012Context ctx; + + GOST34112012Init(&ctx, digest_size_bits); + GOST34112012Update(&ctx, msg, msg_len); + GOST34112012Final(&ctx, out); + GOST34112012Cleanup(&ctx); + return 0; +} + +static int hash_streaming_3_chunks(const uint8_t* msg, + size_t msg_len, + uint8_t* out, + unsigned int digest_size_bits) { + GOST34112012Context ctx; + size_t c1 = msg_len / 3u; + size_t c2 = msg_len / 3u; + size_t c3 = msg_len - c1 - c2; + + GOST34112012Init(&ctx, digest_size_bits); + GOST34112012Update(&ctx, msg, c1); + GOST34112012Update(&ctx, msg + c1, c2); + GOST34112012Update(&ctx, msg + c1 + c2, c3); + GOST34112012Final(&ctx, out); + GOST34112012Cleanup(&ctx); + return 0; +} + +static int test_consistency_for_mode(unsigned int digest_size_bits, size_t digest_len) { + static const uint8_t msg1[] = "streebog-test-message-1"; + static const uint8_t msg2[] = "streebog-test-message-2"; + uint8_t d1_once[64] = {0}; + uint8_t d1_stream[64] = {0}; + uint8_t d2_once[64] = {0}; + + hash_once(msg1, sizeof(msg1) - 1u, d1_once, digest_size_bits); + hash_streaming_3_chunks(msg1, sizeof(msg1) - 1u, d1_stream, digest_size_bits); + hash_once(msg2, sizeof(msg2) - 1u, d2_once, digest_size_bits); + + if (memcmp(d1_once, d1_stream, digest_len) != 0) { + return 1; + } + if (memcmp(d1_once, d2_once, digest_len) == 0) { + return 1; + } + if (digest_is_all_zero(d1_once, digest_len)) { + return 1; + } + + return 0; +} + +int main(void) { + if (test_consistency_for_mode(256u, 32u) != 0) { + return 1; + } + if (test_consistency_for_mode(512u, 64u) != 0) { + return 1; + } + + printf("test_streebog: ok\n"); + return 0; +} diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index c653714..cba9b69 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -208,39 +208,3 @@ if(EXISTS "${KRYZHOVNIK_DIR}/sign.c") else() message(WARNING "kryzhovnik submodule not initialized (run 'git submodule update --init')") endif() - -# ------------------------------------------------------------------- -# 5. iaik_merkle_tree (no CMakeLists.txt – build manually) -# ------------------------------------------------------------------- -set(MERKLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/iaik_merkle_tree) -if(EXISTS "${MERKLE_DIR}/src/merkletree.h") - # Build sources from src - file(GLOB MERKLE_SOURCES "${MERKLE_DIR}/src/*.c") - add_library(merkletree STATIC ${MERKLE_SOURCES}) - target_include_directories(merkletree PUBLIC - $ - ) - # Install (optional) - install(TARGETS merkletree ARCHIVE DESTINATION lib) - install(FILES ${MERKLE_DIR}/src/merkletree.h DESTINATION include) - - # Merkle Tree tests (require CppUnit) - if(BUILD_TESTING AND EXISTS "${MERKLE_DIR}/tests/MerkleTreeTest.cpp") - find_package(PkgConfig) - if(PkgConfig_FOUND) - pkg_check_modules(CPPUNIT REQUIRED cppunit) - else() - find_package(CppUnit REQUIRED) - endif() - if(CPPUNIT_FOUND) - add_executable(test_merkletree ${MERKLE_DIR}/tests/MerkleTreeTest.cpp) - target_include_directories(test_merkletree PRIVATE ${CPPUNIT_INCLUDE_DIRS}) - target_link_libraries(test_merkletree PRIVATE merkletree ${CPPUNIT_LIBRARIES}) - add_test(NAME test_merkletree COMMAND test_merkletree) - else() - message(WARNING "CppUnit not found, skipping merkletree tests") - endif() - endif() -else() - message(WARNING "iaik_merkle_tree submodule not initialized (run 'git submodule update --init')") -endif() \ No newline at end of file diff --git a/third_party/iaik_merkle_tree b/third_party/iaik_merkle_tree deleted file mode 160000 index 85c937e..0000000 --- a/third_party/iaik_merkle_tree +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 85c937e684f8b97219211e5fd9cac9af44e7def9