From 57859c950b7dce993994b02da31c03a078684eed Mon Sep 17 00:00:00 2001 From: Nadav Barak Date: Sun, 12 Apr 2026 19:01:16 +0300 Subject: [PATCH] Added MPC-BAM implementation of https://eprint.iacr.org/archive/2024/1950/20250601:081848. Also improved error handling, expanded test suite, fixed spelling --- .gitlab-ci.yml | 4 +- CMakeLists.txt | 6 + README.md | 25 + benchmarks/CMakeLists.txt | 3 + benchmarks/bam_sign_benchmark.cpp | 314 ++ docker/Dockerfile.bookworm | 15 + docker/{Dockerfile.24.04 => Dockerfile.focal} | 2 +- docker/Dockerfile.jammy | 15 + docker/run_tests.sh | 5 +- include/cosigner/asymmetric_eddsa_cosigner.h | 15 +- .../asymmetric_eddsa_cosigner_client.h | 15 +- .../asymmetric_eddsa_cosigner_server.h | 40 +- include/cosigner/bam_ecdsa_cosigner.h | 298 ++ include/cosigner/bam_ecdsa_cosigner_client.h | 118 + include/cosigner/bam_ecdsa_cosigner_server.h | 100 + include/cosigner/bam_key_persistency_client.h | 24 + include/cosigner/bam_key_persistency_common.h | 28 + include/cosigner/bam_key_persistency_server.h | 25 + .../cosigner/bam_key_persistency_structures.h | 42 + .../bam_key_persistency_structures_client.h | 37 + .../bam_key_persistency_structures_server.h | 32 + include/cosigner/bam_tx_persistency_client.h | 18 + include/cosigner/bam_tx_persistency_common.h | 18 + include/cosigner/bam_tx_persistency_server.h | 20 + .../cosigner/bam_tx_persistency_structures.h | 48 + .../bam_tx_persistency_structures_client.h | 13 + .../bam_tx_persistency_structures_server.h | 20 + .../cmp_ecdsa_offline_signing_service.h | 6 +- include/cosigner/cmp_ecdsa_signing_service.h | 4 +- include/cosigner/cmp_key_persistency.h | 2 +- include/cosigner/cmp_setup_service.h | 7 +- include/cosigner/cosigner_exception.h | 92 +- include/cosigner/cosigner_status.h | 17 + .../cosigner/eddsa_online_signing_service.h | 28 +- include/cosigner/key_persistency_base.h | 2 +- include/cosigner/mpc_globals.h | 6 +- include/cosigner/platform_service.h | 58 +- include/cosigner/timing_map.h | 3 +- include/cosigner/types.h | 48 +- .../crypto/algebra_utils/algebra_utils.h | 0 .../crypto/algebra_utils/status_convert.h | 0 include/crypto/commitments/commitments.h | 8 +- include/crypto/commitments/ring_pedersen.h | 2 +- .../crypto/ed25519_algebra/ed25519_algebra.h | 2 +- .../elliptic_curve256_algebra.h | 16 +- include/crypto/paillier/paillier.h | 12 +- .../paillier_commitment/paillier_commitment.h | 9 +- .../verifiable_secret_sharing.h | 16 +- .../zero_knowledge_proof/range_proofs.h | 110 +- include/crypto/zero_knowledge_proof/schnorr.h | 2 +- include/utils/string_utils.h | 33 + src/common/CMakeLists.txt | 11 + .../cosigner/asymmetric_eddsa_cosigner.cpp | 10 +- .../asymmetric_eddsa_cosigner_client.cpp | 117 +- .../asymmetric_eddsa_cosigner_server.cpp | 234 +- src/common/cosigner/bam_ecdsa_cosigner.cpp | 355 ++ .../cosigner/bam_ecdsa_cosigner_client.cpp | 756 ++++ .../cosigner/bam_ecdsa_cosigner_server.cpp | 940 ++++ .../bam_key_persistency_structures.cpp | 20 + src/common/cosigner/bam_well_formed_proof.cpp | 492 +++ src/common/cosigner/bam_well_formed_proof.h | 43 + .../cmp_ecdsa_offline_signing_service.cpp | 79 +- .../cmp_ecdsa_online_signing_service.cpp | 97 +- .../cosigner/cmp_ecdsa_signing_service.cpp | 37 +- src/common/cosigner/cmp_key_persistency.cpp | 4 - .../cosigner/cmp_offline_refresh_service.cpp | 30 +- src/common/cosigner/cmp_setup_service.cpp | 248 +- src/common/cosigner/cosigner_bn.h | 73 + src/common/cosigner/cosigner_exception.cpp | 65 +- .../cosigner/eddsa_online_signing_service.cpp | 215 +- src/common/cosigner/mta.cpp | 873 ++-- src/common/cosigner/mta.h | 15 +- src/common/cosigner/timing_map.cpp | 2 +- src/common/cosigner/utils.cpp | 2 +- src/common/cosigner/utils.h | 18 + .../GFp_curve_algebra/GFp_curve_algebra.c | 62 +- .../crypto/algebra_utils/algebra_utils.c | 16 +- .../crypto/algebra_utils/status_convert.c | 4 +- src/common/crypto/commitments/commitments.c | 4 +- .../crypto/commitments/damgard_fujisaki.c | 59 +- .../commitments/damgard_fujisaki_internal.h | 6 +- src/common/crypto/commitments/pedersen.c | 2 +- src/common/crypto/commitments/ring_pedersen.c | 80 +- .../commitments/ring_pedersen_internal.h | 2 +- .../crypto/ed25519_algebra/ed25519_algebra.c | 89 +- src/common/crypto/paillier/paillier.c | 273 +- .../crypto/paillier/paillier_internal.h | 4 +- src/common/crypto/paillier/paillier_zkp.c | 347 +- .../paillier_commitment/paillier_commitment.c | 507 ++- .../paillier_commitment_internal.h | 8 +- .../verifiable_secret_sharing.c | 20 +- .../damgard_fujisaki_zkp.c | 122 +- .../zero_knowledge_proof/diffie_hellman_log.c | 33 +- .../zero_knowledge_proof/range_proofs.c | 1550 ++++--- .../crypto/zero_knowledge_proof/schnorr.c | 11 +- .../zkp_constants_internal.h | 8 +- test/CMakeLists.txt | 1 + test/cosigner/CMakeLists.txt | 1 + test/cosigner/bam_test.cpp | 3842 +++++++++++++++++ test/cosigner/ecdsa_offline_test.cpp | 49 +- test/cosigner/ecdsa_online_test.cpp | 67 +- test/cosigner/eddsa_offline_test.cpp | 44 +- test/cosigner/eddsa_online_test.cpp | 64 +- test/cosigner/setup_test.cpp | 76 +- test/cosigner/test_common.h | 8 +- test/crypto/algebra_utils/Makefile | 27 + test/crypto/algebra_utils/tests.cpp | 18 +- test/crypto/drng/tests.cpp | 35 + test/crypto/ed25519_algebra/tests.cpp | 250 +- test/crypto/entropy/CMakeLists.txt | 9 + .../crypto/entropy/entropy_test_framework.cpp | 338 ++ test/crypto/entropy/entropy_test_framework.h | 212 + test/crypto/entropy/tests.cpp | 481 +++ test/crypto/paillier/CMakeLists.txt | 1 + test/crypto/paillier/tests.cpp | 1244 +++++- test/crypto/paillier_commitment/tests.cpp | 6 +- .../crypto/pedersen_commitment/CMakeLists.txt | 1 + test/crypto/pedersen_commitment/tests.cpp | 451 +- test/crypto/secp256k1_algebra/tests.cpp | 949 +++- test/crypto/shamir_secret_sharing/tests.cpp | 4 +- .../zero_knowledge_proof/attack_helpers.h | 204 + test/crypto/zero_knowledge_proof/tests.cpp | 1935 +++++++-- 122 files changed, 17260 insertions(+), 2783 deletions(-) create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/bam_sign_benchmark.cpp create mode 100644 docker/Dockerfile.bookworm rename docker/{Dockerfile.24.04 => Dockerfile.focal} (94%) create mode 100644 docker/Dockerfile.jammy create mode 100644 include/cosigner/bam_ecdsa_cosigner.h create mode 100644 include/cosigner/bam_ecdsa_cosigner_client.h create mode 100644 include/cosigner/bam_ecdsa_cosigner_server.h create mode 100644 include/cosigner/bam_key_persistency_client.h create mode 100644 include/cosigner/bam_key_persistency_common.h create mode 100644 include/cosigner/bam_key_persistency_server.h create mode 100644 include/cosigner/bam_key_persistency_structures.h create mode 100644 include/cosigner/bam_key_persistency_structures_client.h create mode 100644 include/cosigner/bam_key_persistency_structures_server.h create mode 100644 include/cosigner/bam_tx_persistency_client.h create mode 100644 include/cosigner/bam_tx_persistency_common.h create mode 100644 include/cosigner/bam_tx_persistency_server.h create mode 100644 include/cosigner/bam_tx_persistency_structures.h create mode 100644 include/cosigner/bam_tx_persistency_structures_client.h create mode 100644 include/cosigner/bam_tx_persistency_structures_server.h create mode 100644 include/cosigner/cosigner_status.h rename {src/common => include}/crypto/algebra_utils/algebra_utils.h (100%) rename {src/common => include}/crypto/algebra_utils/status_convert.h (100%) create mode 100644 include/utils/string_utils.h create mode 100644 src/common/cosigner/bam_ecdsa_cosigner.cpp create mode 100644 src/common/cosigner/bam_ecdsa_cosigner_client.cpp create mode 100644 src/common/cosigner/bam_ecdsa_cosigner_server.cpp create mode 100644 src/common/cosigner/bam_key_persistency_structures.cpp create mode 100644 src/common/cosigner/bam_well_formed_proof.cpp create mode 100644 src/common/cosigner/bam_well_formed_proof.h create mode 100644 src/common/cosigner/cosigner_bn.h create mode 100644 test/cosigner/bam_test.cpp create mode 100644 test/crypto/algebra_utils/Makefile create mode 100644 test/crypto/entropy/CMakeLists.txt create mode 100644 test/crypto/entropy/entropy_test_framework.cpp create mode 100644 test/crypto/entropy/entropy_test_framework.h create mode 100644 test/crypto/entropy/tests.cpp create mode 100644 test/crypto/zero_knowledge_proof/attack_helpers.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0447b6..12f8afb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ default: - image: docker.io/ubuntu:20.04 + image: registry.gitlab.com/fireblocks/external-docker-images/ubuntu:22.04 tags: - - dev-common-runner + - kub-builders before_script: - apt update - DEBIAN_FRONTEND=noninteractive apt install -y build-essential cmake pkg-config uuid-dev libssl-dev libsecp256k1-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index de66e44..f8ac45e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.13) project(mpc-lib LANGUAGES C CXX) +option(MPC_LIB_BUILD_BENCHMARKS "Build benchmarking targets (requires Google Benchmark)" OFF) + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) @@ -16,3 +18,7 @@ if(${CMAKE_SOURCE_DIR} STREQUAL ${PROJECT_SOURCE_DIR} AND NOT MPC_LIB_SKIP_TESTS enable_testing() add_subdirectory(test) endif() + +if(MPC_LIB_BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() diff --git a/README.md b/README.md index 989b521..e6d3e78 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,31 @@ To execute the test suite, run the command from the same build folder: make test ``` +### Benchmarks and Profiling + +Build the BAM signing benchmark by enabling the benchmark option during configuration: +```sh +cmake -S . -B build -DMPC_LIB_BUILD_BENCHMARKS=ON +cmake --build build --target bam_sign_benchmark +``` + +Run the benchmark executable to measure `bam_key_sign` throughput (use `--benchmark_filter` to focus on a specific algorithm): +```sh +./build/benchmarks/bam_sign_benchmark --benchmark_filter=stark +``` + +To profile the benchmark and visualize the results: +1. Record samples with Linux `perf` (install `hotspot` or another viewer once): + ```sh + perf record -F 999 -g ./build/benchmarks/bam_sign_benchmark --benchmark_filter=secp256k1_default + ``` +2. Open the generated `perf.data` in a graphical viewer, e.g.: + ```sh + hotspot perf.data # Qt GUI with flame graphs + # or convert to Speedscope + perf script | speedscope + ``` + ## Usage A few examples for running a full signing process can be found in the [tests section](https://github.com/fireblocks/mpc-lib/tree/main/test/cosigner) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..380c6c8 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(bam_sign_benchmark bam_sign_benchmark.cpp) +target_link_libraries(bam_sign_benchmark cosigner uuid) +target_include_directories(bam_sign_benchmark PRIVATE ${CMAKE_SOURCE_DIR}/include) diff --git a/benchmarks/bam_sign_benchmark.cpp b/benchmarks/bam_sign_benchmark.cpp new file mode 100644 index 0000000..9aab199 --- /dev/null +++ b/benchmarks/bam_sign_benchmark.cpp @@ -0,0 +1,314 @@ +#include "cosigner/bam_ecdsa_cosigner_client.h" +#include "cosigner/bam_ecdsa_cosigner_server.h" +#include "cosigner/bam_key_persistency_client.h" +#include "cosigner/bam_key_persistency_server.h" +#include "cosigner/bam_tx_persistency_server.h" +#include "cosigner/bam_tx_persistency_client.h" +#include "cosigner/platform_service.h" +#include "cosigner/eddsa_online_signing_service.h" +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "blockchain/mpc/hd_derive.h" +#include "../src/common/cosigner/utils.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef UUID_STR_LEN +#define UUID_STR_LEN 37 +#endif + +namespace fbc = fireblocks::common::cosigner; + +static const constexpr uint64_t server_id = 3213454843; +static const constexpr uint64_t client_id = 45234523; +static std::string _tenant_id; + +static std::string get_tenant_id() +{ + if (_tenant_id.empty()) + { + uuid_t uid; + _tenant_id.resize(UUID_STR_LEN); + uuid_generate_random(uid); + uuid_unparse(uid, _tenant_id.data()); + } + return _tenant_id; +} + +// ---- Persistency stubs (same as test) ---- + +template +class bench_bam_key_persistency +{ +public: + bool key_exist(const std::string& key_id) const { return _keyStore.count(key_id) != 0; } + void store_key(const std::string& key_id, const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key) + { + _keyStore[key_id].first = algorithm; + memcpy(_keyStore[key_id].second, private_key, sizeof(elliptic_curve256_scalar_t)); + } + void load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const + { + const auto it = _keyStore.find(key_id); + if (it == _keyStore.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + memcpy(private_key, it->second.second, sizeof(elliptic_curve256_scalar_t)); + algorithm = it->second.first; + } + void store_key_metadata(const std::string& key_id, const KeyMetadataType& metadata, const bool overwrite) + { + auto it = _metadata_store.find(key_id); + if (it != _metadata_store.end()) { if (!overwrite) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); it->second = metadata; } + else _metadata_store[key_id] = metadata; + } + void load_key_metadata(const std::string& key_id, KeyMetadataType& metadata) const + { + const auto it = _metadata_store.find(key_id); + if (it == _metadata_store.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + metadata = it->second; + } + void store_temp_data(const std::string& key_id, const TempDataType& data) + { + auto it = _temp_data.find(key_id); + if (it != _temp_data.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + if constexpr (std::is_array_v) memcpy(_temp_data[key_id], data, sizeof(TempDataType)); + else _temp_data[key_id] = data; + } + void load_and_delete_temp_data(const std::string& key_id, TempDataType& data) + { + auto it = _temp_data.find(key_id); + if (it == _temp_data.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + if constexpr (std::is_array_v) memcpy(data, it->second, sizeof(TempDataType)); + else data = it->second; + _temp_data.erase(it); + } + void store_tenant_id_for_setup(const std::string& setup_id, const std::string& tenant_id) { _setup_tenant[setup_id] = tenant_id; } + void load_tenant_id_for_setup(const std::string& setup_id, std::string& tenant_id) const + { + const auto it = _setup_tenant.find(setup_id); + if (it == _setup_tenant.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + tenant_id = it->second; + } +private: + std::map> _keyStore; + std::map _metadata_store; + std::map _temp_data; + std::map _setup_tenant; +}; + +class bench_bam_key_persistency_client : public fbc::bam_key_persistency_client, public fbc::bam_tx_persistency_client +{ +public: + virtual ~bench_bam_key_persistency_client() = default; + void store_tenant_id_for_setup(const std::string& s, const std::string& t) override { _store.store_tenant_id_for_setup(s, t); } + void load_tenant_id_for_setup(const std::string& s, std::string& t) const override { _store.load_tenant_id_for_setup(s, t); } + void store_key(const std::string& k, cosigner_sign_algorithm a, const elliptic_curve256_scalar_t& p) override { _store.store_key(k, a, p); } + void load_key(const std::string& k, cosigner_sign_algorithm& a, elliptic_curve256_scalar_t& p) const override { _store.load_key(k, a, p); } + void store_key_metadata(const std::string& k, const fbc::bam_key_metadata_client& m, bool o) override { _store.store_key_metadata(k, m, o); } + void load_key_metadata(const std::string& k, fbc::bam_key_metadata_client& m) const override { _store.load_key_metadata(k, m); } + void store_key_temp_data(const std::string& k, const fbc::bam_temp_key_data_client& d) override { _store.store_temp_data(k, d); } + void load_key_temp_data_and_delete(const std::string& k, fbc::bam_temp_key_data_client& d) override { _store.load_and_delete_temp_data(k, d); } + void store_signature_data(const std::string& tx_id, const fbc::bam_client_signature_data& d) override { _sig_data["txid_" + tx_id] = d; } + void load_signature_data_and_delete(const std::string& tx_id, fbc::bam_client_signature_data& d) override + { + auto it = _sig_data.find("txid_" + tx_id); + if (it == _sig_data.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + d = it->second; _sig_data.erase(it); + } + bool delete_temporary_tx_data(const std::string&) override { return true; } + bool delete_temporary_key_data(const std::string&) override { return true; } + bool delete_key_data(const std::string&) override { return true; } + bool delete_setup_data(const std::string&) override { return true; } + bool backup_key(const std::string&) const override { return true; } +private: + bench_bam_key_persistency _store; + std::map _sig_data; +}; + +class bench_bam_key_persistency_server : public fbc::bam_key_persistency_server, public fbc::bam_tx_persistency_server +{ +public: + virtual ~bench_bam_key_persistency_server() = default; + void store_tenant_id_for_setup(const std::string& s, const std::string& t) override { _store.store_tenant_id_for_setup(s, t); } + void load_tenant_id_for_setup(const std::string& s, std::string& t) const override { _store.load_tenant_id_for_setup(s, t); } + void store_key(const std::string& k, cosigner_sign_algorithm a, const elliptic_curve256_scalar_t& p) override { _store.store_key(k, a, p); } + void load_key(const std::string& k, cosigner_sign_algorithm& a, elliptic_curve256_scalar_t& p) const override { _store.load_key(k, a, p); } + void store_key_metadata(const std::string& k, const fbc::bam_key_metadata_server& m, bool o) override { _store.store_key_metadata(k, m, o); } + void load_key_metadata(const std::string& k, fbc::bam_key_metadata_server& m) const override { _store.load_key_metadata(k, m); } + void store_signature_data(const std::string& tx_id, const std::shared_ptr& d) override { _store.store_temp_data("txid_" + tx_id, *d); } + std::shared_ptr load_signature_data_and_delete(const std::string& tx_id) override + { + auto p = std::make_shared(); + _store.load_and_delete_temp_data("txid_" + tx_id, *p); + return p; + } + bool delete_temporary_tx_data(const std::string&) override { return true; } + bool delete_temporary_key_data(const std::string&) override { return true; } + bool delete_key_data(const std::string&) override { return true; } + bool delete_setup_data(const std::string&) override { return true; } + void store_setup_auxilary_key(const std::string& s, const fbc::bam_setup_auxilary_key_server& d) override { _setup_aux[s] = d; } + void load_setup_auxilary_key(const std::string& s, fbc::bam_setup_auxilary_key_server& d) const override + { + auto it = _setup_aux.find(s); + if (it == _setup_aux.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + d = it->second; + } + void store_setup_metadata(const std::string& s, const fbc::bam_setup_metadata_server& d) override { _setup_meta[s] = d; } + void load_setup_metadata(const std::string& s, fbc::bam_setup_metadata_server& d) const override + { + auto it = _setup_meta.find(s); + if (it == _setup_meta.end()) throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + d = it->second; + } + bool backup_key(const std::string&) const override { return true; } +private: + bench_bam_key_persistency _store; + std::map _setup_aux; + std::map _setup_meta; +}; + +class bench_platform_service : public fbc::platform_service +{ +public: + ~bench_platform_service() = default; + void gen_random(size_t len, uint8_t* random_data) const override { RAND_bytes(random_data, len); } + bool backup_key(const std::string&, cosigner_sign_algorithm, const elliptic_curve256_scalar_t&, const fbc::cmp_key_metadata&, const fbc::auxiliary_keys&) override { return true; } + void derive_initial_share(const fbc::share_derivation_args&, cosigner_sign_algorithm, elliptic_curve256_scalar_t*) const override { assert(0); } + void on_start_signing(const std::string&, const std::string&, const fbc::signing_data&, const std::string&, const std::set&, const signing_type) override {} + bool is_client_id(uint64_t) const override { return false; } + const std::string get_current_tenantid() const override { return get_tenant_id(); } + uint64_t get_id_from_keyid(const std::string&) const override { return 0; } + void fill_signing_info_from_metadata(const std::string&, std::vector&) const override {} + void fill_eddsa_signing_info_from_metadata(std::vector&, const std::string&) const override {} + void fill_bam_signing_info_from_metadata(std::vector&, const std::string&) const override {} + fbc::byte_vector_t encrypt_for_player(const uint64_t, const fbc::byte_vector_t& data, const std::optional&) const override { return data; } + fbc::byte_vector_t decrypt_message(const fbc::byte_vector_t& data) const override { return data; } + void prepare_for_signing(const std::string&, const std::string) override {} + void mark_key_setup_in_progress(const std::string&) const override {} + void clear_key_setup_in_progress(const std::string&) const override {} +}; + +// ---- Benchmark logic ---- + +struct BenchSetup +{ + bench_bam_key_persistency_client persistencyClient; + bench_bam_key_persistency_server persistencyServer; + bench_platform_service platform; + fbc::bam_ecdsa_cosigner_server server{platform, persistencyServer, persistencyServer}; + fbc::bam_ecdsa_cosigner_client client{platform, persistencyClient, persistencyClient}; +}; + +void bam_keygen(const std::string& setup_id, const std::string& key_id, + BenchSetup& s, cosigner_sign_algorithm alg) +{ + fbc::bam_ecdsa_cosigner::client_key_shared_data cm; + fbc::bam_ecdsa_cosigner::server_key_shared_data sm; + fbc::bam_ecdsa_cosigner::generated_public_key gpk; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + + s.server.generate_setup_with_proof(setup_id, get_tenant_id(), alg, setup); + s.client.start_new_key_generation(setup_id, key_id, get_tenant_id(), server_id, client_id, alg); + s.server.generate_share_and_commit(setup_id, key_id, server_id, client_id, alg, B); + s.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, key_id, server_id, setup, B, cm); + s.server.verify_client_proofs_and_decommit_share_with_proof(key_id, client_id, cm, sm); + s.client.verify_key_decommitment_and_proofs(key_id, server_id, client_id, sm, gpk); +} + +double bam_sign_once(const std::string& setup_id, const std::string& key_id, + BenchSetup& s, cosigner_sign_algorithm alg) +{ + uuid_t uid; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + RAND_bytes(hash, sizeof(hash)); + + fbc::signing_data data = {{0}, {{fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), {44, 0, 0, 0, 0}}}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + auto start = std::chrono::high_resolution_clock::now(); + + s.client.prepare_for_signature(key_id, txid, 0, server_id, client_id, data, "", std::set()); + s.server.generate_signature_share(key_id, txid, 0, server_id, client_id, alg, data, "", std::set(), server_shares); + s.client.compute_partial_signature(txid, server_shares, partial_signatures); + s.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm); + + auto end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration(end - start).count(); +} + +void run_benchmark(const char* name, cosigner_sign_algorithm alg, int warmup, int iterations) +{ + BenchSetup s; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + std::string setup_id(keyid); + + std::cout << "=== " << name << " ===" << std::endl; + std::cout << " Key generation..." << std::flush; + bam_keygen(setup_id, keyid, s, alg); + std::cout << " done" << std::endl; + + // Warmup + std::cout << " Warmup (" << warmup << " iterations)..." << std::flush; + for (int i = 0; i < warmup; i++) + { + bam_sign_once(setup_id, keyid, s, alg); + } + std::cout << " done" << std::endl; + + // Measured runs + double total_ms = 0; + double min_ms = 1e9, max_ms = 0; + std::cout << " Benchmarking (" << iterations << " iterations)..." << std::flush; + for (int i = 0; i < iterations; i++) + { + double ms = bam_sign_once(setup_id, keyid, s, alg); + total_ms += ms; + if (ms < min_ms) min_ms = ms; + if (ms > max_ms) max_ms = ms; + } + std::cout << " done" << std::endl; + + double avg_ms = total_ms / iterations; + std::cout << std::fixed << std::setprecision(2); + std::cout << " Results:" << std::endl; + std::cout << " Iterations: " << iterations << std::endl; + std::cout << " Avg: " << avg_ms << " ms" << std::endl; + std::cout << " Min: " << min_ms << " ms" << std::endl; + std::cout << " Max: " << max_ms << " ms" << std::endl; + std::cout << " Total: " << total_ms << " ms" << std::endl; + std::cout << " Throughput: " << std::setprecision(1) << (1000.0 / avg_ms) << " sign/sec" << std::endl; + std::cout << std::endl; +} + +int main(int argc, char** argv) +{ + int warmup = 2; + int iterations = 20; + + if (argc > 1) iterations = atoi(argv[1]); + if (argc > 2) warmup = atoi(argv[2]); + + std::cout << "BAM ECDSA Signing Benchmark" << std::endl; + std::cout << "==========================" << std::endl << std::endl; + + run_benchmark("secp256k1", ECDSA_SECP256K1, warmup, iterations); + run_benchmark("secp256r1", ECDSA_SECP256R1, warmup, iterations); + + return 0; +} diff --git a/docker/Dockerfile.bookworm b/docker/Dockerfile.bookworm new file mode 100644 index 0000000..a7ad114 --- /dev/null +++ b/docker/Dockerfile.bookworm @@ -0,0 +1,15 @@ +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt install -y \ + build-essential \ + uuid-dev \ + libssl-dev \ + libsecp256k1-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/archives/* + +COPY . /usr/src/mpc-lib/ +WORKDIR /usr/src/mpc-lib \ No newline at end of file diff --git a/docker/Dockerfile.24.04 b/docker/Dockerfile.focal similarity index 94% rename from docker/Dockerfile.24.04 rename to docker/Dockerfile.focal index 713b018..afd0006 100644 --- a/docker/Dockerfile.24.04 +++ b/docker/Dockerfile.focal @@ -1,4 +1,4 @@ -FROM ubuntu:24.04 +FROM ubuntu:focal ENV DEBIAN_FRONTEND=noninteractive diff --git a/docker/Dockerfile.jammy b/docker/Dockerfile.jammy new file mode 100644 index 0000000..a884f68 --- /dev/null +++ b/docker/Dockerfile.jammy @@ -0,0 +1,15 @@ +FROM ubuntu:jammy + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt install -y \ + build-essential \ + uuid-dev \ + libssl-dev \ + libsecp256k1-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/cache/apt/archives/* + +COPY . /usr/src/mpc-lib/ +WORKDIR /usr/src/mpc-lib/ \ No newline at end of file diff --git a/docker/run_tests.sh b/docker/run_tests.sh index caf7f23..593a32f 100755 --- a/docker/run_tests.sh +++ b/docker/run_tests.sh @@ -15,7 +15,7 @@ fi input_base_name=$(basename ${Dockerfile}) prefix=$(echo ${input_base_name}| awk '{split($0,a,"."); print a[1]}') -tag=$(echo ${input_base_name} | sed "s/^${prefix}\.//" ) +tag=$(echo ${input_base_name}| awk '{split($0,a,"."); print a[2]}') if [ x${prefix} != "xDockerfile" ]; then echo "Must Select a valid Dockerfile" @@ -33,11 +33,10 @@ CURRENT_DIR=`pwd` cd ${SCRIPT_DIR}/.. -docker build --network=host -f ${CURRENT_DIR}/${Dockerfile} . -t $IMAGE_NAME +docker build -f ${CURRENT_DIR}/${Dockerfile} . -t $IMAGE_NAME docker run \ --rm \ - --net=host \ ${IMAGE_NAME} bash -c "mkdir build_${IMAGE_NAME};cd build_${IMAGE_NAME};cmake ..;make -j && make -j test" cd - diff --git a/include/cosigner/asymmetric_eddsa_cosigner.h b/include/cosigner/asymmetric_eddsa_cosigner.h index 6e739be..66e15ba 100644 --- a/include/cosigner/asymmetric_eddsa_cosigner.h +++ b/include/cosigner/asymmetric_eddsa_cosigner.h @@ -1,5 +1,6 @@ #pragma once +#include "cosigner/platform_service.h" #include "cosigner_export.h" #include "crypto/ed25519_algebra/ed25519_algebra.h" @@ -22,22 +23,18 @@ namespace cosigner { class cmp_key_persistency; -class platform_service; +class cosigner_verifier_chain; +struct eddsa_signature_data; +struct signing_data; static constexpr size_t SHA256_HASH_SIZE = 32; typedef std::array eddsa_commitment; static_assert(sizeof(eddsa_commitment) == SHA256_HASH_SIZE); -struct Rs_and_commitments -{ - std::vector Rs; - eddsa_commitment R_commitment; -}; - class COSIGNER_EXPORT asymmetric_eddsa_cosigner { public: - asymmetric_eddsa_cosigner(platform_service& cosigner_service, const cmp_key_persistency& key_persistency); + asymmetric_eddsa_cosigner(platform_service& service, const cmp_key_persistency& key_persistency); virtual ~asymmetric_eddsa_cosigner() {} protected: @@ -49,7 +46,7 @@ class COSIGNER_EXPORT asymmetric_eddsa_cosigner platform_service& _service; const cmp_key_persistency& _key_persistency; - static const std::unique_ptr _ctx; + std::unique_ptr _ctx; }; } diff --git a/include/cosigner/asymmetric_eddsa_cosigner_client.h b/include/cosigner/asymmetric_eddsa_cosigner_client.h index ef4fabf..82ed879 100644 --- a/include/cosigner/asymmetric_eddsa_cosigner_client.h +++ b/include/cosigner/asymmetric_eddsa_cosigner_client.h @@ -24,24 +24,23 @@ class COSIGNER_EXPORT asymmetric_eddsa_cosigner_client : public asymmetric_eddsa class preprocessing_persistency { public: - virtual ~preprocessing_persistency(); - + virtual ~preprocessing_persistency() {} // This function should allocate preprocessed data array sized size virtual void create_preprocessed_data(const std::string& key_id, uint64_t size) = 0; - // This function set the value k at index, in case index is larger then larger then array size the function should throw exception + // This function set the value k at index, in case index is larger than the array size the function should throw exception virtual void store_preprocessed_data(const std::string& key_id, uint64_t index, const ed25519_scalar_t& k) = 0; - // This function load the at index and deletes it, in case index is larger then larger then array size or the value isn't set the function should throw exception + // This function load the at index and deletes it, in case index is larger than the array size or the value isn't set the function should throw exception virtual void load_preprocessed_data(const std::string& key_id, uint64_t index, ed25519_scalar_t& k) = 0; virtual void delete_preprocessed_data(const std::string& key_id) = 0; }; - asymmetric_eddsa_cosigner_client(platform_service& cosigner_service, const cmp_key_persistency& key_persistency, preprocessing_persistency& preprocessing_persistency); - ~asymmetric_eddsa_cosigner_client(); + asymmetric_eddsa_cosigner_client(platform_service& cosigner_service, cmp_key_persistency& key_persistency, preprocessing_persistency& preprocessing_persistency); + ~asymmetric_eddsa_cosigner_client() {} - virtual void start_signature_preprocessing(const std::string& tenant_id, const std::string& key_id, const std::string& request_id, uint32_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, + virtual void start_signature_preprocessing(const std::string& tenant_id, const std::string& key_id, const std::string& request_id, uint64_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, std::vector>& R_commitments); virtual uint64_t eddsa_sign_offline(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, uint64_t preprocessed_data_index, - const std::map& Rs, std::vector& partial_sigs); + const std::map>& Rs, std::vector& partial_sigs); private: preprocessing_persistency& _preprocessing_persistency; diff --git a/include/cosigner/asymmetric_eddsa_cosigner_server.h b/include/cosigner/asymmetric_eddsa_cosigner_server.h index 0dbed4e..a4285b9 100644 --- a/include/cosigner/asymmetric_eddsa_cosigner_server.h +++ b/include/cosigner/asymmetric_eddsa_cosigner_server.h @@ -3,6 +3,7 @@ #include "cosigner_export.h" #include "asymmetric_eddsa_cosigner.h" +#include "eddsa_online_signing_service.h" #include "crypto/commitments/commitments.h" #include "crypto/ed25519_algebra/ed25519_algebra.h" #include "cosigner/timing_map.h" @@ -11,7 +12,6 @@ #include #include -#include #include #include #include @@ -23,23 +23,8 @@ namespace common namespace cosigner { -struct asymmetric_eddsa_signature_data +struct asymmetric_eddsa_signing_metadata : public eddsa_signing_metadata { - elliptic_curve_scalar k; - elliptic_curve_point R; - std::vector path; - byte_vector_t message; - uint32_t flags; - ~asymmetric_eddsa_signature_data() {OPENSSL_cleanse(k.data, sizeof(asymmetric_eddsa_signature_data));} -}; - -struct asymmetric_eddsa_signing_metadata -{ - std::string key_id; - HDChaincode chaincode; - std::vector sig_data; - std::set signers_ids; - uint32_t version; uint32_t start_index; }; @@ -51,13 +36,12 @@ class COSIGNER_EXPORT asymmetric_eddsa_cosigner_server : public asymmetric_eddsa class signing_persistency { public: - virtual ~signing_persistency(); + virtual ~signing_persistency() {} - // This function should allocate preprocessed data array sized size virtual void create_preprocessed_data(const std::string& key_id, uint64_t size) = 0; - // This function set the value R_commitment at index, in case index is larger then larger then array size the function should throw exception + // This function set the value R_commitment at index, in case index is larger than the array size the function should throw exception virtual void store_preprocessed_data(const std::string& key_id, uint64_t index, const eddsa_commitment& R_commitment) = 0; - // This function load the at index and deletes it, in case index is larger then larger then array size or the value isn't set the function should throw exception + // This function load the at index and deletes it, in case index is larger than the array size or the value isn't set the function should throw exception virtual void load_preprocessed_data(const std::string& key_id, uint64_t index, eddsa_commitment& R_commitment) = 0; virtual void delete_preprocessed_data(const std::string& key_id) = 0; @@ -66,27 +50,25 @@ class COSIGNER_EXPORT asymmetric_eddsa_cosigner_server : public asymmetric_eddsa virtual void delete_commitments(const std::string& txid) = 0; virtual void store_signing_data(const std::string& txid, const asymmetric_eddsa_signing_metadata& data, bool update) = 0; virtual void load_signing_data(const std::string& txid, asymmetric_eddsa_signing_metadata& data) = 0; - virtual void delete_temporary_signing_data(const std::string& txid) = 0; + virtual void delete_signing_data(const std::string& txid) = 0; }; - asymmetric_eddsa_cosigner_server(platform_service& cosigner_service, const cmp_key_persistency& key_persistency, signing_persistency& signing_persistency); + asymmetric_eddsa_cosigner_server(platform_service& service, cmp_key_persistency& key_persistency, signing_persistency& signing_persistency); virtual ~asymmetric_eddsa_cosigner_server() {} - void store_presigning_data(const std::string& key_id, const std::string& request_id, uint32_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, + void store_presigning_data(const std::string& key_id, const std::string& request_id, uint64_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, uint64_t sender, const std::vector& R_commitments); void eddsa_sign_offline(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, uint64_t preprocessed_data_index, - std::vector& R_commitments, Rs_and_commitments& Rs); + std::vector& R_commitments, std::vector& Rs); uint64_t decommit_r(const std::string& txid, const std::map>& commitments, std::vector& Rs); - uint64_t broadcast_r(const std::string& txid, const std::map>& players_R, Rs_and_commitments& Rs, uint64_t& send_to); + uint64_t broadcast_r(const std::string& txid, const std::map>& players_R, std::vector& Rs, uint64_t& send_to); uint64_t broadcast_si(const std::string& txid, uint64_t sender, uint32_t version, const std::vector& partial_sigs, std::vector& sigs, std::set& send_to, bool& final_signature); uint64_t get_eddsa_signature(const std::string& txid, const std::map>& partial_sigs, std::vector& sigs); - void cancel_signing(const std::string& txid); - private: bool verify_client_s(const ed25519_point_t& R, const ed25519_scalar_t& s, const ed25519_le_scalar_t& hram, const elliptic_curve_point& public_share, const ed25519_scalar_t& delta); - void commit_to_Rs(const std::string& txid, uint64_t id, const std::vector& Rs, eddsa_commitment& commitment); + void finalize_signing(const std::string& txid, size_t count); signing_persistency& _signing_persistency; TimingMap _timing_map; diff --git a/include/cosigner/bam_ecdsa_cosigner.h b/include/cosigner/bam_ecdsa_cosigner.h new file mode 100644 index 0000000..5b43794 --- /dev/null +++ b/include/cosigner/bam_ecdsa_cosigner.h @@ -0,0 +1,298 @@ +#pragma once +#include "cosigner_export.h" +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include "crypto/commitments/pedersen.h" +#include "cosigner/types.h" +#include "cosigner/sign_algorithm.h" +#include "cosigner/cosigner_exception.h" +#include "cosigner/bam_key_persistency_common.h" +#include "../../src/common/crypto/zero_knowledge_proof/zkp_constants_internal.h" +#include +#include +#include +#include +#include + +struct bignum_st; // for BIGNUM +struct bignum_ctx; //for BN_CTX + +namespace fireblocks::common::cosigner +{ +struct bam_single_signature_data_base; +class platform_service; + +struct bam_signing_properties +{ + uint32_t flags { common::cosigner::NONE }; +}; + +class COSIGNER_EXPORT bam_ecdsa_cosigner +{ +public: + struct cosigner_params + { + static constexpr const uint32_t PAILLIER_COMMITMENT_BITSIZE = 3072U; + static constexpr const uint32_t TEMPORARY_DAMGARD_FUJISAKI_BITSIZE = 2048U; //client temporary key + static constexpr const uint32_t OPTIMIZED_DAMGARD_FUJISAKI_CHALLENGE_BITSIZE = 40U; + + static constexpr const uint32_t ZKPOK_OPTIM_KAPPA_SIZE = ::ZKPOK_OPTIM_KAPPA_SIZE(); + static constexpr const uint32_t ZKPOK_OPTIM_L_SIZE = ::ZKPOK_OPTIM_L_SIZE(PAILLIER_COMMITMENT_BITSIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NU_SIZE = ::ZKPOK_OPTIM_NU_SIZE(PAILLIER_COMMITMENT_BITSIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NLAMBDA_SIZE = ::ZKPOK_OPTIM_NLAMBDA_SIZE(PAILLIER_COMMITMENT_BITSIZE); + static constexpr const uint32_t ZKPOK_OPTIM_EPSILON_SIZE = ::ZKPOK_OPTIM_EPSILON_SIZE(PAILLIER_COMMITMENT_BITSIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NX_SIZE = ::ZKPOK_OPTIM_NX_SIZE(PAILLIER_COMMITMENT_BITSIZE); + + static constexpr const uint32_t ZKPOK_OPTIM_NA_SIZE = (2 * ZKPOK_OPTIM_KAPPA_SIZE + 2 * ZKPOK_OPTIM_L_SIZE + 4 * ZKPOK_OPTIM_NU_SIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NB_SIZE = (1 * ZKPOK_OPTIM_KAPPA_SIZE + 1 * ZKPOK_OPTIM_L_SIZE + 1 * ZKPOK_OPTIM_NU_SIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NLAMBDA0_SIZE_1 = (1 * ZKPOK_OPTIM_NA_SIZE + 2 * ZKPOK_OPTIM_L_SIZE + 1 * ZKPOK_OPTIM_NU_SIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NLAMBDA0_SIZE_2 = (1 * ZKPOK_OPTIM_NB_SIZE + 1 * ZKPOK_OPTIM_NLAMBDA_SIZE + 1 * ZKPOK_OPTIM_EPSILON_SIZE); + static constexpr const uint32_t ZKPOK_OPTIM_NLAMBDA0_SIZE = std::max(ZKPOK_OPTIM_NLAMBDA0_SIZE_1, ZKPOK_OPTIM_NLAMBDA0_SIZE_2) + ZKPOK_OPTIM_NU_SIZE; + }; + + struct server_setup_shared_data + { + server_setup_shared_data() = default; + + template + server_setup_shared_data(const T& serialized) : + paillier_commitment_pub(serialized.paillier_commitment_pub.begin(), serialized.paillier_commitment_pub.end()), + paillier_blum_zkp(serialized.paillier_blum_zkp.begin(), serialized.paillier_blum_zkp.end()), + small_factors_zkp(serialized.small_factors_zkp.begin(), serialized.small_factors_zkp.end()), + damgard_fujisaki_zkp(serialized.damgard_fujisaki_zkp.begin(), serialized.damgard_fujisaki_zkp.end()) + { + + } + + //paillier commitment key and proofs + byte_vector_t paillier_commitment_pub; + byte_vector_t paillier_blum_zkp; + byte_vector_t small_factors_zkp; + byte_vector_t damgard_fujisaki_zkp; + }; + + //sent by the client to the server during key generation + struct client_key_shared_data + { + client_key_shared_data() = default; + + template + client_key_shared_data(const T& serialized) : + damgard_fujisaki_pub(serialized.damgard_fujisaki_pub.begin(), serialized.damgard_fujisaki_pub.end()), + damgard_fujisaki_proof(serialized.damgard_fujisaki_proof.begin(), serialized.damgard_fujisaki_proof.end()), + schnorr_proof(serialized.schnorr_proof.begin(), serialized.schnorr_proof.end()) + { + if (serialized.X.size() != sizeof(elliptic_curve256_point_t)) + { + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + memcpy(X, serialized.X.data(), sizeof(elliptic_curve256_point_t)); + } + + elliptic_curve256_point_t X; + byte_vector_t damgard_fujisaki_pub; + byte_vector_t damgard_fujisaki_proof; + byte_vector_t schnorr_proof; + }; + + //sent by the server to the client during key generation + struct server_key_shared_data + { + server_key_shared_data() = default; + + template + server_key_shared_data(const T& serialized) : + encrypted_server_share(serialized.encrypted_server_share.begin(), serialized.encrypted_server_share.end()), + enc_dlog_proof(serialized.enc_dlog_proof.begin(), serialized.enc_dlog_proof.end()) + { + if (serialized.server_public_share.size() != sizeof(elliptic_curve256_point_t)) + { + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + memcpy(server_public_share, serialized.server_public_share.data(), sizeof(elliptic_curve256_point_t)); + } + + elliptic_curve256_point_t server_public_share; + byte_vector_t encrypted_server_share; + byte_vector_t enc_dlog_proof; + }; + + //sent by the server to the client as 1st step of the signature + struct server_signature_shared_data + { + server_signature_shared_data() = default; + + template + server_signature_shared_data(const T& serialized) + { + if (serialized.R.size() != sizeof(elliptic_curve256_point_t) || + serialized.Y.size() != sizeof(elliptic_curve256_point_t)) + { + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + memcpy(R, serialized.R.data(), sizeof(elliptic_curve256_point_t)); + memcpy(Y, serialized.Y.data(), sizeof(elliptic_curve256_point_t)); + } + + elliptic_curve256_point_t R; //G^k of the server + elliptic_curve256_point_t Y; //client's public_key^k as proof + }; + + //sent by the client to the server as 2nd step of the signature + struct client_partial_signature_data + { + client_partial_signature_data() = default; + + static constexpr size_t MAX_ENCRYPTED_PARTIAL_SIG_SIZE = 4096; + static constexpr size_t MAX_SIG_PROOF_SIZE = 16384; + + template + client_partial_signature_data(const T& serialized) : + encrypted_partial_sig(serialized.encrypted_partial_sig.begin(), serialized.encrypted_partial_sig.end()), + sig_proof(serialized.sig_proof.begin(), serialized.sig_proof.end()) + { + if (serialized.client_R.size() != sizeof(elliptic_curve256_point_t) || + serialized.common_R.size() != sizeof(elliptic_curve256_point_t)) + { + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + if (serialized.encrypted_partial_sig.size() > MAX_ENCRYPTED_PARTIAL_SIG_SIZE || + serialized.sig_proof.size() > MAX_SIG_PROOF_SIZE) + { + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + memcpy(client_R, serialized.client_R.data(), sizeof(elliptic_curve256_point_t)); + memcpy(common_R, serialized.common_R.data(), sizeof(elliptic_curve256_point_t)); + } + + elliptic_curve256_point_t client_R; // this is the client's R = G^k_client - client's ephemeral key + elliptic_curve256_point_t common_R; // This is the common R which is G^(k_client*k_server). Proves that the client knows k_client + byte_vector_t encrypted_partial_sig; + byte_vector_t sig_proof; + }; + + + struct generated_public_key + { + std::string pub_key; + cosigner_sign_algorithm algorithm; + }; + + static bool is_positive(const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& n); + static inline bool is_odd_point(const elliptic_curve256_point_t& p) + { + return (p[0] & 1) == 1; + } + + static void make_sig_s_positive(const cosigner_sign_algorithm algorithm, const elliptic_curve256_algebra_ctx_t* algebra, recoverable_signature& sig); + + struct add_user_data + { + std::map encrypted_shares; + elliptic_curve_point public_key; + }; + + +protected: + + + explicit bam_ecdsa_cosigner(platform_service& platform_service); + + virtual ~bam_ecdsa_cosigner() = default; + + std::vector fill_bam_signing_info_from_metadata(const std::string& metadata, const uint32_t blocks_num); + + + //commitment to a specific key. + // Seed already contains key_id, client_id and server_id + static void generate_key_commitment(const commitments_sha256_t& seed, + const elliptic_curve256_point_t& public_key, + commitments_sha256_t& B); + + byte_vector_t generate_setup_aad_bytes(const std::string& setup_id, const cosigner_sign_algorithm algorithm) const; + static void generate_aad_for_key_gen(const std::string& key_id, const uint64_t client_id, const uint64_t server_id, commitments_sha256_t& key_aad); + static void generate_aad_for_signature(const std::string& key_id, const uint64_t server_id, const uint64_t client_id, const std::string& tx_id, commitments_sha256_t& signature_add); + + static void check_non_null_message(const elliptic_curve256_scalar_t& message, const elliptic_curve256_algebra_ctx_t* algebra); + static void check_a_valid_point(const elliptic_curve256_point_t& point, const elliptic_curve256_algebra_ctx_t* algebra); + + + + static void derive_and_compute_corrected_R(elliptic_curve256_algebra_ctx_t* algebra, + const std::string& tx_id, + const bam_single_signature_data_base& signature_data, + const elliptic_curve256_point_t& public_key, + const client_partial_signature_data& partial_signature, + elliptic_curve256_point_t& derived_public_key, + elliptic_curve256_scalar_t& hash_shift, + elliptic_curve256_point_t& corrected_R); + + static void bn_randomize_with_factor(struct bignum_st* res, const struct bignum_st* base, const struct bignum_st* factor, const uint32_t randomizer_bitlength); + + + struct bignum_clear_deleter + { + void operator()(struct bignum_st* bn); + }; + + struct bignum_clear + { + void operator()(struct bignum_st* bn); + }; + + + inline elliptic_curve256_algebra_ctx_t* get_algebra(cosigner_sign_algorithm algorithm) const + { + return algorithm == ECDSA_SECP256K1 ? _secp256k1.get() : + algorithm == ECDSA_SECP256R1 ? _secp256r1.get() : + algorithm == ECDSA_STARK ? _stark.get() : + throw cosigner_exception(cosigner_exception::UNKNOWN_ALGORITHM); + } + + void generate_private_share(const cosigner_sign_algorithm algorithm, elliptic_curve_scalar& private_share) const; + void decrypt_and_rebuild_private_share(const uint64_t my_player_id, + const cosigner_sign_algorithm algorithm, + const std::map& data, + elliptic_curve_scalar& private_share, + elliptic_curve256_point_t& expected_public_key) const; + + + + static uint32_t signature_proof_size(const uint32_t paillier_commitment_n_bitsize); + + + void validate_current_tenant_id(const std::string& tenant_id) const; + void validate_tenant_id_setup(bam_key_persistency_common& persistency, const std::string& setup_id) const; + + template + cosigner_sign_algorithm get_public_key(const std::string& key_id, byte_vector_t& public_key, persistency_t& key_persistency) + { + key_metadata_t key_metadata; + + key_persistency.load_key_metadata(key_id, key_metadata); + auto algebra = bam_ecdsa_cosigner::get_algebra(key_metadata.algorithm); // this is member function, so template cannot be statis + public_key.assign(reinterpret_cast(key_metadata.public_key), reinterpret_cast(key_metadata.public_key) + algebra->point_size(algebra)); + return key_metadata.algorithm; + } + + static void derivation_key_delta(const elliptic_curve256_algebra_ctx_t* algebra, + const elliptic_curve256_point_t& public_key, + const HDChaincode& chaincode, + const std::vector& path, + elliptic_curve256_scalar_t& delta); + + platform_service& _platform_service; +private: + const std::unique_ptr _secp256k1; + const std::unique_ptr _secp256r1; + const std::unique_ptr _stark; + + static void compute_hash_shift(const std::string& tx_id, + const elliptic_curve256_scalar_t& hash_message, + const elliptic_curve256_point_t& ephemeral_common_key, + const elliptic_curve256_point_t& client_ephemeral_key, + const elliptic_curve256_point_t& public_key, + elliptic_curve256_scalar_t &hash_shift); + +}; + +} //namespace fireblocks::common::cosigner \ No newline at end of file diff --git a/include/cosigner/bam_ecdsa_cosigner_client.h b/include/cosigner/bam_ecdsa_cosigner_client.h new file mode 100644 index 0000000..f8e56a6 --- /dev/null +++ b/include/cosigner/bam_ecdsa_cosigner_client.h @@ -0,0 +1,118 @@ +#pragma once +#include "cosigner_export.h" +#include "bam_ecdsa_cosigner.h" +#include "crypto/zero_knowledge_proof/range_proofs.h" + +struct damgard_fujisaki_public; +struct elliptic_curve256_algebra_ctx; +struct pedersen_commitment_two_generators; + +namespace fireblocks::common::cosigner +{ + +class bam_key_persistency_client; +class bam_tx_persistency_client; +struct bam_temp_key_data_client; +struct bam_key_metadata_client; +class COSIGNER_EXPORT bam_ecdsa_cosigner_client : public bam_ecdsa_cosigner +{ +public: + bam_ecdsa_cosigner_client(platform_service& platform_service, + bam_key_persistency_client& key_persistecy, + bam_tx_persistency_client& tx_persistency); + + + void start_new_key_generation(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm); + + void start_add_user(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const std::map& data); + + + // Receives server proofs and finalizes key generation + // 1. setup completion function. Receives server generated setup data and verifies it. + // it was decided that the client setup verification is always done a part of the key generation + // so key_id and setup_id are tightly coupled. + // This is the reason the client has only one persistent metadata - key metadata and thus key id + // is required in all function. + // 2. Receives server commitment B and generates client proofs + // it actually unites two steps - temporary storing of the server commitment + // and generation of the client proofs that do not depend on the commitment + // (but commitment has to be received and stored before the proofs are sent out) + // also associates key_id with tenant, algorithm and setup_id + void verify_setup_proof_store_key_commitment_generate_key_proof(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const server_setup_shared_data& setup, + const commitments_sha256_t& B, + client_key_shared_data& client_key_data); + + // retrieves server commitment B and verifies it. The commitment is deleted, so it cannot be reused + void verify_key_decommitment_and_proofs(const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const server_key_shared_data& server_message, + generated_public_key& pub_key_data); + + // 1st signature generation function + // Saves signature information and associates key with tx_id + void prepare_for_signature(const std::string& key_id, + const std::string& tx_id, + const uint32_t version, + const uint64_t server_id, + const uint64_t client_id, + const common::cosigner::signing_data& data, + const std::string& matadata_json, + const std::set& players_set); + + void compute_partial_signature(const std::string& tx_id, + const std::vector& server_shares, + std::vector& partial_signatures); + +private: + + void verify_setup_proof(const std::string& setup_id, + const server_setup_shared_data& setup, + bam_key_metadata_client& client_key_metadata) const; + + void start_key_generation(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const elliptic_curve_scalar& private_share, + const elliptic_curve256_point_t& expected_public_key); + + void generate_share_and_proofs(const std::string& key_id, + const bam_key_metadata_client& client_key_metadata, + bam_temp_key_data_client& client_temp_key_data, + client_key_shared_data& client_key_message) const; + + void well_formed_signature_range_proof_generate(const paillier_commitment_public_key_t *paillier, + elliptic_curve256_algebra_ctx* algebra, + const pedersen_commitment_two_generators* ec_base, + const commitments_sha256_t& signature_add, + const elliptic_curve256_point_t& r_server, + const struct bignum_st* plaintext, + const struct bignum_st* encrypted_share, + const struct bignum_st* exponent, + struct bignum_ctx *ctx, + client_partial_signature_data &partial_signature); + + void validate_tenant_id_setup(const std::string& setup_id) const; + + bam_key_persistency_client& _key_persistency; + bam_tx_persistency_client& _tx_persistency; +}; + +} //namespace fireblocks::common::cosigner \ No newline at end of file diff --git a/include/cosigner/bam_ecdsa_cosigner_server.h b/include/cosigner/bam_ecdsa_cosigner_server.h new file mode 100644 index 0000000..590461d --- /dev/null +++ b/include/cosigner/bam_ecdsa_cosigner_server.h @@ -0,0 +1,100 @@ +#pragma once +#include "cosigner_export.h" +#include "bam_ecdsa_cosigner.h" +#include "bam_key_persistency_structures_server.h" + +struct elliptic_curve256_algebra_ctx; + +namespace fireblocks::common::cosigner +{ + +class bam_key_persistency_server; +class bam_tx_persistency_server; + +class COSIGNER_EXPORT bam_ecdsa_cosigner_server : public bam_ecdsa_cosigner +{ +public: + bam_ecdsa_cosigner_server(platform_service& cosigner_service, bam_key_persistency_server& key_persistecy, bam_tx_persistency_server& tx_persistecy); + + void shutdown(); + + // generate setup proof. Can be reused for multiple clients + void generate_setup_with_proof(const std::string& setup_id, + const std::string& tenant_id, + const cosigner_sign_algorithm algorithm, + server_setup_shared_data& serialized_setup_data); + + + + void generate_share_and_commit(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + commitments_sha256_t& B); + + void add_user_and_commit(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const std::map& data, + commitments_sha256_t& B); + + + // 2nd stage of key generation - verify client proofs and generate server decommitment + void verify_client_proofs_and_decommit_share_with_proof(const std::string& key_id, + const uint64_t client_id, + const client_key_shared_data& client_message, + server_key_shared_data& server_message); + + // Initiate signature process + void generate_signature_share(const std::string& key_id, + const std::string& tx_id, + const uint32_t version, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm requested_algorithm, + const common::cosigner::signing_data& data, + const std::string& metadata_json, + const std::set& players_set, + std::vector& server_commitments); + + // 2nd stage signature function + void verify_partial_signature_and_output_signature(const std::string& tx_id, + const uint64_t client_id, + const std::vector& partial_signatures, + std::vector& signatures, + cosigner_sign_algorithm& algorithm); + + void get_public_key(const std::string& key_id, generated_public_key& pub_key_data) const; + +private: + bam_setup_auxilary_key_server generate_setup_secrets(); + bam_setup_metadata_server generate_setup_metadata(const byte_vector_t& setup_aad, const cosigner_sign_algorithm algorithm); + + void generate_setup_proofs(const bam_setup_auxilary_key_server& setup_keys, + const byte_vector_t& setup_aad, + byte_vector_t& paillier_blum, + byte_vector_t& small_factors, + byte_vector_t& damgard_fujisaki); + + // associates key_id with the setup_id + // and commit to a private share + void commit_to_share(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const elliptic_curve_scalar& private_share, //the share is generated outside + const elliptic_curve256_point_t& expected_public_key, + commitments_sha256_t& B); + + + void validate_tenant_id_setup(const std::string& setup_id) const; + + bam_key_persistency_server& _key_persistency; + bam_tx_persistency_server& _tx_persistency; +}; + +} //namespace fireblocks::common::cosigner \ No newline at end of file diff --git a/include/cosigner/bam_key_persistency_client.h b/include/cosigner/bam_key_persistency_client.h new file mode 100644 index 0000000..1deb0c5 --- /dev/null +++ b/include/cosigner/bam_key_persistency_client.h @@ -0,0 +1,24 @@ +#pragma once + +#include "bam_key_persistency_common.h" +#include "bam_key_persistency_structures_client.h" + +namespace fireblocks::common::cosigner +{ + +class bam_key_persistency_client : public bam_key_persistency_common +{ + +public: + virtual ~bam_key_persistency_client() = default; + + virtual void store_key_metadata(const std::string& key_id, const bam_key_metadata_client& metadata, const bool overwrite) = 0; + virtual void load_key_metadata(const std::string& key_id, bam_key_metadata_client& metadata) const = 0; + + virtual void store_key_temp_data(const std::string& key_id, const bam_temp_key_data_client& temp_key_data_client) = 0; + virtual void load_key_temp_data_and_delete(const std::string& key_id, bam_temp_key_data_client& temp_key_data_client) = 0; + + +}; + +} //namespace fireblocks::common::cosigner diff --git a/include/cosigner/bam_key_persistency_common.h b/include/cosigner/bam_key_persistency_common.h new file mode 100644 index 0000000..d8214b6 --- /dev/null +++ b/include/cosigner/bam_key_persistency_common.h @@ -0,0 +1,28 @@ +#pragma once + +#include "cosigner/types.h" +#include +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" + +namespace fireblocks::common::cosigner +{ + +class bam_key_persistency_common +{ +public: + virtual ~bam_key_persistency_common() = default; + + virtual void store_key(const std::string& key_id, const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key) = 0; + virtual void load_key(const std::string& key_id, cosigner_sign_algorithm &algorithm, elliptic_curve256_scalar_t& private_key) const = 0; + + virtual void store_tenant_id_for_setup(const std::string& setup_id, const std::string& tenant_id) = 0; + virtual void load_tenant_id_for_setup(const std::string& setup_id, std::string& tenant_id) const = 0; + + virtual bool delete_temporary_key_data(const std::string& key_id) = 0; + virtual bool delete_key_data(const std::string& key_id) = 0; + virtual bool delete_setup_data(const std::string& setup_id) = 0; + + virtual bool backup_key(const std::string& key_id) const = 0; +}; + +} \ No newline at end of file diff --git a/include/cosigner/bam_key_persistency_server.h b/include/cosigner/bam_key_persistency_server.h new file mode 100644 index 0000000..ba5d448 --- /dev/null +++ b/include/cosigner/bam_key_persistency_server.h @@ -0,0 +1,25 @@ +#pragma once + +#include "bam_key_persistency_common.h" +#include "bam_key_persistency_structures_server.h" + +namespace fireblocks::common::cosigner +{ + +class bam_key_persistency_server : public bam_key_persistency_common +{ + +public: + virtual ~bam_key_persistency_server() = default; + + virtual void store_setup_metadata(const std::string& setup_id, const bam_setup_metadata_server& setup_metadata) = 0; + virtual void load_setup_metadata(const std::string& setup_id, bam_setup_metadata_server& setup_metadata) const = 0; + + virtual void store_setup_auxilary_key(const std::string& setup_id, const bam_setup_auxilary_key_server& setup_aux_key) = 0; + virtual void load_setup_auxilary_key(const std::string& setup_id, bam_setup_auxilary_key_server& setup_aux_key) const = 0; + + virtual void store_key_metadata(const std::string& key_id, const bam_key_metadata_server& metadata, const bool overwrite) = 0; + virtual void load_key_metadata(const std::string& key_id, bam_key_metadata_server& metadata) const = 0; +}; + +} diff --git a/include/cosigner/bam_key_persistency_structures.h b/include/cosigner/bam_key_persistency_structures.h new file mode 100644 index 0000000..9e52aa4 --- /dev/null +++ b/include/cosigner/bam_key_persistency_structures.h @@ -0,0 +1,42 @@ +#pragma once + +#include "crypto/commitments/commitments.h" +#include "crypto/commitments/pedersen.h" +#include "cosigner/key_persistency_base.h" +#include "cosigner/sign_algorithm.h" +#include "cosigner/types.h" +#include +#include + +namespace fireblocks::common::cosigner +{ + +struct bam_setup_metadata_base +{ + bam_setup_metadata_base() = default; + bam_setup_metadata_base(const cosigner_sign_algorithm _algo) : setup_algorithm(_algo) {} + + //algorithm is required because because ec_base is points on a specific curve + pedersen_commitment_two_generators_t ec_base { {0}, {0} }; + cosigner_sign_algorithm setup_algorithm{(cosigner_sign_algorithm)-1}; +}; + +struct bam_key_metadata_base : public key_metadata_base +{ + bam_key_metadata_base() = default; + bam_key_metadata_base(const cosigner_sign_algorithm _algo, + const std::string _setup_id, + const uint64_t _peer_id, + const elliptic_curve256_point_t& _pub_key); + + commitments_sha256_t seed; // Hash over key_id, client_id, server_id and some constant string + byte_vector_t encrypted_server_share; // Saved by client to generate partial signature. + // Used by server to create full signature (otherwise would require re-encryption) + // In server the presence of this value also serves as an indicator that the key generation is completed (prevents replay attack) + std::string setup_id; // A unique identifier of the setup to which this key belongs + uint64_t peer_id; // server id for the client and client id for the server + bool has_public_key() const { return public_key[0] != 0; } // BAM is ECDSA and it's public key starts always from 0x2 or 0x3 +}; + + +} \ No newline at end of file diff --git a/include/cosigner/bam_key_persistency_structures_client.h b/include/cosigner/bam_key_persistency_structures_client.h new file mode 100644 index 0000000..bec4216 --- /dev/null +++ b/include/cosigner/bam_key_persistency_structures_client.h @@ -0,0 +1,37 @@ +#pragma once + +#include "bam_key_persistency_structures.h" + +#include "crypto/paillier_commitment/paillier_commitment.h" +#include "crypto/commitments/damgard_fujisaki.h" +#include "crypto/commitments/commitments.h" +#include "cosigner/sign_algorithm.h" + +#include + +namespace fireblocks::common::cosigner +{ + + +struct bam_key_metadata_client : public bam_setup_metadata_base, public bam_key_metadata_base +{ + bam_key_metadata_client() = default; + bam_key_metadata_client(const cosigner_sign_algorithm _algo, + const std::string _setup_id, + const uint64_t _peer_id, + const elliptic_curve256_point_t& _pub_key): + bam_setup_metadata_base(_algo), + bam_key_metadata_base(_algo, _setup_id, _peer_id, _pub_key) + { + + } + std::shared_ptr paillier_commitment_pub; +}; + +struct bam_temp_key_data_client +{ + commitments_sha256_t server_commitment; // this is server public commitment + std::shared_ptr damgard_fujisaki_priv; // this is a secret of the client +}; + +} \ No newline at end of file diff --git a/include/cosigner/bam_key_persistency_structures_server.h b/include/cosigner/bam_key_persistency_structures_server.h new file mode 100644 index 0000000..c50f107 --- /dev/null +++ b/include/cosigner/bam_key_persistency_structures_server.h @@ -0,0 +1,32 @@ +#pragma once + +#include "bam_key_persistency_structures.h" +#include "crypto/paillier_commitment/paillier_commitment.h" +#include "crypto/commitments/damgard_fujisaki.h" +#include + +namespace fireblocks::common::cosigner +{ + +using bam_setup_metadata_server = bam_setup_metadata_base; + +struct bam_setup_auxilary_key_server +{ + bam_setup_auxilary_key_server() = default; + std::shared_ptr paillier_commitment_priv; +}; + +// In BAM server does not hold any key specific auxiliary data. +// Instead - auxiliary key data is per "setup" on the server side +struct bam_key_metadata_server : public bam_key_metadata_base +{ + using bam_key_metadata_base::bam_key_metadata_base; + + bam_key_metadata_server() = default; + + elliptic_curve256_point_t client_public_share { 0 }; +}; + + + +} diff --git a/include/cosigner/bam_tx_persistency_client.h b/include/cosigner/bam_tx_persistency_client.h new file mode 100644 index 0000000..e989bf1 --- /dev/null +++ b/include/cosigner/bam_tx_persistency_client.h @@ -0,0 +1,18 @@ +#pragma once +#include + +#include "bam_tx_persistency_common.h" +#include "bam_tx_persistency_structures_client.h" + +namespace fireblocks::common::cosigner +{ + +class bam_tx_persistency_client : public bam_tx_persistency_common +{ +public: + virtual ~bam_tx_persistency_client() = default; + + virtual void store_signature_data(const std::string& tx_id, const bam_client_signature_data& signature_data) = 0; + virtual void load_signature_data_and_delete(const std::string& tx_id, bam_client_signature_data& signature_data) = 0; +}; +} diff --git a/include/cosigner/bam_tx_persistency_common.h b/include/cosigner/bam_tx_persistency_common.h new file mode 100644 index 0000000..419e60a --- /dev/null +++ b/include/cosigner/bam_tx_persistency_common.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace fireblocks::common::cosigner +{ + +class bam_tx_persistency_common +{ +public: + virtual ~bam_tx_persistency_common() = default; + + virtual bool delete_temporary_tx_data(const std::string& tx_id) = 0; + virtual void shutdown() {} + +}; + +} \ No newline at end of file diff --git a/include/cosigner/bam_tx_persistency_server.h b/include/cosigner/bam_tx_persistency_server.h new file mode 100644 index 0000000..deee458 --- /dev/null +++ b/include/cosigner/bam_tx_persistency_server.h @@ -0,0 +1,20 @@ +#pragma once +#include "bam_tx_persistency_common.h" +#include "bam_tx_persistency_structures_server.h" +#include + +namespace fireblocks::common::cosigner +{ + +class bam_tx_persistency_server: public bam_tx_persistency_common +{ + +public: + virtual ~bam_tx_persistency_server() = default; + + // handle temporary signature state + virtual void store_signature_data(const std::string& tx_id, const std::shared_ptr& signature_data) = 0; + virtual std::shared_ptr load_signature_data_and_delete(const std::string& tx_id) = 0; +}; + +} diff --git a/include/cosigner/bam_tx_persistency_structures.h b/include/cosigner/bam_tx_persistency_structures.h new file mode 100644 index 0000000..3ad338e --- /dev/null +++ b/include/cosigner/bam_tx_persistency_structures.h @@ -0,0 +1,48 @@ +#pragma once +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include +#include +#include +namespace fireblocks::common::cosigner +{ + +struct bam_single_signature_data_base +{ + uint32_t flags; // signature flags + elliptic_curve256_scalar_t message; // message to sign + elliptic_curve256_scalar_t derivation_delta; // key derivation + +}; + +struct bam_signature_metadata_header +{ + bam_signature_metadata_header() = default; + bam_signature_metadata_header(const uint32_t ver, const uint64_t server_id, const uint64_t client_id, const std::string& signature_key_id, const int64_t creation_time) : + version(ver), + server_signer_id(server_id), + client_signer_id(client_id), + key_id(signature_key_id), + timestamp(creation_time) + { + + } + + uint32_t version { 0 }; // not used for now and set to zero + uint64_t server_signer_id { 0 }; // player id of the server + uint64_t client_signer_id { 0 }; // player id of the client + std::string key_id; + int64_t timestamp { 0 }; // of creation +}; + +// Signing is done on a bulk of signatures. This is a generic container for BAM signature information +template +struct bam_signature_metadata_base : public bam_signature_metadata_header +{ + using bam_signature_metadata_header::bam_signature_metadata_header; + + std::vector sig_data; + + +}; + +} \ No newline at end of file diff --git a/include/cosigner/bam_tx_persistency_structures_client.h b/include/cosigner/bam_tx_persistency_structures_client.h new file mode 100644 index 0000000..88633c4 --- /dev/null +++ b/include/cosigner/bam_tx_persistency_structures_client.h @@ -0,0 +1,13 @@ +#pragma once + +#include "bam_tx_persistency_structures.h" + +namespace fireblocks::common::cosigner +{ + +typedef bam_single_signature_data_base bam_client_single_signature_data; + +typedef bam_signature_metadata_base bam_client_signature_data; + + +} \ No newline at end of file diff --git a/include/cosigner/bam_tx_persistency_structures_server.h b/include/cosigner/bam_tx_persistency_structures_server.h new file mode 100644 index 0000000..064c279 --- /dev/null +++ b/include/cosigner/bam_tx_persistency_structures_server.h @@ -0,0 +1,20 @@ +#pragma once + +#include "bam_tx_persistency_structures.h" +#include "cosigner/types.h" + +namespace fireblocks::common::cosigner +{ + +// server generates ephemeral "k", sends data to client +// and need to known it's k when client's response is received +// this data is temporal and stored for the duration of a transaction signing +struct bam_server_single_signature_data : public bam_single_signature_data_base +{ + bam_server_single_signature_data() = default; + elliptic_curve_scalar k; // "k" ephemeral share of the server +}; + +typedef bam_signature_metadata_base bam_server_signature_data; + +} \ No newline at end of file diff --git a/include/cosigner/cmp_ecdsa_offline_signing_service.h b/include/cosigner/cmp_ecdsa_offline_signing_service.h index 3847079..b89c300 100644 --- a/include/cosigner/cmp_ecdsa_offline_signing_service.h +++ b/include/cosigner/cmp_ecdsa_offline_signing_service.h @@ -30,9 +30,9 @@ class COSIGNER_EXPORT cmp_ecdsa_offline_signing_service final : public cmp_ecdsa // This function should allocate preprocessed data array sized size virtual void create_preprocessed_data(const std::string& key_id, uint64_t size) = 0; - // This function set the data at index, in case index is larger then larger then array size the function should throw exception + // This function set the data at index, in case index is larger than the array size the function should throw exception virtual void store_preprocessed_data(const std::string& key_id, uint64_t index, const cmp_signature_preprocessed_data& data) = 0; - // This function load the at index and deletes it, in case index is larger then larger then array size or the value isn't set the function should throw exception + // This function load the at index and deletes it, in case index is larger than the array size or the value isn't set the function should throw exception // Note that the function MUST delete the preprocessed data, as using the same preprocessed data twice may lead to share exposure virtual void load_preprocessed_data(const std::string& key_id, uint64_t index, cmp_signature_preprocessed_data& data) = 0; virtual void delete_preprocessed_data(const std::string& key_id) = 0; @@ -41,7 +41,7 @@ class COSIGNER_EXPORT cmp_ecdsa_offline_signing_service final : public cmp_ecdsa cmp_ecdsa_offline_signing_service(platform_service& service, const cmp_key_persistency& key_persistency, preprocessing_persistency& persistency) : cmp_ecdsa_signing_service(service, key_persistency), _preprocessing_persistency(persistency) {} void start_ecdsa_signature_preprocessing(const std::string& tenant_id, const std::string& key_id, const std::string& request_id, uint32_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, std::vector& mta_requests); - uint64_t offline_mta_response(const std::string& request_id, const std::map>& requests, cmp_mta_responses& response); + uint64_t offline_mta_response(const std::string& request_id, const std::map>& requests, uint32_t version, cmp_mta_responses& response); uint64_t offline_mta_verify(const std::string& request_id, const std::map& mta_responses, std::vector& deltas); uint64_t store_presigning_data(const std::string& request_id, const std::map>& deltas, std::string& key_id); void ecdsa_sign(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, uint64_t preprocessed_data_index, int protocol_version, diff --git a/include/cosigner/cmp_ecdsa_signing_service.h b/include/cosigner/cmp_ecdsa_signing_service.h index 8dd1d1f..3f7b978 100644 --- a/include/cosigner/cmp_ecdsa_signing_service.h +++ b/include/cosigner/cmp_ecdsa_signing_service.h @@ -103,7 +103,8 @@ class COSIGNER_EXPORT cmp_ecdsa_signing_service const std::map>& requests, size_t index, const elliptic_curve_scalar& key, - const auxiliary_keys& aux_keys); + const auxiliary_keys& aux_keys, + const uint32_t version); static cmp_mta_deltas verify_block_and_get_delta(ecdsa_preprocessing_data& data, const elliptic_curve256_algebra_ctx_t* algebra, @@ -114,6 +115,7 @@ class COSIGNER_EXPORT cmp_ecdsa_signing_service const std::map& mta_responses, size_t index, const auxiliary_keys& aux_keys, + const uint32_t version, std::map>& verifiers); static void calc_R(ecdsa_preprocessing_data& data, elliptic_curve_point& R, const elliptic_curve256_algebra_ctx_t* algebra, uint64_t my_id, const std::string& uuid, const cmp_key_metadata& metadata, diff --git a/include/cosigner/cmp_key_persistency.h b/include/cosigner/cmp_key_persistency.h index 10a37e5..4764fd3 100644 --- a/include/cosigner/cmp_key_persistency.h +++ b/include/cosigner/cmp_key_persistency.h @@ -47,7 +47,7 @@ struct auxiliary_keys class COSIGNER_EXPORT cmp_key_persistency { public: - virtual ~cmp_key_persistency(); + virtual ~cmp_key_persistency() {} virtual bool key_exist(const std::string& key_id) const = 0; // This function should throw cosigner_exception::BAD_KEY if key doesn't exist virtual void load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const = 0; diff --git a/include/cosigner/cmp_setup_service.h b/include/cosigner/cmp_setup_service.h index 3b319bb..a475bc1 100644 --- a/include/cosigner/cmp_setup_service.h +++ b/include/cosigner/cmp_setup_service.h @@ -53,6 +53,7 @@ struct setup_data commitments_sha256_t seed; elliptic_curve_point public_key; std::map players_schnorr_R; + uint32_t version; // only in devices MPC version >= MPC_EXTENDED_MTA }; // this class implements MPC CMP key generation based on https://eprint.iacr.org/2020/492 paper @@ -69,7 +70,7 @@ class COSIGNER_EXPORT cmp_setup_service final virtual void store_key_metadata(const std::string& key_id, const cmp_key_metadata& metadata, bool allow_override) = 0; virtual void store_auxiliary_keys(const std::string& key_id, const auxiliary_keys& aux) = 0; virtual void store_keyid_tenant_id(const std::string& key_id, const std::string& tenant_id) = 0; - virtual void store_setup_data(const std::string& key_id, const setup_data& metadata) = 0; + virtual void store_setup_data(const std::string& key_id, const setup_data& metadata, bool override) = 0; virtual void load_setup_data(const std::string& key_id, setup_data& metadata) = 0; virtual void store_setup_commitments(const std::string& key_id, const std::map& commitments) = 0; virtual void load_setup_commitments(const std::string& key_id, std::map& commitments) = 0; @@ -78,7 +79,7 @@ class COSIGNER_EXPORT cmp_setup_service final cmp_setup_service(platform_service& service, setup_key_persistency& key_persistency) : _service(service), _key_persistency(key_persistency) {} void generate_setup_commitments(const std::string& key_id, const std::string& tenant_id, cosigner_sign_algorithm algorithm, const std::vector& players_ids, uint8_t t, uint64_t ttl, const share_derivation_args& derive_from, commitment& setup_commitment); - void store_setup_commitments(const std::string& key_id, const std::map& commitments, setup_decommitment& decommitment); + void store_setup_commitments(const std::string& key_id, const std::map& commitments, const uint32_t version, setup_decommitment& decommitment); void generate_setup_proofs(const std::string& key_id, const std::map& decommitments, setup_zk_proofs& proofs); void verify_setup_proofs(const std::string& key_id, const std::map& proofs, std::map& paillier_large_factor_proofs); void create_secret(const std::string& key_id, const std::map>& paillier_large_factor_proofs, std::string& public_key, cosigner_sign_algorithm& algorithm); @@ -101,7 +102,7 @@ class COSIGNER_EXPORT cmp_setup_service final void ack_message(const std::map& commitments, commitments_sha256_t* ack); void verify_and_load_setup_decommitments(const std::string& key_id, const std::map& commitments, const std::map& decommitments, std::map& players_info); void generate_setup_proofs(const std::string& key_id, const elliptic_curve256_algebra_ctx_t* algebra, const setup_data& metadata, const commitments_sha256_t srid, setup_zk_proofs& proofs); - void verify_setup_proofs(const std::string& key_id, const cmp_key_metadata& metadata, const std::map& proofs); + void verify_setup_proofs(const std::string& key_id, const cmp_key_metadata& metadata, const std::map& proofs, const setup_data& temp_data); static std::vector build_aad(const std::string& sid, uint64_t id, const commitments_sha256_t srid); static inline elliptic_curve256_algebra_ctx_t* get_algebra(cosigner_sign_algorithm algorithm); diff --git a/include/cosigner/cosigner_exception.h b/include/cosigner/cosigner_exception.h index 71cf865..587f7b6 100644 --- a/include/cosigner/cosigner_exception.h +++ b/include/cosigner/cosigner_exception.h @@ -1,15 +1,16 @@ #pragma once -#include "cosigner_export.h" +#include +#include +#include "cosigner_export.h" #include "crypto/commitments/commitments.h" #include "crypto/commitments/ring_pedersen.h" #include "crypto/elliptic_curve_algebra/elliptic_curve_algebra_status.h" #include "crypto/shamir_secret_sharing/verifiable_secret_sharing.h" #include "crypto/zero_knowledge_proof/zero_knowledge_proof_status.h" - -#include -#include +#include "crypto/drng/drng.h" +#include "cosigner_status.h" namespace fireblocks { @@ -47,8 +48,6 @@ class COSIGNER_EXPORT cosigner_exception : public std::exception }; cosigner_exception(exception_code err) : _err(err) {} - ~cosigner_exception(); - const char* what() const noexcept override { switch (_err) @@ -56,7 +55,7 @@ class COSIGNER_EXPORT cosigner_exception : public std::exception case INTERNAL_ERROR: return "Internal error has occurred"; case INVALID_PARAMETERS: return "Invalid parameter"; case BAD_KEY: return "Wrong keyid was specified"; - case NOT_ALIGNED_DATA: return "Data to signed size must be a alligned to key size"; + case NOT_ALIGNED_DATA: return "Data to signed size must be a aligned to key size"; case NOT_IMPLEMENTED: return "Operation not yet implemented"; case UNKNOWN_ALGORITHM: return "Unknown signing algorithm was specified"; case INVALID_TRANSACTION: return "Invalid transaction id"; @@ -70,7 +69,7 @@ class COSIGNER_EXPORT cosigner_exception : public std::exception case INVALID_PRESIGNING_INDEX: return "The presigning info index is invalid"; case BAD_IMPORTED_PUBLIC_KEY: return "The imported public key is invalid"; case BAD_IMPORTED_PRIVATE_KEY: return "The imported private key is invalid or incorrectly encrypted"; - case BAD_IMPORTED_KEY_ALREADY_EXISTS: return "The specified key id already is already registered"; + case BAD_IMPORTED_KEY_ALREADY_EXISTS: return "The specified key id is already registered"; case NO_SIGNING_INFO_GIVEN: return "No signing info was given"; case PARTIAL_SIGNING_INFO_GIVEN: return "Partial signing info was given"; case GENERIC_ERROR: @@ -83,7 +82,7 @@ class COSIGNER_EXPORT cosigner_exception : public std::exception const exception_code _err; }; -class COSIGNER_EXPORT unknown_txid_exception : public std::exception +class unknown_txid_exception : public std::exception { public: unknown_txid_exception(const std::string& txid) : _txid(txid) {} @@ -92,13 +91,74 @@ class COSIGNER_EXPORT unknown_txid_exception : public std::exception const std::string _txid; }; -COSIGNER_EXPORT void throw_cosigner_exception(verifiable_secret_sharing_status status); -COSIGNER_EXPORT void throw_cosigner_exception(elliptic_curve_algebra_status status); -COSIGNER_EXPORT void throw_cosigner_exception(commitments_status status); -COSIGNER_EXPORT void throw_cosigner_exception(zero_knowledge_proof_status status); -COSIGNER_EXPORT void throw_paillier_exception(long status); -COSIGNER_EXPORT void throw_cosigner_exception(ring_pedersen_status status); +template +struct cosigner_exception_type_name +{ + static constexpr const bool implemented = false; +}; + +#define DECLARE_COSIGNER_EXCEPTION_TYPE_EX(TYPE, ANNOTATION) \ + ANNOTATION COSIGNER_EXPORT void do_throw_cosigner_exception(TYPE status); \ + template <> \ + struct cosigner_exception_type_name \ + { \ + static constexpr const bool implemented = true;\ + static constexpr const char* name = #TYPE;\ + } + +#define DECLARE_COSIGNER_EXCEPTION_TYPE(TYPE) DECLARE_COSIGNER_EXCEPTION_TYPE_EX(TYPE, ) + +enum paillier_dummy_error_code +{ + +}; + +DECLARE_COSIGNER_EXCEPTION_TYPE_EX(cosigner_exception::exception_code, [[noreturn]]); +DECLARE_COSIGNER_EXCEPTION_TYPE(verifiable_secret_sharing_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(elliptic_curve_algebra_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(commitments_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(zero_knowledge_proof_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(paillier_dummy_error_code); +DECLARE_COSIGNER_EXCEPTION_TYPE(ring_pedersen_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(drng_status); +DECLARE_COSIGNER_EXCEPTION_TYPE(cosigner_status_t); + +// use this function instead of including logging.h file directly. +// Otherwise will force the cosigner tests to link with the full logging stack. +COSIGNER_EXPORT void log_exception(const std::string& name, const std::string& what, const char* file, const char* func, const int line); + +#define throw_cosigner_exception(X) do \ + { \ + typedef std::remove_const::type>::type basa_type; \ + static_assert(::fireblocks::common::cosigner::cosigner_exception_type_name::implemented, "Must be implemented"); \ + basa_type ret(static_cast(-1)); \ + try \ + { \ + ret = (X); \ + ::fireblocks::common::cosigner::do_throw_cosigner_exception(ret);\ + } \ + catch(const std::exception& ex##__LINE__) \ + { \ + ::fireblocks::common::cosigner::log_exception(::fireblocks::common::cosigner::cosigner_exception_type_name::name, \ + (ex##__LINE__).what(), \ + __FILE__, \ + __PRETTY_FUNCTION__, \ + __LINE__); \ + throw; \ + } \ + catch(...) \ + { \ + ::fireblocks::common::cosigner::log_exception(::fireblocks::common::cosigner::cosigner_exception_type_name::name, \ + "", \ + __FILE__, \ + __PRETTY_FUNCTION__, \ + __LINE__); \ + throw; \ + } \ + } while(false) + +#define throw_paillier_exception(X) throw_cosigner_exception((paillier_dummy_error_code)(X)) } } -} +} \ No newline at end of file diff --git a/include/cosigner/cosigner_status.h b/include/cosigner/cosigner_status.h new file mode 100644 index 0000000..136a0b8 --- /dev/null +++ b/include/cosigner/cosigner_status.h @@ -0,0 +1,17 @@ +#pragma once + +typedef enum cosigner_status_t +{ + COSIGNER_STATUS_SUCCESS, + COSIGNER_STATUS_INVALID_PARAMETER, + COSIGNER_STATUS_BAD_PRIVATE_KEY, + COSIGNER_STATUS_BAD_KEY_LEN, + COSIGNER_STATUS_BAD_DATA_LEN, + COSIGNER_STATUS_PUBLIC_KEY_TOO_SMALL, + COSIGNER_STATUS_SIGNATURE_BLOCK_TOO_SMALL, + COSIGNER_STATUS_NOT_IMPLEMENTED, + COSIGNER_STATUS_UNKNOWN_ALGORITHM, + COSIGNER_STATUS_INTERNAL_ERROR +} cosigner_status; + + diff --git a/include/cosigner/eddsa_online_signing_service.h b/include/cosigner/eddsa_online_signing_service.h index ceed101..cf9e2b0 100644 --- a/include/cosigner/eddsa_online_signing_service.h +++ b/include/cosigner/eddsa_online_signing_service.h @@ -38,6 +38,8 @@ struct eddsa_signing_metadata std::vector sig_data; std::set signers_ids; uint32_t version; + int64_t timestamp; + commitments_map commitments; }; class COSIGNER_EXPORT eddsa_online_signing_service final @@ -46,27 +48,25 @@ class COSIGNER_EXPORT eddsa_online_signing_service final class signing_persistency { public: - virtual ~signing_persistency(); - virtual void store_signing_data(const std::string& txid, const eddsa_signing_metadata& data) = 0; - virtual void load_signing_data(const std::string& txid, eddsa_signing_metadata& data) const = 0; - virtual void update_signing_data(const std::string& txid, const eddsa_signing_metadata& data) = 0; - virtual void store_signing_commitments(const std::string& txid, const std::map>& commitments) = 0; - virtual void load_signing_commitments(const std::string& txid, std::map>& commitments) = 0; - virtual void delete_temporary_signing_data(const std::string& txid) = 0; - }; + virtual ~signing_persistency() = default; + virtual void store_eddsa_signing_data(const std::string& txid, const std::shared_ptr& data) = 0; + virtual std::shared_ptr load_eddsa_signing_data(const std::string& txid) const = 0; + virtual void update_eddsa_signing_data(const std::string& txid, const std::shared_ptr& data) = 0; + virtual void store_signing_commitments(const std::string& txid, const commitments_map& commitments) = 0; + virtual void load_signing_commitments(const std::string& txid, commitments_map& commitments) = 0; + virtual bool delete_eddsa_signing_data(const std::string& txid) = 0; +}; - eddsa_online_signing_service(platform_service& service, const cmp_key_persistency& key_persistency, signing_persistency& preprocessing_persistency); + eddsa_online_signing_service(platform_service& service, const cmp_key_persistency& key_persistency, signing_persistency& signing_persistency) : _service(service), _key_persistency(key_persistency), _signing_persistency(signing_persistency), _timing_map(service) {} void start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, std::vector& commitments); - uint64_t store_commitments(const std::string& txid, const std::map>& commitments, uint32_t version, std::vector& R); + uint64_t store_commitments(const std::string& txid, const commitments_map& commitments, uint32_t version, std::vector& R); uint64_t broadcast_si(const std::string& txid, const std::map>& Rs, std::vector& si); uint64_t get_eddsa_signature(const std::string& txid, const std::map>& s, std::vector& sig); - void cancel_signing(const std::string& request_id); + void cancel_signing(const std::string& txid); private: - static std::vector build_aad(const std::string& sid, uint64_t id, const commitments_sha256_t srid); - static elliptic_curve_scalar derivation_key_delta(const elliptic_curve256_algebra_ctx_t* algebra, const elliptic_curve256_point_t& public_key, const HDChaincode& chaincode, const std::vector& path, uint8_t split_factor); - + void calc_w(elliptic_curve_scalar& x, uint64_t my_id, const std::set& ids); platform_service& _service; const cmp_key_persistency& _key_persistency; signing_persistency& _signing_persistency; diff --git a/include/cosigner/key_persistency_base.h b/include/cosigner/key_persistency_base.h index 59db144..dd20fd6 100644 --- a/include/cosigner/key_persistency_base.h +++ b/include/cosigner/key_persistency_base.h @@ -8,7 +8,7 @@ struct key_metadata_base { key_metadata_base() = default; key_metadata_base(cosigner_sign_algorithm algo) : algorithm(algo) {} - elliptic_curve256_point_t public_key{0}; // public key of all playes + elliptic_curve256_point_t public_key{0}; // public key of all players cosigner_sign_algorithm algorithm{(cosigner_sign_algorithm)-1}; // signing algorithm }; diff --git a/include/cosigner/mpc_globals.h b/include/cosigner/mpc_globals.h index d26286f..71fe7d1 100644 --- a/include/cosigner/mpc_globals.h +++ b/include/cosigner/mpc_globals.h @@ -18,9 +18,11 @@ constexpr int MPC_STARK_VERSION = 7; constexpr int MPC_RAND_R_VERSION = 8; constexpr int MPC_ASYMMETRIC_EDDSA = 9; constexpr int MPC_REDISTRIBUTE_KEY = 10; -constexpr int MPC_BAM_ECDSA = 11; +constexpr int MPC_EXTENDED_MTA = 11; +constexpr int MPC_BAM_ECDSA_BETA = 12; +constexpr int MPC_BAM_ECDSA = 13; -constexpr int MPC_PROTOCOL_VERSION = MPC_REDISTRIBUTE_KEY; +constexpr int MPC_PROTOCOL_VERSION = MPC_BAM_ECDSA; } } diff --git a/include/cosigner/platform_service.h b/include/cosigner/platform_service.h index eb0bd75..2b00217 100644 --- a/include/cosigner/platform_service.h +++ b/include/cosigner/platform_service.h @@ -2,23 +2,21 @@ #include "cosigner_export.h" -#include "cosigner/types.h" - #include #include #include #include +#include +#include "cosigner/types.h" -namespace fireblocks -{ -namespace common -{ -namespace cosigner +namespace fireblocks::common::cosigner { struct cmp_key_metadata; struct auxiliary_keys; struct signing_data; +struct eddsa_signature_data; +struct bam_signing_properties; class COSIGNER_EXPORT platform_service { @@ -32,33 +30,49 @@ class COSIGNER_EXPORT platform_service virtual const std::string get_current_tenantid() const = 0; // returns the player id based on the key used virtual uint64_t get_id_from_keyid(const std::string& key_id) const = 0; - // derive a key share from a master seed defined by derive_from - virtual void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const = 0; - // encrypts a message for specific player, used to send unicast messages in the add user and key refresh flows - virtual byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const = 0; - // decrypts a message sent to the signer in the add user flow - virtual byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const = 0; - // called upon new key creation, if this function returns false the key will not be created - virtual bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) = 0; - // this is a callback to notify about a new signing request, this is a good point to verify the request data, this function should thow exception if the signing request is not authorized + // this is a callback to notify about a new signing request, this is a good point to verify the request data, this function should throw exception if the signing request is not authorized enum signing_type { MULTI_ROUND_SIGNATURE, // All signatures beside BAM SINGLE_ROUND_SIGNATURE // For now only BAM client }; - // this is a callback to notify about a new signing request, this is a good point to verify the request data, this function should thow exception if the signing request is not authorized - virtual void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) = 0; - // set the siging flags (as bitset of SIGNING_FLAGS) based on the metadata + virtual void on_start_signing(const std::string& key_id, + const std::string& txid, + const signing_data& data, + const std::string& metadata_json, + const std::set& players, + const signing_type signature_type) = 0; + + // function called to prepare key for the signature. Might be used to preload all key data into memory or to restore the key + virtual void prepare_for_signing(const std::string& key_id, const std::string tx_id) = 0; + + // set the signing flags (as bitset of SIGNING_FLAGS) based on the metadata virtual void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const = 0; + virtual void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const = 0; + virtual void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const = 0; + // derive a key share from a master seed defined by derive_from + virtual void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const = 0; + + virtual void report_signing_time(const std::string& /*algorithm*/, uint64_t /*sign_time*/, uint32_t /*num_blocks*/) const {} + // encrypts a message for specific player, used to send unicast messages in the add user and key refresh flows + // verify_modulus if set will contain expected modulus of the players key + // and will be verified before encryption + virtual byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const = 0; + // decrypts a message sent to the signer in the add user flow + virtual byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const = 0; + // called upon new key creation, if this function returns false the key will not be created + virtual bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) = 0; + // inform platform about key generation + virtual void mark_key_setup_in_progress(const std::string& key_id) const = 0; + virtual void clear_key_setup_in_progress(const std::string& key_id) const = 0; + // returns if the player id is a client device, used in asymmetric protocols virtual bool is_client_id(uint64_t player_id) const = 0; - // get the current time in millseconds since the Unix Epoch + // get the current time in milliseconds since the Unix Epoch virtual uint64_t now_msec() const { return 0; } }; } -} -} \ No newline at end of file diff --git a/include/cosigner/timing_map.h b/include/cosigner/timing_map.h index 9216171..9cf7525 100644 --- a/include/cosigner/timing_map.h +++ b/include/cosigner/timing_map.h @@ -1,5 +1,6 @@ #pragma once +#include "cosigner_export.h" #include #include #include @@ -11,7 +12,7 @@ namespace common namespace cosigner { -class TimingMap final +class COSIGNER_EXPORT TimingMap final { public: TimingMap(platform_service& platform_service); diff --git a/include/cosigner/types.h b/include/cosigner/types.h index 32ba709..6c31b80 100644 --- a/include/cosigner/types.h +++ b/include/cosigner/types.h @@ -8,10 +8,13 @@ #include #include - +#include +#include #include #include +#include #include +#include namespace fireblocks { @@ -22,6 +25,31 @@ namespace cosigner typedef std::vector byte_vector_t; +class byte_vector_ostreambuf : public std::streambuf +{ +public: + explicit byte_vector_ostreambuf(byte_vector_t& vec) : vec_(vec) {} + +protected: + std::streamsize xsputn(const char_type* s, std::streamsize count) override + { + vec_.insert(vec_.end(), reinterpret_cast(s), reinterpret_cast(s + count)); + return count; + } + + int_type overflow(int_type ch) override + { + if (ch != traits_type::eof()) + { + vec_.push_back(static_cast(ch)); + } + return ch; + } + +private: + byte_vector_t& vec_; +}; + struct elliptic_curve_point { elliptic_curve256_point_t data; @@ -35,13 +63,26 @@ struct elliptic_curve_scalar ~elliptic_curve_scalar() {OPENSSL_cleanse(&data, sizeof(elliptic_curve_scalar));} }; +template +class scalar_cleaner +{ + static_assert(std::is_trivially_destructible::value, "scalar_cleaner requires a trivially destructible type (plain array or POD)"); +public: + explicit scalar_cleaner(T& secret) : _secret(secret) {} + ~scalar_cleaner() {OPENSSL_cleanse(_secret, sizeof(_secret));} + scalar_cleaner(const scalar_cleaner&) = delete; + scalar_cleaner& operator=(const scalar_cleaner&) = delete; +private: + T& _secret; +}; + struct commitment { commitment() {OPENSSL_cleanse(&data, sizeof(commitments_commitment_t));} commitment(const commitments_commitment_t* hash) {memcpy(&data, hash, sizeof(commitments_commitment_t));} commitments_commitment_t data; }; - +using commitments_map = std::map>; struct share_derivation_args { std::string master_key_id; @@ -56,9 +97,10 @@ struct preprocessing_metadata uint32_t start_index; uint32_t count; commitments_sha256_t ack; + uint32_t version; }; -enum SIGNING_FLAGS +enum SIGNING_FLAGS { NONE = 0x00, POSITIVE_R = 0x01, diff --git a/src/common/crypto/algebra_utils/algebra_utils.h b/include/crypto/algebra_utils/algebra_utils.h similarity index 100% rename from src/common/crypto/algebra_utils/algebra_utils.h rename to include/crypto/algebra_utils/algebra_utils.h diff --git a/src/common/crypto/algebra_utils/status_convert.h b/include/crypto/algebra_utils/status_convert.h similarity index 100% rename from src/common/crypto/algebra_utils/status_convert.h rename to include/crypto/algebra_utils/status_convert.h diff --git a/include/crypto/commitments/commitments.h b/include/crypto/commitments/commitments.h index ba0bcf6..eb84305 100644 --- a/include/crypto/commitments/commitments.h +++ b/include/crypto/commitments/commitments.h @@ -31,10 +31,10 @@ typedef enum /* Creates commitment (SHA256) the data */ COSIGNER_EXPORT commitments_status commitments_create_commitment_for_data(const uint8_t *data, uint32_t data_len, commitments_commitment_t *commitment); -/* Verfies the data commitment (SHA256) */ +/* Verifies the data commitment (SHA256) */ COSIGNER_EXPORT commitments_status commitments_verify_commitment(const uint8_t *data, uint32_t data_len, const commitments_commitment_t *commitment); -/* Commitment context functions are usfull to create/verify commitment on scattered data */ +/* Commitment context functions are useful to create/verify commitment on scattered data */ /* Creates commitment context */ COSIGNER_EXPORT commitments_status commitments_ctx_commitment_new(commitments_ctx_t **ctx); @@ -47,10 +47,10 @@ COSIGNER_EXPORT commitments_status commitments_ctx_commitment_final(commitments_ COSIGNER_EXPORT commitments_status commitments_ctx_verify_new(commitments_ctx_t **ctx, const commitments_commitment_t *commitment); /* Updates commitment verification context with data */ COSIGNER_EXPORT commitments_status commitments_ctx_verify_update(commitments_ctx_t *ctx, const void *data, uint32_t data_len); -/* Verfies the commitment, and frees the commitment context */ +/* Verifies the commitment, and frees the commitment context */ COSIGNER_EXPORT commitments_status commitments_ctx_verify_final(commitments_ctx_t *ctx); -// frees the commitment context, usefull for breaking in the middle of commit/verify operation +// frees the commitment context, useful for breaking in the middle of commit/verify operation COSIGNER_EXPORT void commitments_ctx_free(commitments_ctx_t *ctx); #ifdef __cplusplus diff --git a/include/crypto/commitments/ring_pedersen.h b/include/crypto/commitments/ring_pedersen.h index d4c8913..642cb32 100644 --- a/include/crypto/commitments/ring_pedersen.h +++ b/include/crypto/commitments/ring_pedersen.h @@ -37,7 +37,7 @@ COSIGNER_EXPORT uint8_t *ring_pedersen_public_serialize(const ring_pedersen_publ COSIGNER_EXPORT ring_pedersen_public_t *ring_pedersen_public_deserialize(const uint8_t *buffer, uint32_t buffer_len); COSIGNER_EXPORT void ring_pedersen_free_public(ring_pedersen_public_t *pub); -COSIGNER_EXPORT const ring_pedersen_public_t* ring_pedersen_private_key_get_public(const ring_pedersen_private_t *priv); // the returned public pey must not be freed! +COSIGNER_EXPORT const ring_pedersen_public_t* ring_pedersen_private_key_get_public(const ring_pedersen_private_t *priv); // the returned public key must not be freed! COSIGNER_EXPORT uint8_t *ring_pedersen_private_serialize(const ring_pedersen_private_t *priv, uint8_t *buffer, uint32_t buffer_len, uint32_t *real_buffer_len); COSIGNER_EXPORT ring_pedersen_private_t *ring_pedersen_private_deserialize(const uint8_t *buffer, uint32_t buffer_len); COSIGNER_EXPORT void ring_pedersen_free_private(ring_pedersen_private_t *priv); diff --git a/include/crypto/ed25519_algebra/ed25519_algebra.h b/include/crypto/ed25519_algebra/ed25519_algebra.h index 7ebc09b..e89ebbb 100644 --- a/include/crypto/ed25519_algebra/ed25519_algebra.h +++ b/include/crypto/ed25519_algebra/ed25519_algebra.h @@ -65,7 +65,7 @@ COSIGNER_EXPORT elliptic_curve_algebra_status ed25519_algebra_le_to_be(ed25519_s COSIGNER_EXPORT elliptic_curve_algebra_status ed25519_algebra_be_to_le(ed25519_le_scalar_t *res, const ed25519_scalar_t *n); /* Calculates H(RAM) the hash of R || public key || message and reduces the result to ed25519 field */ COSIGNER_EXPORT elliptic_curve_algebra_status ed25519_calc_hram(const ed25519_algebra_ctx_t *ctx, ed25519_le_scalar_t *hram, const ed25519_point_t *R, const ed25519_point_t *public_key, const uint8_t *message, uint32_t message_size, uint8_t use_keccak); -/* Signs the message using the private key directly (without diriving the private key from the private seed) */ +/* Signs the message using the private key directly (without deriving the private key from the private seed) */ COSIGNER_EXPORT elliptic_curve_algebra_status ed25519_algebra_sign(const ed25519_algebra_ctx_t *ctx, const ed25519_scalar_t *private_key, const uint8_t *message, uint32_t message_size, uint8_t use_keccak, uint8_t signature[64]); /* Verifies the signature using the message and public_key key */ COSIGNER_EXPORT int ed25519_verify(const ed25519_algebra_ctx_t *ctx, const uint8_t *message, size_t message_len, const uint8_t signature[64], const uint8_t public_key[32], uint8_t use_keccak); diff --git a/include/crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h b/include/crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h index 775d9e4..d1e7f18 100644 --- a/include/crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h +++ b/include/crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h @@ -43,6 +43,14 @@ typedef elliptic_curve_algebra_status (*elliptic_curve256_inverse)(const struct typedef elliptic_curve_algebra_status (*elliptic_curve256_rand)(const struct elliptic_curve256_algebra_ctx *ctx, elliptic_curve256_scalar_t *res); typedef elliptic_curve_algebra_status (*elliptic_curve256_reduce)(const struct elliptic_curve256_algebra_ctx *ctx, elliptic_curve256_scalar_t *res, const elliptic_curve256_scalar_t *val); typedef elliptic_curve_algebra_status (*elliptic_curve256_hash_on_curve)(const struct elliptic_curve256_algebra_ctx *ctx, elliptic_curve256_point_t *res, const uint8_t *msg, uint32_t msg_len); +/* + * Validates that a point is a valid encoding and is NOT the point-at-infinity. + * Returns: + * - ELLIPTIC_CURVE_ALGEBRA_SUCCESS on valid, non-infinity points + * - ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT on infinity or invalid encodings + * - ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER on bad input/context + */ +typedef elliptic_curve_algebra_status (*elliptic_curve256_validate_non_infinity_point)(const struct elliptic_curve256_algebra_ctx *ctx, const elliptic_curve256_point_t *p); typedef struct elliptic_curve256_algebra_ctx { @@ -51,13 +59,13 @@ typedef struct elliptic_curve256_algebra_ctx int (*release)(struct elliptic_curve256_algebra_ctx *ctx); - /* Returns the gruop order, this is a const vaule 256bit long */ + /* Returns the group order, this is a const value 256bit long */ const uint8_t *(*order)(const struct elliptic_curve256_algebra_ctx *ctx); /* Returns the size (in bytes) needed to represent a point on the curve, size must be <= ELLIPTIC_CURVE_COMPRESSED_POINT_LEN */ uint8_t (*point_size)(const struct elliptic_curve256_algebra_ctx *ctx); - /* Returns the infinity point of the curve, this is a const vaule */ + /* Returns the infinity point of the curve, this is a const value */ const elliptic_curve256_point_t *(*infinity_point)(const struct elliptic_curve256_algebra_ctx *ctx); - + elliptic_curve256_validate_non_infinity_point validate_non_infinity_point; elliptic_curve256_generator_mul_data generator_mul_data; elliptic_curve256_verify verify; elliptic_curve256_verify_linear_combination verify_linear_combination; @@ -72,7 +80,7 @@ typedef struct elliptic_curve256_algebra_ctx elliptic_curve256_reduce reduce; elliptic_curve256_hash_on_curve hash_on_curve; - /* Returns the internal represantation of group order */ + /* Returns the internal representation of group order */ const struct bignum_st *(*order_internal)(const struct elliptic_curve256_algebra_ctx *ctx); } elliptic_curve256_algebra_ctx_t; diff --git a/include/crypto/paillier/paillier.h b/include/crypto/paillier/paillier.h index 909cf27..55efeaf 100644 --- a/include/crypto/paillier/paillier.h +++ b/include/crypto/paillier/paillier.h @@ -39,8 +39,8 @@ COSIGNER_EXPORT long paillier_verify_factorization_zkpok(const paillier_public_k COSIGNER_EXPORT long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uint8_t *aad, uint32_t aad_len, uint8_t *y, uint32_t y_len, uint32_t *y_real_len); COSIGNER_EXPORT long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t *aad, uint32_t aad_len, const uint8_t *y, uint32_t y_len); -COSIGNER_EXPORT long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *proof_real_len); -COSIGNER_EXPORT long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len); +COSIGNER_EXPORT long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, uint8_t compute_all_nth_roots, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *proof_real_len); +COSIGNER_EXPORT long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, uint8_t use_all_nth_roots, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len); COSIGNER_EXPORT long paillier_public_key_n(const paillier_public_key_t *pub, uint8_t *n, uint32_t n_len, uint32_t *n_real_len); COSIGNER_EXPORT uint32_t paillier_public_key_size(const paillier_public_key_t *pub); @@ -49,7 +49,7 @@ COSIGNER_EXPORT paillier_public_key_t *paillier_public_key_deserialize(const uin COSIGNER_EXPORT void paillier_free_public_key(paillier_public_key_t *pub); COSIGNER_EXPORT long paillier_private_key_n(const paillier_private_key_t *priv, uint8_t *n, uint32_t n_len, uint32_t *n_real_len); -COSIGNER_EXPORT const paillier_public_key_t* paillier_private_key_get_public(const paillier_private_key_t *priv); // the returned public pey must not be freed! +COSIGNER_EXPORT const paillier_public_key_t* paillier_private_key_get_public(const paillier_private_key_t *priv); // the returned public key must not be freed! COSIGNER_EXPORT uint8_t *paillier_private_key_serialize(const paillier_private_key_t *priv, uint8_t *buffer, uint32_t buffer_len, uint32_t *real_buffer_len); COSIGNER_EXPORT paillier_private_key_t *paillier_private_key_deserialize(const uint8_t *buffer, uint32_t buffer_len); COSIGNER_EXPORT void paillier_free_private_key(paillier_private_key_t *priv); @@ -61,17 +61,17 @@ COSIGNER_EXPORT long paillier_decrypt(const paillier_private_key_t *key, const u COSIGNER_EXPORT long paillier_decrypt_integer(const paillier_private_key_t *key, const uint8_t *ciphertext, uint32_t ciphertext_len, uint64_t *plaintext); // result = a + b -COSIGNER_EXPORT long paillier_add(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_ciphertext, uint32_t b_ciphertext_len, +COSIGNER_EXPORT long paillier_add(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_ciphertext, uint32_t b_ciphertext_len, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); COSIGNER_EXPORT long paillier_add_integer(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, uint64_t b, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); // result = a - b -COSIGNER_EXPORT long paillier_sub(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_ciphertext, uint32_t b_ciphertext_len, +COSIGNER_EXPORT long paillier_sub(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_ciphertext, uint32_t b_ciphertext_len, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); COSIGNER_EXPORT long paillier_sub_integer(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, uint64_t b, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); // result = a * b -COSIGNER_EXPORT long paillier_mul(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_plaintext, uint32_t b_plaintext_len, +COSIGNER_EXPORT long paillier_mul(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, const uint8_t *b_plaintext, uint32_t b_plaintext_len, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); COSIGNER_EXPORT long paillier_mul_integer(const paillier_public_key_t *key, const uint8_t *a_ciphertext, uint32_t a_ciphertext_len, uint64_t b, uint8_t *result, uint32_t result_len, uint32_t *result_real_len); diff --git a/include/crypto/paillier_commitment/paillier_commitment.h b/include/crypto/paillier_commitment/paillier_commitment.h index 7e3be7f..3f0d5d3 100644 --- a/include/crypto/paillier_commitment/paillier_commitment.h +++ b/include/crypto/paillier_commitment/paillier_commitment.h @@ -255,7 +255,7 @@ COSIGNER_EXPORT long paillier_commitment_paillier_blum_zkp_verify(const paillier /** * @brief return minimal size of "d" prime required for range_proof_paillier_large_factors_quadratic_zkp_generate() * - * This function requires initialized public key and returnes the minimal size in bits required for the "d" safe prime + * This function requires initialized public key and returns the minimal size in bits required for the "d" safe prime * * @param[in] pub Pointer to the Paillier public key. * @@ -319,8 +319,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status paillier_commitment_damgard_fujisaki const paillier_commitment_private_key_t *priv, const uint8_t* aad, const uint32_t aad_len, - const uint32_t challenge_bitlength, - uint8_t* serialized_proof, + uint8_t* serialized_proof, const uint32_t proof_len, uint32_t* proof_real_len); @@ -330,8 +329,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status paillier_commitment_damgard_fujisaki * @param pub Pointer to the public key. * @param aad Additional authenticated data for the ZKP. * @param aad_len Length of the additional authenticated data. - * @param challenge_bitlen Length of the ZKP challenge in bits. - * @param serialized_proof Buffer containing the serialized ZKP. + * @param serialized_proof Buffer containing the serialized ZKP. * @param proof_len Length of the buffer. * @return Status of the ZKP verification (success or error). */ @@ -339,7 +337,6 @@ COSIGNER_EXPORT zero_knowledge_proof_status paillier_commitment_damgard_fujisaki const paillier_commitment_public_key_t *pub, const uint8_t* aad, const uint32_t aad_len, - const uint32_t challenge_bitlen, const uint8_t* serialized_proof, const uint32_t proof_len); diff --git a/include/crypto/shamir_secret_sharing/verifiable_secret_sharing.h b/include/crypto/shamir_secret_sharing/verifiable_secret_sharing.h index 517d52e..667f0c8 100644 --- a/include/crypto/shamir_secret_sharing/verifiable_secret_sharing.h +++ b/include/crypto/shamir_secret_sharing/verifiable_secret_sharing.h @@ -43,22 +43,22 @@ typedef enum /* Splits the secret to n shares, so any subset t of them can reconstruct the secret */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_split(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *secret, uint32_t secret_len, uint8_t t, uint8_t n, verifiable_secret_sharing_t **shares); /* Splits the secret to n shares, so any subset t of them can reconstruct the secret using user provided ids for the users (instead of running index) - * ids must be an arry of size n */ + * ids must be an array of size n */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_split_with_custom_ids(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *secret, uint32_t secret_len, uint8_t t, uint8_t n, uint64_t *ids, verifiable_secret_sharing_t **shares); -/* Gets a spesific share (zero indexed) from initialized shares */ +/* Gets a specific share (zero indexed) from initialized shares */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_share(const verifiable_secret_sharing_t *shares, uint8_t index, shamir_secret_share_t *share); -/* Gets the ID of a spesific share (zero indexed) from initialized shares */ +/* Gets the ID of a specific share (zero indexed) from initialized shares */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_share_id(const verifiable_secret_sharing_t *shares, uint8_t index, uint64_t* id); -/* Gets a spesific share and it's zero knowledge proof (zero indexed) from initialized shares */ +/* Gets a specific share and its zero knowledge proof (zero indexed) from initialized shares */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_share_and_proof(const verifiable_secret_sharing_t *shares, uint8_t index, shamir_secret_share_t *share, elliptic_curve256_point_t *proof); /* Gets a commitment for all shares */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_shares_commitment(const verifiable_secret_sharing_t *shares, commitments_commitment_t *commitment); /* Gets the number of players e.g. n, -1 is returned if shares are invalid */ COSIGNER_EXPORT int verifiable_secret_sharing_get_number_of_players(const verifiable_secret_sharing_t *shares); -/* Gets the threshold of the of the schema e.g. t, -1 is returned if shares are invalid */ +/* Gets the threshold of of the schema e.g. t, -1 is returned if shares are invalid */ COSIGNER_EXPORT int verifiable_secret_sharing_get_threshold(const verifiable_secret_sharing_t *shares); /* Gets proofs for the polynom coefficients e.g. G^coef, proofs_count must be >= threshold */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_polynom_proofs(const verifiable_secret_sharing_t *shares, elliptic_curve256_point_t *proofs, uint8_t proofs_count); @@ -66,14 +66,14 @@ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_p COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_get_polynom_commitment(const verifiable_secret_sharing_t *shares, commitments_commitment_t *commitment); /* Reconstruct the secret using shares_count shares, the actual size of the share is optionally returned via out_secret_len - * if shares_count is less then the needed t shares wrong secret will be generated */ + * if shares_count is less than the needed t shares wrong secret will be generated */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_reconstruct(const elliptic_curve256_algebra_ctx_t *algebra, const shamir_secret_share_t *shares, uint8_t shares_count, uint8_t *secret, uint32_t secret_len, uint32_t *out_secret_len); -/* Verifies that share proof for share id id, is a vaild share for polynom represented by coefficient_proofs +/* Verifies that share proof for share id id, is a valid share for polynom represented by coefficient_proofs * The share proof it self should be authenticated using secp256k1_algebra_verify function * each share proof and coefficient proof should be verified using the pre given commitments and the verifiable_secret_sharing_verify_commitment func */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_verify_share(const elliptic_curve256_algebra_ctx_t *algebra, uint64_t id, const elliptic_curve256_point_t *share_proof, uint8_t threshold, const elliptic_curve256_point_t *coefficient_proofs); -/* Verfies the proofs commitment (SHA256) */ +/* Verifies the proofs commitment (SHA256) */ COSIGNER_EXPORT verifiable_secret_sharing_status verifiable_secret_sharing_verify_commitment(const elliptic_curve256_point_t *proofs, uint8_t proofs_count, const commitments_commitment_t *commitment); COSIGNER_EXPORT void verifiable_secret_sharing_free_shares(verifiable_secret_sharing_t *shares); diff --git a/include/crypto/zero_knowledge_proof/range_proofs.h b/include/crypto/zero_knowledge_proof/range_proofs.h index 89ff321..2509a76 100644 --- a/include/crypto/zero_knowledge_proof/range_proofs.h +++ b/include/crypto/zero_knowledge_proof/range_proofs.h @@ -64,7 +64,7 @@ typedef struct * @return Status of the zero-knowledge proof generation. */ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const paillier_ciphertext_t *ciphertext, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len); + const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const paillier_ciphertext_t *ciphertext, const uint8_t use_extended_seed, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len); /** * @brief Generates a Paillier encryption along with a range proof for the given exponent. @@ -82,7 +82,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_ * @return Status of the zero-knowledge proof generation. */ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_encrypt_with_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, paillier_with_range_proof_t **proof); + const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const uint8_t use_extended_seed, paillier_with_range_proof_t **proof); /** * @brief Verifies a range proof for Paillier encryption with a known exponent. @@ -96,30 +96,19 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_encrypt_with_ex * @param[in] aad_len Length of the additional authenticated data. * @param[in] public_point Pointer to the elliptic curve public point. * @param[in] proof Pointer to the Paillier ciphertext and range proof. + * @param[in] strict_ciphertext_length Verify that the ciphertext length is exactly same as n2 of the paillier * * @return Status of the zero-knowledge proof verification. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_point_t *public_point, const paillier_with_range_proof_t *proof); - -/** - * @brief Batch verification for range proofs of Paillier encryption with a known exponent. - * - * This function verifies multiple range proofs for Paillier ciphertexts with known public points in a batch. - * - * @param[in] ring_pedersen Pointer to the Ring Pedersen private parameters. - * @param[in] paillier Pointer to the Paillier public key. - * @param[in] algebra Pointer to the elliptic curve algebra context. - * @param[in] aad Pointer to additional authenticated data. - * @param[in] aad_len Length of the additional authenticated data. - * @param[in] batch_size Number of proofs to verify. - * @param[in] public_points Pointer to the array of elliptic curve public points. - * @param[in] proofs Pointer to the array of Paillier ciphertexts and range proofs. - * - * @return Status of the zero-knowledge proof batch verification. - */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_exponent_zkpok_batch_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, uint32_t batch_size, const elliptic_curve256_point_t *public_points, const paillier_with_range_proof_t *proofs); +COSIGNER_EXPORT zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_point_t *public_point, + const paillier_with_range_proof_t *proof, + const uint8_t strict_ciphertext_length, + const uint8_t use_extended_seed); /** * @brief Generates a Diffie-Hellman range proof for a relationship involving Paillier encryption. @@ -143,7 +132,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_exponent_zkpok_batch_ver */ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, const paillier_ciphertext_t *ciphertext, - uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len); + const uint8_t use_extended_seed, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len); /** * @brief Generates a Paillier encryption along with a Diffie-Hellman range proof. @@ -163,7 +152,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_gen * @return Status of the zero-knowledge proof generation. */ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, paillier_with_range_proof_t **proof); + const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, const uint8_t use_extended_seed, paillier_with_range_proof_t **proof); /** * @brief Verifies a Diffie-Hellman range proof for a relationship involving Paillier encryption. @@ -179,11 +168,21 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_encrypt_with_di * @param[in] A Pointer to the first elliptic curve point. * @param[in] B Pointer to the second elliptic curve point. * @param[in] proof Pointer to the Paillier ciphertext and range proof. + * @param[in] strict_ciphertext_length Verify that the ciphertext length is exactly same as n2 of the paillier * * @return Status of the zero-knowledge proof verification. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_point_t *public_point, const elliptic_curve256_point_t *A, const elliptic_curve256_point_t *B, const paillier_with_range_proof_t *proof); +COSIGNER_EXPORT zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_point_t *public_point, + const elliptic_curve256_point_t *A, + const elliptic_curve256_point_t *B, + const paillier_with_range_proof_t *proof, + const uint8_t strict_ciphertext_length, + const uint8_t use_extended_seed); /** * @brief Frees the memory associated with a Paillier ciphertext and range proof. @@ -203,13 +202,21 @@ COSIGNER_EXPORT void range_proof_free_paillier_with_range_proof(paillier_with_ra * @param[in] ring_pedersen Pointer to the Ring Pedersen public parameters. * @param[in] aad Pointer to additional authenticated data. * @param[in] aad_len Length of the additional authenticated data. + * @param[in] use_extended_seed Use extended * @param[out] serialized_proof Pointer to the buffer to store the serialized proof. * @param[in] proof_len Length of the proof buffer. * @param[out] real_proof_len Pointer to store the actual length of the generated proof. * * @return Status of the zero-knowledge proof generation. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(const paillier_private_key_t *priv, const ring_pedersen_public_t *ring_pedersen, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len); +COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(const paillier_private_key_t *priv, + const ring_pedersen_public_t *ring_pedersen, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + uint8_t *serialized_proof, + uint32_t proof_len, + uint32_t *real_proof_len); /** * @brief Verifies a range proof for Paillier encryption with large factors. @@ -220,17 +227,24 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_z * @param[in] ring_pedersen Pointer to the Ring Pedersen private parameters. * @param[in] aad Pointer to additional authenticated data. * @param[in] aad_len Length of the additional authenticated data. + * @param[in] use_extended_seed Use extended version of the seed * @param[in] serialized_proof Pointer to the serialized proof data. * @param[in] proof_len Length of the serialized proof. * * @return Status of the zero-knowledge proof verification. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const paillier_public_key_t *pub, const ring_pedersen_private_t *ring_pedersen, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len); +COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const paillier_public_key_t *pub, + const ring_pedersen_private_t *ring_pedersen, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + const uint8_t *serialized_proof, + uint32_t proof_len); /** * @brief return minimal size of "d" prime required for range_proof_paillier_large_factors_quadratic_zkp_generate() * - * This function requires initialized public key and returnes the minimal size in bits required for the "d" safe prime + * This function requires initialized public key and returns the minimal size in bits required for the "d" safe prime * * @param[in] pub Pointer to the Paillier public key. * @@ -246,7 +260,7 @@ COSIGNER_EXPORT uint32_t range_proof_paillier_large_factors_quadratic_zkp_comput * @param[in] priv Pointer to the Paillier private key. * @param[in] aad Pointer to additional authenticated data. * @param[in] aad_len Length of the additional authenticated data. - * @param[in] d_prime Optional large safe prime. If NULL is geven will be generated inside + * @param[in] d_prime Optional large safe prime. If NULL is given will be generated inside * @param[in] d_prime_len Length of d_prime * @param[out] serialized_proof Pointer to the buffer to store the serialized proof. * @param[in] proof_len Length of the proof buffer. @@ -299,14 +313,15 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_large_factors_q * * @return Status of the zero-knowledge proof generation. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, - const paillier_commitment_private_key_t *paillier, - const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, - const uint32_t aad_len, - const uint8_t* secret, - const uint32_t secret_len, - paillier_with_range_proof_t **proof); +COSIGNER_EXPORT zero_knowledge_proof_status paillier_commitment_encrypt_with_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, + const paillier_commitment_private_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t* secret, + const uint32_t secret_len, + const uint8_t use_extended_seed, + paillier_with_range_proof_t **proof); @@ -316,7 +331,7 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_commitment_expo * This function verifies the provided range proof for a small group Paillier ciphertext with a known public point. * * @param[in] damgard_fujisaki Pointer to the Damgård-Fujisaki private parameters. - * @param[in] paillier Pointer to the Paillier commitement public key. + * @param[in] paillier Pointer to the Paillier commitment public key. * @param[in] algebra Pointer to the elliptic curve algebra context. * @param[in] aad Pointer to additional authenticated data. * @param[in] aad_len Length of the additional authenticated data. @@ -325,13 +340,14 @@ COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_commitment_expo * * @return Status of the zero-knowledge proof verification. */ -COSIGNER_EXPORT zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verify(const damgard_fujisaki_private_t* damgard_fujisaki, - const paillier_commitment_public_key_t* paillier, - const elliptic_curve256_algebra_ctx_t* algebra, - const uint8_t* aad, - const uint32_t aad_len, - const elliptic_curve256_point_t* public_point, - const const_paillier_with_range_proof_t* proof); +COSIGNER_EXPORT zero_knowledge_proof_status paillier_commitment_exponent_zkpok_verify(const damgard_fujisaki_private_t* damgard_fujisaki, + const paillier_commitment_public_key_t* paillier, + const elliptic_curve256_algebra_ctx_t* algebra, + const uint8_t* aad, + const uint32_t aad_len, + const elliptic_curve256_point_t* public_point, + const const_paillier_with_range_proof_t* proof, + const uint8_t use_extended_seed); #ifdef __cplusplus } diff --git a/include/crypto/zero_knowledge_proof/schnorr.h b/include/crypto/zero_knowledge_proof/schnorr.h index 9601056..b82e6b8 100644 --- a/include/crypto/zero_knowledge_proof/schnorr.h +++ b/include/crypto/zero_knowledge_proof/schnorr.h @@ -19,7 +19,7 @@ typedef struct schnorr_zkp /* Creates schnorr zero knowledge proof for secret having public point public_data */ COSIGNER_EXPORT zero_knowledge_proof_status schnorr_zkp_generate(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *prover_id, uint32_t id_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_point_t *public_data, schnorr_zkp_t *proof); -/* Creates schnorr zero knowledge proof for raw secret and addtionaly returns the public point */ +/* Creates schnorr zero knowledge proof for raw secret and additionally returns the public point */ COSIGNER_EXPORT zero_knowledge_proof_status schnorr_zkp_generate_for_data(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *prover_id, uint32_t id_len, const uint8_t *secret, uint32_t secret_size, elliptic_curve256_point_t *public_data, schnorr_zkp_t *proof); /* Creates schnorr zero knowledge proof for secret having public point public_data using randomness such that R = g^randomness, a.k.a makriyannis schnorr zkp */ COSIGNER_EXPORT zero_knowledge_proof_status schnorr_zkp_generate_with_custom_randomness(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *prover_id, uint32_t id_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_point_t *public_data, diff --git a/include/utils/string_utils.h b/include/utils/string_utils.h new file mode 100644 index 0000000..7eb5ebc --- /dev/null +++ b/include/utils/string_utils.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +namespace fireblocks::common::utils +{ + +typedef std::vector byte_vector_t; + +template +class container_cleaner +{ +public: + [[nodiscard]] explicit container_cleaner(T& secret) : _secret(secret) {} + ~container_cleaner() {OPENSSL_cleanse(&_secret[0], _secret.size());} + + container_cleaner(const container_cleaner&) = delete; + container_cleaner& operator=(const container_cleaner&) = delete; + container_cleaner(container_cleaner&&) = delete; + container_cleaner& operator=(container_cleaner&&) = delete; + + T& operator*() { return _secret; } +private: + T& _secret; +}; + +using string_cleaner = container_cleaner; +using byte_vector_cleaner = container_cleaner; + +} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e3631ae..f4c328c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,6 +1,10 @@ set(COSIGNER_FILES blockchain/mpc/hd_derive.cpp + crypto/algebra_utils/algebra_utils.c + crypto/algebra_utils/status_convert.c crypto/commitments/commitments.c + crypto/commitments/damgard_fujisaki.c + crypto/commitments/pedersen.c crypto/commitments/ring_pedersen.c crypto/commitments/damgard_fujisaki.c crypto/commitments/pedersen.c @@ -11,7 +15,9 @@ set(COSIGNER_FILES crypto/keccak1600/keccak1600.c crypto/paillier/paillier_zkp.c crypto/paillier/paillier.c + crypto/paillier_commitment/paillier_commitment.c crypto/shamir_secret_sharing/verifiable_secret_sharing.c + crypto/zero_knowledge_proof/damgard_fujisaki_zkp.c crypto/zero_knowledge_proof/diffie_hellman_log.c crypto/zero_knowledge_proof/range_proofs.c crypto/zero_knowledge_proof/schnorr.c @@ -21,6 +27,11 @@ set(COSIGNER_FILES cosigner/asymmetric_eddsa_cosigner_client.cpp cosigner/asymmetric_eddsa_cosigner_server.cpp cosigner/asymmetric_eddsa_cosigner.cpp + cosigner/bam_well_formed_proof.cpp + cosigner/bam_ecdsa_cosigner_client.cpp + cosigner/bam_ecdsa_cosigner_server.cpp + cosigner/bam_ecdsa_cosigner.cpp + cosigner/bam_key_persistency_structures.cpp cosigner/cmp_ecdsa_offline_signing_service.cpp cosigner/cmp_ecdsa_online_signing_service.cpp cosigner/cmp_ecdsa_signing_service.cpp diff --git a/src/common/cosigner/asymmetric_eddsa_cosigner.cpp b/src/common/cosigner/asymmetric_eddsa_cosigner.cpp index fc28533..7dd9c77 100644 --- a/src/common/cosigner/asymmetric_eddsa_cosigner.cpp +++ b/src/common/cosigner/asymmetric_eddsa_cosigner.cpp @@ -13,15 +13,13 @@ namespace common namespace cosigner { -const std::unique_ptr asymmetric_eddsa_cosigner::_ctx(elliptic_curve256_new_ed25519_algebra(), elliptic_curve256_algebra_ctx_free); - -asymmetric_eddsa_cosigner::asymmetric_eddsa_cosigner(platform_service& cosigner_service, const cmp_key_persistency& key_persistency) : - _service(cosigner_service), _key_persistency(key_persistency) +asymmetric_eddsa_cosigner::asymmetric_eddsa_cosigner(platform_service& service, const cmp_key_persistency& key_persistency) : + _service(service), _key_persistency(key_persistency), _ctx(elliptic_curve256_new_ed25519_algebra(), elliptic_curve256_algebra_ctx_free) { if (!_ctx) { LOG_ERROR("Failed to create ed25519 algebra"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } } @@ -37,7 +35,7 @@ void asymmetric_eddsa_cosigner::derivation_key_delta(const elliptic_curve256_poi if (HD_DERIVE_SUCCESS != retval) { LOG_ERROR("Error deriving private key: %d", retval); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } memcpy(derived_pubkey, tmp_derived_pubkey, sizeof(ed25519_point_t)); diff --git a/src/common/cosigner/asymmetric_eddsa_cosigner_client.cpp b/src/common/cosigner/asymmetric_eddsa_cosigner_client.cpp index 0a92530..b3bc960 100644 --- a/src/common/cosigner/asymmetric_eddsa_cosigner_client.cpp +++ b/src/common/cosigner/asymmetric_eddsa_cosigner_client.cpp @@ -1,4 +1,5 @@ #include "cosigner/asymmetric_eddsa_cosigner_client.h" +#include "cosigner/eddsa_online_signing_service.h" #include "cosigner/cmp_key_persistency.h" #include "cosigner/cosigner_exception.h" #include "cosigner/platform_service.h" @@ -17,35 +18,10 @@ namespace common namespace cosigner { -template -static inline std::string HexStr(const T itbegin, const T itend) -{ - std::string rv; - static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - rv.reserve((itend-itbegin)*3); - for(T it = itbegin; it < itend; ++it) - { - unsigned char val = (unsigned char)(*it); - rv.push_back(hexmap[val>>4]); - rv.push_back(hexmap[val&15]); - } - - return rv; -} - -asymmetric_eddsa_cosigner_client::preprocessing_persistency::~preprocessing_persistency() -{ -} - -asymmetric_eddsa_cosigner_client::asymmetric_eddsa_cosigner_client(platform_service& cosigner_service, const cmp_key_persistency& key_persistency, preprocessing_persistency& preprocessing_persistency) : +asymmetric_eddsa_cosigner_client::asymmetric_eddsa_cosigner_client(platform_service& cosigner_service, cmp_key_persistency& key_persistency, preprocessing_persistency& preprocessing_persistency) : asymmetric_eddsa_cosigner(cosigner_service, key_persistency), _preprocessing_persistency(preprocessing_persistency) {} -asymmetric_eddsa_cosigner_client::~asymmetric_eddsa_cosigner_client() -{ -} - -void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std::string& tenant_id, const std::string& key_id, const std::string& request_id, uint32_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, +void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std::string& tenant_id, const std::string& key_id, const std::string& request_id, uint64_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, std::vector>& R_commitments) { LOG_INFO("Entering request id = %s", request_id.c_str()); @@ -53,7 +29,7 @@ void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std:: if (tenant_id.compare(_key_persistency.get_tenantid_from_keyid(key_id)) != 0) { LOG_ERROR("key id %s is not part of tenant %s", key_id.c_str(), tenant_id.c_str()); - throw cosigner_exception(cosigner_exception::UNAUTHORIZED); + throw_cosigner_exception(cosigner_exception::UNAUTHORIZED); } cmp_key_metadata metadata; _key_persistency.load_key_metadata(key_id, metadata, false); @@ -61,13 +37,13 @@ void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std:: if (metadata.algorithm != EDDSA_ED25519) { LOG_ERROR("Key %s was created for algorithm %d, not for ED25519 (%d)", key_id.c_str(), metadata.algorithm, EDDSA_ED25519); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (metadata.players_info.size() != players_ids.size()) { - LOG_ERROR("asymmetric eddsa protocol doesn't support threshold signatures, the key was created with %lu players and the signing reques is for %lu players", metadata.players_info.size(), players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("asymmetric eddsa protocol doesn't support threshold signatures, the key was created with %lu players and the signing request is for %lu players", metadata.players_info.size(), players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = players_ids.begin(); i != players_ids.end(); ++i) @@ -75,10 +51,16 @@ void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std:: if (metadata.players_info.find(*i) == metadata.players_info.end()) { LOG_ERROR("Player %" PRIu64 " is not part of key %s", *i, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } + if (count > 0 && start_index > UINT64_MAX - count) + { + LOG_ERROR("start_index + count overflow: start_index=%" PRIu64 " count=%" PRIu32, start_index, count); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + uint64_t my_id = _service.get_id_from_keyid(key_id); R_commitments.reserve(count); ed25519_algebra_ctx_t* ed25519 = (ed25519_algebra_ctx_t*)_ctx->ctx; @@ -87,18 +69,18 @@ void asymmetric_eddsa_cosigner_client::start_signature_preprocessing(const std:: for (size_t i = 0; i < count; i++) { size_t index = start_index + i; - ed25519_scalar_t k; + elliptic_curve_scalar k; ed25519_point_t R; - throw_cosigner_exception(ed25519_algebra_rand(ed25519, &k)); - throw_cosigner_exception(ed25519_algebra_generator_mul(ed25519, &R, &k)); + + throw_cosigner_exception(_ctx->rand(_ctx.get(), &k.data)); + throw_cosigner_exception(ed25519_algebra_generator_mul(ed25519, &R, &k.data)); R_commitments.push_back(commit_to_r(key_id, index, my_id, R)); - _preprocessing_persistency.store_preprocessed_data(key_id, index, k); - OPENSSL_cleanse(k, sizeof(ed25519_scalar_t)); + _preprocessing_persistency.store_preprocessed_data(key_id, index, k.data); } } uint64_t asymmetric_eddsa_cosigner_client::eddsa_sign_offline(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, uint64_t preprocessed_data_index, - const std::map& Rs, std::vector& partial_sigs) + const std::map>& Rs, std::vector& partial_sigs) { (void)players; LOG_INFO("Entering txid = %s", txid.c_str()); @@ -111,13 +93,13 @@ uint64_t asymmetric_eddsa_cosigner_client::eddsa_sign_offline(const std::string& if (players_ids.size() != metadata.n) { LOG_ERROR("got signing request for %lu players, but the key was created for %u/%u players", players_ids.size(), metadata.t, metadata.n); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (players_ids.size() < 2) { - LOG_ERROR("We can't do asymmetric signign with %lu < 2 players", players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("We can't do asymmetric signing with %lu < 2 players", players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = players_ids.begin(); i != players_ids.end(); ++i) @@ -125,21 +107,21 @@ uint64_t asymmetric_eddsa_cosigner_client::eddsa_sign_offline(const std::string& if (metadata.players_info.find(*i) == metadata.players_info.end()) { LOG_ERROR("player %" PRIu64 " is not part of key %s", *i, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } if (Rs.size() != (size_t)(metadata.t - 1)) { LOG_ERROR("got Rs from %lu players, but the key was created with t = %u players", players_ids.size(), metadata.t); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } size_t blocks = data.blocks.size(); if (blocks > MAX_BLOCKS_TO_SIGN) { LOG_ERROR("got too many blocks to sign %lu", blocks); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint64_t my_id = _service.get_id_from_keyid(key_id); @@ -150,49 +132,36 @@ uint64_t asymmetric_eddsa_cosigner_client::eddsa_sign_offline(const std::string& if (i->first == my_id) { LOG_ERROR("got Rs from myself"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } else if (metadata.players_info.find(i->first) == metadata.players_info.end()) { LOG_ERROR("got Rs from player %" PRIu64 " who is not part of key %s", i->first, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - else if (i->second.Rs.size() != blocks) + else if (i->second.size() != blocks) { - LOG_ERROR("got %lu Rs from player %" PRIu64 " but the siging request is for %lu blocks", i->second.Rs.size(), i->first, blocks); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("got %lu Rs from player %" PRIu64 " but the signing request is for %lu blocks", i->second.size(), i->first, blocks); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha, txid.data(), txid.size()); - SHA256_Update(&sha, &i->first, sizeof(uint64_t)); - for (size_t j = 0; j < i->second.Rs.size(); ++j) + if (i != first_player) { - SHA256_Update(&sha, i->second.Rs[j].data, sizeof(elliptic_curve256_point_t)); - if (i != first_player) + for (size_t j = 0; j < i->second.size(); ++j) { - if (memcmp(first_player->second.Rs[j].data, i->second.Rs[j].data, sizeof(elliptic_curve256_point_t)) != 0) + if (memcmp(first_player->second[j].data, i->second[j].data, sizeof(elliptic_curve256_point_t)) != 0) { LOG_ERROR("R indexed %lu from player %" PRIu64 " is different from player %" PRIu64 " R", j, i->first, first_player->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } } - - eddsa_commitment md; - SHA256_Final(md.data(), &sha); - if (md != i->second.R_commitment) - { - LOG_ERROR("R commitments %s from player %" PRIu64 " is different from claculated %s", HexStr(i->second.R_commitment.begin(), i->second.R_commitment.end()).c_str(), i->first, HexStr(md.begin(), md.end()).c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); - } } ed25519_algebra_ctx_t* ed25519 = (ed25519_algebra_ctx_t*)_ctx->ctx; - std::vector flags(blocks, 0); - _service.fill_signing_info_from_metadata(metadata_json, flags); + std::vector sig_data(blocks); + _service.fill_eddsa_signing_info_from_metadata(sig_data, metadata_json); elliptic_curve_scalar key; cosigner_sign_algorithm algo; @@ -200,31 +169,29 @@ uint64_t asymmetric_eddsa_cosigner_client::eddsa_sign_offline(const std::string& if (algo != EDDSA_ED25519 || metadata.algorithm != EDDSA_ED25519) { LOG_ERROR("Can't sign eddsa with this key (%u)", algo); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - partial_sigs.reserve(first_player->second.Rs.size()); - for (size_t i = 0; i < first_player->second.Rs.size(); ++i) + partial_sigs.reserve(first_player->second.size()); + for (size_t i = 0; i < first_player->second.size(); ++i) { elliptic_curve_scalar k; ed25519_point_t R; eddsa_signature sig; _preprocessing_persistency.load_preprocessed_data(key_id, preprocessed_data_index + i, k.data); throw_cosigner_exception(ed25519_algebra_generator_mul(ed25519, &sig.R, &k.data)); - throw_cosigner_exception(ed25519_algebra_add_points(ed25519, &R, &sig.R, (ed25519_point_t*)&first_player->second.Rs[i].data)); + throw_cosigner_exception(ed25519_algebra_add_points(ed25519, &R, &sig.R, (ed25519_point_t*)&first_player->second[i].data)); ed25519_point_t derived_public_key; ed25519_scalar_t delta; derivation_key_delta(metadata.public_key, data.chaincode, data.blocks[i].path, players_ids.size(), delta, derived_public_key); ed25519_le_scalar_t hram; - throw_cosigner_exception(ed25519_calc_hram(ed25519, &hram, &R, &derived_public_key, (const uint8_t*)data.blocks[i].data.data(), data.blocks[i].data.size(), flags[i] & EDDSA_KECCAK)); + throw_cosigner_exception(ed25519_calc_hram(ed25519, &hram, &R, &derived_public_key, (const uint8_t*)data.blocks[i].data.data(), data.blocks[i].data.size(), sig_data[i].flags & EDDSA_KECCAK)); elliptic_curve_scalar x; throw_cosigner_exception(ed25519_algebra_add_scalars(ed25519, &x.data, key.data, sizeof(elliptic_curve256_scalar_t), delta, sizeof(ed25519_scalar_t))); throw_cosigner_exception(ed25519_algebra_be_to_le(&x.data, &x.data)); throw_cosigner_exception(ed25519_algebra_be_to_le(&k.data, &k.data)); throw_cosigner_exception(ed25519_algebra_mul_add(ed25519, &sig.s, &hram, &x.data, &k.data)); throw_cosigner_exception(ed25519_algebra_le_to_be(&sig.s, &sig.s)); - ed25519_algebra_le_to_be(&hram, &hram); - ed25519_algebra_le_to_be(&x.data, &x.data); partial_sigs.push_back(sig); } diff --git a/src/common/cosigner/asymmetric_eddsa_cosigner_server.cpp b/src/common/cosigner/asymmetric_eddsa_cosigner_server.cpp index f39e8ef..ddc97c5 100644 --- a/src/common/cosigner/asymmetric_eddsa_cosigner_server.cpp +++ b/src/common/cosigner/asymmetric_eddsa_cosigner_server.cpp @@ -18,23 +18,10 @@ namespace common namespace cosigner { -class ed25519_scalar_cleaner -{ -public: - ed25519_scalar_cleaner(ed25519_scalar_t& secret) : _secret(secret) {} - ~ed25519_scalar_cleaner() {OPENSSL_cleanse(_secret, sizeof(_secret));} -private: - ed25519_scalar_t& _secret; -}; - -asymmetric_eddsa_cosigner_server::signing_persistency::~signing_persistency() -{ -} +asymmetric_eddsa_cosigner_server::asymmetric_eddsa_cosigner_server(platform_service& service, cmp_key_persistency& key_persistency, signing_persistency& signing_persistency) : + asymmetric_eddsa_cosigner(service, key_persistency), _signing_persistency(signing_persistency), _timing_map(service) {} -asymmetric_eddsa_cosigner_server::asymmetric_eddsa_cosigner_server(platform_service& cosigner_service, const cmp_key_persistency& key_persistency, signing_persistency& signing_persistency) : - asymmetric_eddsa_cosigner(cosigner_service, key_persistency), _signing_persistency(signing_persistency), _timing_map(cosigner_service) {} - -void asymmetric_eddsa_cosigner_server::store_presigning_data(const std::string& key_id, const std::string& request_id, uint32_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, +void asymmetric_eddsa_cosigner_server::store_presigning_data(const std::string& key_id, const std::string& request_id, uint64_t start_index, uint32_t count, uint32_t total_count, const std::set& players_ids, uint64_t sender, const std::vector& R_commitments) { LOG_INFO("Entering request id = %s", request_id.c_str()); @@ -44,27 +31,28 @@ void asymmetric_eddsa_cosigner_server::store_presigning_data(const std::string& if (R_commitments.size() != count) { LOG_ERROR("Got %lu commitments but the request is for %u blocks", R_commitments.size(), count); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } + + if (!_service.is_client_id(sender)) + { + LOG_ERROR("client id %" PRIu64 " is not a mobile device", sender); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + cmp_key_metadata metadata; _key_persistency.load_key_metadata(key_id, metadata, false); if (metadata.algorithm != EDDSA_ED25519) { LOG_ERROR("Key %s was created for algorithm %d, not for ED25519 (%d)", key_id.c_str(), metadata.algorithm, EDDSA_ED25519); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (metadata.players_info.size() != players_ids.size()) { - LOG_ERROR("asymmetric eddsa protocol doesn't support threshold signatures, the key was created with %lu players and the signing reques is for %lu players", metadata.players_info.size(), players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); - } - - if (!_service.is_client_id(sender)) - { - LOG_ERROR("client id %" PRIu64 " is not an mobile device", sender); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("asymmetric eddsa protocol doesn't support threshold signatures, the key was created with %lu players and the signing request is for %lu players", metadata.players_info.size(), players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = players_ids.begin(); i != players_ids.end(); ++i) @@ -72,16 +60,22 @@ void asymmetric_eddsa_cosigner_server::store_presigning_data(const std::string& if (metadata.players_info.find(*i) == metadata.players_info.end()) { LOG_ERROR("Player %" PRIu64 " is not part of key %s", *i, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (*i != sender && _service.is_client_id(*i)) { - LOG_ERROR("Key %s was created with more then one client device %" PRIu64 ", and sender %" PRIu64, key_id.c_str(), *i, sender); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Key %s was created with more than one client device %" PRIu64 ", and sender %" PRIu64, key_id.c_str(), *i, sender); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } + if (count > 0 && start_index > UINT64_MAX - count) + { + LOG_ERROR("start_index + count overflow: start_index=%" PRIu64 " count=%" PRIu32, start_index, count); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + _signing_persistency.create_preprocessed_data(key_id, total_count); for (size_t i = 0; i < count; i++) @@ -89,9 +83,12 @@ void asymmetric_eddsa_cosigner_server::store_presigning_data(const std::string& } void asymmetric_eddsa_cosigner_server::eddsa_sign_offline(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, uint64_t preprocessed_data_index, - std::vector& R_commitments, Rs_and_commitments& Rs) + std::vector& R_commitments, std::vector& Rs) { LOG_INFO("Entering txid = %s", txid.c_str()); + + _service.prepare_for_signing(key_id, txid); + verify_tenant_id(_service, _key_persistency, key_id); cmp_key_metadata metadata; @@ -100,20 +97,20 @@ void asymmetric_eddsa_cosigner_server::eddsa_sign_offline(const std::string& key if (metadata.algorithm != EDDSA_ED25519) { LOG_ERROR("key %s has algorithm %d, but the request is for eddsa", key_id.c_str(), metadata.algorithm); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } // CMP key are created using additive secret sharing, so t must be equal to n, see cmp_setup_service::generate_setup_commitments if (players_ids.size() != metadata.n) { LOG_ERROR("got signing request for %lu players, but the key was created for %u/%u players", players_ids.size(), metadata.t, metadata.n); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (players_ids.size() < 2) { - LOG_ERROR("We can't do asymmetric signign with %lu < 2 players", players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("We can't do asymmetric signing with %lu < 2 players", players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = players_ids.begin(); i != players_ids.end(); ++i) @@ -121,16 +118,17 @@ void asymmetric_eddsa_cosigner_server::eddsa_sign_offline(const std::string& key if (metadata.players_info.find(*i) == metadata.players_info.end()) { LOG_ERROR("playerid %" PRIu64 " not part of key, for keyid = %s, txid = %s", *i, key_id.c_str(), txid.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } _service.on_start_signing(key_id, txid, data, metadata_json, players, platform_service::MULTI_ROUND_SIGNATURE); + size_t blocks = data.blocks.size(); if (blocks > MAX_BLOCKS_TO_SIGN) { LOG_ERROR("got too many blocks to sign %lu", blocks); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } _timing_map.insert(txid); @@ -138,42 +136,29 @@ void asymmetric_eddsa_cosigner_server::eddsa_sign_offline(const std::string& key uint64_t my_id = _service.get_id_from_keyid(key_id); LOG_INFO("Starting signing process keyid = %s, txid = %s", key_id.c_str(), txid.c_str()); - asymmetric_eddsa_signing_metadata info = {key_id}; + asymmetric_eddsa_signing_metadata info = { {key_id} }; memcpy(info.chaincode, data.chaincode, sizeof(HDChaincode)); info.signers_ids.insert(players_ids.begin(), players_ids.end()); info.version = common::cosigner::MPC_PROTOCOL_VERSION; info.start_index = preprocessed_data_index; R_commitments.reserve(blocks); - Rs.Rs.reserve(blocks); + Rs.reserve(blocks); info.sig_data.reserve(blocks); - elliptic_curve_algebra_status status = ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR; - elliptic_curve256_scalar_t k; - + for (size_t i = 0; i < blocks; i++) { - size_t j = 0; - while (j < 1024 && status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - { - j++; - status = _ctx->rand(_ctx.get(), &k); - } - - if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - { - LOG_ERROR("Failed to generate k"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); - } - - asymmetric_eddsa_signature_data sigdata; - throw_cosigner_exception(_ctx->generator_mul(_ctx.get(), &sigdata.R.data, &k)); - throw_cosigner_exception(ed25519_algebra_be_to_le(&sigdata.k.data, &k)); + elliptic_curve_scalar k; + eddsa_signature_data sigdata; + throw_cosigner_exception(_ctx->rand(_ctx.get(), &k.data)); + throw_cosigner_exception(_ctx->generator_mul(_ctx.get(), &sigdata.R.data, &k.data)); + throw_cosigner_exception(ed25519_algebra_be_to_le(&sigdata.k.data, &k.data)); if (metadata.n == 2) { LOG_INFO("Doing MPC 2/2 no need to send commitments"); - Rs.Rs.push_back(sigdata.R); + Rs.push_back(sigdata.R); } else { @@ -189,17 +174,9 @@ void asymmetric_eddsa_cosigner_server::eddsa_sign_offline(const std::string& key } if (metadata.n > 2) { - Rs.Rs.clear(); - } - else - { - commit_to_Rs(txid, my_id, Rs.Rs, Rs.R_commitment); + Rs.clear(); } - OPENSSL_cleanse(k, sizeof(elliptic_curve256_scalar_t)); - std::vector flags(blocks, 0); - _service.fill_signing_info_from_metadata(metadata_json, flags); - for (size_t i = 0; i < blocks; i++) - info.sig_data[i].flags = flags[i]; + _service.fill_eddsa_signing_info_from_metadata(info.sig_data, metadata_json); _signing_persistency.store_signing_data(txid, info, false); } @@ -215,15 +192,15 @@ uint64_t asymmetric_eddsa_cosigner_server::decommit_r(const std::string& txid, c if (data.signers_ids.size() - 1 != commitments.size()) { - LOG_ERROR("commitments size %lu is different then expected size %lu", commitments.size(), data.signers_ids.size() - 1); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("commitments size %lu is different than expected size %lu", commitments.size(), data.signers_ids.size() - 1); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) { if (!_service.is_client_id(*i) && commitments.find(*i) == commitments.end()) { LOG_ERROR("commitment for player %" PRIu64 " not found in commitments list", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } for (auto i = commitments.begin(); i != commitments.end(); ++i) @@ -231,7 +208,7 @@ uint64_t asymmetric_eddsa_cosigner_server::decommit_r(const std::string& txid, c if (i->second.size() != data.sig_data.size()) { LOG_ERROR("commitment for player %" PRIu64 " size %lu is different from block size %lu", i->first, i->second.size(), data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -244,7 +221,7 @@ uint64_t asymmetric_eddsa_cosigner_server::decommit_r(const std::string& txid, c if (!verify_commit_to_r(my_commit->second[i], txid, i + data.start_index, my_id, data.sig_data[i].R.data)) { LOG_ERROR("Failed to verify my commitment to block %lu", i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } Rs.push_back(data.sig_data[i].R); } @@ -253,10 +230,10 @@ uint64_t asymmetric_eddsa_cosigner_server::decommit_r(const std::string& txid, c return my_id; } -uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, const std::map>& players_R, Rs_and_commitments& Rs, uint64_t& send_to) +uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, const std::map>& players_R, std::vector& Rs, uint64_t& send_to) { LOG_INFO("Entering txid = %s", txid.c_str()); - Rs.Rs.clear(); + Rs.clear(); asymmetric_eddsa_signing_metadata data; _signing_persistency.load_signing_data(txid, data); verify_tenant_id(_service, _key_persistency, data.key_id); @@ -265,8 +242,8 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, if (data.signers_ids.size() - 1 != players_R.size()) { - LOG_ERROR("Rs size %lu is different then expected size %lu", players_R.size(), data.signers_ids.size() - 1); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Rs size %lu is different than expected size %lu", players_R.size(), data.signers_ids.size() - 1); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) { @@ -275,7 +252,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, else if (players_R.find(*i) == players_R.end()) { LOG_ERROR("Rs for player %" PRIu64 " not found in Rs list", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } for (auto i = players_R.begin(); i != players_R.end(); ++i) @@ -283,7 +260,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, if (i->second.size() != data.sig_data.size()) { LOG_ERROR("Rs for player %" PRIu64 " size %lu is different from block size %lu", i->first, i->second.size(), data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -295,7 +272,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, if (it == players_R.end()) { LOG_ERROR("R from player %" PRIu64 " missing", i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (size_t j = 0; j < data.sig_data.size(); ++j) @@ -303,7 +280,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, if (!verify_commit_to_r(i->second[j], txid, j + data.start_index, i->first, it->second[j].data)) { LOG_ERROR("Failed to verify commitment from player %" PRIu64 " to block %lu", i->first, j); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (i->first != my_id) @@ -314,10 +291,9 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_r(const std::string& txid, } _signing_persistency.delete_commitments(txid); - Rs.Rs.reserve(data.sig_data.size()); + Rs.reserve(data.sig_data.size()); for (size_t i = 0; i < data.sig_data.size(); ++i) - Rs.Rs.push_back(data.sig_data[i].R); - commit_to_Rs(txid, my_id, Rs.Rs, Rs.R_commitment); + Rs.push_back(data.sig_data[i].R); _signing_persistency.store_signing_data(txid, data, true); return my_id; @@ -334,16 +310,22 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, const uint64_t my_id = _service.get_id_from_keyid(data.key_id); + if (!_service.is_client_id(sender)) + { + LOG_ERROR("client id %" PRIu64 " is not a mobile device", sender); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + if (data.signers_ids.find(sender) == data.signers_ids.end()) { LOG_ERROR("player %" PRIu64 " is not part of signers list", sender); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (partial_sigs.size() != data.sig_data.size()) { LOG_ERROR("partial sigs from player %" PRIu64 " size %lu is different from block size %lu", sender, partial_sigs.size(), data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ed25519_algebra_ctx_t* ed25519 = (ed25519_algebra_ctx_t*)_ctx->ctx; @@ -355,7 +337,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, { LOG_ERROR("player %" PRIu64 " is not part of key %s", sender, data.key_id.c_str()); assert(0); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } elliptic_curve_scalar key; @@ -364,7 +346,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, if (algo != EDDSA_ED25519) { LOG_ERROR("Can't sign eddsa with this key (%u)", algo); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } const bool final_sig = data.signers_ids.size() == 2; @@ -385,7 +367,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, if (!verify_commit_to_r(commitment, data.key_id, i + data.start_index, sender, partial_sigs[i].R)) { LOG_ERROR("Failed to verify commitment from player %" PRIu64 " to block %lu", sender, i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } eddsa_signature sig; throw_cosigner_exception(ed25519_algebra_add_points(ed25519, &sig.R, (ed25519_point_t*)&data.sig_data[i].R.data, &partial_sigs[i].R)); @@ -398,11 +380,11 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, if (!verify_client_s(partial_sigs[i].R, partial_sigs[i].s, hram, sender_info->second.public_share, delta)) { LOG_ERROR("Failed to verify the signature s sent by client %" PRIu64 " for block %lu txid %s", sender, i, txid.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ed25519_scalar_t x; throw_cosigner_exception(ed25519_algebra_add_scalars(ed25519, &x, key.data, sizeof(elliptic_curve256_scalar_t), delta, sizeof(ed25519_scalar_t))); - ed25519_scalar_cleaner xcleaner(x); + scalar_cleaner xcleaner(x); throw_cosigner_exception(ed25519_algebra_be_to_le(&x, &x)); throw_cosigner_exception(ed25519_algebra_mul_add(ed25519, &sig.s, &hram, &x, &data.sig_data[i].k.data)); @@ -423,7 +405,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, else { LOG_FATAL("failed to verify signature for block %lu", i); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } else @@ -438,20 +420,7 @@ uint64_t asymmetric_eddsa_cosigner_server::broadcast_si(const std::string& txid, } if (final_sig) - { - _signing_persistency.delete_temporary_signing_data(txid); - - const std::optional diff = _timing_map.extract(txid); - if (!diff) - { - LOG_WARN("transaction %s is missing from timing map??", txid.c_str()); - LOG_INFO("Finished signing transaction %s", txid.c_str()); - } - else - { - LOG_INFO("Finished signing %lu blocks for transaction %s (tenant %s) in %" PRIu64 "ms", data.signers_ids.size(), txid.c_str(), _service.get_current_tenantid().c_str(), *diff); - } - } + finalize_signing(txid, sigs.size()); else _signing_persistency.store_signing_data(txid, data, true); final_signature = final_sig; @@ -471,7 +440,7 @@ uint64_t asymmetric_eddsa_cosigner_server::get_eddsa_signature(const std::string if (partial_sigs.size() != data.signers_ids.size() - 1) { LOG_ERROR("got wrong number of s, got %lu expected %lu", partial_sigs.size(), data.signers_ids.size() - 1); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) @@ -482,20 +451,21 @@ uint64_t asymmetric_eddsa_cosigner_server::get_eddsa_signature(const std::string if (it == partial_sigs.end()) { LOG_ERROR("partial sig for player %" PRIu64 " not found in Rs list", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (it->second.size() != data.sig_data.size()) { LOG_ERROR("number of s (%lu) from player %" PRIu64 " is different from block size %lu", it->second.size(), *i, data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } + sigs.clear(); ed25519_algebra_ctx_t *ed25519 = (ed25519_algebra_ctx_t*)_ctx->ctx; cmp_key_metadata metadata; _key_persistency.load_key_metadata(data.key_id, metadata, false); - + for (size_t index = 0; index < data.sig_data.size(); ++index) { eddsa_signature cur_sig; @@ -506,7 +476,7 @@ uint64_t asymmetric_eddsa_cosigner_server::get_eddsa_signature(const std::string if (memcmp(cur_sig.R, i->second[index].R, sizeof(ed25519_point_t)) != 0) { LOG_ERROR("R from player %" PRIu64 " is different from stored R", i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ed25519_le_scalar_t s; throw_cosigner_exception(ed25519_algebra_be_to_le(&s, &i->second[index].s)); @@ -519,7 +489,7 @@ uint64_t asymmetric_eddsa_cosigner_server::get_eddsa_signature(const std::string if (derivation_status != HD_DERIVE_SUCCESS) { LOG_ERROR("failed to derive public key for block %lu, error %d", index, derivation_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } unsigned char raw_sig[64]; memcpy(raw_sig, cur_sig.R, 32); @@ -531,25 +501,13 @@ uint64_t asymmetric_eddsa_cosigner_server::get_eddsa_signature(const std::string else { LOG_FATAL("failed to verify signature for block %lu", index); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } sigs.push_back(cur_sig); } - _signing_persistency.delete_temporary_signing_data(txid); - - const std::optional diff = _timing_map.extract(txid); - if (!diff) - { - LOG_WARN("transaction %s is missing from timing map??", txid.c_str()); - LOG_INFO("Finished signing transaction %s", txid.c_str()); - } - else - { - LOG_INFO("Finished signing %lu blocks for transaction %s (tenant %s) in %" PRIu64 "ms", sigs.size(), txid.c_str(), _service.get_current_tenantid().c_str(), *diff); - } - + finalize_signing(txid, sigs.size()); return my_id; } @@ -571,23 +529,21 @@ bool asymmetric_eddsa_cosigner_server::verify_client_s(const ed25519_point_t& R, return memcmp(p1, p2, sizeof(elliptic_curve256_point_t)) == 0; } -void asymmetric_eddsa_cosigner_server::cancel_signing(const std::string& txid) +void asymmetric_eddsa_cosigner_server::finalize_signing(const std::string& txid, size_t count) { - _signing_persistency.delete_commitments(txid); - _signing_persistency.delete_temporary_signing_data(txid); -} + _signing_persistency.delete_signing_data(txid); -void asymmetric_eddsa_cosigner_server::commit_to_Rs(const std::string& txid, uint64_t id, const std::vector& Rs, eddsa_commitment& commitment) -{ - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha, txid.data(), txid.size()); - SHA256_Update(&sha, &id, sizeof(uint64_t)); - for (size_t i = 0; i < Rs.size(); ++i) + const std::optional diff = _timing_map.extract(txid); + if (!diff) + { + LOG_INFO("Finished signing transaction %s", txid.c_str()); + } + else { - SHA256_Update(&sha, Rs[i].data, sizeof(elliptic_curve256_point_t)); + LOG_INFO("Finished signing %lu blocks for transaction %s (tenant %s) in %" PRIu64 "ms", count, txid.c_str(), _service.get_current_tenantid().c_str(), *diff); + static const std::string ALGORITHM("asymmetric EdDSA"); + _service.report_signing_time(ALGORITHM, *diff, count); } - SHA256_Final(commitment.data(), &sha); } } diff --git a/src/common/cosigner/bam_ecdsa_cosigner.cpp b/src/common/cosigner/bam_ecdsa_cosigner.cpp new file mode 100644 index 0000000..764a086 --- /dev/null +++ b/src/common/cosigner/bam_ecdsa_cosigner.cpp @@ -0,0 +1,355 @@ +#include "cosigner/bam_ecdsa_cosigner.h" +#include "cosigner/cosigner_exception.h" +#include +#include +#include +#include "logging/logging_t.h" +#include +#include "crypto/common/byteswap.h" +#include "blockchain/mpc/hd_derive.h" +#include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "cosigner/platform_service.h" +#include "cosigner/bam_key_persistency_structures.h" +#include "cosigner/bam_tx_persistency_structures.h" +#include "cosigner_bn.h" + +namespace fireblocks::common::cosigner +{ + + +static constexpr const char SETUP_AAD_PREFIX[] = "BAM_ECDSA_COSIGNER_SETUP_AAD_"; +static const std::string BAM_ECDSA_SALT_KEY_GEN("BAM ECDSA Key Generation AAD"); +static const std::string BAM_ECDSA_SALT_SIGNATURE("BAM ECDSA Signature AAD"); + +bam_ecdsa_cosigner::bam_ecdsa_cosigner(platform_service& platform_service) : + _platform_service(platform_service), + _secp256k1(elliptic_curve256_new_secp256k1_algebra(), elliptic_curve256_algebra_ctx_free), + _secp256r1(elliptic_curve256_new_secp256r1_algebra(), elliptic_curve256_algebra_ctx_free), + _stark(elliptic_curve256_new_stark_algebra(), elliptic_curve256_algebra_ctx_free) +{ + if (!_secp256k1 || !_secp256r1 || !_stark) + { + throw cosigner_exception(cosigner_exception::NO_MEM); + } +} + +std::vector bam_ecdsa_cosigner::fill_bam_signing_info_from_metadata(const std::string& metadata, const uint32_t blocks_num) +{ + std::vector signature_request_data(blocks_num); + _platform_service.fill_bam_signing_info_from_metadata(signature_request_data, metadata); + return signature_request_data; +} + +byte_vector_t bam_ecdsa_cosigner::generate_setup_aad_bytes(const std::string& setup_id, const cosigner_sign_algorithm algorithm) const +{ + byte_vector_t setup_aad; + setup_aad.reserve(sizeof(SETUP_AAD_PREFIX) - 1 + setup_id.size() + sizeof(algorithm) + sizeof(elliptic_curve256_point_t)); + + // Add prefix + setup_aad.insert(setup_aad.end(), SETUP_AAD_PREFIX, SETUP_AAD_PREFIX + sizeof(SETUP_AAD_PREFIX) - 1); + + // Add setup ID + setup_aad.insert(setup_aad.end(), setup_id.begin(), setup_id.end()); + + // Add algorithm + const char* algo_ptr = reinterpret_cast(&algorithm); + setup_aad.insert(setup_aad.end(), algo_ptr, algo_ptr + sizeof(algorithm)); + + return setup_aad; +} + +void bam_ecdsa_cosigner::generate_aad_for_key_gen(const std::string& key_id, const uint64_t client_id, const uint64_t server_id, commitments_sha256_t& key_aad) +{ + SHA256_CTX hash_ctx; + SHA256_Init(&hash_ctx); + SHA256_Update(&hash_ctx, BAM_ECDSA_SALT_KEY_GEN.c_str(), BAM_ECDSA_SALT_KEY_GEN.size()); + SHA256_Update(&hash_ctx, key_id.c_str(), key_id.size()); + SHA256_Update(&hash_ctx, &client_id, sizeof(client_id)); + SHA256_Update(&hash_ctx, &server_id, sizeof(server_id)); + SHA256_Final(key_aad, &hash_ctx); +} + +void bam_ecdsa_cosigner::generate_aad_for_signature(const std::string& key_id, const uint64_t server_id, const uint64_t client_id, const std::string& tx_id, commitments_sha256_t& signature_add) +{ + SHA256_CTX hash_ctx; + SHA256_Init(&hash_ctx); + // seed contains key_id, server_id, client_id and tx_id + SHA256_Update(&hash_ctx, BAM_ECDSA_SALT_SIGNATURE.c_str(), BAM_ECDSA_SALT_SIGNATURE.size()); + SHA256_Update(&hash_ctx, key_id.c_str(), key_id.size()); + SHA256_Update(&hash_ctx, tx_id.c_str(), tx_id.size()); + SHA256_Update(&hash_ctx, &server_id, sizeof(server_id)); + SHA256_Update(&hash_ctx, &client_id, sizeof(client_id)); + SHA256_Final(signature_add, &hash_ctx); +} + +void bam_ecdsa_cosigner::generate_key_commitment(const commitments_sha256_t& seed, + const elliptic_curve256_point_t& public_key, + commitments_sha256_t& B) +{ + SHA256_CTX hash_ctx; + SHA256_Init(&hash_ctx); + // seed contains key_id, server_id and client id + SHA256_Update(&hash_ctx, seed, sizeof(commitments_sha256_t)); + SHA256_Update(&hash_ctx, public_key, sizeof(elliptic_curve256_point_t)); + SHA256_Final(B, &hash_ctx); +} + +void bam_ecdsa_cosigner::check_non_null_message(const elliptic_curve256_scalar_t& message, const elliptic_curve256_algebra_ctx_t* algebra) +{ + static const elliptic_curve256_scalar_t zero = {0}; + elliptic_curve256_scalar_t tmp; + // reduce in the scalar field, to check whether it's zero or not. + throw_cosigner_exception(algebra->add_scalars(algebra, &tmp, message, sizeof(elliptic_curve256_scalar_t), zero, 1)); + + if (memcmp(tmp, zero, sizeof(elliptic_curve256_scalar_t)) == 0) + { + throw cosigner_exception(cosigner_exception::INVALID_TRANSACTION); + } +} + +void bam_ecdsa_cosigner::check_a_valid_point(const elliptic_curve256_point_t& point, const elliptic_curve256_algebra_ctx_t* algebra) +{ + const elliptic_curve_algebra_status st = algebra->validate_non_infinity_point(algebra, &point); + if (st != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) + { + LOG_ERROR("Unexpected invalid point received"); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } +} + +void bam_ecdsa_cosigner::compute_hash_shift(const std::string& tx_id, + const elliptic_curve256_scalar_t& hash_to_sign, + const elliptic_curve256_point_t& ephemeral_common_key, + const elliptic_curve256_point_t& client_ephemeral_key, + const elliptic_curve256_point_t& public_key, + elliptic_curve256_scalar_t& hash_shift) +{ + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha, tx_id.data(), tx_id.size()); + SHA256_Update(&sha, public_key, sizeof(elliptic_curve256_point_t)); + SHA256_Update(&sha, client_ephemeral_key, sizeof(elliptic_curve256_point_t)); + SHA256_Update(&sha, ephemeral_common_key, sizeof(elliptic_curve256_point_t)); + SHA256_Update(&sha, hash_to_sign, sizeof(elliptic_curve256_scalar_t)); + SHA256_Final(&hash_shift[0], &sha); +} + +void bam_ecdsa_cosigner::bignum_clear_deleter::operator()(struct bignum_st* bn) +{ + BN_clear_free(bn); +} + +void bam_ecdsa_cosigner::bignum_clear::operator()(struct bignum_st* bn) +{ + BN_clear(bn); +} + + + +void bam_ecdsa_cosigner::validate_current_tenant_id(const std::string& tenant_id) const +{ + if (tenant_id != _platform_service.get_current_tenantid()) + { + LOG_ERROR("Tenant id mismatch. Request for tenant %s while current tenant is %s", tenant_id.c_str(), _platform_service.get_current_tenantid().c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } +} + + +void bam_ecdsa_cosigner::validate_tenant_id_setup(bam_key_persistency_common& persistency, const std::string& setup_id) const +{ + std::string setup_tenant_id; + persistency.load_tenant_id_for_setup(setup_id, setup_tenant_id); + validate_current_tenant_id(setup_tenant_id); +} + + +void bam_ecdsa_cosigner::make_sig_s_positive(const cosigner_sign_algorithm algorithm, const elliptic_curve256_algebra_ctx_t* algebra, recoverable_signature& sig) +{ + // calling is_positive as optimization for not calling GFp_curve_algebra_abs unless needed + if (!is_positive(algorithm, sig.s)) + { + uint8_t parity = sig.s[31] & 1; + throw_cosigner_exception(GFp_curve_algebra_abs((GFp_curve_algebra_ctx_t*)algebra->ctx, &sig.s, &sig.s)); + sig.v ^= (parity ^ (sig.s[31] & 1)); + } +} + + +bool bam_ecdsa_cosigner::is_positive(const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& n) +{ + switch (algorithm) + { + case ECDSA_SECP256K1: return (n[0] & 0x80) == 0; + + case ECDSA_SECP256R1: + { + static constexpr const uint64_t half_n_first_8_bytes_le = 0x7FFFFFFF80000000ULL; + const uint64_t n_val = bswap_64(*reinterpret_cast(&n)); //convert highest 64 bits big endian to little endian + return n_val < half_n_first_8_bytes_le; + } + + case ECDSA_STARK: return n[0] < 4; // stark curve is 252bit + + case EDDSA_ED25519: //fallthrough to default + default: + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } +} + +void bam_ecdsa_cosigner::derivation_key_delta(const elliptic_curve256_algebra_ctx_t* algebra, + const elliptic_curve256_point_t& public_key, + const HDChaincode& chaincode, + const std::vector& path, + elliptic_curve256_scalar_t& delta) +{ + static const elliptic_curve256_scalar_t ZERO = {0}; + + if (path.size()) + { + assert(path.size() == BIP44_PATH_LENGTH); + hd_derive_status retval = derive_private_key_generic(algebra, delta, public_key, ZERO, chaincode, path.data(), path.size()); //derive 0 to get the derivation delta + if (HD_DERIVE_SUCCESS != retval) + { + LOG_ERROR("Error deriving private key: %d", retval); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + } + else + { + memcpy(delta, ZERO, sizeof(elliptic_curve_scalar)); + } +} + +// Randomizes a BIGNUM by adding a random multiple of a given factor +// The randomizer bit length defines the range for randomness +void bam_ecdsa_cosigner::bn_randomize_with_factor(struct bignum_st* res, const struct bignum_st* base, const struct bignum_st* factor, const uint32_t randomizer_bitlength) +{ + long ret = -1; + BIGNUM* randomizer = NULL; + + if (!res || !base || !factor || !randomizer_bitlength) + { + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER); + } + + BN_CTX_guard ctx_guard; + auto ctx = ctx_guard.get(); + + randomizer = BN_CTX_get(ctx); + if (!randomizer) + { + goto cleanup; + } + + if (!BN_rand(randomizer, randomizer_bitlength, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY)) + { + goto cleanup; + } + + if (!BN_mul(randomizer, randomizer, factor, ctx)) + { + goto cleanup; + } + + if (!BN_add(res, base, randomizer)) + { + goto cleanup; + } + + ret = ELLIPTIC_CURVE_ALGEBRA_SUCCESS; + +cleanup: + // Error handling and cleanup + if (-1 == ret) + { + ERR_clear_error(); + ret = ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR; + } + + if (randomizer) + { + BN_clear(randomizer); + } + + + if (ret != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) + { + LOG_ERROR("bn_randomize_with_factor() failed with error %ld", ret); + throw_cosigner_exception((elliptic_curve_algebra_status)ret); + } +} + +void bam_ecdsa_cosigner::generate_private_share(const cosigner_sign_algorithm algorithm, elliptic_curve_scalar& private_share) const +{ + const auto algebra = get_algebra(algorithm); + throw_cosigner_exception(algebra->rand(algebra, &private_share.data)); +} + +void bam_ecdsa_cosigner::decrypt_and_rebuild_private_share(const uint64_t my_player_id, + const cosigner_sign_algorithm algorithm, + const std::map& data, + elliptic_curve_scalar& private_share, + elliptic_curve256_point_t& expected_public_key) const +{ + const auto algebra = get_algebra(algorithm); + OPENSSL_cleanse(private_share.data, sizeof(private_share.data)); + if (data.size() == 0) + { + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + bool is_first = true; + // perform validations + for (const auto& src_player_data : data) + { + if (is_first) + { + is_first = false; + memcpy(&expected_public_key[0], &src_player_data.second.public_key.data[0], sizeof(elliptic_curve256_point_t)); + } + else if (memcmp(&expected_public_key[0], &src_player_data.second.public_key.data[0], sizeof(elliptic_curve256_point_t)) != 0) + { + LOG_ERROR("Public key from player %" PRIu64 " is different from the key sent by player %" PRIu64, src_player_data.first, data.begin()->first); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + else if (src_player_data.second.encrypted_shares.size() != 2) + { + LOG_ERROR("Incorrect encrypted shares size %u from player %" PRIu64, (uint32_t) src_player_data.second.encrypted_shares.size(), src_player_data.first); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + const auto my_encrypted_share = src_player_data.second.encrypted_shares.find(my_player_id); + if (my_encrypted_share == src_player_data.second.encrypted_shares.end()) + { + LOG_ERROR("Player %" PRIu64 " didn't send share to me", src_player_data.first); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + auto share = _platform_service.decrypt_message(my_encrypted_share->second); + throw_cosigner_exception(algebra->add_scalars(algebra, &private_share.data, &private_share.data[0], sizeof(elliptic_curve256_scalar_t), (const uint8_t*)share.data(), share.size())); + } +} + + void bam_ecdsa_cosigner::derive_and_compute_corrected_R(elliptic_curve256_algebra_ctx_t* algebra, + const std::string& tx_id, + const bam_single_signature_data_base& signature_data, + const elliptic_curve256_point_t& public_key, + const client_partial_signature_data& partial_signature, + elliptic_curve256_point_t& derived_public_key, + elliptic_curve256_scalar_t& hash_shift, + elliptic_curve256_point_t& corrected_R) +{ + throw_cosigner_exception(algebra->generator_mul(algebra, &derived_public_key, &signature_data.derivation_delta)); + throw_cosigner_exception(algebra->add_points(algebra, &derived_public_key, &derived_public_key, &public_key)); + + // recalculate shift in the same way the client did it to fix the signature + compute_hash_shift(tx_id, signature_data.message, partial_signature.common_R, partial_signature.client_R, derived_public_key, hash_shift); + + // corrected_R = G^(k_client * hash_shift) + throw_cosigner_exception(algebra->point_mul(algebra, &corrected_R, &partial_signature.client_R, &hash_shift)); + //compute the "R" of the signature by adding G^(k_client * k_server) + G^(k_client * hash_shift) = G^(k_client(k_server+ hash_shift)) + throw_cosigner_exception(algebra->add_points(algebra, &corrected_R, &corrected_R, &partial_signature.common_R)); +} + +} \ No newline at end of file diff --git a/src/common/cosigner/bam_ecdsa_cosigner_client.cpp b/src/common/cosigner/bam_ecdsa_cosigner_client.cpp new file mode 100644 index 0000000..f84495c --- /dev/null +++ b/src/common/cosigner/bam_ecdsa_cosigner_client.cpp @@ -0,0 +1,756 @@ +#include "cosigner/bam_ecdsa_cosigner_client.h" +#include "cosigner/cosigner_exception.h" +#include "cosigner/platform_service.h" +#include "cosigner/bam_key_persistency_client.h" +#include "cosigner/bam_tx_persistency_client.h" +#include "crypto/commitments/damgard_fujisaki.h" +#include "crypto/zero_knowledge_proof/range_proofs.h" +#include "crypto/zero_knowledge_proof/schnorr.h" +#include "../crypto/paillier_commitment/paillier_commitment_internal.h" +#include "crypto/algebra_utils/algebra_utils.h" +#include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "bam_well_formed_proof.h" +#include "cosigner_bn.h" + +#include "logging/logging_t.h" +#include + +namespace fireblocks::common::cosigner +{ + +bam_ecdsa_cosigner_client::bam_ecdsa_cosigner_client(platform_service& platform_service, + bam_key_persistency_client& key_persistecy, + bam_tx_persistency_client& tx_persistecy): + bam_ecdsa_cosigner(platform_service), + _key_persistency(key_persistecy), + _tx_persistency(tx_persistecy) +{ + +} + +void bam_ecdsa_cosigner_client::start_new_key_generation(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm) +{ + validate_current_tenant_id(tenant_id); + + elliptic_curve_scalar private_share; + generate_private_share(algorithm, private_share); + const auto algebra = get_algebra(algorithm); + + start_key_generation(setup_id, key_id, tenant_id, server_id, client_id, algorithm, private_share, *(algebra->infinity_point(algebra))); +} + +void bam_ecdsa_cosigner_client::start_add_user(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const std::map& data) +{ + validate_current_tenant_id(tenant_id); + + elliptic_curve_scalar private_share; + elliptic_curve256_point_t expected_public_key; + + decrypt_and_rebuild_private_share(client_id, algorithm, data, private_share, expected_public_key); + + start_key_generation(setup_id, key_id, tenant_id, server_id, client_id, algorithm, private_share, expected_public_key); +} + + +void bam_ecdsa_cosigner_client::start_key_generation(const std::string& setup_id, + const std::string& key_id, + const std::string& tenant_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const elliptic_curve_scalar& private_share, + const elliptic_curve256_point_t& expected_public_key) +{ + if (client_id == server_id) + { + LOG_ERROR("Client and server ids are the same"); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + LOG_INFO("Generating client share for key %s tenant %s server id %" PRIu64 ", client id %" PRIu64 ", algorithm %d", key_id.c_str(), tenant_id.c_str(), server_id, client_id, algorithm); + + bam_key_metadata_client client_key_metadata(algorithm, setup_id, server_id, expected_public_key); + + generate_aad_for_key_gen(key_id, client_id, server_id, client_key_metadata.seed); + + _platform_service.mark_key_setup_in_progress(key_id); + + _key_persistency.store_tenant_id_for_setup(setup_id, tenant_id); + _key_persistency.store_key_metadata(key_id, client_key_metadata, false); //first time storing key metadata + _key_persistency.store_key(key_id, algorithm, private_share.data); +} + +void bam_ecdsa_cosigner_client::verify_setup_proof_store_key_commitment_generate_key_proof(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const server_setup_shared_data& setup, + const commitments_sha256_t& B, + client_key_shared_data& client_key_message) +{ + bam_key_metadata_client client_key_metadata; + _key_persistency.load_key_metadata(key_id, client_key_metadata); + + if (client_key_metadata.peer_id != server_id) + { + LOG_ERROR("Wrong server id for key %s. %" PRIu64 " != %" PRIu64, key_id.c_str(), client_key_metadata.peer_id, server_id); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + if (client_key_metadata.setup_id != setup_id) + { + LOG_ERROR("Setup data for the key %s was originally initiated for setup %s. And now it is %s", key_id.c_str(), client_key_metadata.setup_id.c_str(), setup_id.c_str()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + validate_tenant_id_setup(setup_id); + + if (client_key_metadata.paillier_commitment_pub) + { + LOG_ERROR("Setup data for the key %s is already stored", key_id.c_str()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + verify_setup_proof(setup_id, setup, client_key_metadata); + + bam_temp_key_data_client client_temp_key_data; // client holds only temporary auxiliary key data + memcpy(&client_temp_key_data.server_commitment[0], &B[0], sizeof(commitments_sha256_t)); + + generate_share_and_proofs(key_id, client_key_metadata, client_temp_key_data, client_key_message); + + // if all of them are verified, then store the setup keys. + _key_persistency.store_key_metadata(key_id, client_key_metadata, true); + _key_persistency.store_key_temp_data(key_id, client_temp_key_data); +} + +void bam_ecdsa_cosigner_client::verify_setup_proof(const std::string& setup_id, + const server_setup_shared_data& setup, + bam_key_metadata_client& client_key_metadata) const +{ + + // Paillier + client_key_metadata.paillier_commitment_pub = + std::shared_ptr( + paillier_commitment_public_key_deserialize(1, + setup.paillier_commitment_pub.data(), + setup.paillier_commitment_pub.size()), + paillier_commitment_free_public_key); + + if (!client_key_metadata.paillier_commitment_pub) + { + LOG_ERROR("Failed to deserialize Paillier public key."); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + // we'll allow Paillier keys to be at most 16 bits smaller than the expected one. + const uint32_t paillier_public_key_size = paillier_commitment_public_bitsize(client_key_metadata.paillier_commitment_pub.get()); + if (paillier_public_key_size < cosigner_params::PAILLIER_COMMITMENT_BITSIZE) + { + LOG_ERROR("Paillier commitment key size too small. %u < %u", paillier_public_key_size, cosigner_params::PAILLIER_COMMITMENT_BITSIZE); + throw_paillier_exception(PAILLIER_ERROR_KEYLEN_TOO_SHORT); + } + + // EC base + const byte_vector_t setup_aad = generate_setup_aad_bytes(setup_id, client_key_metadata.algorithm); + commitments_status com_status = pedersen_commitment_two_generators_base_generate(&client_key_metadata.ec_base, + setup_aad.data(), + (uint32_t)setup_aad.size(), + get_algebra(client_key_metadata.algorithm)); + if (com_status != COMMITMENTS_SUCCESS) + { + LOG_ERROR("failed to generate elliptic curve base for commitments, error %d", com_status); + throw_cosigner_exception(com_status); + } + + // Finished restoration of data, now start verification + + // Verify the proofs + const auto paillier_res = paillier_commitment_paillier_blum_zkp_verify(client_key_metadata.paillier_commitment_pub.get(), + setup_aad.data(), + setup_aad.size(), + setup.paillier_blum_zkp.data(), + setup.paillier_blum_zkp.size()); + if (paillier_res != PAILLIER_SUCCESS) + { + LOG_ERROR("Fail to verify the Paillier Blum proof"); + throw_paillier_exception(paillier_res); + } + + auto zkp_res = range_proof_paillier_commitment_large_factors_zkp_verify(client_key_metadata.paillier_commitment_pub.get(), + setup_aad.data(), + setup_aad.size(), + setup.small_factors_zkp.data(), + setup.small_factors_zkp.size()); + if (zkp_res != ZKP_SUCCESS) + { + LOG_ERROR("Failed to verify the Paillier Large Factor proof. Error %d", zkp_res); + throw_cosigner_exception((zero_knowledge_proof_status)zkp_res); + } + + zkp_res = paillier_commitment_damgard_fujisaki_parameters_zkp_verify(client_key_metadata.paillier_commitment_pub.get(), + setup_aad.data(), + setup_aad.size(), + setup.damgard_fujisaki_zkp.data(), + setup.damgard_fujisaki_zkp.size()); + if (zkp_res != ZKP_SUCCESS) + { + LOG_ERROR("Failed to verify the Batch Ring Damgard Fujisaki proof. Error %d", zkp_res); + throw_cosigner_exception((zero_knowledge_proof_status)zkp_res); + } +} + +void bam_ecdsa_cosigner_client::generate_share_and_proofs(const std::string& key_id, + const bam_key_metadata_client& client_key_metadata, + bam_temp_key_data_client& client_temp_key_data, + client_key_shared_data& client_key_message) const +{ + // generate damgard fujisaki private ephemeral private key + damgard_fujisaki_private* damgard_fujisaki_priv = NULL; + ring_pedersen_status damgard_ret = damgard_fujisaki_generate_private_key(cosigner_params::TEMPORARY_DAMGARD_FUJISAKI_BITSIZE, 2, &damgard_fujisaki_priv); + if (damgard_ret != RING_PEDERSEN_SUCCESS) + { + LOG_ERROR("Failed to generate damgard_fujisaki private key, error %d for key %s", damgard_ret, key_id.c_str()); + throw_cosigner_exception(damgard_ret); + } + + client_temp_key_data.damgard_fujisaki_priv = std::shared_ptr(damgard_fujisaki_priv, damgard_fujisaki_free_private); + + // damgard_fujisaki proof + uint32_t proof_len = 0; + zero_knowledge_proof_status zkp_ret = damgard_fujisaki_parameters_zkp_generate(client_temp_key_data.damgard_fujisaki_priv.get(), + client_key_metadata.seed, + sizeof(client_key_metadata.seed), + cosigner_params::OPTIMIZED_DAMGARD_FUJISAKI_CHALLENGE_BITSIZE, + NULL, + 0, + &proof_len); + if (zkp_ret != ZKP_INSUFFICIENT_BUFFER) + { + LOG_ERROR("failed to estimate size of generated damgard_fujisaki Proof, error %d for key %s", zkp_ret, key_id.c_str()); + throw_cosigner_exception(zkp_ret); + } + client_key_message.damgard_fujisaki_proof.resize(proof_len); + zkp_ret = damgard_fujisaki_parameters_zkp_generate(client_temp_key_data.damgard_fujisaki_priv.get(), + client_key_metadata.seed, + sizeof(client_key_metadata.seed), + cosigner_params::OPTIMIZED_DAMGARD_FUJISAKI_CHALLENGE_BITSIZE, + client_key_message.damgard_fujisaki_proof.data(), + proof_len, + &proof_len); + if (zkp_ret != ZKP_SUCCESS) + { + LOG_ERROR("failed to generate damgard_fujisaki Proof, error %d for key %s", zkp_ret, key_id.c_str()); + throw_cosigner_exception(zkp_ret); + } + else if ((uint32_t)client_key_message.damgard_fujisaki_proof.size() != proof_len) + { + LOG_ERROR("failed to generate Damgard Fujisaki Proof, size mismatch %u != %u", proof_len, (uint32_t)client_key_message.damgard_fujisaki_proof.size()); + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + const auto algebra = get_algebra(client_key_metadata.algorithm); + + // serialization of damgard_fujisaki public key + uint32_t damgard_fujisaki_pub_size = 0; + damgard_fujisaki_public_serialize(damgard_fujisaki_private_key_get_public(client_temp_key_data.damgard_fujisaki_priv.get()), NULL, 0, &damgard_fujisaki_pub_size); + client_key_message.damgard_fujisaki_pub.resize(damgard_fujisaki_pub_size); + if (!damgard_fujisaki_public_serialize(damgard_fujisaki_private_key_get_public(client_temp_key_data.damgard_fujisaki_priv.get()), + client_key_message.damgard_fujisaki_pub.data(), + damgard_fujisaki_pub_size, + &damgard_fujisaki_pub_size)) + { + LOG_ERROR("Cannot serialize Damgard Fujisaki public key for key id %s.", key_id.c_str()); + throw_cosigner_exception(COMMITMENTS_INTERNAL_ERROR); + } + else if (damgard_fujisaki_pub_size != (uint32_t)client_key_message.damgard_fujisaki_pub.size()) + { + LOG_ERROR("Error serialize Damgard Fujisaki public key for key id %s. Size mismatch %u != %u", key_id.c_str(), damgard_fujisaki_pub_size, (uint32_t)client_key_message.damgard_fujisaki_pub.size()); + throw_cosigner_exception(COMMITMENTS_INTERNAL_ERROR); + } + + elliptic_curve_scalar private_share; + cosigner_sign_algorithm algorithm; + _key_persistency.load_key(key_id, algorithm, private_share.data); + + if (algorithm != client_key_metadata.algorithm) + { + LOG_ERROR("Metadata algorithm mismatch for key %s. %d != %d", key_id.c_str(), algorithm, client_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + // fill client's public share + throw_cosigner_exception(algebra->generator_mul(algebra, &client_key_message.X, &private_share.data)); + + // Generate Schnorr DLOG proof + client_key_message.schnorr_proof.resize(sizeof(schnorr_zkp_t)); + throw_cosigner_exception(schnorr_zkp_generate(algebra, + client_key_metadata.seed, + sizeof(client_key_metadata.seed), + &private_share.data, + &client_key_message.X, + (schnorr_zkp_t*) client_key_message.schnorr_proof.data())); +} + +void bam_ecdsa_cosigner_client::verify_key_decommitment_and_proofs(const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const server_key_shared_data& server_message, + generated_public_key& pub_key_data) +{ + bam_key_metadata_client client_key_metadata; + bam_temp_key_data_client client_temp_key_data; + _key_persistency.load_key_metadata(key_id, client_key_metadata); + + (void)client_id; + + if (client_key_metadata.peer_id != server_id) + { + LOG_ERROR("Wrong server id for key %s. %" PRIu64 " != %" PRIu64, key_id.c_str(), client_key_metadata.peer_id, server_id); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + validate_tenant_id_setup(client_key_metadata.setup_id); + + if (!client_key_metadata.paillier_commitment_pub) + { + LOG_ERROR("Setup data for the key %s is not ready", key_id.c_str()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + // verify that servers share is unknown + if (!client_key_metadata.encrypted_server_share.empty()) + { + LOG_ERROR("Already have server share for key %s.", key_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + _key_persistency.load_key_temp_data_and_delete(key_id, client_temp_key_data); + + // Check server commitment + commitments_sha256_t B; + generate_key_commitment(client_key_metadata.seed, server_message.server_public_share, B); + + if (memcmp(client_temp_key_data.server_commitment, B, sizeof(commitments_sha256_t)) != 0) + { + LOG_FATAL("Failed to verify the server key share commitment for the key %s", key_id.c_str()); + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + // Check Paillier Range proof + const const_paillier_with_range_proof_t proof = + { + server_message.encrypted_server_share.data(), + (uint32_t) server_message.encrypted_server_share.size(), + server_message.enc_dlog_proof.data(), + (uint32_t) server_message.enc_dlog_proof.size() + }; + + auto algebra = get_algebra(client_key_metadata.algorithm); + zero_knowledge_proof_status paillier_ret = paillier_commitment_exponent_zkpok_verify(client_temp_key_data.damgard_fujisaki_priv.get(), + client_key_metadata.paillier_commitment_pub.get(), + algebra, + client_key_metadata.seed, + sizeof(client_key_metadata.seed), + &server_message.server_public_share, + &proof, + /*use_extended_seed=*/1); + if (paillier_ret != ZKP_SUCCESS) + { + LOG_ERROR("Failed to verify range proof small expo. for key %s. Error %d", key_id.c_str(), paillier_ret); + throw_cosigner_exception(paillier_ret); + } + + elliptic_curve256_point_t common_public_key; + elliptic_curve_scalar private_share; + cosigner_sign_algorithm algorithm; + _key_persistency.load_key(key_id, algorithm, private_share.data); + + if (algorithm != client_key_metadata.algorithm) + { + LOG_ERROR("Metadata algorithm mismatch for key %s. %d != %d", key_id.c_str(), algorithm, client_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + //client's public key from private -> common_public_key + throw_cosigner_exception(algebra->generator_mul(algebra, &common_public_key, &private_share.data)); + + //add server's part + throw_cosigner_exception(algebra->add_points(algebra, + &common_public_key, + &common_public_key, + &server_message.server_public_share)); + if (!client_key_metadata.has_public_key()) + { + memcpy(&client_key_metadata.public_key[0], &common_public_key[0], sizeof(elliptic_curve256_point_t)); + } + else if (memcmp(&client_key_metadata.public_key[0], &common_public_key[0], sizeof(elliptic_curve256_point_t)) != 0) + { + LOG_ERROR("Public key mismatch for the key %s", key_id.c_str()); + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + client_key_metadata.encrypted_server_share = server_message.encrypted_server_share; + + _key_persistency.store_key_metadata(key_id, client_key_metadata, true); + + if (!_key_persistency.backup_key(key_id)) + { + LOG_ERROR("Failed to backup key %s", key_id.c_str()); + _key_persistency.delete_key_data(key_id); + throw cosigner_exception(cosigner_exception::BACKUP_FAILED); + } + + _platform_service.clear_key_setup_in_progress(key_id); + + pub_key_data.pub_key.assign((const char*)client_key_metadata.public_key, algebra->point_size(algebra)); + pub_key_data.algorithm = client_key_metadata.algorithm; +} + +void bam_ecdsa_cosigner_client::well_formed_signature_range_proof_generate(const paillier_commitment_public_key_t *paillier, + elliptic_curve256_algebra_ctx *algebra, + const pedersen_commitment_two_generators *ec_base, //two points h and f + const commitments_sha256_t& signature_aad, + const elliptic_curve256_point_t& r_server, + const struct bignum_st *plaintext, // this is u, called a in proof + const struct bignum_st *encrypted_share, // encrypted server share + const struct bignum_st *exponent, // v, called b in proof + struct bignum_ctx *ctx, + client_partial_signature_data &partial_signature) +{ + + byte_vector_t plaintext_bin; + byte_vector_t exponent_bin; + + if (!paillier || !algebra || !ec_base || !plaintext || !encrypted_share || !exponent) + { + throw_cosigner_exception(ZKP_INVALID_PARAMETER); + } + + bn_ctx_frame bn_ctx(ctx); + + BIGNUM *S = BN_CTX_get(ctx); + BIGNUM *lambda0 = BN_CTX_get(ctx); + + + if (!S || !lambda0) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + plaintext_bin.resize(BN_num_bytes(plaintext)); + exponent_bin.resize(BN_num_bytes(exponent)); + + if (!BN_bn2bin(plaintext, plaintext_bin.data()) || + !BN_bn2bin(exponent, exponent_bin.data()) || + !BN_rand(lambda0, cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE * 8, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + throw_paillier_exception(paillier_commitment_commit_internal(paillier, plaintext, lambda0, encrypted_share, exponent, S, ctx)); + + partial_signature.encrypted_partial_sig.resize(BN_num_bytes(S)); + + if (!BN_bn2bin(S, partial_signature.encrypted_partial_sig.data())) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + bam_well_formed_proof::generate_signature_proof(paillier, algebra, ctx, ec_base, plaintext, encrypted_share, exponent, lambda0, S, signature_aad, plaintext_bin, exponent_bin, r_server, partial_signature.sig_proof); +} + +void bam_ecdsa_cosigner_client::prepare_for_signature(const std::string& key_id, + const std::string& tx_id, + const uint32_t version, + const uint64_t server_id, + const uint64_t client_id, + const common::cosigner::signing_data& data, + const std::string& metadata_json, + const std::set& players_set) +{ + LOG_INFO("Entering txid = %s", tx_id.c_str()); + + _platform_service.prepare_for_signing(key_id, tx_id); + + //validate client and server id + bam_key_metadata_client client_key_metadata; + _key_persistency.load_key_metadata(key_id, client_key_metadata); + + + if (client_key_metadata.peer_id != server_id) + { + LOG_ERROR("Wrong server id for key %s. %" PRIu64 " != %" PRIu64, key_id.c_str(), client_key_metadata.peer_id, server_id); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + validate_tenant_id_setup(client_key_metadata.setup_id); + + // verify that servers share is unknown + if (client_key_metadata.encrypted_server_share.empty()) + { + LOG_ERROR("Key generation is not completed for key %s.", key_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + const auto signature_request_data = fill_bam_signing_info_from_metadata(metadata_json, static_cast(data.blocks.size())); + if (0 == signature_request_data.size()) + { + LOG_ERROR("Key %s, tx_id %s: Empty signature batch request", key_id.c_str(), tx_id.c_str()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + else if (data.blocks.size() != signature_request_data.size()) //must not happen + { + LOG_ERROR("number of blocks %u is different than number of flags %u", static_cast(data.blocks.size()), static_cast(signature_request_data.size())); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + _platform_service.on_start_signing(key_id, tx_id, data, metadata_json, players_set, platform_service::SINGLE_ROUND_SIGNATURE); + + auto algebra = get_algebra(client_key_metadata.algorithm); + + bam_client_signature_data client_signature_data(version, server_id, client_id, key_id, static_cast(_platform_service.now_msec() / 1000)); + client_signature_data.sig_data.resize(signature_request_data.size()); + + for (uint32_t i = 0; i < (uint32_t)signature_request_data.size(); ++ i) + { + auto& sig_request = signature_request_data[i]; // request which has to be signed + auto& persistant_sig_data = client_signature_data.sig_data[i]; // data about the signature that the client should save + + persistant_sig_data.flags = sig_request.flags; + if (data.blocks[i].data.size() != sizeof(elliptic_curve256_scalar_t)) + { + LOG_ERROR("block %u has illegal size of %u", i, static_cast(data.blocks[i].data.size())); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + memcpy(persistant_sig_data.message, data.blocks[i].data.data(), sizeof(elliptic_curve256_scalar_t)); + derivation_key_delta(algebra, client_key_metadata.public_key, data.chaincode, data.blocks[i].path, persistant_sig_data.derivation_delta); + } + + _tx_persistency.store_signature_data(tx_id, client_signature_data); +} + +void bam_ecdsa_cosigner_client::compute_partial_signature(const std::string& tx_id, + const std::vector& server_shares, + std::vector& partial_signatures) +{ + elliptic_curve_scalar k, k_inv, r, v; + elliptic_curve_scalar private_share; + elliptic_curve_scalar derived_private_share; + elliptic_curve256_point_t tmp; + bam_client_signature_data client_signature_data; + bam_key_metadata_client client_key_metadata; + commitments_sha256_t signature_aad; + cosigner_sign_algorithm algorithm; + + _tx_persistency.load_signature_data_and_delete(tx_id, client_signature_data); + if (server_shares.size() != client_signature_data.sig_data.size()) + { + LOG_ERROR("Tx_id %s: Server shares size mismatch. %u != %u", tx_id.c_str(), (uint32_t)server_shares.size(), (uint32_t) client_signature_data.sig_data.size()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + _key_persistency.load_key_metadata(client_signature_data.key_id, client_key_metadata); + + validate_tenant_id_setup(client_key_metadata.setup_id); + + // verify that servers share is unknown + if (client_key_metadata.encrypted_server_share.empty()) + { + LOG_ERROR("Key generation is not completed for key %s.", client_signature_data.key_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + _key_persistency.load_key(client_signature_data.key_id, algorithm, private_share.data); + if (algorithm != client_key_metadata.algorithm) + { + LOG_ERROR("Metadata algorithm mismatch for tx_id %s. %d != %d", tx_id.c_str(), algorithm, client_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + BN_CTX_guard ctx; + + const auto algebra = get_algebra(client_key_metadata.algorithm); + + + generate_aad_for_signature(client_signature_data.key_id, client_signature_data.server_signer_id, client_signature_data.client_signer_id, tx_id, signature_aad); + + partial_signatures.resize(server_shares.size()); + + // prepare server encrypted share + BIGNUM *bn_encrypted_share = BN_CTX_get(ctx.get()); + if (!bn_encrypted_share) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!BN_bin2bn(client_key_metadata.encrypted_server_share.data(), client_key_metadata.encrypted_server_share.size(), bn_encrypted_share)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + for (uint32_t i = 0; i < (uint32_t)server_shares.size(); ++ i ) + { + auto& server_share = server_shares[i]; + auto& partial_signature = partial_signatures[i]; + auto& persistant_sig_data = client_signature_data.sig_data[i]; + + bn_ctx_frame ctx_frame(ctx.get()); + + // required because of the Claim 7.15 in the BAM article from + check_non_null_message(persistant_sig_data.message, algebra); + + // check that the server share values + // server 'R' should not be the point at infinity + // simply verify compressed point to check that it is legal. + // This is a simply sanity check - zero point is not considered a valid random + check_a_valid_point(server_share.R, algebra); + + // It is required derive private key based on public key derivation shift for the validation + // And from now on use only derived private share + throw_cosigner_exception(algebra->add_scalars(algebra, + &derived_private_share.data, + private_share.data, + sizeof(elliptic_curve256_scalar_t), + persistant_sig_data.derivation_delta, + sizeof(elliptic_curve256_scalar_t))); + + // R2^derived_private_share.data == Y + // It verifies that the server knows the discrete log of it's R + // (because it should have calculated client's public share to the power of k to compute the Y) + throw_cosigner_exception(algebra->point_mul(algebra, &tmp, &server_share.R, &derived_private_share.data)); + if (memcmp(tmp, server_share.Y, sizeof(elliptic_curve256_point_t)) != 0) + { + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + // generate own share + throw_cosigner_exception(algebra->rand(algebra, &k.data)); //generate random k + + //set partial_signature.client_R to hold G^k_client + throw_cosigner_exception(algebra->generator_mul(algebra, &partial_signature.client_R, &k.data)); + + //set partial_signature.common_R to hold R^k_client which is G^(k_server * k_client) + throw_cosigner_exception(algebra->point_mul(algebra, &partial_signature.common_R, &server_share.R, &k.data)); + + GFp_curve_algebra_ctx_t* curve = (GFp_curve_algebra_ctx_t*)algebra->ctx; + + // To compute 'r' of the signature, we compute: + // R2^k1 + R1^H(X||R1||R2^k1||m) and take the x-coordinate of it + uint8_t overflow = 0; + elliptic_curve256_scalar_t hash_shift; //not needed + //calculate corrected R + elliptic_curve256_point_t corrected_R; + derive_and_compute_corrected_R(algebra, tx_id, persistant_sig_data, client_key_metadata.public_key, partial_signature, tmp, hash_shift, corrected_R); + + // r = x projection of the corrected_R point + throw_cosigner_exception(GFp_curve_algebra_get_point_projection(curve, &r.data, &corrected_R, &overflow)); + + // POSITIVE_R handling (client-side) + // + // We cannot just “pre-correct” R and send that to the server. In this protocol + // r is not an arbitrary choice: both parties derive it from the SAME public data, + // namely + // R* = R_com + R_client^{H(X || R_client || R_com || m)} + // r = x(R*) + // where R_client = g^{k_client}, R_com = R_server^{k_client}, + // and the hash “shift” binds r to (X, R_client, R_com, m). The server independently + // recomputes R* and r from (R_client, R_com, m), so any pre-massaged “positive R” we + // send would be ignored and, if inconsistent with k1/k2, rejected. + // + // Enforcing positivity is therefore deterministic and symmetric: if r isn’t + // positive, BOTH sides conceptually add R again (and again…) until it is. + // Each addition corresponds to scaling our nonce by the same small factor t: + // k_client' = k_client · t + // We must reflect this by updating k_client -> k_client' and using k_client'^{-1} when forming our + // partial (v, u), which keeps our ZK proof and the final signature consistent + // with the server’s recomputed r. + // + // Important: the server should NOT apply any extra correction to s. It just + // runs the same positivity loop to recover the identical r and then combines + // our well-formed partial. Our partial already “bakes in” the correction via + // k'^{-1}; a second correction on the server would break correctness. + if (persistant_sig_data.flags & POSITIVE_R) + { + uint8_t r_correction_cycles = 1; + + memcpy(tmp, corrected_R, sizeof(elliptic_curve256_point_t)); + while (!is_positive(client_key_metadata.algorithm, r.data)) + { + assert(r_correction_cycles < 255); + if (r_correction_cycles == 255) + { + LOG_ERROR("Failed to find a positive r in 256 iterations key_id %s, tx_id %s", client_signature_data.key_id.c_str(), tx_id.c_str()); + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + ++r_correction_cycles; + throw_cosigner_exception(algebra->add_points(algebra, &corrected_R, &corrected_R, &tmp)); + throw_cosigner_exception(GFp_curve_algebra_get_point_projection(curve, &r.data, &corrected_R, &overflow)); + } + + // now update the k.data so it will hold the corrected value + throw_cosigner_exception(algebra->mul_scalars(algebra, &k.data, k.data, sizeof(k.data), &r_correction_cycles, sizeof(r_correction_cycles))); + } + + // v = k_client^(-1) * r + // If R was modified, a modified k should be used to compute k_inv + throw_cosigner_exception(algebra->inverse(algebra, &k_inv.data, &k.data)); //calculate k^(-1) + throw_cosigner_exception(algebra->mul_scalars(algebra, &v.data, k_inv.data, sizeof(k_inv.data), r.data, sizeof(r.data))); + + // r is no more needed, so will be reused to hold u (now it holds the shifter projection) + // r = r * derived_private_share + throw_cosigner_exception(algebra->mul_scalars(algebra, &r.data, r.data, sizeof(r.data), derived_private_share.data, sizeof(derived_private_share.data))); + // r = r + hash_to_sign = shifter_projection * derived_private_share + hash_to_sign + throw_cosigner_exception(algebra->add_scalars(algebra, &r.data, r.data, sizeof(r.data), persistant_sig_data.message, sizeof(elliptic_curve256_scalar_t))); + // r = r * k_client^(-1) = (shifter_projection * derived_private_share + hash_to_sign) * k_client ^ (-1) + throw_cosigner_exception(algebra->mul_scalars(algebra, &r.data, r.data, sizeof(r.data), k_inv.data, sizeof(k_inv.data))); + + BIGNUM *bn_u_ptr = BN_CTX_get(ctx.get()); + BIGNUM *bn_expo_ptr = BN_CTX_get(ctx.get()); + + if (!bn_u_ptr || !bn_expo_ptr) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + std::unique_ptr bn_u(BN_bin2bn(r.data, sizeof(r.data), bn_u_ptr), bignum_clear()); + std::unique_ptr bn_expo(BN_bin2bn(v.data, sizeof(v.data), bn_expo_ptr), bignum_clear()); + + if (!bn_u ||!bn_expo) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + bn_randomize_with_factor(bn_u.get(), bn_u.get(), algebra->order_internal(algebra), (cosigner_params::ZKPOK_OPTIM_NA_SIZE - cosigner_params::ZKPOK_OPTIM_KAPPA_SIZE) * 8); + bn_randomize_with_factor(bn_expo.get(), bn_expo.get(), algebra->order_internal(algebra), (cosigner_params::ZKPOK_OPTIM_NB_SIZE - cosigner_params::ZKPOK_OPTIM_KAPPA_SIZE)*8); + + // Generate proof + well_formed_signature_range_proof_generate(client_key_metadata.paillier_commitment_pub.get(), + algebra, + &client_key_metadata.ec_base, + signature_aad, + server_share.R, + bn_u.get(), + bn_encrypted_share, + bn_expo.get(), + ctx.get(), + partial_signature); + } + +} + +void bam_ecdsa_cosigner_client::validate_tenant_id_setup(const std::string& setup_id) const +{ + bam_ecdsa_cosigner::validate_tenant_id_setup(_key_persistency, setup_id); +} + +} //namespace fireblocks::common::cosigner \ No newline at end of file diff --git a/src/common/cosigner/bam_ecdsa_cosigner_server.cpp b/src/common/cosigner/bam_ecdsa_cosigner_server.cpp new file mode 100644 index 0000000..719fb18 --- /dev/null +++ b/src/common/cosigner/bam_ecdsa_cosigner_server.cpp @@ -0,0 +1,940 @@ +#include "cosigner/bam_ecdsa_cosigner_server.h" +#include "logging/logging_t.h" + +#include "cosigner/cosigner_exception.h" +#include "cosigner/platform_service.h" +#include "cosigner/bam_key_persistency_server.h" +#include "cosigner/bam_tx_persistency_server.h" +#include "crypto/commitments/damgard_fujisaki.h" +#include "crypto/zero_knowledge_proof/range_proofs.h" +#include "crypto/zero_knowledge_proof/schnorr.h" +#include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "utils/string_utils.h" +#include "bam_well_formed_proof.h" +#include "crypto/algebra_utils/algebra_utils.h" +#include "../crypto/paillier_commitment/paillier_commitment_internal.h" + +#include +#include +#include +#include + + +namespace fireblocks::common::cosigner +{ + + +bam_ecdsa_cosigner_server::bam_ecdsa_cosigner_server(platform_service& cosigner_service, + bam_key_persistency_server& key_persistecy, + bam_tx_persistency_server& tx_persistecy): + bam_ecdsa_cosigner(cosigner_service), + _key_persistency(key_persistecy), + _tx_persistency(tx_persistecy) +{ + +} + +void bam_ecdsa_cosigner_server::shutdown() +{ + _tx_persistency.shutdown(); +} +void bam_ecdsa_cosigner_server::generate_setup_with_proof(const std::string& setup_id, + const std::string& tenant_id, + const cosigner_sign_algorithm algorithm, + server_setup_shared_data& serialized_setup_data) +{ + //validate tenant before generating a new setup + validate_current_tenant_id(tenant_id); + + const byte_vector_t setup_aad = generate_setup_aad_bytes(setup_id, algorithm); + + const bam_setup_auxilary_key_server server_setup_auxilary_key = generate_setup_secrets(); + const bam_setup_metadata_server server_setup_metadata = generate_setup_metadata(setup_aad, algorithm); + + const auto paillier_commitment_pub = paillier_commitment_private_cast_to_public(server_setup_auxilary_key.paillier_commitment_priv.get()); + + uint32_t paillier_pub_size = 0; + + // Paillier Key - dont include rho + paillier_commitment_public_key_serialize(paillier_commitment_pub, 1, NULL, 0, &paillier_pub_size); + + if (0 == paillier_pub_size) + { + LOG_ERROR("Cannot serialize Paillier commitment public key. Returned size is zero"); + throw_cosigner_exception(COMMITMENTS_INTERNAL_ERROR); + } + + serialized_setup_data.paillier_commitment_pub.resize(paillier_pub_size); + + if (PAILLIER_SUCCESS != paillier_commitment_public_key_serialize(paillier_commitment_pub, + 1, + &serialized_setup_data.paillier_commitment_pub[0], + paillier_pub_size, + &paillier_pub_size)) + { + LOG_ERROR("Cannot serialize Paillier public key."); + throw_paillier_exception(PAILLIER_ERROR_BUFFER_TOO_SHORT); + } + else if (paillier_pub_size != (uint32_t)serialized_setup_data.paillier_commitment_pub.size()) + { + LOG_ERROR("Cannot serialize Paillier commitment public key. Size mismatch. %u != %u", paillier_pub_size, (uint32_t)serialized_setup_data.paillier_commitment_pub.size()); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + // Generate ZK proofs + generate_setup_proofs(server_setup_auxilary_key, + setup_aad, + serialized_setup_data.paillier_blum_zkp, + serialized_setup_data.small_factors_zkp, + serialized_setup_data.damgard_fujisaki_zkp); + + _key_persistency.store_setup_auxilary_key(setup_id, server_setup_auxilary_key); + _key_persistency.store_setup_metadata(setup_id, server_setup_metadata); + _key_persistency.store_tenant_id_for_setup(setup_id, tenant_id); +} + +bam_setup_metadata_server bam_ecdsa_cosigner_server::generate_setup_metadata(const byte_vector_t& setup_aad, + const cosigner_sign_algorithm algorithm) +{ + bam_setup_metadata_server server_setup_metadata(algorithm); + + // Generate base commitment + const commitments_status com_status = pedersen_commitment_two_generators_base_generate(&server_setup_metadata.ec_base, + setup_aad.data(), + (uint32_t)setup_aad.size(), + get_algebra(algorithm)); + if (com_status != COMMITMENTS_SUCCESS) + { + LOG_ERROR("failed to generate elliptic curve base for commitments, error %d", com_status); + throw_cosigner_exception(com_status); + } + + return server_setup_metadata; +} + +bam_setup_auxilary_key_server bam_ecdsa_cosigner_server::generate_setup_secrets() +{ + bam_setup_auxilary_key_server setup_secrets; + paillier_commitment_private_key_t* paillier_commitment_priv = NULL; + + // generate the Paillier small group private key + long res = paillier_commitment_generate_private_key(cosigner_params::PAILLIER_COMMITMENT_BITSIZE, &paillier_commitment_priv); + if (res != PAILLIER_SUCCESS) + { + LOG_ERROR("failed to create paillier commitment key pair, error %ld", res); + throw_paillier_exception(res); + } + setup_secrets.paillier_commitment_priv = std::shared_ptr(paillier_commitment_priv, paillier_commitment_free_private_key); + + return setup_secrets; +} + +void bam_ecdsa_cosigner_server::generate_setup_proofs(const bam_setup_auxilary_key_server& server_setup_auxilary_key, + const byte_vector_t& setup_aad, + byte_vector_t& paillier_blum, + byte_vector_t& small_factors, + byte_vector_t& damgard_fujisaki) +{ + // Since any prime strong prime big should be good + // Simply choose the 1st prime that meets the security requirement + // it is 2^3459 + 1169115 - the smallest 2360 bits prime + static const uint8_t hardcoded_d[] = + { + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0xd6, + 0xdb + }; + + uint32_t proof_len = 0; + auto paillier_commitment_priv = server_setup_auxilary_key.paillier_commitment_priv.get(); + + // Proof of well formed Modulus + (void)paillier_commitment_paillier_blum_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + NULL, + 0, + &proof_len); + if (0 == proof_len) + { + LOG_ERROR("Failed to generate Paillier-Blum proof, size is zero"); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + paillier_blum.resize(proof_len); + const auto paillier_res = paillier_commitment_paillier_blum_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + paillier_blum.data(), + proof_len, + &proof_len); + if (paillier_res != PAILLIER_SUCCESS) + { + LOG_ERROR("Failed to generate Paillier-Blum proof, error %ld", paillier_res); + throw_paillier_exception(paillier_res); + } + else if (proof_len != (uint32_t)paillier_blum.size()) + { + LOG_ERROR("Failed to generate Paillier-Blum proof, wrong size %u != %u", proof_len, (uint32_t)paillier_blum.size()); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + proof_len = 0; + // Proof of no small factors. + (void)range_proof_paillier_commitment_large_factors_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + hardcoded_d, + (uint32_t)sizeof(hardcoded_d), + NULL, + 0, + &proof_len); + + if (0 == proof_len) + { + LOG_ERROR("Failed to generate Paillier Commitment Large Factors ZKP. Size is zero."); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + small_factors.resize(proof_len); + const auto paillier_zkp = range_proof_paillier_commitment_large_factors_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + hardcoded_d, + (uint32_t)sizeof(hardcoded_d), + small_factors.data(), + proof_len, + &proof_len); + if (paillier_zkp != ZKP_SUCCESS) + { + LOG_ERROR("failed to generate Small Factor Proof, error %d", paillier_zkp); + throw_cosigner_exception((zero_knowledge_proof_status)paillier_zkp); + } + else if (proof_len > (uint32_t)small_factors.size()) + { + LOG_ERROR("failed to generate Small Factor Proof, size mismatch %u != %u", proof_len, (uint32_t)small_factors.size()); + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + else if (proof_len < (uint32_t)small_factors.size()) + { + LOG_WARN("generate Small Factor Proof, size mismatch %u != %u", proof_len, (uint32_t)small_factors.size()); + small_factors.resize(proof_len); + } + + proof_len = 0; + // Damgard Fujisaki proof + (void)paillier_commitment_damgard_fujisaki_parameters_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + damgard_fujisaki.data(), + 0, + &proof_len); + if (0 == proof_len) + { + LOG_ERROR("Failed to generate Damgard Fujisaki parameters ZKP, size is zero"); + throw_paillier_exception(PAILLIER_ERROR_UNKNOWN); + } + + damgard_fujisaki.resize(proof_len); + const auto damgard_fujisaki_zkp = paillier_commitment_damgard_fujisaki_parameters_zkp_generate(paillier_commitment_priv, + setup_aad.data(), + setup_aad.size(), + damgard_fujisaki.data(), + proof_len, + &proof_len); + if (damgard_fujisaki_zkp != ZKP_SUCCESS) + { + LOG_ERROR("failed to generate Damgard Fujisaki Proof, error %d", damgard_fujisaki_zkp); + throw_cosigner_exception((zero_knowledge_proof_status)damgard_fujisaki_zkp); + } + else if (proof_len != (uint32_t)damgard_fujisaki.size()) + { + LOG_ERROR("failed to generate Damgard Fujisaki Proof, size mismatch %u != %u", proof_len, (uint32_t)damgard_fujisaki.size()); + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } +} + +void bam_ecdsa_cosigner_server::commit_to_share(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const elliptic_curve_scalar& private_share, + const elliptic_curve256_point_t& expected_public_key, + commitments_sha256_t& B) +{ + if (client_id == server_id) + { + LOG_ERROR("Client and server ids are the same"); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + bam_key_metadata_server server_key_metadata(algorithm, setup_id, client_id, expected_public_key); + auto algebra = get_algebra(algorithm); + + generate_aad_for_key_gen(key_id, client_id, server_id, server_key_metadata.seed); + + elliptic_curve256_point_t server_public_share; + + // compute the server public share. + throw_cosigner_exception(algebra->generator_mul(algebra, &server_public_share, &private_share.data)); + + // commitment to public key. + generate_key_commitment(server_key_metadata.seed, server_public_share, B); + + _platform_service.mark_key_setup_in_progress(key_id); + + _key_persistency.store_key_metadata(key_id, server_key_metadata, false); + _key_persistency.store_key(key_id, algorithm, private_share.data); +} + + +void bam_ecdsa_cosigner_server::add_user_and_commit(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + const std::map& data, + commitments_sha256_t& B) +{ + elliptic_curve_scalar private_share; + elliptic_curve256_point_t expected_public_key; + LOG_INFO("Server committing to share for key %s server id %" PRIu64 ", client id %" PRIu64 ", algorithm %d", key_id.c_str(), server_id, client_id, algorithm); + + validate_tenant_id_setup(setup_id); + + + decrypt_and_rebuild_private_share(server_id, algorithm, data, private_share, expected_public_key); + + commit_to_share(setup_id, + key_id, + server_id, + client_id, + algorithm, + private_share, + expected_public_key, + B); + +} + +void bam_ecdsa_cosigner_server::generate_share_and_commit(const std::string& setup_id, + const std::string& key_id, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm algorithm, + commitments_sha256_t& B) +{ + LOG_INFO("Generating server share for key %s server id %" PRIu64 ", client id %" PRIu64 ", algorithm %d", key_id.c_str(), server_id, client_id, algorithm); + + validate_tenant_id_setup(setup_id); + + elliptic_curve_scalar private_share; + generate_private_share(algorithm, private_share); + const auto algebra = get_algebra(algorithm); + + commit_to_share(setup_id, + key_id, + server_id, + client_id, + algorithm, + private_share, + *(algebra->infinity_point(algebra)), + B); +} + +void bam_ecdsa_cosigner_server::validate_tenant_id_setup(const std::string& setup_id) const +{ + bam_ecdsa_cosigner::validate_tenant_id_setup(_key_persistency, setup_id); +} + +void bam_ecdsa_cosigner_server::verify_client_proofs_and_decommit_share_with_proof(const std::string& key_id, + const uint64_t client_id, + const client_key_shared_data& client_message, + server_key_shared_data& server_message) +{ + bam_key_metadata_server server_key_metadata; + bam_setup_metadata_server server_setup_metadata; + bam_setup_auxilary_key_server server_setup_auxilary_key; + elliptic_curve_scalar private_key; + + _key_persistency.load_key_metadata(key_id, server_key_metadata); + + validate_tenant_id_setup(server_key_metadata.setup_id); + + auto algebra = get_algebra(server_key_metadata.algorithm); + + // validation to prevent replay attack + if (!server_key_metadata.encrypted_server_share.empty()) + { + LOG_ERROR("Already got client share for client id %" PRIu64 ", key id %s", client_id, key_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + std::shared_ptr damgard_fujisaki_pub( + damgard_fujisaki_public_deserialize(client_message.damgard_fujisaki_pub.data(), + client_message.damgard_fujisaki_pub.size()), + damgard_fujisaki_free_public); + + + if (!damgard_fujisaki_pub) + { + LOG_ERROR("Failed to deserialize damgard fujisaki public key for client id %" PRIu64 ", key id %s", client_id, key_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + if (damgard_fujisaki_public_size(damgard_fujisaki_pub.get()) != cosigner_params::TEMPORARY_DAMGARD_FUJISAKI_BITSIZE) + { + LOG_ERROR("Client Damgard Fujisaki key size %u is incorrect for client id %" PRIu64 ", key id %s", damgard_fujisaki_public_size(damgard_fujisaki_pub.get()), client_id, key_id.c_str()); + throw_cosigner_exception(RING_PEDERSEN_KEYLEN_TOO_SHORT); + } + + zero_knowledge_proof_status damgard_ret = damgard_fujisaki_parameters_zkp_verify(damgard_fujisaki_pub.get(), + server_key_metadata.seed, + sizeof(commitments_sha256_t), + cosigner_params::OPTIMIZED_DAMGARD_FUJISAKI_CHALLENGE_BITSIZE, + client_message.damgard_fujisaki_proof.data(), + client_message.damgard_fujisaki_proof.size()); + if (damgard_ret != ZKP_SUCCESS) + { + LOG_ERROR("Client Damgard Fujisaki zkp verification failed for client %" PRIu64 ", key id %s. Error %d", client_id, key_id.c_str(), damgard_ret); + throw_cosigner_exception(damgard_ret); + } + + if (client_message.schnorr_proof.size() != sizeof(schnorr_zkp_t)) + { + LOG_ERROR("Schnorr zkp illegal size for client %" PRIu64 ", key id %s. %u != %u", client_id, key_id.c_str(), (uint32_t)client_message.schnorr_proof.size(), (uint32_t)sizeof(schnorr_zkp_t)); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + zero_knowledge_proof_status schnorr_ret = schnorr_zkp_verify(algebra, + server_key_metadata.seed, + sizeof(server_key_metadata.seed), + &client_message.X, + (const schnorr_zkp_t*)client_message.schnorr_proof.data()); + + if (schnorr_ret != ZKP_SUCCESS) + { + LOG_ERROR("Client Schnorr zkp verification failed for client %" PRIu64 ", key id %s. Error %d", client_id, key_id.c_str(), schnorr_ret); + throw_cosigner_exception(schnorr_ret); + } + + // Decommit share and generate proof + _key_persistency.load_setup_metadata(server_key_metadata.setup_id, server_setup_metadata); + if (server_setup_metadata.setup_algorithm != server_key_metadata.algorithm) + { + LOG_ERROR("Algorithm mismatch for client %" PRIu64 ", key id %s. %u != %u", client_id, key_id.c_str(), (uint32_t)server_setup_metadata.setup_algorithm, (uint32_t)server_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + cosigner_sign_algorithm algorithm; + _key_persistency.load_key(key_id, algorithm, private_key.data); + _key_persistency.load_setup_auxilary_key(server_key_metadata.setup_id, server_setup_auxilary_key); + if (server_key_metadata.algorithm != algorithm) + { + LOG_ERROR("Metadata algorithm mismatch for key %s. %d != %d", key_id.c_str(), algorithm, server_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + byte_vector_t server_share(cosigner_params::ZKPOK_OPTIM_NX_SIZE); //320 bits + utils::byte_vector_cleaner server_share_deleter(server_share); + + //must use OPENSSL_clear_free because it holds the private key, even if temporary + std::unique_ptr private_key_bn( + BN_bin2bn(private_key.data, sizeof(elliptic_curve256_scalar_t), NULL), + bignum_clear_deleter()); + + if (!private_key_bn) + { + LOG_ERROR("Cannot allocate BIGNUM for the client %" PRIu64 ", the key id is %s.", client_id, key_id.c_str()); + throw cosigner_exception(cosigner_exception::NO_MEM); + } + + + // Pad-and-canonicalize the server secret for ZK: + // We encrypt x' = x + r·q so the Paillier plaintext is an NX-byte integer + // whose residue mod q equals the EC exponent x. This (1) matches the ZK + // size bounds and keeps transcripts zero-knowledge, (2) fixes a canonical + // representative of the coset x + qZ so the ciphertext/proof bind to a + // single value, and (3) prevents homomorphic “coset hopping” or cross- + // session linkage. The client never sees x' (only a ciphertext + ZK proof), + // so it cannot reduce mod q to recover x. + + // private_key_bn = private_key_bn + rand() * phi(n) + // x' = x + r·q with x,q < 2^256 and r < 2^64. + // Max x' = (2^256-1) + (2^64-1)(2^256-1) = 2^320 - 2^64 < 2^320. + // Therefore x' fits in NX = 40 bytes, so BN_bn2binpad(..., 40) is safe + bn_randomize_with_factor(private_key_bn.get(), private_key_bn.get(), algebra->order_internal(algebra), (cosigner_params::ZKPOK_OPTIM_NX_SIZE - cosigner_params::ZKPOK_OPTIM_KAPPA_SIZE) * 8); + + if (!BN_bn2binpad(private_key_bn.get(), server_share.data(), server_share.size())) + { + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + paillier_with_range_proof_t* paillier_proof = NULL; + + zero_knowledge_proof_status zkp_ret = paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub.get(), + server_setup_auxilary_key.paillier_commitment_priv.get(), + algebra, + server_key_metadata.seed, + sizeof(commitments_sha256_t), + server_share.data(), + server_share.size(), + /*use_extended_seed=*/1, + &paillier_proof); + if (zkp_ret != ZKP_SUCCESS) + { + LOG_ERROR("Failed to generate range proof with exponent paillier commitment. Client id %" PRIu64 ", the key id is %s.", client_id, key_id.c_str()); + throw_cosigner_exception(zkp_ret); + } + + auto paillier_proof_guard = std::unique_ptr( + paillier_proof, + range_proof_free_paillier_with_range_proof); + + // prepare message to client + server_message.encrypted_server_share.resize(paillier_proof->ciphertext_len); + memcpy(server_message.encrypted_server_share.data(), paillier_proof->ciphertext, paillier_proof->ciphertext_len); + + //update key metadata and store encrypted server share. + server_key_metadata.encrypted_server_share = server_message.encrypted_server_share; + + server_message.enc_dlog_proof.resize(paillier_proof->proof_len); + memcpy(server_message.enc_dlog_proof.data(), paillier_proof->serialized_proof, paillier_proof->proof_len); + + elliptic_curve256_point_t joint_public_key; + + // compute the server public share. + throw_cosigner_exception(algebra->generator_mul(algebra, &joint_public_key, &private_key.data)); + + // decommit the server public share + memcpy(server_message.server_public_share, joint_public_key, sizeof(elliptic_curve256_point_t)); + + // calculate the joint public key and temporary store in joint_public_key + elliptic_curve_algebra_status algebra_ret = algebra->add_points(algebra, &joint_public_key, &joint_public_key, &client_message.X); + if (algebra_ret != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) + { + LOG_ERROR("Algebra failed for for the client %" PRIu64 ", the key verify_client_proofs_and_decommit_share_with_proofid is %s. Error %d", client_id, key_id.c_str(), algebra_ret); + throw_cosigner_exception(algebra_ret); + } + + // check if it is the expected share for cases when we know public key in advance + // when we are regenerating existing key the public key will be known in advance + // so in this cases we must prevent the pub key forgery + if (!server_key_metadata.has_public_key()) + { + memcpy(&server_key_metadata.public_key[0], &joint_public_key[0], sizeof(elliptic_curve256_point_t)); + } + else if (memcmp(&server_key_metadata.public_key[0], &joint_public_key[0], sizeof(elliptic_curve256_point_t)) != 0) + { + LOG_ERROR("Public key mismatch for the client %" PRIu64 ", the key verify_client_proofs_and_decommit_share_with_proofid is %s", client_id, key_id.c_str()); + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + // save client's public key in the client_public_share + static_assert(sizeof(server_key_metadata.client_public_share) == sizeof(client_message.X)); + memcpy(server_key_metadata.client_public_share, client_message.X, sizeof(server_key_metadata.client_public_share)); + + _key_persistency.store_key_metadata(key_id, server_key_metadata, true); + if (!_key_persistency.backup_key(key_id)) + { + LOG_ERROR("Failed to backup key %s", key_id.c_str()); + _key_persistency.delete_key_data(key_id); + throw cosigner_exception(cosigner_exception::BACKUP_FAILED); + } + + _platform_service.clear_key_setup_in_progress(key_id); +} + +void bam_ecdsa_cosigner_server::generate_signature_share(const std::string& key_id, + const std::string& tx_id, + const uint32_t version, + const uint64_t server_id, + const uint64_t client_id, + const cosigner_sign_algorithm requested_algorithm, + const common::cosigner::signing_data& data, + const std::string& metadata_json, + const std::set& players_set, + std::vector& server_commitments) +{ + LOG_INFO("Entering txid = %s", tx_id.c_str()); + + _platform_service.prepare_for_signing(key_id, tx_id); + + auto server_signature_data_ptr = std::make_shared(version, server_id, client_id, key_id, static_cast(_platform_service.now_msec() / 1000)); + auto& server_signature_data = *server_signature_data_ptr; + + bam_key_metadata_server server_key_metadata; + + const auto signature_request_data = fill_bam_signing_info_from_metadata(metadata_json, static_cast(data.blocks.size())); + if (0 == signature_request_data.size()) + { + LOG_ERROR("Key %s, tx_id %s: Empty signature batch request", key_id.c_str(), tx_id.c_str()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + else if (data.blocks.size() != signature_request_data.size()) //must not happen + { + LOG_ERROR("number of blocks %u is different than number of flags %u", static_cast(data.blocks.size()), static_cast(signature_request_data.size())); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + _key_persistency.load_key_metadata(key_id, server_key_metadata); + if (server_key_metadata.encrypted_server_share.empty()) + { + LOG_ERROR("Key id %s tx_id %s: trying to sign while key was not fully created", key_id.c_str(), tx_id.c_str()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + validate_tenant_id_setup(server_key_metadata.setup_id); + + if (requested_algorithm != server_key_metadata.algorithm) + { + LOG_ERROR("Key %s, tx_id %s: algorithm mismatch, requested %d but key has %d", key_id.c_str(), tx_id.c_str(), requested_algorithm, server_key_metadata.algorithm); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + if (server_key_metadata.peer_id != client_id) + { + LOG_ERROR("Wrong client id for key %s. %" PRIu64 " != %" PRIu64, key_id.c_str(), server_key_metadata.peer_id, client_id); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + _platform_service.on_start_signing(key_id, tx_id, data, metadata_json, players_set, platform_service::MULTI_ROUND_SIGNATURE); + + auto algebra = get_algebra(server_key_metadata.algorithm); + server_commitments.resize(signature_request_data.size()); + server_signature_data.sig_data.resize(signature_request_data.size()); + + for (uint32_t i = 0; i < (uint32_t)signature_request_data.size(); ++ i) + { + auto& sig_request = signature_request_data[i]; // request which has to be signed + auto& persistant_sig_data = server_signature_data.sig_data[i]; // data about the signature that the server should save + auto& server_message = server_commitments[i]; // the message to the client + + persistant_sig_data.flags = sig_request.flags; + if (data.blocks[i].data.size() != sizeof(elliptic_curve256_scalar_t)) + { + LOG_ERROR("block %u has illegal size of %u", i, static_cast(data.blocks[i].data.size())); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + memcpy(persistant_sig_data.message, data.blocks[i].data.data(), sizeof(elliptic_curve256_scalar_t)); + + // required because of the Claim 7.15 is the BAM article + check_non_null_message(persistant_sig_data.message, algebra); + + derivation_key_delta(algebra, server_key_metadata.public_key, data.chaincode, data.blocks[i].path, persistant_sig_data.derivation_delta); + + throw_cosigner_exception(algebra->rand(algebra, &persistant_sig_data.k.data)); + throw_cosigner_exception(algebra->generator_mul(algebra, &server_message.R, &persistant_sig_data.k.data)); + + // It is required to calculate client's public key to the power of k + // which in turn would serve as a discrete log proof of the k. + // But it is also required to have the derivation specific proof, so commit to the derived public share + throw_cosigner_exception(algebra->generator_mul(algebra, &server_message.Y, &persistant_sig_data.derivation_delta)); + // server_key_metadata.client_public_share hold client's public key at this point + throw_cosigner_exception(algebra->add_points(algebra, &server_message.Y, &server_message.Y, &server_key_metadata.client_public_share)); + throw_cosigner_exception(algebra->point_mul(algebra, &server_message.Y, &server_message.Y, &persistant_sig_data.k.data)); + } + + // we don't care about storing R... + _tx_persistency.store_signature_data(tx_id, server_signature_data_ptr); +} + +void bam_ecdsa_cosigner_server::verify_partial_signature_and_output_signature(const std::string& tx_id, + const uint64_t client_id, + const std::vector& partial_signatures, + std::vector& signatures, + cosigner_sign_algorithm& algorithm) +{ + bam_setup_auxilary_key_server server_setup_auxilary_key; + bam_setup_metadata_server server_setup_metadata; + bam_key_metadata_server server_key_metadata; + commitments_sha256_t signature_add; + + auto server_signature_data_ptr = _tx_persistency.load_signature_data_and_delete(tx_id); + auto& server_signature_data = *server_signature_data_ptr; + const auto& key_id = server_signature_data.key_id; + + _key_persistency.load_key_metadata(key_id, server_key_metadata); + + validate_tenant_id_setup(server_key_metadata.setup_id); + + if (client_id != server_signature_data.client_signer_id) + { + LOG_ERROR("Key %s, tx_id %s: client player id mismatch, got %" PRIu64 " expected %" PRIu64, key_id.c_str(), tx_id.c_str(), client_id, server_signature_data.client_signer_id); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + _key_persistency.load_setup_auxilary_key(server_key_metadata.setup_id, server_setup_auxilary_key); + _key_persistency.load_setup_metadata(server_key_metadata.setup_id, server_setup_metadata); + + if (server_signature_data.sig_data.size() != partial_signatures.size()) + { + LOG_ERROR("Key %s, tx_id %s: client partial signatures size mismatch. %u != %u", key_id.c_str(), tx_id.c_str(), (uint32_t)partial_signatures.size(), (uint32_t) server_signature_data.sig_data.size()); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + signatures.resize(partial_signatures.size()); + + // verified during key generation - here only for sanity + assert(server_setup_metadata.setup_algorithm == server_key_metadata.algorithm); + + std::unique_ptr ctx(BN_CTX_new(), BN_CTX_free); + if (!ctx) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + generate_aad_for_signature(server_signature_data.key_id, server_signature_data.server_signer_id, server_signature_data.client_signer_id, tx_id, signature_add); + + auto algebra = get_algebra(server_key_metadata.algorithm); + + BN_CTX_start(ctx.get()); + std::unique_ptr ctx_start_guard(ctx.get(), BN_CTX_end); + + BIGNUM* half_n = BN_CTX_get(ctx.get()); + BIGNUM* encrypted_share_bn = BN_CTX_get(ctx.get()); + if (!half_n || !encrypted_share_bn) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + if (!BN_rshift1(half_n, server_setup_auxilary_key.paillier_commitment_priv->pub.n)) + { + LOG_ERROR("shift right failed error %lu", ERR_get_error()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + if (!BN_bin2bn(server_key_metadata.encrypted_server_share.data(), server_key_metadata.encrypted_server_share.size(), encrypted_share_bn)) + { + LOG_ERROR("Error converting encrypted server share %lu", ERR_get_error()); + throw cosigner_exception(cosigner_exception::NO_MEM); + } + + for (uint32_t i = 0; i < (uint32_t)partial_signatures.size(); ++ i ) + { + BN_CTX_start(ctx.get()); + std::unique_ptr ctx_for_loop_guard(ctx.get(), BN_CTX_end); + + auto& signature = signatures[i]; + auto& partial_signature = partial_signatures[i]; + auto& persistant_sig_data = server_signature_data.sig_data[i]; + elliptic_curve256_point_t tmp_point; + elliptic_curve_scalar v; + + // check that the client share is not the point at infinity + check_a_valid_point(partial_signature.client_R, algebra); + + GFp_curve_algebra_ctx_t* curve = (GFp_curve_algebra_ctx_t*)algebra->ctx; + // tmp_point = G^(k_client * k_server) + throw_cosigner_exception(algebra->point_mul(algebra, &tmp_point, &partial_signature.client_R, &persistant_sig_data.k.data)); + + // DH-consistency check: ensure the client’s R1 and common_share encode the SAME nonce k1. + // We verify R1^k2 == common_share, i.e., (g^k1)^k2 == (g^k2)^k1 == g^(k1*k2). + // Why this is critical: + // - Proves the client used a single k1 for both R1 and common_share (PoK w.r.t. both bases). + // - Prevents “chosen-R” attacks where the client tries to pick common_share to bias r. + // - Ensures both parties derive the same r after the hash-based shift. + // - This DH-tuple consistency is required by the signing protocol’s security argument + // (see paper: Two-round signing → compute/verify the common R; equality-of-exponents / DH consistency). + if (memcmp(tmp_point, partial_signature.common_R, sizeof(elliptic_curve256_point_t)) != 0) + { + LOG_ERROR("Invalid common share point for client %" PRIu64 ", key_id %s, tx_id %s", server_signature_data.client_signer_id, key_id.c_str(), tx_id.c_str()); + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + elliptic_curve256_scalar_t hash_shift; + elliptic_curve256_point_t derived_public_key, corrected_R; + derive_and_compute_corrected_R(algebra, tx_id, persistant_sig_data, server_key_metadata.public_key, partial_signature, derived_public_key, hash_shift, corrected_R); + uint8_t overflow = 0; + + // r + throw_cosigner_exception(GFp_curve_algebra_get_point_projection(curve, &signature.r, &corrected_R, &overflow)); + + // POSITIVE_R handling (server-side) + // + // Do NOT trust or accept any “pre-corrected” R from the client. In this + // protocol the x-coordinate r is fixed by public data and the server’s nonce: + // R* = R_com + R_client^{H(X || R_client || R_com || m)} + // r = x(R*) + // where R_client = g^{k_client} and R_com = R_server^{k_client}. We already + // checked that R_com = R_client^{k_server}, so R* (and thus r) is determined + // independently by the server. We therefore recompute the same positivity + // loop locally: while r is not “positive”, add R_com again. + // + // Important cross-party invariant: + // If t additions are needed, the CLIENT must update its nonce to k_client' = k_client·t + // and use k_client'^{-1} when producing its encrypted partial (u, v). Its + // well-formedness ZK proof binds (u, v) to that adjusted k_client' and to the same r. + // See client code: + // bam_ecdsa_cosigner_client::compute_partial_signature(...) + // → the block under `if (persistant_sig_data.flags & POSITIVE_R)` + // + // Consequence for the server: + // We MUST NOT apply any extra “positivity correction” to the partial or to s. + // We simply: + // 1) recompute r via the same deterministic loop, + // 2) verify the client’s proof, + // 3) decrypt u and combine as specified. + // Any additional correction here would double-apply the factor t and break + // correctness. The client has already baked the correction into its share. + if (persistant_sig_data.flags & POSITIVE_R) + { + //check that R is good for us + // try to correct R multiplying share by n= 2,3... untill we find the right R and remember the n + // also correct the signature.r, + uint8_t r_correction_cycles = 1; + memcpy(tmp_point, corrected_R, sizeof(elliptic_curve256_point_t)); + while (!is_positive(server_key_metadata.algorithm, signature.r)) + { + assert(r_correction_cycles < 255); + if (r_correction_cycles == 255) + { + LOG_ERROR("Failed to find a positive r in 256 iterations key_id %s, tx_id %s", key_id.c_str(), tx_id.c_str()); + throw_cosigner_exception(ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + ++r_correction_cycles; + throw_cosigner_exception(algebra->add_points(algebra, &corrected_R, &corrected_R, &tmp_point)); + throw_cosigner_exception(GFp_curve_algebra_get_point_projection(curve, &signature.r, &corrected_R, &overflow)); + } + } + + BIGNUM* encrypted_partial_signature_bn = BN_CTX_get(ctx.get()); + BIGNUM* decrypted_partial_signature_bn = BN_CTX_get(ctx.get()); + BIGNUM* hash_shift_bn = BN_CTX_get(ctx.get()); + BIGNUM* s_bn = BN_CTX_get(ctx.get()); + + if (!encrypted_partial_signature_bn || !decrypted_partial_signature_bn || !hash_shift_bn || !s_bn) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + if (!BN_bin2bn(partial_signature.encrypted_partial_sig.data(), partial_signature.encrypted_partial_sig.size(), encrypted_partial_signature_bn)) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + // restore server's R + throw_cosigner_exception(algebra->generator_mul(algebra, &tmp_point, &persistant_sig_data.k.data)); + + + bam_well_formed_proof::verify_signature_proof(partial_signature.sig_proof, + server_setup_auxilary_key.paillier_commitment_priv.get(), + algebra, + &server_setup_metadata.ec_base, + signature_add, + encrypted_share_bn, + encrypted_partial_signature_bn, + tmp_point, + ctx.get()); + + // s + throw_cosigner_exception(algebra->add_scalars(algebra, + &hash_shift, //reuse the variable + hash_shift, + sizeof(elliptic_curve256_scalar_t), + persistant_sig_data.k.data, + sizeof(elliptic_curve256_scalar_t))); + + throw_cosigner_exception(algebra->inverse(algebra, &hash_shift, &hash_shift)); + + if (!BN_bin2bn(hash_shift, sizeof(elliptic_curve256_scalar_t), hash_shift_bn)) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + // decrypt the partial signature and store it inside the same place + throw_paillier_exception(paillier_commitment_decrypt_openssl_internal(server_setup_auxilary_key.paillier_commitment_priv.get(), + encrypted_partial_signature_bn, + decrypted_partial_signature_bn, + ctx.get())); + + // Canonicalize the Paillier plaintext before switching moduli. + // + // Because we work with different modulus we need to switch from mod n to mod q (order of the curve) + // To map the value correctly we need to center the value around 0 before switching + if (BN_cmp(decrypted_partial_signature_bn, half_n) > 0) + { + if (!BN_mod_sub(decrypted_partial_signature_bn, + decrypted_partial_signature_bn, + server_setup_auxilary_key.paillier_commitment_priv->pub.n, + algebra->order_internal(algebra), + ctx.get())) + { + LOG_ERROR("Failed to shift by N/2 error %lu", ERR_get_error()); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + } + + if (!BN_mod_mul(s_bn, hash_shift_bn, decrypted_partial_signature_bn, algebra->order_internal(algebra), ctx.get())) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + if (!BN_bn2binpad(s_bn, signature.s, sizeof(signature.s))) + { + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + + signature.v = (overflow ? 2 : 0) | (is_odd_point(corrected_R) ? 1 : 0); + make_sig_s_positive(server_key_metadata.algorithm, algebra, signature); + + //Use derived public key + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(curve, + &derived_public_key, + &persistant_sig_data.message, + &signature.r, + &signature.s); + if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) + { + LOG_FATAL("failed to verify signature for client %" PRIu64 ", error %d", server_signature_data.client_signer_id, status); + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + } + + algorithm = server_key_metadata.algorithm; +} + +void bam_ecdsa_cosigner_server::get_public_key(const std::string& key_id, generated_public_key& pub_key_data) const +{ + bam_key_metadata_server server_key_metadata; + _key_persistency.load_key_metadata(key_id, server_key_metadata); + auto algebra = get_algebra(server_key_metadata.algorithm); + pub_key_data.pub_key.assign(reinterpret_cast(server_key_metadata.public_key), algebra->point_size(algebra)); + pub_key_data.algorithm = server_key_metadata.algorithm; +} + + +} //namespace fireblocks::common::cosigner diff --git a/src/common/cosigner/bam_key_persistency_structures.cpp b/src/common/cosigner/bam_key_persistency_structures.cpp new file mode 100644 index 0000000..72e82ec --- /dev/null +++ b/src/common/cosigner/bam_key_persistency_structures.cpp @@ -0,0 +1,20 @@ +#include "cosigner/bam_key_persistency_structures.h" + + +namespace fireblocks::common::cosigner +{ + +bam_key_metadata_base::bam_key_metadata_base(const cosigner_sign_algorithm _algo, + const std::string _setup_id, + const uint64_t _peer_id, + const elliptic_curve256_point_t& _pub_key): + key_metadata_base(_algo), + setup_id(_setup_id), + peer_id(_peer_id) +{ + memcpy(public_key, _pub_key, sizeof(public_key)); +} + + + +} diff --git a/src/common/cosigner/bam_well_formed_proof.cpp b/src/common/cosigner/bam_well_formed_proof.cpp new file mode 100644 index 0000000..a200368 --- /dev/null +++ b/src/common/cosigner/bam_well_formed_proof.cpp @@ -0,0 +1,492 @@ +#include "bam_well_formed_proof.h" + +#include +#include +#include + +#include "cosigner/bam_ecdsa_cosigner.h" +#include "cosigner_bn.h" +#include "logging/logging_t.h" +#include "../crypto/paillier_commitment/paillier_commitment_internal.h" + +namespace fireblocks::common::cosigner::bam_well_formed_proof +{ +using cosigner_params = bam_ecdsa_cosigner::cosigner_params; + +namespace //anonymous namespace +{ +struct well_formed_signature_proof +{ + explicit well_formed_signature_proof(struct bignum_ctx* ctx) : + bn_ctx(ctx) + { + D = BN_CTX_get(ctx); + z1 = BN_CTX_get(ctx); + z2 = BN_CTX_get(ctx); + w2 = BN_CTX_get(ctx); + + if (!D || !z1 || !z2 || !w2) + { + throw cosigner_exception(cosigner_exception::NO_MEM); + } + } + + struct bignum_st* D; + elliptic_curve256_point_t U; + elliptic_curve256_point_t V; + struct bignum_st* z1 { nullptr }; + struct bignum_st* z2 { nullptr }; + elliptic_curve256_scalar_t w0; + struct bignum_st* w2 { nullptr }; +private: + bn_ctx_frame bn_ctx; +}; + +static uint32_t signature_proof_size(const uint32_t paillier_commitment_n_size) +{ + return sizeof(uint32_t) + // size of the paillier public key + 2 * paillier_commitment_n_size + // sizeof(D) + sizeof(elliptic_curve256_point_t) + // sizeof(U) + sizeof(elliptic_curve256_point_t) + // sizeof(V) + cosigner_params::ZKPOK_OPTIM_NA_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_size * 8) + // sizeof(z1) + cosigner_params::ZKPOK_OPTIM_NB_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_size * 8) + // sizeof(z2) + sizeof(elliptic_curve256_scalar_t) + // sizeof(w0) + cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_size * 8); // sizeof(w2) +} + +void serialize_well_formed_proof(const uint32_t paillier_commitment_n_bitsize, const well_formed_signature_proof& proof, byte_vector_t &serialized) +{ + const uint32_t paillier_size = (paillier_commitment_n_bitsize + 7) / 8; + serialized.resize(signature_proof_size(paillier_size)); + + uint8_t* ptr = serialized.data(); + + *(uint32_t *)ptr = paillier_size; + ptr += sizeof(uint32_t); + + if (BN_bn2binpad(proof.D, ptr, 2 * paillier_size) <= 0) + { + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + ptr += 2 * paillier_size; + + memcpy(ptr, proof.U, sizeof(elliptic_curve256_point_t)); + ptr += sizeof(elliptic_curve256_point_t); + + memcpy(ptr, proof.V, sizeof(elliptic_curve256_point_t)); + ptr += sizeof(elliptic_curve256_point_t); + + if (BN_bn2binpad(proof.z1, ptr, cosigner_params::ZKPOK_OPTIM_NA_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize)) <= 0) + { + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + ptr += cosigner_params::ZKPOK_OPTIM_NA_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize); + + if (BN_bn2binpad(proof.z2, ptr, cosigner_params::ZKPOK_OPTIM_NB_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize)) <= 0) + { + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + ptr += cosigner_params::ZKPOK_OPTIM_NB_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize); + + memcpy(ptr, proof.w0, sizeof(elliptic_curve256_scalar_t)); + ptr += sizeof(elliptic_curve256_scalar_t); + + if (BN_bn2binpad(proof.w2, ptr, cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize)) <= 0) + { + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + ptr += cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize); + + assert(ptr == serialized.data() + serialized.size()); +} + +void deserialize_well_formed_proof(const uint32_t paillier_commitment_n_bitsize, well_formed_signature_proof& proof, const byte_vector_t& serialized) +{ + const uint32_t expected_paillier_commitment_size = (paillier_commitment_n_bitsize + 7) / 8; + + const uint8_t* ptr = serialized.data(); + + const uint32_t expected_signature_size = signature_proof_size(expected_paillier_commitment_size); + + if (expected_signature_size > serialized.size()) + { + LOG_ERROR("Error in deserialize_well_formed_proof. Size mismatch %u != %u", expected_signature_size, (uint32_t)serialized.size()); + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + const uint32_t paillier_size = *(const uint32_t *)ptr; + ptr += sizeof(uint32_t); + + if (paillier_size != expected_paillier_commitment_size) + { + LOG_ERROR("Error in deserialize_well_formed_proof. Size of n mismatch %u != %u", paillier_size, expected_paillier_commitment_size); + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + if (!BN_bin2bn(ptr, 2 * paillier_size, proof.D)) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + ptr += 2 * paillier_size; + + memcpy(proof.U, ptr, sizeof(elliptic_curve256_point_t)); + ptr += sizeof(elliptic_curve256_point_t); + + memcpy(proof.V, ptr, sizeof(elliptic_curve256_point_t)); + ptr += sizeof(elliptic_curve256_point_t); + + if (!BN_bin2bn(ptr, cosigner_params::ZKPOK_OPTIM_NA_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize), proof.z1)) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + ptr += (cosigner_params::ZKPOK_OPTIM_NA_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize)); + + if (!BN_bin2bn(ptr, cosigner_params::ZKPOK_OPTIM_NB_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize), proof.z2)) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + ptr += cosigner_params::ZKPOK_OPTIM_NB_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize); + + memcpy(proof.w0, ptr, sizeof(elliptic_curve256_scalar_t)); + ptr += sizeof(elliptic_curve256_scalar_t); + + if (!BN_bin2bn(ptr, cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize), proof.w2)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + ptr += cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + ZKPOK_OPTIM_EPSILON_SIZE(paillier_commitment_n_bitsize); + + assert(expected_signature_size == (uint32_t)(ptr - serialized.data())); +} + + +static byte_vector_t compute_e(const well_formed_signature_proof& proof, + const struct bignum_st* S, + const struct bignum_st* encrypted_share, + const commitments_sha256_t& signature_aad, + const pedersen_commitment_two_generators_t& ec_base, + const paillier_commitment_public_key_t& paillier, + const elliptic_curve256_point_t& r_server) +{ + byte_vector_t ret(sizeof(commitments_sha256_t)); + SHA256_CTX ctx; + if (!SHA256_Init(&ctx)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!SHA256_Update(&ctx, &signature_aad[0], sizeof(commitments_sha256_t)) || + !SHA256_Update(&ctx, ec_base.h, sizeof(elliptic_curve256_point_t)) || + !SHA256_Update(&ctx, ec_base.f, sizeof(elliptic_curve256_point_t))) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + const uint32_t paillier_size = BN_num_bytes(paillier.n); + const uint32_t paillier_size_n2 = BN_num_bytes(paillier.n2); + + assert(paillier_size_n2 > paillier_size); + + byte_vector_t tmp(paillier_size_n2); // use maximum size in advance + if (BN_bn2binpad(paillier.n, tmp.data(), paillier_size) != (int)paillier_size || + !SHA256_Update(&ctx, tmp.data(), paillier_size)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + assert(BN_num_bytes(paillier.t) <= (int)paillier_size); + if (BN_bn2binpad(paillier.t, tmp.data(), paillier_size) != (int)paillier_size || + !SHA256_Update(&ctx, tmp.data(), paillier_size)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + assert(BN_num_bytes(paillier.s) <= (int)paillier_size); + if (BN_bn2binpad(paillier.s, tmp.data(), paillier_size) != (int)paillier_size || + !SHA256_Update(&ctx, tmp.data(), paillier_size)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (BN_bn2binpad(S, tmp.data(), paillier_size_n2) != (int)paillier_size_n2 || + !SHA256_Update(&ctx, tmp.data(), paillier_size_n2)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (BN_bn2binpad(encrypted_share, tmp.data(), paillier_size_n2) != (int)paillier_size_n2 || + !SHA256_Update(&ctx, tmp.data(), paillier_size_n2)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!SHA256_Update(&ctx, &r_server[0], sizeof(elliptic_curve256_point_t))) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!SHA256_Update(&ctx, proof.U, sizeof(proof.U)) || + !SHA256_Update(&ctx, proof.V, sizeof(proof.V))) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + assert(BN_num_bytes(proof.D) <= (int)paillier_size_n2); + if (BN_bn2binpad(proof.D, tmp.data(), paillier_size_n2) != (int)paillier_size_n2 || + !SHA256_Update(&ctx, tmp.data(), paillier_size_n2)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!SHA256_Final(ret.data(), &ctx)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + return ret; +} + + +} //anonymous namespace + +void generate_signature_proof(const paillier_commitment_public_key_t *paillier, + elliptic_curve256_algebra_ctx *algebra, + struct bignum_ctx *ctx, + const pedersen_commitment_two_generators_t *ec_base, //two points h and f + const struct bignum_st *plaintext, // this is u, called a in proof + const struct bignum_st *encrypted_share, // encrypted server share + const struct bignum_st *exponent, // v, called b in proof + const struct bignum_st *lambda0, // same lambda0 used in the partial signature encryption + const struct bignum_st* S, // partial signature + const commitments_sha256_t& signature_aad, + const byte_vector_t& plaintext_bin, // plaintext binary + const byte_vector_t& exponent_bin, // exponent binary + const elliptic_curve256_point_t& r_server, + byte_vector_t &serialized) +{ + if (!paillier || !algebra || !ec_base || !lambda0 || !plaintext_bin.size() || !exponent_bin.size()) + { + throw_cosigner_exception(ZKP_INVALID_PARAMETER); + } + + well_formed_signature_proof well_formed_proof(ctx); //also starts the bn context + + elliptic_curve_scalar gamma; + elliptic_curve_scalar gamma_p; + + BIGNUM *alpha = BN_CTX_get(ctx); + BIGNUM *beta = BN_CTX_get(ctx); + BIGNUM *lambda_p = BN_CTX_get(ctx); + BIGNUM *e = BN_CTX_get(ctx); + + if (!alpha || !beta || !lambda_p || !e) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + // generate all random parameters + if (algebra->rand(algebra, &gamma.data) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS || // generate random gamma + algebra->rand(algebra, &gamma_p.data)!= ELLIPTIC_CURVE_ALGEBRA_SUCCESS || // generate random gamma_p + !BN_rand(alpha, (cosigner_params::ZKPOK_OPTIM_NA_SIZE + cosigner_params::ZKPOK_OPTIM_EPSILON_SIZE) * 8, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) || + !BN_rand(beta, (cosigner_params::ZKPOK_OPTIM_NB_SIZE + cosigner_params::ZKPOK_OPTIM_EPSILON_SIZE) * 8, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) || + !BN_rand(lambda_p,(cosigner_params::ZKPOK_OPTIM_NLAMBDA0_SIZE + cosigner_params::ZKPOK_OPTIM_EPSILON_SIZE) * 8, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) + ) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + auto ret = pedersen_commitment_two_generators_create_commitment(&well_formed_proof.U, + ec_base, + plaintext_bin.data(), + plaintext_bin.size(), + exponent_bin.data(), + exponent_bin.size(), + gamma.data, + sizeof(gamma.data), + algebra); + if (ret != COMMITMENTS_SUCCESS) + { + LOG_ERROR("Error creating elliptic curve commitment. Error %d", ret); + throw_cosigner_exception((commitments_status)ret); + } + + // Generate V / B / D + byte_vector_t alpha_bin, beta_bin; + + + alpha_bin.resize(BN_num_bytes(alpha)); + beta_bin.resize(BN_num_bytes(beta)); + + if (!BN_bn2bin(alpha, alpha_bin.data()) || + !BN_bn2bin(beta, beta_bin.data())) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + ret = pedersen_commitment_two_generators_create_commitment(&well_formed_proof.V, + ec_base, + alpha_bin.data(), // alpha + alpha_bin.size(), + beta_bin.data(), // beta + beta_bin.size(), + gamma_p.data, + sizeof(gamma_p.data), + algebra); + if (ret != COMMITMENTS_SUCCESS) + { + LOG_ERROR("Error creating elliptic curve commitment. Error %d", ret); + throw_cosigner_exception((commitments_status)ret); + } + + throw_paillier_exception(paillier_commitment_commit_internal(paillier, alpha, lambda_p, encrypted_share, beta, well_formed_proof.D, ctx)); + + // compute e from all previous data. + auto e_bin = compute_e(well_formed_proof, S, encrypted_share, signature_aad, *ec_base, *paillier, r_server); + + assert(e_bin.size() >= cosigner_params::ZKPOK_OPTIM_L_SIZE); + + e_bin.resize(cosigner_params::ZKPOK_OPTIM_L_SIZE); + + if (!BN_bin2bn(e_bin.data(), e_bin.size(), e)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!BN_mul(well_formed_proof.z1, e, plaintext, ctx) || // z1 = e * a + !BN_add(well_formed_proof.z1, well_formed_proof.z1, alpha) || // z1 = e * a + alpha + !BN_mul(well_formed_proof.z2, e, exponent, ctx) || // z2 = e * b + !BN_add(well_formed_proof.z2, well_formed_proof.z2, beta) || // z2 = e * b + beta + !BN_mul(well_formed_proof.w2, e, lambda0, ctx) || // w2 = e * lambda0 + !BN_add(well_formed_proof.w2, well_formed_proof.w2, lambda_p)) // w2 = e * lambda0 + lambda_p + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + + //compute w0 = e * gamma + gammp_p in scalar field + throw_cosigner_exception(algebra->mul_scalars(algebra, &well_formed_proof.w0, e_bin.data(), e_bin.size(), gamma.data, sizeof(gamma.data))); + throw_cosigner_exception(algebra->add_scalars(algebra, &well_formed_proof.w0, well_formed_proof.w0, sizeof(well_formed_proof.w0), gamma_p.data, sizeof(gamma_p.data))); + + serialize_well_formed_proof(cosigner_params::PAILLIER_COMMITMENT_BITSIZE , well_formed_proof, serialized); +} + +void verify_signature_proof(const byte_vector_t& serialized, + const paillier_commitment_private_key_t* paillier, + elliptic_curve256_algebra_ctx* algebra, + const pedersen_commitment_two_generators_t* ec_base, + const commitments_sha256_t& signature_aad, + const struct bignum_st* encrypted_share, // encrypted share of the server + const struct bignum_st* encrypted_signature, // encrypted partial signature from client + const elliptic_curve256_point_t& r_server, + struct bignum_ctx* ctx) +{ + BIGNUM *tmp1 = NULL, *tmp2 = NULL, *e_bn = NULL; + + well_formed_signature_proof proof(ctx); // also starts the bn context + + if (is_coprime_fast(encrypted_signature, paillier->pub.n, ctx) != 1) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + deserialize_well_formed_proof(cosigner_params::PAILLIER_COMMITMENT_BITSIZE, proof, serialized); + + tmp1 = BN_CTX_get(ctx); + tmp2 = BN_CTX_get(ctx); + e_bn = BN_CTX_get(ctx); + if (!tmp1 || !tmp2 || !e_bn) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + if (!BN_set_bit(tmp1, (cosigner_params::ZKPOK_OPTIM_NA_SIZE + cosigner_params::ZKPOK_OPTIM_EPSILON_SIZE) * 8) || + BN_cmp(proof.z1, tmp1) >= 0) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + BN_zero(tmp1); + + if (!BN_set_bit(tmp1, (cosigner_params::ZKPOK_OPTIM_NB_SIZE + cosigner_params::ZKPOK_OPTIM_EPSILON_SIZE) * 8) || + BN_cmp(proof.z2, tmp1) >= 0) + { + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + auto e_bin = compute_e(proof, encrypted_signature, encrypted_share, signature_aad, *ec_base, paillier->pub, r_server); + assert(e_bin.size() >= cosigner_params::ZKPOK_OPTIM_L_SIZE); + + e_bin.resize(cosigner_params::ZKPOK_OPTIM_L_SIZE); + if (!BN_bin2bn(e_bin.data(), e_bin.size(), e_bn)) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + uint8_t zero = 0; + elliptic_curve256_scalar_t e_scalar; + throw_cosigner_exception(algebra->add_scalars(algebra, &e_scalar, e_bin.data(), e_bin.size(), &zero, sizeof(uint8_t))); + + elliptic_curve256_point_t ec_left_commitment; + elliptic_curve256_point_t ec_rigth_commitment; + + byte_vector_t z1_bin(BN_num_bytes(proof.z1)); + byte_vector_t z2_bin(BN_num_bytes(proof.z2)); + byte_vector_t w2_bin(BN_num_bytes(proof.w2)); + + if (!BN_bn2bin(proof.z1, z1_bin.data()) || + !BN_bn2bin(proof.z2, z2_bin.data()) || + !BN_bn2bin(proof.w2, w2_bin.data())) + { + throw_cosigner_exception(ZKP_OUT_OF_MEMORY); + } + + + // Check that g^z1 * f^z2 * f^w0 == V.U^e + throw_cosigner_exception( + pedersen_commitment_two_generators_create_commitment(&ec_left_commitment, + ec_base, + z1_bin.data(), + z1_bin.size(), + z2_bin.data(), + z2_bin.size(), + proof.w0, + sizeof(proof.w0), + algebra)); + + throw_cosigner_exception(algebra->point_mul(algebra, &ec_rigth_commitment, &proof.U, &e_scalar)); + throw_cosigner_exception(algebra->add_points(algebra, &ec_rigth_commitment, &ec_rigth_commitment, &proof.V)); + + if (memcmp(ec_left_commitment, ec_rigth_commitment, sizeof(elliptic_curve256_point_t)) != 0) + { + LOG_ERROR("ec_left does not equal ec_right commitment"); + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } + + // Check that Enc(z1, rho^w2). E^z2 = D.S^e [N^2] + // We will check this equality mod p^2 AND mod q^2. If one of them does not hold, then + // the proof verification has failed. + + auto ret = paillier_commitment_commit_with_private_internal(paillier, proof.z1, proof.w2, encrypted_share, proof.z2, tmp1, ctx); + if (ret != PAILLIER_SUCCESS) + { + LOG_ERROR("paillier_commitment_commit_with_private_internal failed with error %ld", ret); + throw_paillier_exception(ret); + } + + if (!BN_mod_exp_mont(tmp2, encrypted_signature, e_bn, paillier->pub.n2, ctx, paillier->pub.mont_n2) || + !BN_mod_mul(tmp2, tmp2, proof.D, paillier->pub.n2, ctx)) + { + throw_cosigner_exception(ZKP_UNKNOWN_ERROR); + } + + if (BN_cmp(tmp1, tmp2) != 0) + { + LOG_ERROR("verification failed."); + throw_cosigner_exception(ZKP_VERIFICATION_FAILED); + } +} + + + + + +} \ No newline at end of file diff --git a/src/common/cosigner/bam_well_formed_proof.h b/src/common/cosigner/bam_well_formed_proof.h new file mode 100644 index 0000000..edbe443 --- /dev/null +++ b/src/common/cosigner/bam_well_formed_proof.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include "crypto/commitments/commitments.h" +#include "crypto/paillier_commitment/paillier_commitment.h" +#include "crypto//commitments/pedersen.h" +#include "utils/string_utils.h" + +struct bignum_st; +struct bignum_ctx; + +namespace fireblocks::common::cosigner::bam_well_formed_proof +{ + using byte_vector_t = common::utils::byte_vector_t; + + void generate_signature_proof(const paillier_commitment_public_key_t *paillier, + elliptic_curve256_algebra_ctx *algebra, + struct bignum_ctx *ctx, + const pedersen_commitment_two_generators_t *ec_base, //two points h and f + const struct bignum_st *plaintext, // this is u, called a in proof + const struct bignum_st *encrypted_share, // encrypted server share + const struct bignum_st *exponent, // v, called b in proof + const struct bignum_st *lambda0, // same lambda0 used in the partial signature encryption + const struct bignum_st* S, // partial signature + const commitments_sha256_t& signature_aad, + const byte_vector_t& plaintext_bin, // plaintext binary + const byte_vector_t& exponent_bin, // exponent binary + const elliptic_curve256_point_t& r_server, + byte_vector_t &serialized); + + void verify_signature_proof(const byte_vector_t& serialized, + const paillier_commitment_private_key_t* paillier, + elliptic_curve256_algebra_ctx *algebra, + const pedersen_commitment_two_generators_t* ec_base, + const commitments_sha256_t& signature_aad, + const struct bignum_st* encrypted_share, // encrypted share of the server + const struct bignum_st* encrypted_signature, // encrypted partial signature from client + const elliptic_curve256_point_t& r_server, + struct bignum_ctx* ctx); + + +} //namespace fireblocks::common::cosigner::bam_well_formed_proof + diff --git a/src/common/cosigner/cmp_ecdsa_offline_signing_service.cpp b/src/common/cosigner/cmp_ecdsa_offline_signing_service.cpp index eb945c7..ff86ec2 100644 --- a/src/common/cosigner/cmp_ecdsa_offline_signing_service.cpp +++ b/src/common/cosigner/cmp_ecdsa_offline_signing_service.cpp @@ -33,7 +33,7 @@ void cmp_ecdsa_offline_signing_service::start_ecdsa_signature_preprocessing(cons if (metadata.players_info.size() != players_ids.size()) { - LOG_ERROR("CMP protocol doesn't support threshold signatures, the key was created with %lu players and the signing reques is for %lu players", metadata.players_info.size(), players_ids.size()); + LOG_ERROR("CMP protocol doesn't support threshold signatures, the key was created with %lu players and the signing request is for %lu players", metadata.players_info.size(), players_ids.size()); throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } @@ -46,10 +46,17 @@ void cmp_ecdsa_offline_signing_service::start_ecdsa_signature_preprocessing(cons } } + if (count > 0 && start_index > UINT32_MAX - count) + { + LOG_ERROR("start_index + count overflow: start_index=%" PRIu32 " count=%" PRIu32, start_index, count); + throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + _preprocessing_persistency.create_preprocessed_data(key_id, total_count); preprocessing_metadata processing_metadata = {key_id, metadata.algorithm, players_ids, start_index, count}; memset(processing_metadata.ack, 0, sizeof(commitments_sha256_t)); + processing_metadata.version = common::cosigner::MPC_PROTOCOL_VERSION; _preprocessing_persistency.store_preprocessing_metadata(request_id, processing_metadata); uint64_t my_id = _service.get_id_from_keyid(key_id); @@ -67,7 +74,7 @@ void cmp_ecdsa_offline_signing_service::start_ecdsa_signature_preprocessing(cons } } -uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::string& request_id, const std::map>& requests, cmp_mta_responses& response) +uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::string& request_id, const std::map>& requests, uint32_t version, cmp_mta_responses& response) { LOG_INFO("Entering request id = %s", request_id.c_str()); preprocessing_metadata metadata; @@ -77,15 +84,25 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::stri if (requests.size() != metadata.players_ids.size()) { LOG_ERROR("got %lu mta requests but the request is for %lu players", requests.size(), metadata.players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } static const commitments_sha256_t ZERO = {0}; if (memcmp(metadata.ack, ZERO, sizeof(commitments_sha256_t)) != 0) { LOG_ERROR("Can't change mta message ack"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + + if ((uint32_t)version > metadata.version) + { + LOG_FATAL("Min version %d is more than mpc version %d ", version, metadata.version); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } + + metadata.version = version; + ack_mta_request(metadata.count, requests, metadata.players_ids, metadata.ack); memcpy(response.ack, metadata.ack, sizeof(commitments_sha256_t)); _preprocessing_persistency.store_preprocessing_metadata(request_id, metadata, true); @@ -108,11 +125,22 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::stri if (my_proof == req_it->second[i].mta_proofs.end()) { LOG_ERROR("Player %" PRIu64 " didn't send k rddh proof to me in block %lu", req_it->first, i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } paillier_with_range_proof_t proof = {(uint8_t*)req_it->second[i].mta.message.data(), (uint32_t)req_it->second[i].mta.message.size(), (uint8_t*)my_proof->second.data(), (uint32_t)my_proof->second.size()}; - auto status = range_proof_diffie_hellman_zkpok_verify(aux.ring_pedersen.get(), key_md.players_info.at(req_it->first).paillier.get(), algebra, aad.data(), aad.size(), - &req_it->second[i].Z.data, &req_it->second[i].A.data, &req_it->second[i].B.data, &proof); + const uint8_t strict_ciphertext_length = (version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) ? 1 : 0; + + auto status = range_proof_diffie_hellman_zkpok_verify(aux.ring_pedersen.get(), + key_md.players_info.at(req_it->first).paillier.get(), + algebra, + aad.data(), + aad.size(), + &req_it->second[i].Z.data, + &req_it->second[i].A.data, + &req_it->second[i].B.data, + &proof, + strict_ciphertext_length, + /*use_extended_seed=*/0); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to verify k rddh proof from player %" PRIu64 " block %lu, error %d", req_it->first, i, status); @@ -121,6 +149,7 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::stri } } + LOG_INFO("Calculating mta response"); elliptic_curve_scalar key; cosigner_sign_algorithm algo; _key_persistency.load_key(metadata.key_id, algo, key.data); @@ -130,13 +159,14 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_response(const std::stri { ecdsa_preprocessing_data data; _preprocessing_persistency.load_preprocessing_data(request_id, metadata.start_index + i, data); - cmp_mta_response resp = create_mta_response(data, algebra, my_id, aad, key_md, requests, i, key, aux); + cmp_mta_response resp = create_mta_response(data, algebra, my_id, aad, key_md, requests, i, key, aux, version); _preprocessing_persistency.store_preprocessing_data(request_id, metadata.start_index + i, data); response.response.push_back(std::move(resp)); } return my_id; } + uint64_t cmp_ecdsa_offline_signing_service::offline_mta_verify(const std::string& request_id, const std::map& mta_responses, std::vector& deltas) { LOG_INFO("Entering request id = %s", request_id.c_str()); @@ -147,7 +177,7 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_verify(const std::string if (mta_responses.size() != metadata.players_ids.size()) { LOG_ERROR("got %lu mta responses but the request is for %lu players", mta_responses.size(), metadata.players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint64_t my_id = _service.get_id_from_keyid(metadata.key_id); @@ -163,17 +193,17 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_verify(const std::string if (it == mta_responses.end()) { LOG_ERROR("missing mta response from player %" PRIu64, *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (it->first != my_id && it->second.response.size() != metadata.count) { LOG_ERROR("got %lu mta responses from player %" PRIu64 ", but the request is for %" PRIu32 " presigning data", it->second.response.size(), *i, metadata.count); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (memcmp(it->second.ack, metadata.ack, sizeof(commitments_sha256_t)) != 0) { LOG_ERROR("got wrong ack from player %" PRIu64, *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -185,8 +215,7 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_verify(const std::string continue; const auto& other = key_md.players_info.at(it->first); auto aad = build_aad(uuid, it->first, key_md.seed); - - verifiers[it->first] = mta::new_response_verifier(metadata.count, it->first, algebra, aad, aux.paillier, other.paillier, aux.ring_pedersen); + verifiers[it->first] = mta::new_response_verifier(metadata.version, metadata.count, it->first, algebra, aad, aux.paillier, other.paillier, aux.ring_pedersen, get_min_mta_batch_size_threshold()); } auto aad = build_aad(uuid, my_id, key_md.seed); @@ -194,7 +223,7 @@ uint64_t cmp_ecdsa_offline_signing_service::offline_mta_verify(const std::string { ecdsa_preprocessing_data data; _preprocessing_persistency.load_preprocessing_data(request_id, metadata.start_index + i, data); - cmp_mta_deltas delta = verify_block_and_get_delta(data, algebra, my_id, uuid, aad, key_md, mta_responses, i, aux, verifiers); + cmp_mta_deltas delta = verify_block_and_get_delta(data, algebra, my_id, uuid, aad, key_md, mta_responses, i, aux, metadata.version, verifiers); deltas.push_back(std::move(delta)); _preprocessing_persistency.store_preprocessing_data(request_id, metadata.start_index + i, data); } @@ -357,22 +386,22 @@ void cmp_ecdsa_offline_signing_service::ecdsa_sign(const std::string& key_id, co // the probability of not getting positive r after 255 attemps is 1/2^255 if (!counter) { - LOG_ERROR("failed to found positive R, WTF???"); + LOG_ERROR("failed to find positive R"); throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); } LOG_INFO("calculating sig with R' = R * %u", counter); - // clac sig.s = k(m + r * delta) +r(k * x + Chi) - elliptic_curve256_scalar_t tmp; - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, sig.r, sizeof(elliptic_curve256_scalar_t), delta.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), (const uint8_t*)data.blocks[i].data.data(), data.blocks[i].data.size())); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &sig.s, tmp, sizeof(elliptic_curve256_scalar_t), preprocessed_data.k.data, sizeof(elliptic_curve256_scalar_t))); + // calc sig.s = k(m + r * delta) +r(k * x + Chi) + elliptic_curve_scalar tmp; + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, sig.r, sizeof(elliptic_curve256_scalar_t), delta.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), (const uint8_t*)data.blocks[i].data.data(), data.blocks[i].data.size())); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &sig.s, tmp.data, sizeof(elliptic_curve256_scalar_t), preprocessed_data.k.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, preprocessed_data.k.data, sizeof(elliptic_curve256_scalar_t), key.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), preprocessed_data.chi.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), sig.r, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &sig.s, sig.s, sizeof(elliptic_curve256_scalar_t), tmp, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, preprocessed_data.k.data, sizeof(elliptic_curve256_scalar_t), key.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), preprocessed_data.chi.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), sig.r, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &sig.s, sig.s, sizeof(elliptic_curve256_scalar_t), tmp.data, sizeof(elliptic_curve256_scalar_t))); if (protocol_version >= MPC_RAND_R_VERSION) throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &sig.s, sig.s, sizeof(elliptic_curve256_scalar_t), hram_invers, sizeof(elliptic_curve256_scalar_t))); diff --git a/src/common/cosigner/cmp_ecdsa_online_signing_service.cpp b/src/common/cosigner/cmp_ecdsa_online_signing_service.cpp index 3a0fe67..ba762c2 100644 --- a/src/common/cosigner/cmp_ecdsa_online_signing_service.cpp +++ b/src/common/cosigner/cmp_ecdsa_online_signing_service.cpp @@ -17,25 +17,6 @@ namespace common namespace cosigner { -#ifdef DEBUG -template -static inline std::string HexStr(const T itbegin, const T itend) -{ - std::string rv; - static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - rv.reserve((itend-itbegin)*3); - for(T it = itbegin; it < itend; ++it) - { - unsigned char val = (unsigned char)(*it); - rv.push_back(hexmap[val>>4]); - rv.push_back(hexmap[val&15]); - } - - return rv; -} -#endif - static inline const char* to_string(cosigner_sign_algorithm algorithm) { switch (algorithm) @@ -143,7 +124,6 @@ void cmp_ecdsa_online_signing_service::start_signing(const std::string& key_id, uint64_t cmp_ecdsa_online_signing_service::mta_response(const std::string& txid, const std::map>& requests, uint32_t version, cmp_mta_responses& response) { - (void)version; LOG_INFO("Entering txid = %s", txid.c_str()); cmp_signing_metadata metadata; _signing_persistency.load_cmp_signing_data(txid, metadata); @@ -152,20 +132,30 @@ uint64_t cmp_ecdsa_online_signing_service::mta_response(const std::string& txid, if (requests.size() != metadata.signers_ids.size()) { LOG_ERROR("got %lu mta requests but the request is for %lu players", requests.size(), metadata.signers_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } static const commitments_sha256_t ZERO = {0}; if (memcmp(metadata.ack, ZERO, sizeof(commitments_sha256_t)) != 0) { LOG_ERROR("Can't change mta message ack"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } + if (version > metadata.version) + { + LOG_FATAL("Min version %d is more than mpc version %d ", version, metadata.version); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + #ifndef MOBILE - _timing_map.insert(txid); + { + _timing_map.insert(txid); + } #endif + metadata.version = version; + ack_mta_request(metadata.sig_data.size(), requests, metadata.signers_ids, metadata.ack); memcpy(response.ack, metadata.ack, sizeof(commitments_sha256_t)); _signing_persistency.update_cmp_signing_data(txid, metadata); @@ -188,11 +178,23 @@ uint64_t cmp_ecdsa_online_signing_service::mta_response(const std::string& txid, if (my_proof == req_it->second[i].mta_proofs.end()) { LOG_ERROR("Player %" PRIu64 " didn't send k rddh proof to me in block %lu", req_it->first, i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } + paillier_with_range_proof_t proof = {(uint8_t*)req_it->second[i].mta.message.data(), (uint32_t)req_it->second[i].mta.message.size(), (uint8_t*)my_proof->second.data(), (uint32_t)my_proof->second.size()}; - auto status = range_proof_diffie_hellman_zkpok_verify(aux.ring_pedersen.get(), key_md.players_info.at(req_it->first).paillier.get(), algebra, aad.data(), aad.size(), - &req_it->second[i].Z.data, &req_it->second[i].A.data, &req_it->second[i].B.data, &proof); + const uint8_t strict_ciphertext_length = (version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) ? 1 : 0; + + auto status = range_proof_diffie_hellman_zkpok_verify(aux.ring_pedersen.get(), + key_md.players_info.at(req_it->first).paillier.get(), + algebra, + aad.data(), + aad.size(), + &req_it->second[i].Z.data, + &req_it->second[i].A.data, + &req_it->second[i].B.data, + &proof, + strict_ciphertext_length, + /*use_extended_seed=*/0); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to verify k rddh proof from player %" PRIu64 " block %lu, error %d", req_it->first, i, status); @@ -201,6 +203,7 @@ uint64_t cmp_ecdsa_online_signing_service::mta_response(const std::string& txid, } } + LOG_INFO("Calculating mta response"); elliptic_curve_scalar key; cosigner_sign_algorithm algo; _key_persistency.load_key(metadata.key_id, algo, key.data); @@ -209,7 +212,7 @@ uint64_t cmp_ecdsa_online_signing_service::mta_response(const std::string& txid, for (size_t i = 0; i < metadata.sig_data.size(); i++) { cmp_signature_data& data = metadata.sig_data[i]; - cmp_mta_response resp = create_mta_response(data, algebra, my_id, aad, key_md, requests, i, key, aux); + cmp_mta_response resp = create_mta_response(data, algebra, my_id, aad, key_md, requests, i, key, aux, version); response.response.push_back(std::move(resp)); } _signing_persistency.update_cmp_signing_data(txid, metadata); @@ -226,7 +229,7 @@ uint64_t cmp_ecdsa_online_signing_service::mta_verify(const std::string& txid, c if (mta_responses.size() != metadata.signers_ids.size()) { LOG_ERROR("got %lu mta responses but the request is for %lu players", mta_responses.size(), metadata.signers_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint64_t my_id = _service.get_id_from_keyid(metadata.key_id); @@ -242,22 +245,24 @@ uint64_t cmp_ecdsa_online_signing_service::mta_verify(const std::string& txid, c if (it == mta_responses.end()) { LOG_ERROR("missing mta response from player %" PRIu64, *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (it->first != my_id && it->second.response.size() != metadata.sig_data.size()) { LOG_ERROR("got %lu mta responses from player %" PRIu64 ", but the request is for %lu presigning data", it->second.response.size(), *i, metadata.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (memcmp(it->second.ack, metadata.ack, sizeof(commitments_sha256_t)) != 0) { LOG_ERROR("got wrong ack from player %" PRIu64, *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } std::string uuid = metadata.key_id + txid; - std::map> verifiers; + std::map > verifiers; + + //for each party (excluding myself) create a verifier for (auto it = mta_responses.begin(); it != mta_responses.end(); ++it) { if (it->first == my_id) @@ -265,7 +270,8 @@ uint64_t cmp_ecdsa_online_signing_service::mta_verify(const std::string& txid, c const auto& other = key_md.players_info.at(it->first); auto aad = build_aad(uuid, it->first, key_md.seed); - verifiers[it->first] = mta::new_response_verifier(metadata.sig_data.size(), + verifiers[it->first] = mta::new_response_verifier(metadata.version, + metadata.sig_data.size(), it->first, algebra, aad, @@ -290,6 +296,7 @@ uint64_t cmp_ecdsa_online_signing_service::mta_verify(const std::string& txid, c mta_responses, //to be able to perform an optimization all mta responses are passed i, aux, + metadata.version, verifiers); deltas.push_back(std::move(delta)); @@ -318,7 +325,7 @@ uint64_t cmp_ecdsa_online_signing_service::get_si(const std::string& txid, const cmp_key_metadata key_md; _key_persistency.load_key_metadata(metadata.key_id, key_md, false); - // corrently GFp_curve_algebra_abs can be used for both secp256k1, secp256r1 and stark + // currently GFp_curve_algebra_abs can be used for both secp256k1, secp256r1 and stark if (key_md.algorithm != ECDSA_SECP256K1 && key_md.algorithm != ECDSA_SECP256R1 && key_md.algorithm != ECDSA_STARK) { LOG_ERROR("Can't use key type %d for ECDSA", key_md.algorithm); @@ -386,23 +393,23 @@ uint64_t cmp_ecdsa_online_signing_service::get_si(const std::string& txid, const if (!counter) { - LOG_ERROR("failed to found positive R, WTF???"); + LOG_ERROR("failed to find positive R"); throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); } LOG_INFO("calculating sig with R' = R * %u", counter); memcpy(data.R.data, R, sizeof(elliptic_curve256_point_t)); - // clac sig.s = k(m + r * delta) +r(k * x + Chi) - elliptic_curve256_scalar_t tmp; - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, r, sizeof(elliptic_curve256_scalar_t), delta.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), data.message, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &s.data, tmp, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); + // calc sig.s = k(m + r * delta) +r(k * x + Chi) + elliptic_curve_scalar tmp; + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, r, sizeof(elliptic_curve256_scalar_t), delta.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), data.message, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &s.data, tmp.data, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, data.k.data, sizeof(elliptic_curve256_scalar_t), key.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), data.chi.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), r, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &s.data, s.data, sizeof(elliptic_curve256_scalar_t), tmp, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, data.k.data, sizeof(elliptic_curve256_scalar_t), key.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), data.chi.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_mul_scalars(curve, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), r, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(GFp_curve_algebra_add_scalars(curve, &s.data, s.data, sizeof(elliptic_curve256_scalar_t), tmp.data, sizeof(elliptic_curve256_scalar_t))); if (counter > 1) { elliptic_curve256_scalar_t counter_inverse = {0}; @@ -449,7 +456,7 @@ uint64_t cmp_ecdsa_online_signing_service::get_cmp_signature(const std::string& cmp_key_metadata key_md; _key_persistency.load_key_metadata(metadata.key_id, key_md, false); - // corrently secp256k1_algebra can be used for both secp256k1 and secp256r1 + // currently secp256k1_algebra can be used for both secp256k1 and secp256r1 if (key_md.algorithm != ECDSA_SECP256K1 && key_md.algorithm != ECDSA_SECP256R1 && key_md.algorithm != ECDSA_STARK) { LOG_ERROR("Can't use key type %d for ECDSA", key_md.algorithm); diff --git a/src/common/cosigner/cmp_ecdsa_signing_service.cpp b/src/common/cosigner/cmp_ecdsa_signing_service.cpp index f38ca1c..35ba3ce 100644 --- a/src/common/cosigner/cmp_ecdsa_signing_service.cpp +++ b/src/common/cosigner/cmp_ecdsa_signing_service.cpp @@ -5,7 +5,7 @@ #include "crypto/zero_knowledge_proof/diffie_hellman_log.h" #include "crypto/zero_knowledge_proof/range_proofs.h" #include "logging/logging_t.h" - +#include "cosigner/mpc_globals.h" #include #include @@ -39,10 +39,10 @@ cmp_mta_request cmp_ecdsa_signing_service::create_mta_request(ecdsa_preprocessin cmp_mta_request msg; throw_cosigner_exception(algebra->generator_mul(algebra, &msg.A.data, &data.a.data)); throw_cosigner_exception(algebra->generator_mul(algebra, &msg.B.data, &data.b.data)); - elliptic_curve256_scalar_t tmp; - throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp, data.a.data, sizeof(elliptic_curve256_scalar_t), data.b.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->generator_mul(algebra, &msg.Z.data, &tmp)); + elliptic_curve_scalar tmp; + throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp.data, data.a.data, sizeof(elliptic_curve256_scalar_t), data.b.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->add_scalars(algebra, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->generator_mul(algebra, &msg.Z.data, &tmp.data)); msg.mta = mta::request(my_id, algebra, data.k, data.gamma, data.a, data.b, aad, paillier, metadata.players_info, msg.mta_proofs, data.G_proofs); data.mta_request = msg.mta.message; @@ -95,7 +95,8 @@ cmp_mta_response cmp_ecdsa_signing_service::create_mta_response( const std::map>& requests, size_t index, const elliptic_curve_scalar& key, - const auxiliary_keys& aux_keys) + const auxiliary_keys& aux_keys, + const uint32_t version) { cmp_mta_response resp; resp.GAMMA = data.GAMMA; @@ -110,10 +111,10 @@ cmp_mta_response cmp_ecdsa_signing_service::create_mta_response( continue; const auto& other = metadata.players_info.at(req_it->first); auto& gamma_mta = resp.k_gamma_mta[req_it->first]; - auto beta = mta::answer_mta_request(algebra, req_it->second[index].mta, data.gamma.data, sizeof(elliptic_curve256_scalar_t), aad, aux_keys.paillier, other.paillier, other.ring_pedersen, gamma_mta); + auto beta = mta::answer_mta_request(algebra, req_it->second[index].mta, data.gamma.data, sizeof(elliptic_curve256_scalar_t), aad, aux_keys.paillier, other.paillier, other.ring_pedersen, version, gamma_mta); throw_cosigner_exception(algebra->sub_scalars(algebra, &data.delta.data, data.delta.data, sizeof(elliptic_curve256_scalar_t), beta.data, sizeof(elliptic_curve256_scalar_t))); auto& x_mta = resp.k_x_mta[req_it->first]; - beta = mta::answer_mta_request(algebra, req_it->second[index].mta, key.data, sizeof(elliptic_curve256_scalar_t), aad, aux_keys.paillier, other.paillier, other.ring_pedersen, x_mta); + beta = mta::answer_mta_request(algebra, req_it->second[index].mta, key.data, sizeof(elliptic_curve256_scalar_t), aad, aux_keys.paillier, other.paillier, other.ring_pedersen, version, x_mta); throw_cosigner_exception(algebra->sub_scalars(algebra, &data.chi.data, data.chi.data, sizeof(elliptic_curve256_scalar_t), beta.data, sizeof(elliptic_curve256_scalar_t))); auto& pub = data.public_data[req_it->first]; pub.A = req_it->second[index].A; @@ -126,7 +127,7 @@ cmp_mta_response cmp_ecdsa_signing_service::create_mta_response( } cmp_mta_deltas cmp_ecdsa_signing_service::verify_block_and_get_delta( - ecdsa_preprocessing_data& data, //this block singing data + ecdsa_preprocessing_data& data, //this block signing data const elliptic_curve256_algebra_ctx_t* algebra, uint64_t my_id, const std::string& uuid, @@ -135,6 +136,7 @@ cmp_mta_deltas cmp_ecdsa_signing_service::verify_block_and_get_delta( const std::map& mta_responses, //all responses from all parties size_t index, //this block (message) index const auxiliary_keys& aux_keys, + const uint32_t version, std::map >& verifiers) { //iterate over all responses from all signers @@ -143,7 +145,7 @@ cmp_mta_deltas cmp_ecdsa_signing_service::verify_block_and_get_delta( if (it->first == my_id) continue; - //other partie parameters + //other party's parameters const auto& other = metadata.players_info.at(it->first); auto other_aad = build_aad(uuid, it->first, metadata.seed); auto& pub = data.public_data.at(it->first); @@ -162,13 +164,16 @@ cmp_mta_deltas cmp_ecdsa_signing_service::verify_block_and_get_delta( (uint32_t)proof_for_me.size()}; //verify "log" proof + const uint8_t strict_ciphertext_length = (version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) ? 1 : 0; auto status = range_proof_exponent_zkpok_verify(aux_keys.ring_pedersen.get(), //my secret ring pedersen params - other.paillier.get(), //other partie's public paillier + other.paillier.get(), //other party's public paillier algebra, other_aad.data(), other_aad.size(), &pub.GAMMA.data, - &proof); + &proof, + strict_ciphertext_length, + /*use_extended_seed=*/0); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to verify gamma log proof from player %" PRIu64 " block %lu, error %d", it->first, index, status); @@ -194,10 +199,10 @@ cmp_mta_deltas cmp_ecdsa_signing_service::verify_block_and_get_delta( diffie_hellman_log_public_data_t pub; throw_cosigner_exception(algebra->generator_mul(algebra, &pub.A, &data.a.data)); throw_cosigner_exception(algebra->generator_mul(algebra, &pub.B, &data.b.data)); - elliptic_curve256_scalar_t tmp; - throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp, data.a.data, sizeof(elliptic_curve256_scalar_t), data.b.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->generator_mul(algebra, &pub.C, &tmp)); + elliptic_curve_scalar tmp; + throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp.data, data.a.data, sizeof(elliptic_curve256_scalar_t), data.b.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->add_scalars(algebra, &tmp.data, tmp.data, sizeof(elliptic_curve256_scalar_t), data.k.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->generator_mul(algebra, &pub.C, &tmp.data)); memcpy(pub.X, delta.DELTA.data, sizeof(elliptic_curve256_point_t)); diffie_hellman_log_zkp_t proof; throw_cosigner_exception(diffie_hellman_log_zkp_generate(algebra, aad.data(), aad.size(), &data.GAMMA.data, &data.k.data, &data.a.data, &data.b.data, &pub, &proof)); diff --git a/src/common/cosigner/cmp_key_persistency.cpp b/src/common/cosigner/cmp_key_persistency.cpp index debeffc..3ed2ba7 100644 --- a/src/common/cosigner/cmp_key_persistency.cpp +++ b/src/common/cosigner/cmp_key_persistency.cpp @@ -7,10 +7,6 @@ namespace common namespace cosigner { -cmp_key_persistency::~cmp_key_persistency() -{ -} - } } } diff --git a/src/common/cosigner/cmp_offline_refresh_service.cpp b/src/common/cosigner/cmp_offline_refresh_service.cpp index 0e55420..f97eaba 100644 --- a/src/common/cosigner/cmp_offline_refresh_service.cpp +++ b/src/common/cosigner/cmp_offline_refresh_service.cpp @@ -166,36 +166,36 @@ void cmp_offline_refresh_service::refresh_key(const std::string& key_id, const s LOG_INFO("Refreshing presigning data for key %s", key_id.c_str()); _refresh_key_persistency.transform_preprocessed_data_and_store_temporary(key_id, request_id, [algebra, &private_key, &new_private_key, &mine_prfs_k, &mine_prfs_chi, &other_prfs_k, &other_prfs_chi] (uint64_t index, cmp_signature_preprocessed_data& data) { - elliptic_curve256_scalar_t k; - memcpy(k, data.k.data, sizeof(elliptic_curve256_scalar_t)); - elliptic_curve256_scalar_t chi; - memcpy(chi, data.chi.data, sizeof(elliptic_curve256_scalar_t)); + elliptic_curve_scalar k; + memcpy(k.data, data.k.data, sizeof(elliptic_curve256_scalar_t)); + elliptic_curve_scalar chi; + memcpy(chi.data, data.chi.data, sizeof(elliptic_curve256_scalar_t)); commitments_sha256_t val; for (auto i = 0ul; i != mine_prfs_k.size(); ++i) { other_prfs_k[i].run(index, val); - throw_cosigner_exception(algebra->add_scalars(algebra, &k, k, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); + throw_cosigner_exception(algebra->add_scalars(algebra, &k.data, k.data, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); mine_prfs_k[i].run(index, val); - throw_cosigner_exception(algebra->sub_scalars(algebra, &k, k, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); + throw_cosigner_exception(algebra->sub_scalars(algebra, &k.data, k.data, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); } - elliptic_curve256_scalar_t tmp; - throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp, data.k.data, sizeof(elliptic_curve256_scalar_t), reinterpret_cast(private_key.data), sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->add_scalars(algebra, &chi, chi, sizeof(elliptic_curve256_scalar_t), tmp, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp, k, sizeof(elliptic_curve256_scalar_t), new_private_key.data, sizeof(elliptic_curve256_scalar_t))); - throw_cosigner_exception(algebra->sub_scalars(algebra, &chi, chi, sizeof(elliptic_curve256_scalar_t), tmp, sizeof(elliptic_curve256_scalar_t))); + elliptic_curve_scalar tmp; + throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp.data, data.k.data, sizeof(elliptic_curve256_scalar_t), reinterpret_cast(private_key.data), sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->add_scalars(algebra, &chi.data, chi.data, sizeof(elliptic_curve256_scalar_t), tmp.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->mul_scalars(algebra, &tmp.data, k.data, sizeof(elliptic_curve256_scalar_t), new_private_key.data, sizeof(elliptic_curve256_scalar_t))); + throw_cosigner_exception(algebra->sub_scalars(algebra, &chi.data, chi.data, sizeof(elliptic_curve256_scalar_t), tmp.data, sizeof(elliptic_curve256_scalar_t))); for (auto i = 0ul; i != mine_prfs_chi.size(); ++i) { other_prfs_chi[i].run(index, val); - throw_cosigner_exception(algebra->add_scalars(algebra, &chi, chi, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); + throw_cosigner_exception(algebra->add_scalars(algebra, &chi.data, chi.data, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); mine_prfs_chi[i].run(index, val); - throw_cosigner_exception(algebra->sub_scalars(algebra, &chi, chi, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); + throw_cosigner_exception(algebra->sub_scalars(algebra, &chi.data, chi.data, sizeof(elliptic_curve256_scalar_t), val, sizeof(commitments_sha256_t))); } - memcpy(data.chi.data, chi, sizeof(elliptic_curve256_scalar_t)); - memcpy(data.k.data, k, sizeof(elliptic_curve256_scalar_t)); + memcpy(data.chi.data, chi.data, sizeof(elliptic_curve256_scalar_t)); + memcpy(data.k.data, k.data, sizeof(elliptic_curve256_scalar_t)); }); _refresh_key_persistency.delete_refresh_key_seeds(request_id); diff --git a/src/common/cosigner/cmp_setup_service.cpp b/src/common/cosigner/cmp_setup_service.cpp index 2ae92df..99c8133 100644 --- a/src/common/cosigner/cmp_setup_service.cpp +++ b/src/common/cosigner/cmp_setup_service.cpp @@ -1,6 +1,8 @@ #include "cosigner/cmp_setup_service.h" #include "cosigner/cosigner_exception.h" +#include "cosigner/mpc_globals.h" #include "utils.h" +#include "utils/string_utils.h" #include "crypto/zero_knowledge_proof/schnorr.h" #include "logging/logging_t.h" @@ -55,40 +57,40 @@ void cmp_setup_service::generate_setup_commitments(const std::string& key_id, co { const size_t n = players_ids.size(); if (!n || !t || t > n || n > UINT8_MAX) - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); if (t != n) { LOG_ERROR("CMP protocol doesn't support threshold signatures"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint64_t my_id = _service.get_id_from_keyid(key_id); if (std::find(players_ids.begin(), players_ids.end(), my_id) == players_ids.end()) { LOG_ERROR("my id (%" PRIu64 ") is not part of setup request, abort", my_id); - throw cosigner_exception(cosigner_exception::BAD_KEY); + throw_cosigner_exception(cosigner_exception::BAD_KEY); } std::set distinct_players_ids(players_ids.begin(), players_ids.end()); // make the players_ids list unique if (distinct_players_ids.size() != players_ids.size()) { - LOG_ERROR("Received setup request with duplicated player id, players_ids size %lu but only %lu uniq ones", players_ids.size(), distinct_players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Received setup request with duplicated player id, players_ids size %lu but only %lu unique ones", players_ids.size(), distinct_players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (_key_persistency.key_exist(key_id)) { LOG_ERROR("key id %s already exists", key_id.c_str()); - throw cosigner_exception(cosigner_exception::BAD_KEY); + throw_cosigner_exception(cosigner_exception::BAD_KEY); } auto algebra = get_algebra(algorithm); - elliptic_curve256_scalar_t key; + elliptic_curve_scalar key; if (!derive_from.master_key_id.empty()) { - _service.derive_initial_share(derive_from, algorithm, &key); + _service.derive_initial_share(derive_from, algorithm, &key.data); } else { @@ -98,14 +100,14 @@ void cmp_setup_service::generate_setup_commitments(const std::string& key_id, co size_t i = 0; while (i < MAX_ATTEMPTS) { - _service.gen_random(sizeof(elliptic_curve256_scalar_t), key); - status = algebra->reduce(algebra, &key, &key); + _service.gen_random(sizeof(elliptic_curve256_scalar_t), key.data); + status = algebra->reduce(algebra, &key.data, &key.data); if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) break; else if (status != ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR) { LOG_ERROR("failed to create key share, error %d", status); - throw_cosigner_exception(status); + throw_cosigner_exception(status); } i++; } @@ -113,15 +115,14 @@ void cmp_setup_service::generate_setup_commitments(const std::string& key_id, co if (i == MAX_ATTEMPTS) { LOG_ERROR("failed to create key share"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } - generate_setup_commitments(key_id, tenant_id, algorithm, algebra, players_ids, t, ttl, key, algebra->infinity_point(algebra), setup_commitment); - OPENSSL_cleanse(key, sizeof(elliptic_curve256_scalar_t)); + generate_setup_commitments(key_id, tenant_id, algorithm, algebra, players_ids, t, ttl, key.data, algebra->infinity_point(algebra), setup_commitment); } -void cmp_setup_service::store_setup_commitments(const std::string& key_id, const std::map& commitments, setup_decommitment& decommitment) +void cmp_setup_service::store_setup_commitments(const std::string& key_id, const std::map& commitments, const uint32_t version, setup_decommitment& decommitment) { verify_tenant_id(_service, _key_persistency, key_id); setup_data temp_data; @@ -138,15 +139,23 @@ void cmp_setup_service::store_setup_commitments(const std::string& key_id, const if (commitments.size() != metadata.players_info.size()) { LOG_ERROR("got %lu commitments but the key was created for %lu players", commitments.size(), metadata.players_info.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + + if (version > MPC_PROTOCOL_VERSION) + { + LOG_FATAL("Illegal mpc version. My version is %u, got %u", MPC_PROTOCOL_VERSION, version); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } + temp_data.version = version; //update to the min version + for (auto i = metadata.players_info.begin(); i != metadata.players_info.end(); ++i) { if (!commitments.count(i->first)) { - LOG_ERROR("missing commitment from player %" PRIu64, i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("missing commitment from player %" PRIu64 "", i->first); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } commitment commit = commitments.at(my_id); @@ -156,6 +165,8 @@ void cmp_setup_service::store_setup_commitments(const std::string& key_id, const create_setup_commitment(key_id, my_id, decommitment, commit, true); ack_message(commitments, &decommitment.ack); _key_persistency.store_setup_commitments(key_id, commitments); + _key_persistency.store_setup_data(key_id, temp_data, true); + } void cmp_setup_service::generate_setup_proofs(const std::string& key_id, const std::map& decommitments, setup_zk_proofs& proofs) @@ -166,7 +177,7 @@ void cmp_setup_service::generate_setup_proofs(const std::string& key_id, const s std::map commitments; _key_persistency.load_setup_commitments(key_id, commitments); verify_and_load_setup_decommitments(key_id, commitments, decommitments, metadata.players_info); - + auto algebra = get_algebra(metadata.algorithm); setup_data temp_data; _key_persistency.load_setup_data(key_id, temp_data); @@ -179,7 +190,7 @@ void cmp_setup_service::generate_setup_proofs(const std::string& key_id, const s } generate_setup_proofs(key_id, algebra, temp_data, metadata.seed, proofs); - _key_persistency.store_setup_data(key_id, temp_data); + _key_persistency.store_setup_data(key_id, temp_data, true); _key_persistency.store_key_metadata(key_id, metadata, true); } @@ -188,7 +199,10 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const std verify_tenant_id(_service, _key_persistency, key_id); cmp_key_metadata metadata; _key_persistency.load_key_metadata(key_id, metadata, true); - verify_setup_proofs(key_id, metadata, proofs); + setup_data temp_data; + _key_persistency.load_setup_data(key_id, temp_data); + + verify_setup_proofs(key_id, metadata, proofs, temp_data); elliptic_curve256_point_t pubkey; auto algebra = get_algebra(metadata.algorithm); @@ -202,7 +216,7 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const std if (memcmp(pubkey, metadata.public_key, sizeof(elliptic_curve256_point_t)) != 0) { LOG_ERROR("The sum of all public key shares is different from the public key"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } else @@ -213,6 +227,8 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const std auto aad = build_aad(key_id, my_id, metadata.seed); auxiliary_keys aux; _key_persistency.load_auxiliary_keys(key_id, aux); + + const uint8_t use_extended_seed = (temp_data.version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) ? 1 : 0; for (auto i = metadata.players_info.begin(); i != metadata.players_info.end(); ++i) { @@ -220,19 +236,39 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const std continue; uint32_t len = 0; - range_proof_paillier_large_factors_zkp_generate(aux.paillier.get(), i->second.ring_pedersen.get(), aad.data(), aad.size(), NULL, 0, &len); + range_proof_paillier_large_factors_zkp_generate(aux.paillier.get(), + i->second.ring_pedersen.get(), + aad.data(), + aad.size(), + use_extended_seed, + NULL, + 0, + &len); + auto& buffer = paillier_large_factor_proofs[i->first]; buffer.resize(len); - throw_cosigner_exception(range_proof_paillier_large_factors_zkp_generate(aux.paillier.get(), i->second.ring_pedersen.get(), aad.data(), aad.size(), buffer.data(), buffer.size(), &len)); + + throw_cosigner_exception(range_proof_paillier_large_factors_zkp_generate(aux.paillier.get(), + i->second.ring_pedersen.get(), + aad.data(), + aad.size(), + use_extended_seed, + buffer.data(), + buffer.size(), + &len)); } } -void cmp_setup_service::create_secret(const std::string& key_id, const std::map>& paillier_large_factor_proofs, std::string& public_key, cosigner_sign_algorithm& algorithm) +void cmp_setup_service::create_secret(const std::string& key_id, + const std::map>& paillier_large_factor_proofs, + std::string& public_key, + cosigner_sign_algorithm& algorithm) { verify_tenant_id(_service, _key_persistency, key_id); cmp_key_metadata metadata; _key_persistency.load_key_metadata(key_id, metadata, true); - + setup_data temp_data; + _key_persistency.load_setup_data(key_id, temp_data); uint64_t my_id = _service.get_id_from_keyid(key_id); auxiliary_keys aux; _key_persistency.load_auxiliary_keys(key_id, aux); @@ -240,9 +276,10 @@ void cmp_setup_service::create_secret(const std::string& key_id, const std::map< if (paillier_large_factor_proofs.size() != metadata.players_info.size()) { LOG_ERROR("got %lu proofs but the key was created for %lu players", paillier_large_factor_proofs.size(), metadata.players_info.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } + const uint8_t use_extended_seed = (temp_data.version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) ? 1 : 0; for (auto i = paillier_large_factor_proofs.begin(); i != paillier_large_factor_proofs.end(); ++i) { if (i->first == my_id) @@ -252,18 +289,25 @@ void cmp_setup_service::create_secret(const std::string& key_id, const std::map< if (player_it == metadata.players_info.end()) { LOG_ERROR("player %" PRIu64 " is not part of key %s", i->first, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } auto proof_it = i->second.find(my_id); if (proof_it == i->second.end()) { - LOG_ERROR("missing proof from player %" PRIu64, i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("missing proof from player %" PRIu64 "", i->first); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } auto aad = build_aad(key_id, i->first, metadata.seed); - auto status = range_proof_paillier_large_factors_zkp_verify(player_it->second.paillier.get(), aux.ring_pedersen.get(), aad.data(), aad.size(), proof_it->second.data(), proof_it->second.size()); + + auto status = range_proof_paillier_large_factors_zkp_verify(player_it->second.paillier.get(), + aux.ring_pedersen.get(), + aad.data(), + aad.size(), + use_extended_seed, + proof_it->second.data(), + proof_it->second.size()); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to verify player %" PRIu64 " paillier key has large factors, error %d", i->first, status); @@ -284,9 +328,9 @@ void cmp_setup_service::create_secret(const std::string& key_id, const std::map< { LOG_ERROR("failed to backup key id %s", key_id.c_str()); _key_persistency.delete_temporary_key_data(key_id, true); - throw cosigner_exception(cosigner_exception::BACKUP_FAILED); + throw_cosigner_exception(cosigner_exception::BACKUP_FAILED); } - + _service.clear_key_setup_in_progress(key_id); algorithm = algo; LOG_INFO("key share created for keyid %s, and algorithm %s", key_id.c_str(), to_string(metadata.algorithm)); } @@ -298,38 +342,43 @@ void cmp_setup_service::add_user_request(const std::string& key_id, cosigner_sig if (distinct_players_ids.size() != players_ids.size()) { - LOG_ERROR("Received add user request with duplicated player id, players_ids size %lu but only %lu uniq ones", players_ids.size(), distinct_players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Received add user request with duplicated player id, players_ids size %lu but only %lu unique ones", players_ids.size(), distinct_players_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + if (players_ids.size() > UINT8_MAX) + { + LOG_ERROR("Too many players: %lu exceeds maximum of %u", players_ids.size(), UINT8_MAX); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } const uint8_t n = players_ids.size(); if (t != n) { LOG_ERROR("CMP protocol doesn't support threshold signatures"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!_key_persistency.key_exist(key_id)) { LOG_ERROR("key id %s doesn't exists", key_id.c_str()); - throw cosigner_exception(cosigner_exception::BAD_KEY); + throw_cosigner_exception(cosigner_exception::BAD_KEY); } if (_key_persistency.key_exist(new_key_id)) { LOG_ERROR("key id %s already exists", new_key_id.c_str()); - throw cosigner_exception(cosigner_exception::BAD_KEY); + throw_cosigner_exception(cosigner_exception::BAD_KEY); } if (t <= 1 || t > players_ids.size()) { LOG_ERROR("invalid t = %d, for keyid = %s with %lu players", t, key_id.c_str(), players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (players_ids.size() > UINT8_MAX) { LOG_ERROR("got too many players %lu for keyid = %s", players_ids.size(), key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } cmp_key_metadata metadata; @@ -338,7 +387,7 @@ void cmp_setup_service::add_user_request(const std::string& key_id, cosigner_sig if (metadata.algorithm != algorithm) { LOG_ERROR("key %s has algorithm %s, but the request is for algorithm %s", key_id.c_str(), to_string(metadata.algorithm), to_string(algorithm)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } auto algebra = get_algebra(algorithm); @@ -346,7 +395,7 @@ void cmp_setup_service::add_user_request(const std::string& key_id, cosigner_sig if (metadata.players_info.find(*i) != metadata.players_info.end()) { LOG_ERROR("playerid %" PRIu64 " is already part of key, for keyid = %s", *i, key_id.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } memcpy(data.public_key.data, metadata.public_key, sizeof(elliptic_curve256_point_t)); @@ -361,12 +410,16 @@ void cmp_setup_service::add_user_request(const std::string& key_id, cosigner_sig elliptic_curve256_scalar_t share; throw_cosigner_exception(algebra->rand(algebra, &share)); throw_cosigner_exception(algebra->sub_scalars(algebra, &last_share.data, last_share.data, sizeof(elliptic_curve256_scalar_t), share, sizeof(elliptic_curve256_scalar_t))); - data.encrypted_shares[id] = _service.encrypt_for_player(id, byte_vector_t(share, &share[sizeof(elliptic_curve256_scalar_t)])); + byte_vector_t share_bytes(share, &share[sizeof(elliptic_curve256_scalar_t)]); + utils::byte_vector_cleaner share_bytes_cleaner(share_bytes); + data.encrypted_shares[id] = _service.encrypt_for_player(id, share_bytes); OPENSSL_cleanse(share, sizeof(elliptic_curve256_scalar_t)); } - + uint64_t id = players_ids[n - 1]; - data.encrypted_shares[id] = _service.encrypt_for_player(id, byte_vector_t(last_share.data, &last_share.data[sizeof(elliptic_curve256_scalar_t)])); + byte_vector_t last_share_bytes(last_share.data, &last_share.data[sizeof(elliptic_curve256_scalar_t)]); + utils::byte_vector_cleaner last_share_bytes_cleaner(last_share_bytes); + data.encrypted_shares[id] = _service.encrypt_for_player(id, last_share_bytes); } void cmp_setup_service::add_user(const std::string& tenant_id, const std::string& key_id, cosigner_sign_algorithm algorithm, uint8_t t, const std::map& data, uint64_t ttl, commitment& setup_commitment) @@ -374,7 +427,7 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (data.size() == 0) { LOG_ERROR("Got empty add user data map"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } const size_t n = data.begin()->second.encrypted_shares.size(); @@ -383,17 +436,17 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (n != i->second.encrypted_shares.size()) { LOG_ERROR("Number of new player (%lu) from player %" PRIu64 " is different from the number of players (%lu) from player %" PRIu64, i->second.encrypted_shares.size(), i->first, n, data.begin()->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } if (!n || !t || t > n || n > UINT8_MAX || n <= 1) - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); if (t != n) { LOG_ERROR("CMP protocol doesn't support threshold signatures"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint64_t my_id = _service.get_id_from_keyid(key_id); @@ -401,10 +454,10 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (_key_persistency.key_exist(key_id)) { LOG_ERROR("key id %s already exists", key_id.c_str()); - throw cosigner_exception(cosigner_exception::BAD_KEY); + throw_cosigner_exception(cosigner_exception::BAD_KEY); } - elliptic_curve256_scalar_t key = {0}; + elliptic_curve_scalar key; elliptic_curve256_point_t pubkey = {0}; auto algebra = get_algebra(algorithm); std::vector players_ids; @@ -424,12 +477,12 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (memcmp(pubkey, i->second.public_key.data, sizeof(elliptic_curve256_point_t)) != 0) { LOG_ERROR("Public key from player %" PRIu64 " is different from the key sent by player %" PRIu64, i->first, data.begin()->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (i->second.encrypted_shares.size() != players_ids.size()) { LOG_ERROR("Number of shares from player %" PRIu64 " is different from the number sent by player %" PRIu64, i->first, data.begin()->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto j = i->second.encrypted_shares.begin(); j != i->second.encrypted_shares.end(); ++j) @@ -437,7 +490,7 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (std::find(players_ids.begin(), players_ids.end(), j->first) == players_ids.end()) { LOG_ERROR("Shares for player %" PRIu64 " from player %" PRIu64 " wasn't sent by player %" PRIu64, j->first, i->first, data.begin()->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } } @@ -446,24 +499,33 @@ void cmp_setup_service::add_user(const std::string& tenant_id, const std::string if (it == i->second.encrypted_shares.end()) { LOG_ERROR("Player %" PRIu64 " didnt send share to me", i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } auto share = _service.decrypt_message(it->second); - throw_cosigner_exception(algebra->add_scalars(algebra, &key, key, sizeof(elliptic_curve256_scalar_t), (const uint8_t*)share.data(), share.size())); + throw_cosigner_exception(algebra->add_scalars(algebra, &key.data, key.data, sizeof(elliptic_curve256_scalar_t), (const uint8_t*)share.data(), share.size())); } - generate_setup_commitments(key_id, tenant_id, algorithm, algebra, players_ids, t, ttl, key, &pubkey, setup_commitment); - OPENSSL_cleanse(key, sizeof(elliptic_curve256_scalar_t)); + generate_setup_commitments(key_id, tenant_id, algorithm, algebra, players_ids, t, ttl, key.data, &pubkey, setup_commitment); } -void cmp_setup_service::generate_setup_commitments(const std::string& key_id, const std::string& tenant_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_algebra_ctx_t* algebra, const std::vector& players_ids, - uint8_t t, uint64_t ttl, const elliptic_curve256_scalar_t& key, const elliptic_curve256_point_t* pubkey, commitment& setup_commitment) +void cmp_setup_service::generate_setup_commitments(const std::string& key_id, + const std::string& tenant_id, + cosigner_sign_algorithm algorithm, + const elliptic_curve256_algebra_ctx_t* algebra, + const std::vector& players_ids, + uint8_t t, + uint64_t ttl, + const elliptic_curve256_scalar_t& key, + const elliptic_curve256_point_t* pubkey, + commitment& setup_commitment) { setup_data temp_data; + temp_data.version = fireblocks::common::cosigner::MPC_PROTOCOL_VERSION; + auto status = algebra->rand(algebra, &temp_data.k.data); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("failed to create k"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } throw_cosigner_exception(algebra->generator_mul(algebra, &temp_data.public_key.data, &key)); @@ -486,9 +548,10 @@ void cmp_setup_service::generate_setup_commitments(const std::string& key_id, co metadata.players_info[*i] = cmp_player_info(); memset(metadata.seed, 0, sizeof(commitments_sha256_t)); memcpy(metadata.public_key, *pubkey, sizeof(elliptic_curve256_point_t)); - + + _service.mark_key_setup_in_progress(key_id); _key_persistency.store_key_metadata(key_id, metadata, false); - _key_persistency.store_setup_data(key_id, temp_data); + _key_persistency.store_setup_data(key_id, temp_data, false); _key_persistency.store_keyid_tenant_id(key_id, tenant_id); _key_persistency.store_auxiliary_keys(key_id, aux); _key_persistency.store_key(key_id, algorithm, key, ttl); @@ -529,12 +592,12 @@ void cmp_setup_service::serialize_auxiliary_keys(const auxiliary_keys& aux, std: if (paillier_public == NULL) { LOG_ERROR("Could not serialize NULL paillier public key"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (ring_pedersen_public == NULL) { LOG_ERROR("Could not serialize NULL ring pedersen public key"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } uint32_t size = 0U; @@ -543,7 +606,7 @@ void cmp_setup_service::serialize_auxiliary_keys(const auxiliary_keys& aux, std: if (!paillier_public_key_serialize(paillier_public, paillier_public_key.data(), size, &size)) { LOG_ERROR("failed to serialize paillier public key"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } size = 0; @@ -552,7 +615,7 @@ void cmp_setup_service::serialize_auxiliary_keys(const auxiliary_keys& aux, std: if (!ring_pedersen_public_serialize(ring_pedersen_public, ring_pedersen_public_key.data(), size, &size)) { LOG_ERROR("failed to serialize ring pedersen public key"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } @@ -563,36 +626,36 @@ void cmp_setup_service::deserialize_auxiliary_keys(uint64_t id, const std::vecto if (!paillier) { LOG_ERROR("failed to parse paillier public key from player %" PRIu64, id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (paillier_public_key_size(paillier.get()) < PAILLIER_KEY_SIZE) { - LOG_ERROR("paillier public key from player %" PRIu64 " size %u, is smaller then the minimum key size %u", id, paillier_public_key_size(paillier.get()), PAILLIER_KEY_SIZE); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("paillier public key from player %" PRIu64 " size %u, is smaller than the minimum key size %u", id, paillier_public_key_size(paillier.get()), PAILLIER_KEY_SIZE); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ring_pedersen.reset(ring_pedersen_public_deserialize(ring_pedersen_public_key.data(), ring_pedersen_public_key.size()), ring_pedersen_free_public); if (!ring_pedersen) { LOG_ERROR("failed to parse ring pedersen public key from player %" PRIu64, id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (ring_pedersen_public_size(ring_pedersen.get()) < RING_PEDERSEN_KEY_SIZE) { - LOG_ERROR("ring pedersen public key from player %" PRIu64 " size %u, is smaller then the minimum key size %u", id, ring_pedersen_public_size(ring_pedersen.get()), RING_PEDERSEN_KEY_SIZE); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("ring pedersen public key from player %" PRIu64 " size %u, is smaller than the minimum key size %u", id, ring_pedersen_public_size(ring_pedersen.get()), RING_PEDERSEN_KEY_SIZE); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } void cmp_setup_service::serialize_auxiliary_keys_zkp(const auxiliary_keys& aux, const std::vector& aad, std::vector& paillier_blum_zkp, std::vector& ring_pedersen_param_zkp) { uint32_t size = 0; - paillier_generate_paillier_blum_zkp(aux.paillier.get(), aad.data(), aad.size(), NULL, 0, &size); + paillier_generate_paillier_blum_zkp(aux.paillier.get(), 1, aad.data(), aad.size(), NULL, 0, &size); paillier_blum_zkp.resize(size); - if (paillier_generate_paillier_blum_zkp(aux.paillier.get(), aad.data(), aad.size(), paillier_blum_zkp.data(), paillier_blum_zkp.size(), &size) != PAILLIER_SUCCESS) + if (paillier_generate_paillier_blum_zkp(aux.paillier.get(), 1, aad.data(), aad.size(), paillier_blum_zkp.data(), paillier_blum_zkp.size(), &size) != PAILLIER_SUCCESS) { LOG_ERROR("failed to generate paillier blum zkp"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } size = 0; @@ -601,7 +664,7 @@ void cmp_setup_service::serialize_auxiliary_keys_zkp(const auxiliary_keys& aux, if (ring_pedersen_parameters_zkp_generate(aux.ring_pedersen.get(), aad.data(), aad.size(), ring_pedersen_param_zkp.data(), ring_pedersen_param_zkp.size(), &size) != ZKP_SUCCESS) { LOG_ERROR("failed to generate ring pedersen parameters zkp"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } @@ -669,7 +732,7 @@ void cmp_setup_service::verify_and_load_setup_decommitments(const std::string& k if (decommitments.size() != commitments.size()) { LOG_ERROR("got %lu decommitments but the key was created for %lu players", decommitments.size(), commitments.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } commitments_sha256_t ack; @@ -681,15 +744,15 @@ void cmp_setup_service::verify_and_load_setup_decommitments(const std::string& k if (decommit_it == decommitments.end()) { LOG_ERROR("missing decommitment from player %" PRIu64, i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } commitment commit = i->second; create_setup_commitment(key_id, i->first, decommit_it->second, commit, true); if (memcmp(ack, decommit_it->second.ack, sizeof(commitments_sha256_t)) != 0) { - LOG_ERROR("ack from player %" PRIu64 " is different from my claculated ack", i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("ack from player %" PRIu64 " is different from my calculated ack", i->first); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } auto& info = players_info.at(i->first); @@ -717,15 +780,14 @@ void cmp_setup_service::generate_setup_proofs(const std::string& key_id, const e memcpy(proofs.schnorr_s.data, schnorr_proof.s, sizeof(elliptic_curve256_scalar_t)); } -void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const cmp_key_metadata& metadata, const std::map& proofs) +void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const cmp_key_metadata& metadata, const std::map& proofs, const setup_data& temp_data) { if (proofs.size() != metadata.players_info.size()) { LOG_ERROR("got %lu proofs but the key was created for %lu players", proofs.size(), metadata.players_info.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - setup_data temp_data; - _key_persistency.load_setup_data(key_id, temp_data); + auto algebra = get_algebra(metadata.algorithm); uint64_t my_id = _service.get_id_from_keyid(key_id); @@ -734,8 +796,8 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const cmp auto proof = proofs.find(i->first); if (proof == proofs.end()) { - LOG_ERROR("missing proof from player %" PRIu64, i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("missing proof from player %" PRIu64 "", i->first); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (i->first == my_id) continue; @@ -747,21 +809,21 @@ void cmp_setup_service::verify_setup_proofs(const std::string& key_id, const cmp auto status = schnorr_zkp_verify(algebra, aad.data(), aad.size(), &i->second.public_share.data, &schnorr); if (status != ZKP_SUCCESS) { - LOG_ERROR("Failed to verify schnorr zkp from player %" PRIu64, i->first); + LOG_ERROR("Failed to verify schnorr zkp from player %" PRIu64 "", i->first); throw_cosigner_exception(status); } - auto paillier_status = paillier_verify_paillier_blum_zkp(i->second.paillier.get(), aad.data(), aad.size(), proof->second.paillier_blum_zkp.data(), proof->second.paillier_blum_zkp.size()); + auto paillier_status = paillier_verify_paillier_blum_zkp(i->second.paillier.get(), 1, aad.data(), aad.size(), proof->second.paillier_blum_zkp.data(), proof->second.paillier_blum_zkp.size()); if (paillier_status != PAILLIER_SUCCESS) { - LOG_ERROR("Failed to verify paillier blum zkp from player %" PRIu64, i->first); - throw_paillier_exception(paillier_status); + LOG_ERROR("Failed to verify paillier blum zkp from player %" PRIu64 "", i->first); + throw_paillier_exception(paillier_status); } status = ring_pedersen_parameters_zkp_verify(i->second.ring_pedersen.get(), aad.data(), aad.size(), proof->second.ring_pedersen_param_zkp.data(), proof->second.ring_pedersen_param_zkp.size()); if (status != ZKP_SUCCESS) { - LOG_ERROR("Failed to verify ring pedersen parameters zkp from player %" PRIu64, i->first); + LOG_ERROR("Failed to verify ring pedersen parameters zkp from player %" PRIu64 "", i->first); throw_cosigner_exception(status); } } @@ -786,7 +848,7 @@ elliptic_curve256_algebra_ctx_t* cmp_setup_service::get_algebra(cosigner_sign_al case EDDSA_ED25519: return _ed25519.get(); case ECDSA_STARK: return _stark.get(); default: - throw cosigner_exception(cosigner_exception::UNKNOWN_ALGORITHM); + throw_cosigner_exception(cosigner_exception::UNKNOWN_ALGORITHM); } } } diff --git a/src/common/cosigner/cosigner_bn.h b/src/common/cosigner/cosigner_bn.h new file mode 100644 index 0000000..da19bb0 --- /dev/null +++ b/src/common/cosigner/cosigner_bn.h @@ -0,0 +1,73 @@ +#pragma once +#include + +namespace fireblocks::common::cosigner +{ + +struct bn_ctx_frame +{ + [[nodiscard]] explicit bn_ctx_frame(BN_CTX* ctx) : _ctx(ctx) {BN_CTX_start(_ctx);} + ~bn_ctx_frame() {if (_ctx) BN_CTX_end(_ctx);} + bn_ctx_frame(const bn_ctx_frame&) = delete; + bn_ctx_frame(bn_ctx_frame&&) = delete; + bn_ctx_frame& operator=(const bn_ctx_frame&) = delete; + bn_ctx_frame& operator=(bn_ctx_frame&&) = delete; + + void reset() + { + if (_ctx) + BN_CTX_end(_ctx); + _ctx = NULL; + } + BN_CTX* _ctx; +}; + + +class BN_CTX_guard +{ +public: + [[nodiscard]] explicit BN_CTX_guard(bool secure = false) + { + _ctx = secure ? BN_CTX_secure_new() : BN_CTX_new(); + if (!_ctx) + throw_cosigner_exception(cosigner_exception::NO_MEM); + BN_CTX_start(_ctx); + } + + ~BN_CTX_guard() + { + BN_CTX_end(_ctx); + BN_CTX_free(_ctx); + } + + [[nodiscard]] BN_CTX* get() const {return _ctx;} + + BN_CTX_guard(const BN_CTX_guard&) = delete; + BN_CTX_guard& operator=(const BN_CTX_guard&) = delete; + BN_CTX_guard(BN_CTX_guard&&) = delete; + BN_CTX_guard& operator=(BN_CTX_guard&&) = delete; +private: + BN_CTX* _ctx; +}; + +template +class container_cleaner; + +template<> +class container_cleaner +{ +public: + [[nodiscard]] explicit container_cleaner(bignum_st* secret) : _secret(secret) {} + ~container_cleaner() {BN_clear(_secret);} + container_cleaner(const container_cleaner&) = delete; + container_cleaner& operator=(const container_cleaner&) = delete; + container_cleaner(container_cleaner&&) = delete; + container_cleaner& operator=(container_cleaner&&) = delete; + +private: + bignum_st* _secret; +}; + +using bignum_cleaner = container_cleaner; + +} \ No newline at end of file diff --git a/src/common/cosigner/cosigner_exception.cpp b/src/common/cosigner/cosigner_exception.cpp index 5d64fd4..f041379 100644 --- a/src/common/cosigner/cosigner_exception.cpp +++ b/src/common/cosigner/cosigner_exception.cpp @@ -9,11 +9,7 @@ namespace common namespace cosigner { -cosigner_exception::~cosigner_exception() -{ -} - -void throw_cosigner_exception(verifiable_secret_sharing_status status) +void do_throw_cosigner_exception(verifiable_secret_sharing_status status) { switch (status) { @@ -30,7 +26,7 @@ void throw_cosigner_exception(verifiable_secret_sharing_status status) } } -void throw_cosigner_exception(elliptic_curve_algebra_status status) +void do_throw_cosigner_exception(elliptic_curve_algebra_status status) { switch (status) { @@ -46,7 +42,7 @@ void throw_cosigner_exception(elliptic_curve_algebra_status status) } } -void throw_cosigner_exception(commitments_status status) +void do_throw_cosigner_exception(commitments_status status) { switch (status) { @@ -60,7 +56,7 @@ void throw_cosigner_exception(commitments_status status) } } -void throw_cosigner_exception(zero_knowledge_proof_status status) +void do_throw_cosigner_exception(zero_knowledge_proof_status status) { switch (status) { @@ -74,8 +70,9 @@ void throw_cosigner_exception(zero_knowledge_proof_status status) } } -void throw_paillier_exception(long status) +void do_throw_cosigner_exception(paillier_dummy_error_code paillier_error_code) { + long status = (long)paillier_error_code; if (status == PAILLIER_SUCCESS) { return; @@ -93,7 +90,7 @@ void throw_paillier_exception(long status) throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); } -void throw_cosigner_exception(ring_pedersen_status status) +void do_throw_cosigner_exception(ring_pedersen_status status) { switch (status) { @@ -108,6 +105,54 @@ void throw_cosigner_exception(ring_pedersen_status status) } } +void do_throw_cosigner_exception(drng_status status) +{ + switch (status) + { + case DRNG_SUCCESS: return; + case DRNG_INVALID_PARAMETER: throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + case DRNG_OUT_OF_MEMORY: throw cosigner_exception(cosigner_exception::NO_MEM); + case DRNG_INTERNAL_ERROR: + default: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } +} + + +void do_throw_cosigner_exception(cosigner_status_t status) +{ + switch (status) + { + case COSIGNER_STATUS_SUCCESS: return; + case COSIGNER_STATUS_INVALID_PARAMETER: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + case COSIGNER_STATUS_BAD_PRIVATE_KEY: throw cosigner_exception(cosigner_exception::BAD_KEY); + case COSIGNER_STATUS_BAD_KEY_LEN: throw cosigner_exception(cosigner_exception::BAD_KEY); + case COSIGNER_STATUS_BAD_DATA_LEN: throw cosigner_exception(cosigner_exception::NOT_ALIGNED_DATA); + case COSIGNER_STATUS_PUBLIC_KEY_TOO_SMALL: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + case COSIGNER_STATUS_SIGNATURE_BLOCK_TOO_SMALL: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + case COSIGNER_STATUS_NOT_IMPLEMENTED: throw cosigner_exception(cosigner_exception::NOT_IMPLEMENTED); + case COSIGNER_STATUS_UNKNOWN_ALGORITHM: throw cosigner_exception(cosigner_exception::UNKNOWN_ALGORITHM); + case COSIGNER_STATUS_INTERNAL_ERROR: + default: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } +} + +void log_exception(const std::string& name, const std::string& what, const char* file, const char* func, const int line) +{ + if (!what.empty()) + { + cosigner_log_msg(COSIGNER_LOG_LEVEL_ERROR, file, line, func, "Throwing cosigner exception %s:%s", name.c_str(), what.c_str() ); + } + else + { + cosigner_log_msg(COSIGNER_LOG_LEVEL_ERROR, file, line, func, "Throwing cosigner exception %s", name.c_str()); + } +} + +void do_throw_cosigner_exception(cosigner_exception::exception_code code) +{ + throw cosigner_exception(code); +} + } } } diff --git a/src/common/cosigner/eddsa_online_signing_service.cpp b/src/common/cosigner/eddsa_online_signing_service.cpp index 9cd5a48..41b3caf 100644 --- a/src/common/cosigner/eddsa_online_signing_service.cpp +++ b/src/common/cosigner/eddsa_online_signing_service.cpp @@ -5,9 +5,9 @@ #include "cosigner/platform_service.h" #include "utils.h" #include "logging/logging_t.h" - #include -#include +#include + namespace fireblocks { @@ -31,22 +31,11 @@ static inline const char* to_string(cosigner_sign_algorithm algorithm) const std::unique_ptr eddsa_online_signing_service::_ed25519(elliptic_curve256_new_ed25519_algebra(), elliptic_curve256_algebra_ctx_free); -eddsa_online_signing_service::signing_persistency::~signing_persistency() -{ -} - -eddsa_online_signing_service::eddsa_online_signing_service(platform_service& service, const cmp_key_persistency& key_persistency, signing_persistency& preprocessing_persistency): - _service(service), - _key_persistency(key_persistency), - _signing_persistency(preprocessing_persistency), - _timing_map(service) {} - - void eddsa_online_signing_service::start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const std::set& players_ids, std::vector& commitments) { - (void)players; // UNUSED - LOG_INFO("Entering txid = %s", txid.c_str()); + _service.prepare_for_signing(key_id, txid); + commitments.clear(); verify_tenant_id(_service, _key_persistency, key_id); cmp_key_metadata metadata; @@ -55,20 +44,21 @@ void eddsa_online_signing_service::start_signing(const std::string& key_id, cons if (metadata.algorithm != EDDSA_ED25519) { LOG_ERROR("key %s has algorithm %s, but the request is for eddsa", key_id.c_str(), to_string(metadata.algorithm)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (players_ids.size() < metadata.t) { LOG_ERROR("invalid number of signers for keyid = %s, txid = %s, signers = %lu", key_id.c_str(), txid.c_str(), players_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } + for (auto i = players_ids.begin(); i != players_ids.end(); ++i) { if (metadata.players_info.find(*i) == metadata.players_info.end()) { LOG_ERROR("playerid %" PRIu64 " not part of key, for keyid = %s, txid = %s", *i, key_id.c_str(), txid.c_str()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -82,19 +72,22 @@ void eddsa_online_signing_service::start_signing(const std::string& key_id, cons if (blocks > MAX_BLOCKS_TO_SIGN) { LOG_ERROR("got too many blocks to sign %lu", blocks); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } LOG_INFO("Starting signing process keyid = %s, txid = %s", key_id.c_str(), txid.c_str()); - eddsa_signing_metadata info = {key_id}; + auto metadata_ptr = std::shared_ptr(new eddsa_signing_metadata{key_id}); + auto &info = *metadata_ptr; + memcpy(info.chaincode, data.chaincode, sizeof(HDChaincode)); info.signers_ids.insert(players_ids.begin(), players_ids.end()); info.version = common::cosigner::MPC_PROTOCOL_VERSION; commitments.reserve(blocks); info.sig_data.reserve(blocks); + info.timestamp = _service.now_msec(); + - for (size_t i = 0; i < blocks; i++) { elliptic_curve_scalar k; @@ -111,22 +104,20 @@ void eddsa_online_signing_service::start_signing(const std::string& key_id, cons sigdata.flags = NONE; info.sig_data.push_back(sigdata); } - std::vector flags(blocks, 0); - _service.fill_signing_info_from_metadata(metadata_json, flags); - for (size_t i = 0; i < blocks; i++) - info.sig_data[i].flags = flags[i]; - _signing_persistency.store_signing_data(txid, info); + + _service.fill_eddsa_signing_info_from_metadata(info.sig_data, metadata_json); + _signing_persistency.store_eddsa_signing_data(txid, metadata_ptr); } -uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid, const std::map>& commitments, uint32_t version, std::vector& R) +uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid, const commitments_map& commitments, uint32_t version, std::vector& R) { LOG_INFO("Entering txid = %s", txid.c_str()); R.clear(); - eddsa_signing_metadata data; - _signing_persistency.load_signing_data(txid, data); + auto metadata_ptr = _signing_persistency.load_eddsa_signing_data(txid); + auto & data = *metadata_ptr; verify_tenant_id(_service, _key_persistency, data.key_id); - + #ifndef MOBILE { _timing_map.insert(txid); @@ -138,15 +129,15 @@ uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid if (data.signers_ids.size() != commitments.size()) { - LOG_ERROR("commitments size %lu is different then expected size %lu", commitments.size(), data.signers_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("commitments size %lu is different than expected size %lu", commitments.size(), data.signers_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) { if (commitments.find(*i) == commitments.end()) { LOG_ERROR("commitment for player %" PRIu64 " not found in commitments list", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } for (auto i = commitments.begin(); i != commitments.end(); ++i) @@ -154,7 +145,7 @@ uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid if (i->second.size() != data.sig_data.size()) { LOG_ERROR("commitment for player %" PRIu64 " size %lu is different from block size %lu", i->first, i->second.size(), data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -171,7 +162,7 @@ uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid if (data.version != version) { data.version = version; - _signing_persistency.update_signing_data(txid, data); + _signing_persistency.update_eddsa_signing_data(txid, metadata_ptr); } _signing_persistency.store_signing_commitments(txid, commitments); return my_id; @@ -180,23 +171,23 @@ uint64_t eddsa_online_signing_service::store_commitments(const std::string& txid uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, const std::map>& Rs, std::vector& si) { LOG_INFO("Entering txid = %s", txid.c_str()); - eddsa_signing_metadata data; - _signing_persistency.load_signing_data(txid, data); + auto metadata_ptr = _signing_persistency.load_eddsa_signing_data(txid); + auto & data = *metadata_ptr; verify_tenant_id(_service, _key_persistency, data.key_id); const uint64_t my_id = _service.get_id_from_keyid(data.key_id); if (data.signers_ids.size() != Rs.size()) { - LOG_ERROR("commitments size %lu is different then expected size %lu", Rs.size(), data.signers_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("commitments size %lu is different than expected size %lu", Rs.size(), data.signers_ids.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) { if (Rs.find(*i) == Rs.end()) { LOG_ERROR("commitment for player %" PRIu64 " not found in commitments list", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } for (auto i = Rs.begin(); i != Rs.end(); ++i) @@ -204,21 +195,27 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con if (i->second.size() != data.sig_data.size()) { LOG_ERROR("commitment for player %" PRIu64 " size %lu is different from block size %lu", i->first, i->second.size(), data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } // validate decommitments - std::map> commitments; + commitments_map commitments; _signing_persistency.load_signing_commitments(txid, commitments); + if (commitments.empty()) + { + LOG_ERROR("Empty commitments"); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + + } for (auto i = commitments.begin(); i != commitments.end(); ++i) { auto it = Rs.find(i->first); if (it == Rs.end()) { LOG_ERROR("R from player %" PRIu64" missing", i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } for (size_t j = 0; j < data.sig_data.size(); ++j) @@ -226,7 +223,7 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con if (commitments_verify_commitment(it->second[j].data, sizeof(elliptic_curve256_point_t), &i->second[j].data) != COMMITMENTS_SUCCESS) { LOG_ERROR("failed to verify gamma commitment for player %" PRIu64, i->first); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } } @@ -242,9 +239,8 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con if (algo != metadata.algorithm) { LOG_FATAL("key algorithm %d is different from the key metadata algorithm %d", algo, metadata.algorithm); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - elliptic_curve_scalar x; for (size_t i = 0; i < data.sig_data.size(); ++i) @@ -271,7 +267,7 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con if (derivation_status != HD_DERIVE_SUCCESS) { LOG_ERROR("failed to derive public key for block %lu, error %d", i, derivation_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } else @@ -296,7 +292,7 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con si.push_back(data.sig_data[i].s); } - _signing_persistency.update_signing_data(txid, data); + _signing_persistency.update_eddsa_signing_data(txid, metadata_ptr); return my_id; } @@ -304,15 +300,15 @@ uint64_t eddsa_online_signing_service::broadcast_si(const std::string& txid, con uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& txid, const std::map>& s, std::vector& sig) { LOG_INFO("Entering txid = %s", txid.c_str()); - eddsa_signing_metadata data; - _signing_persistency.load_signing_data(txid, data); + auto metadata_ptr = _signing_persistency.load_eddsa_signing_data(txid); + auto & data = *metadata_ptr; verify_tenant_id(_service, _key_persistency, data.key_id); uint64_t my_id = _service.get_id_from_keyid(data.key_id); if (s.size() != data.signers_ids.size()) { LOG_ERROR("got wrong number of s, got %lu expected %lu", s.size(), data.signers_ids.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } const std::vector* my_s = NULL; for (auto i = data.signers_ids.begin(); i != data.signers_ids.end(); ++i) @@ -321,12 +317,12 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx if (it == s.end()) { LOG_ERROR("s from player %" PRIu64 " missing", *i); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (it->second.size() != data.sig_data.size()) { LOG_ERROR("number of s (%lu) from player %" PRIu64 " is different from block size %lu", it->second.size(), *i, data.sig_data.size()); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (*i == my_id) my_s = &it->second; @@ -335,7 +331,7 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx if (my_s == NULL) { LOG_ERROR("inconsistent state detected in persistent storage"); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } sig.clear(); @@ -349,8 +345,8 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx { if (memcmp(my_s->at(index).data, &data.sig_data[index].s.data, sizeof(elliptic_curve_scalar)) != 0) { - LOG_ERROR("missmatch between my stored s and broadcasted s"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("mismatch between my stored s and broadcasted s"); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } elliptic_curve256_scalar_t s_sum = {0}; @@ -360,7 +356,7 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx eddsa_signature cur_sig; memcpy(cur_sig.R, data.sig_data[index].R.data, sizeof(ed25519_point_t)); - ed25519_algebra_be_to_le(&cur_sig.s, &s_sum); + throw_cosigner_exception(ed25519_algebra_be_to_le(&cur_sig.s, &s_sum)); // verify signature elliptic_curve256_point_t derived_public_key; @@ -368,7 +364,7 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx if (derivation_status != HD_DERIVE_SUCCESS) { LOG_ERROR("failed to derive public key for block %lu, error %d", index, derivation_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } unsigned char raw_sig[64]; memcpy(raw_sig, cur_sig.R, 32); @@ -380,13 +376,13 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx else { LOG_FATAL("failed to verify signature for block %lu, error %d", index, derivation_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } sig.push_back(cur_sig); } - _signing_persistency.delete_temporary_signing_data(txid); + _signing_persistency.delete_eddsa_signing_data(txid); const std::optional diff = _timing_map.extract(txid); if (!diff) @@ -395,12 +391,109 @@ uint64_t eddsa_online_signing_service::get_eddsa_signature(const std::string& tx } else { + static const std::string ALGORITHM("additive EdDSA"); LOG_INFO("Finished signing %lu blocks for transaction %s (tenant %s) in %" PRIu64 "ms", sig.size(), txid.c_str(), _service.get_current_tenantid().c_str(), *diff); + _service.report_signing_time(ALGORITHM, *diff, sig.size()); } return my_id; } +void eddsa_online_signing_service::cancel_signing(const std::string& txid) +{ + _signing_persistency.delete_eddsa_signing_data(txid); + _timing_map.erase(txid); +} + +class BN_CTX_guard +{ +public: + [[nodiscard]] explicit BN_CTX_guard() + { + _ctx = BN_CTX_secure_new(); + if (!_ctx) + throw_cosigner_exception(cosigner_exception::NO_MEM); + BN_CTX_start(_ctx); + } + + ~BN_CTX_guard() + { + BN_CTX_end(_ctx); + BN_CTX_free(_ctx); + } + + [[nodiscard]] BN_CTX* get() const {return _ctx;} + + BN_CTX_guard(const BN_CTX_guard&) = delete; + BN_CTX_guard& operator=(const BN_CTX_guard&) = delete; + BN_CTX_guard(BN_CTX_guard&&) = delete; + BN_CTX_guard& operator=(BN_CTX_guard&&) = delete; +private: + BN_CTX* _ctx; +}; + +void eddsa_online_signing_service::calc_w(elliptic_curve_scalar& x, uint64_t my_id, const std::set& ids) +{ + BN_CTX_guard ctx; + BIGNUM* bn_w = BN_CTX_get(ctx.get()); + BIGNUM* tmp = BN_CTX_get(ctx.get()); + BIGNUM* bn_other_id = BN_CTX_get(ctx.get()); + BIGNUM* bn_my_id = BN_CTX_get(ctx.get()); + + if (!bn_w || !tmp || !bn_other_id || !bn_my_id) + throw_cosigner_exception(cosigner_exception::NO_MEM); + + if (!BN_one(bn_w)) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + if (!BN_set_word(bn_my_id, my_id)) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + + const BIGNUM* field = _ed25519->order_internal(_ed25519.get()); + + for (auto it = ids.begin(); it != ids.end(); ++it) + { + uint64_t other_id = *it; + if (other_id == my_id) + continue; + + if (!BN_set_word(bn_other_id, other_id)) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + + // tmp = other_id - my_id + if (!BN_mod_sub_quick(tmp, bn_other_id, bn_my_id, field)) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + + // tmp = inverse(tmp) = inverse(other_id - my_id) + if (!BN_mod_inverse(tmp, tmp, field, ctx.get())) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + + // tmp *= other_id + // tmp = other_id * inverse(other_id - my_id) = other_id/(other_id - my_id) + if (!BN_mod_mul(tmp, tmp, bn_other_id, field, ctx.get())) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + + // product *= tmp + if (!BN_mod_mul(bn_w, bn_w, tmp, field, ctx.get())) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + BN_set_flags(bn_w, BN_FLG_CONSTTIME); + BN_set_flags(tmp, BN_FLG_CONSTTIME); + + if (!BN_bin2bn(x.data, sizeof(elliptic_curve256_scalar_t), tmp)) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + if (!BN_mod_mul(bn_w, bn_w, tmp, field, ctx.get())) + { + BN_clear(tmp); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + BN_clear(tmp); + int bytes = BN_bn2binpad(bn_w, x.data, sizeof(elliptic_curve256_scalar_t)); + BN_clear(bn_w); + if (bytes <= 0) + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); +} + +} } } -} \ No newline at end of file diff --git a/src/common/cosigner/mta.cpp b/src/common/cosigner/mta.cpp index 1857024..e50d68b 100644 --- a/src/common/cosigner/mta.cpp +++ b/src/common/cosigner/mta.cpp @@ -1,12 +1,14 @@ #include "mta.h" -#include "crypto/paillier_commitment/paillier_commitment.h" -#include "../crypto/paillier_commitment/paillier_commitment_internal.h" -#include "../crypto/paillier/paillier_internal.h" -#include "../crypto/commitments/ring_pedersen_internal.h" -#include "../crypto/algebra_utils/algebra_utils.h" -#include "crypto/drng/drng.h" #include "cosigner/cmp_key_persistency.h" +#include "cosigner/cosigner_exception.h" +#include "utils/string_utils.h" +#include "cosigner/mpc_globals.h" #include "crypto/zero_knowledge_proof/range_proofs.h" +#include "crypto/drng/drng.h" +#include "crypto/algebra_utils/algebra_utils.h" +#include "../../common/crypto/paillier/paillier_internal.h" +#include "../../common/crypto/commitments/ring_pedersen_internal.h" +#include "cosigner_bn.h" #ifndef TEST_ONLY #include "logging/logging_t.h" @@ -19,51 +21,14 @@ #include #include #include -#include -namespace fireblocks -{ -namespace common -{ -namespace cosigner -{ -namespace mta +namespace fireblocks::common::cosigner::mta { -static void throw_cosigner_exception(drng_status status) -{ - switch (status) - { - case DRNG_SUCCESS: return; - case DRNG_INVALID_PARAMETER: throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); - case DRNG_OUT_OF_MEMORY: throw cosigner_exception(cosigner_exception::NO_MEM); - case DRNG_INTERNAL_ERROR: - default: throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); - } -} - - static const uint32_t MTA_ZKP_EPSILON_SIZE = 2 * sizeof(elliptic_curve256_scalar_t); static const char MTA_ZKP_SALT[] = "Affine Operation with Group Commitment in Range ZK"; static const uint32_t BETA_HIDING_FACTOR = 5; -struct [[nodiscard]] bn_ctx_frame -{ - explicit bn_ctx_frame(BN_CTX* ctx) : _ctx(ctx) {BN_CTX_start(ctx);} - ~bn_ctx_frame() {if (_ctx) BN_CTX_end(_ctx);} - bn_ctx_frame(const bn_ctx_frame&) = delete; - bn_ctx_frame(bn_ctx_frame&&) = delete; - bn_ctx_frame& operator=(const bn_ctx_frame&) = delete; - bn_ctx_frame& operator=(bn_ctx_frame&&) = delete; - - void reset() - { - if (_ctx) - BN_CTX_end(_ctx); - _ctx = NULL; - } - BN_CTX* _ctx; -}; struct mta_range_zkp { @@ -81,34 +46,95 @@ struct mta_range_zkp BIGNUM* wy; elliptic_curve256_point_t Bx; - mta_range_zkp(BN_CTX* ctx): A(BN_CTX_get(ctx)), By(BN_CTX_get(ctx)), E(BN_CTX_get(ctx)), F(BN_CTX_get(ctx)), S(BN_CTX_get(ctx)), T(BN_CTX_get(ctx)), + mta_range_zkp(BN_CTX* ctx): A(BN_CTX_get(ctx)), By(BN_CTX_get(ctx)), E(BN_CTX_get(ctx)), F(BN_CTX_get(ctx)), S(BN_CTX_get(ctx)), T(BN_CTX_get(ctx)), z1(BN_CTX_get(ctx)), z2(BN_CTX_get(ctx)), z3(BN_CTX_get(ctx)), z4(BN_CTX_get(ctx)), w(BN_CTX_get(ctx)), wy(BN_CTX_get(ctx)) { if (!A || !By || !E || !F || !z1 || !z2 || !z3 || !z4 || !w || !wy) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } }; -static inline void genarate_mta_range_zkp_seed(const cmp_mta_message& response, const mta_range_zkp& proof, const std::vector& aad, uint8_t *seed) +struct Sha256Hasher +{ + Sha256Hasher(SHA256_CTX& ctx) : _ctx(ctx) {} + void hash_vector_with_size(const byte_vector_t& vec) + { + const uint32_t size = vec.size(); + SHA256_Update(&_ctx, &size, sizeof(uint32_t)); + SHA256_Update(&_ctx, vec.data(), vec.size()); + } + + void hash_bn(BIGNUM* bn, const size_t size, const char* name) + { + _vec.resize(size); + if (BN_bn2binpad(bn, _vec.data(), _vec.size()) <= 0) + { + const uint32_t required_size = BN_num_bytes(bn); + LOG_ERROR("Error hashing %s- not enough buffer. Required %u, given %u", name, required_size, (uint32_t) _vec.size()); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + } + SHA256_Update(&_ctx, _vec.data(), size); + } + + SHA256_CTX& _ctx; + std::vector _vec; +}; + +static inline void generate_mta_range_zkp_extended_seed(const cmp_mta_message& response, + const mta_range_zkp& proof, + const std::vector& aad, + const ring_pedersen_public_t* ring_pedersen_verifier, + const paillier_public_key_t* public_key_prover, + const paillier_public_key_t* public_key_verifier, + uint8_t *seed) { SHA256_CTX ctx; - + const uint32_t verifier_ring_pedersen_n_size = BN_num_bytes(ring_pedersen_verifier->n); + const uint32_t prover_paillier_pub_n_size = BN_num_bytes(public_key_prover->n); + const uint32_t verifier_paillier_pub_n_size = BN_num_bytes(public_key_verifier->n); + + SHA256_Init(&ctx); + SHA256_Update(&ctx, MTA_ZKP_SALT, sizeof(MTA_ZKP_SALT)); + + Sha256Hasher hasher(ctx); + + hasher.hash_vector_with_size(aad); + hasher.hash_vector_with_size(response.message); + hasher.hash_vector_with_size(response.commitment); + hasher.hash_bn(proof.A, verifier_paillier_pub_n_size * 2, "A"); + SHA256_Update(&ctx, proof.Bx, sizeof(elliptic_curve256_point_t)); + hasher.hash_bn(proof.By, prover_paillier_pub_n_size * 2, "By"); + hasher.hash_bn(proof.E, verifier_ring_pedersen_n_size, "E"); + hasher.hash_bn(proof.F, verifier_ring_pedersen_n_size, "F"); + hasher.hash_bn(proof.S, verifier_ring_pedersen_n_size, "S"); + hasher.hash_bn(proof.T, verifier_ring_pedersen_n_size, "T"); + + SHA256_Final(seed, &ctx); +} + +static inline void generate_mta_range_zkp_seed(const cmp_mta_message& response, + const mta_range_zkp& proof, + const std::vector& aad, + uint8_t *seed) +{ + SHA256_CTX ctx; + SHA256_Init(&ctx); SHA256_Update(&ctx, MTA_ZKP_SALT, sizeof(MTA_ZKP_SALT)); SHA256_Update(&ctx, aad.data(), aad.size()); SHA256_Update(&ctx, response.message.data(), response.message.size()); SHA256_Update(&ctx, response.commitment.data(), response.commitment.size()); - + std::vector n(BN_num_bytes(proof.A)); BN_bn2bin(proof.A, n.data()); SHA256_Update(&ctx, n.data(), BN_num_bytes(proof.S)); // right size of S is ensured during serialization - + SHA256_Update(&ctx, proof.Bx, sizeof(elliptic_curve256_point_t)); n.resize(BN_num_bytes(proof.By)); BN_bn2bin(proof.By, n.data()); SHA256_Update(&ctx, n.data(), BN_num_bytes(proof.By)); - + n.resize(BN_num_bytes(proof.E)); BN_bn2bin(proof.E, n.data()); SHA256_Update(&ctx, n.data(), BN_num_bytes(proof.E)); @@ -130,7 +156,7 @@ static inline void genarate_mta_range_zkp_seed(const cmp_mta_message& response, static inline uint32_t exponent_zkpok_serialized_size(const ring_pedersen_public_t* ring_pedersen, const paillier_private_key_t* private_key, const paillier_public_key_t* public_key) { - return + return sizeof(uint32_t) + // sizeof(ring_pedersen->n) sizeof(uint32_t) + // sizeof(private_key->n) sizeof(uint32_t) + // sizeof(public_key->n) @@ -146,7 +172,10 @@ static inline uint32_t exponent_zkpok_serialized_size(const ring_pedersen_public BN_num_bytes(private_key->pub.n); // sizeof(wy) } -static inline std::vector serialize_mta_range_zkp(const mta_range_zkp& proof, const ring_pedersen_public_t* ring_pedersen, const paillier_private_key_t* private_key, const paillier_public_key_t* public_key) +static inline std::vector serialize_mta_range_zkp(const mta_range_zkp& proof, + const ring_pedersen_public_t* ring_pedersen, + const paillier_private_key_t* private_key, + const paillier_public_key_t* public_key) { const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen->n); const uint32_t paillier_priv_n_size = BN_num_bytes(private_key->pub.n); @@ -159,32 +188,68 @@ static inline std::vector serialize_mta_range_zkp(const mta_range_zkp& ptr += sizeof(uint32_t); *(uint32_t*)ptr = paillier_pub_n_size; ptr += sizeof(uint32_t); - BN_bn2binpad(proof.A, ptr, paillier_pub_n_size * 2); + if (BN_bn2binpad(proof.A, ptr, paillier_pub_n_size * 2) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += paillier_pub_n_size * 2; memcpy(ptr, proof.Bx, sizeof(elliptic_curve256_point_t)); ptr += sizeof(elliptic_curve256_point_t); - BN_bn2binpad(proof.By, ptr, paillier_priv_n_size * 2); + if (BN_bn2binpad(proof.By, ptr, paillier_priv_n_size * 2) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += paillier_priv_n_size * 2; - BN_bn2binpad(proof.E, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof.E, ptr, ring_pedersen_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof.F, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof.F, ptr, ring_pedersen_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof.S, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof.S, ptr, ring_pedersen_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof.T, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof.T, ptr, ring_pedersen_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof.z1, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1); + if (BN_bn2binpad(proof.z1, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1; - BN_bn2binpad(proof.z2, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + 1); + if (BN_bn2binpad(proof.z2, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + 1) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + 1; - BN_bn2binpad(proof.z3, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1); + if (BN_bn2binpad(proof.z3, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1; - BN_bn2binpad(proof.z4, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1); + if (BN_bn2binpad(proof.z4, ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1; - BN_bn2binpad(proof.w, ptr, paillier_pub_n_size); + if (BN_bn2binpad(proof.w, ptr, paillier_pub_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += paillier_pub_n_size; - BN_bn2binpad(proof.wy, ptr, paillier_priv_n_size); + if (BN_bn2binpad(proof.wy, ptr, paillier_priv_n_size) <= 0) + { + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ptr += paillier_priv_n_size; assert(ptr == ret.data() + ret.size()); return ret; @@ -194,42 +259,42 @@ static inline void deserialize_mta_range_zkp(std::vector buff, const ri { if (buff.size() != exponent_zkpok_serialized_size(ring_pedersen, private_key, public_key)) { - LOG_ERROR("Invlid buffer size %lu, expected %u", buff.size(), exponent_zkpok_serialized_size(ring_pedersen, private_key, public_key)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Invalid buffer size %lu, expected %u", buff.size(), exponent_zkpok_serialized_size(ring_pedersen, private_key, public_key)); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen->n); const uint32_t paillier_priv_n_size = BN_num_bytes(private_key->pub.n); const uint32_t paillier_pub_n_size = BN_num_bytes(public_key->n); static const constexpr uint32_t EPSILON_BYTES = 8; //max bytes that the variables can be smaller - + const uint8_t *ptr = buff.data(); if (*(uint32_t*)ptr != ring_pedersen_n_size) { LOG_ERROR("Wrong ring pedersen key size %u, expected %u", *(uint32_t*)ptr, ring_pedersen_n_size); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ptr += sizeof(uint32_t); if (*(uint32_t*)ptr != paillier_priv_n_size) { LOG_ERROR("Wrong paillier private key size %u, expected %u", *(uint32_t*)ptr, paillier_priv_n_size); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ptr += sizeof(uint32_t); if (*(uint32_t*)ptr != paillier_pub_n_size) { LOG_ERROR("Wrong paillier public key size %u, expected %u", *(uint32_t*)ptr, paillier_pub_n_size); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } ptr += sizeof(uint32_t); if (!BN_bin2bn(ptr, paillier_pub_n_size * 2, proof.A)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } - ptr += paillier_pub_n_size * 2; + ptr += paillier_pub_n_size * 2; if (static_cast(BN_num_bytes(proof.A)) < paillier_pub_n_size * 2 - EPSILON_BYTES) { LOG_ERROR("Proof A too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.A)), paillier_pub_n_size * 2 - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } memcpy(proof.Bx, ptr, sizeof(elliptic_curve256_point_t)); @@ -237,114 +302,124 @@ static inline void deserialize_mta_range_zkp(std::vector buff, const ri if (!BN_bin2bn(ptr, paillier_priv_n_size * 2, proof.By)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += paillier_priv_n_size * 2; if (static_cast(BN_num_bytes(proof.By)) < paillier_pub_n_size * 2 - EPSILON_BYTES) { LOG_ERROR("Proof By too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.By)), paillier_pub_n_size * 2 - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_bin2bn(ptr, ring_pedersen_n_size, proof.E)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += ring_pedersen_n_size; if (static_cast(BN_num_bytes(proof.E)) < ring_pedersen_n_size - EPSILON_BYTES) { LOG_ERROR("Proof E too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.E)), ring_pedersen_n_size - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_bin2bn(ptr, ring_pedersen_n_size, proof.F)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += ring_pedersen_n_size; if (static_cast(BN_num_bytes(proof.F)) < ring_pedersen_n_size - EPSILON_BYTES) { LOG_ERROR("Proof F too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.F)), ring_pedersen_n_size - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_bin2bn(ptr, ring_pedersen_n_size, proof.S)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += ring_pedersen_n_size; if (static_cast(BN_num_bytes(proof.S)) < ring_pedersen_n_size - EPSILON_BYTES) { LOG_ERROR("Proof S too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.S)), ring_pedersen_n_size - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_bin2bn(ptr, ring_pedersen_n_size, proof.T)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += ring_pedersen_n_size; if (static_cast(BN_num_bytes(proof.T)) < ring_pedersen_n_size - EPSILON_BYTES) { LOG_ERROR("Proof T too small. Expected to be at %u >= %u", static_cast(BN_num_bytes(proof.T)), ring_pedersen_n_size - EPSILON_BYTES); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_bin2bn(ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1, proof.z1)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1; if (!BN_bin2bn(ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + 1, proof.z2)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + 1; if (!BN_bin2bn(ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1, proof.z3)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1; if (!BN_bin2bn(ptr, MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1, proof.z4)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += MTA_ZKP_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + BN_num_bytes(ring_pedersen->n) + 1; if (!BN_bin2bn(ptr, paillier_pub_n_size, proof.w)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } ptr += paillier_pub_n_size; - + if (!BN_bin2bn(ptr, paillier_priv_n_size, proof.wy)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } buff.clear(); } -static std::vector mta_range_generate_zkp(const elliptic_curve256_algebra_ctx_t* algebra, const ring_pedersen_public_t* ring_pedersen, const paillier_private_key_t* private_key, const paillier_public_key_t* public_key, - const std::vector& aad, const BIGNUM* x, const BIGNUM* y, const BIGNUM* mta_request, const BIGNUM* mta_response_r, const paillier_ciphertext_t* commitment, const cmp_mta_message& response) +static std::vector mta_range_generate_zkp(const elliptic_curve256_algebra_ctx_t* algebra, + const ring_pedersen_public_t* ring_pedersen, + const paillier_private_key_t* private_key, + const paillier_public_key_t* public_key, + const std::vector& aad, + const BIGNUM* x, + const BIGNUM* y, + const BIGNUM* mta_request, + const BIGNUM* mta_response_r, + const paillier_ciphertext_t* commitment, + const uint32_t version, + const cmp_mta_message& response) { std::unique_ptr ctx(BN_CTX_new(), BN_CTX_free); if (!ctx) - throw cosigner_exception(cosigner_exception::NO_MEM); - + throw_cosigner_exception(cosigner_exception::NO_MEM); + if (is_coprime_fast(mta_response_r, public_key->n, ctx.get()) != 1) { LOG_ERROR("mta response r is not coprime to verifier paillier public key"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(commitment->r, private_key->pub.n, ctx.get()) != 1) { LOG_ERROR("commitment r is not coprime to prover paillier public key"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } bn_ctx_frame ctx_guard(ctx.get()); @@ -358,9 +433,9 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb BIGNUM* nu = BN_CTX_get(ctx.get()); BIGNUM* e = BN_CTX_get(ctx.get()); BIGNUM* tmp = BN_CTX_get(ctx.get()); - + if (!alpha || !beta|| !r || !ry || !gamma || !delta || !mu || !nu || !e || !tmp) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); mta_range_zkp proof(ctx.get()); @@ -369,25 +444,25 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb if (!BN_set_bit(tmp, (sizeof(elliptic_curve256_scalar_t) + MTA_ZKP_EPSILON_SIZE) * 8) || !BN_rand_range(alpha, tmp)) { LOG_ERROR("Failed to rand alpha error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_set_bit(tmp, BN_num_bits(y) + MTA_ZKP_EPSILON_SIZE * 8) || !BN_rand_range(beta, tmp)) { LOG_ERROR("Failed to rand beta error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_lshift(tmp, ring_pedersen->n, sizeof(elliptic_curve256_scalar_t) * 8) || !BN_rand_range(mu, tmp) || !BN_rand_range(nu, tmp)) { LOG_ERROR("Failed to rand mu error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_lshift(tmp, tmp, MTA_ZKP_EPSILON_SIZE * 8) || !BN_rand_range(gamma, tmp) || !BN_rand_range(delta, tmp)) { LOG_ERROR("Failed to rand gamma error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } long paillier_status; @@ -396,21 +471,21 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb if (!BN_rand_range(r, public_key->n)) { LOG_ERROR("Failed to rand r error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } paillier_status = paillier_encrypt_openssl_internal(public_key, proof.A, r, beta, ctx.get()); } while (paillier_status == PAILLIER_ERROR_INVALID_RANDOMNESS); - + if (paillier_status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt beta error %ld", paillier_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp(tmp, mta_request, alpha, public_key->n2, ctx.get()) || !BN_mod_mul(proof.A, proof.A, tmp, public_key->n2, ctx.get())) { LOG_ERROR("Failed to calc A error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } do @@ -418,66 +493,77 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb if (!BN_rand_range(ry, public_key->n)) { LOG_ERROR("Failed to rand ry error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } paillier_status = paillier_encrypt_openssl_internal(&private_key->pub, proof.By, ry, beta, ctx.get()); } while (paillier_status == PAILLIER_ERROR_INVALID_RANDOMNESS); - + if (paillier_status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt beta error %ld", paillier_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } auto rp_status = ring_pedersen_create_commitment_internal(ring_pedersen, alpha, gamma, proof.E, ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { LOG_ERROR("Failed to create alpha commitment error %d", rp_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } rp_status = ring_pedersen_create_commitment_internal(ring_pedersen, x, mu, proof.S, ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { LOG_ERROR("Failed to create x commitment error %d", rp_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } rp_status = ring_pedersen_create_commitment_internal(ring_pedersen, beta, delta, proof.F, ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { LOG_ERROR("Failed to create beta commitment error %d", rp_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } rp_status = ring_pedersen_create_commitment_internal(ring_pedersen, y, nu, proof.T, ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { LOG_ERROR("Failed to create y commitment error %d", rp_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + elliptic_curve256_scalar_t alpha_bin; if (!BN_mod(tmp, alpha, q, ctx.get())) { LOG_ERROR("Failed to to alpha mod q error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } + + if (BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)) <= 0) { + LOG_ERROR("Failed to serialize alpha"); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - - BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)); auto status = algebra->generator_mul(algebra, &proof.Bx, &alpha_bin); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc Bx error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } // sample e uint8_t seed[SHA256_DIGEST_LENGTH]; - genarate_mta_range_zkp_seed(response, proof, aad, seed); + if (version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) + { + generate_mta_range_zkp_extended_seed(response, proof, aad, ring_pedersen, &private_key->pub, public_key, seed); + } + else + { + generate_mta_range_zkp_seed(response, proof, aad, seed); + } + drng_t* rng = NULL; if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) { LOG_ERROR("Failed to create drng"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } std::unique_ptr drng_guard(rng, drng_free); @@ -488,7 +574,7 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb if (!BN_bin2bn(val, sizeof(elliptic_curve256_scalar_t), e)) { LOG_ERROR("Failed to load e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } } while (BN_cmp(e, q) >= 0); drng_guard.reset(); @@ -496,43 +582,43 @@ static std::vector mta_range_generate_zkp(const elliptic_curve256_algeb if (!BN_mul(proof.z1, e, x, ctx.get()) || !BN_add(proof.z1, proof.z1, alpha)) { LOG_ERROR("Failed to calc z1, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul(proof.z2, e, y, ctx.get()) || !BN_add(proof.z2, proof.z2, beta)) { LOG_ERROR("Failed to calc z2, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul(proof.z3, e, mu, ctx.get()) || !BN_add(proof.z3, proof.z3, gamma)) { LOG_ERROR("Failed to calc z3, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul(proof.z4, e, nu, ctx.get()) || !BN_add(proof.z4, proof.z4, delta)) { LOG_ERROR("Failed to calc z4, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp(proof.w, mta_response_r, e, public_key->n, ctx.get()) || !BN_mod_mul(proof.w, proof.w, r, public_key->n, ctx.get())) { LOG_ERROR("Failed to calc w, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp(proof.wy, commitment->r, e, private_key->pub.n, ctx.get()) || !BN_mod_mul(proof.wy, proof.wy, ry, private_key->pub.n, ctx.get())) { LOG_ERROR("Failed to calc wy, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } return serialize_mta_range_zkp(proof, ring_pedersen, private_key, public_key); } //implements phase 1 of ECDSA signing -//Since the cmp_mta_message has a common part for all parties and a specific part for each party -//this function prefills the common part and creates a map of proofs to feel the remaining part for each party individually. -cmp_mta_message request(const uint64_t my_id, - const elliptic_curve256_algebra_ctx_t* algebra, +//Since the cmp_mta_message has a common part for all parties and a specific part for each party +//this function prefills the common part and creates a map of proofs to fill the remaining part for each party individually. +cmp_mta_message request(const uint64_t my_id, + const elliptic_curve256_algebra_ctx_t* algebra, const elliptic_curve_scalar& k, //signing secret (randomness), saved on ecdsa_preprocessing_data state const elliptic_curve_scalar& gamma, //signing secret (required for MtA), saved on ecdsa_preprocessing_data state const elliptic_curve_scalar& a, //secret used for Rddh proof, saved on ecdsa_preprocessing_data state @@ -552,78 +638,97 @@ cmp_mta_message request(const uint64_t my_id, if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt k status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + paillier_ciphertext_t *commitment = NULL; status = paillier_encrypt_to_ciphertext(paillier.get(), gamma.data, sizeof(elliptic_curve256_scalar_t), &commitment); std::unique_ptr commitment_guard(commitment, paillier_free_ciphertext); if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt gamma status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + for (auto i = players.begin(); i != players.end(); ++i) { if (i->first == my_id) continue; uint32_t len = 0; - range_proof_diffie_hellman_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &k.data, &a.data, &b.data, ciphertext, NULL, 0, &len); + range_proof_diffie_hellman_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &k.data, &a.data, &b.data, ciphertext, /*use_extended_seed=*/0, NULL, 0, &len); auto& proof = proofs[i->first]; proof.resize(len); - auto status = range_proof_diffie_hellman_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &k.data, &a.data, &b.data, ciphertext, proof.data(), proof.size(), &len); + auto status = range_proof_diffie_hellman_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &k.data, &a.data, &b.data, ciphertext, /*use_extended_seed=*/0, proof.data(), proof.size(), &len); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to generate rddh zkp status: %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } len = 0; - range_proof_paillier_exponent_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &gamma.data, commitment, NULL, 0, &len); + range_proof_paillier_exponent_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &gamma.data, commitment, /*use_extended_seed=*/0, NULL, 0, &len); auto& G_proof = G_proofs[i->first]; G_proof.resize(len); - status = range_proof_paillier_exponent_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &gamma.data, commitment, G_proof.data(), G_proof.size(), &len); + status = range_proof_paillier_exponent_zkpok_generate(i->second.ring_pedersen.get(), paillier.get(), algebra, aad.data(), aad.size(), &gamma.data, commitment, /*use_extended_seed=*/0, G_proof.data(), G_proof.size(), &len); if (status != ZKP_SUCCESS) { LOG_ERROR("Failed to generate log zkp status: %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } - mta.message.resize(BN_num_bytes(ciphertext->ciphertext)); - BN_bn2bin(ciphertext->ciphertext, mta.message.data()); + mta.message.resize(BN_num_bytes(paillier->n2)); + assert(ciphertext->cipher_size == mta.message.size()); + if (BN_bn2binpad(ciphertext->ciphertext, mta.message.data(), mta.message.size()) <= 0) + { + LOG_ERROR("Failed to encode mta message"); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } ciphertext_guard.reset(); - mta.commitment.resize(BN_num_bytes(commitment->ciphertext)); - BN_bn2bin(commitment->ciphertext, mta.commitment.data()); + mta.commitment.resize(BN_num_bytes(paillier->n2)); + assert(commitment->cipher_size == mta.commitment.size()); + if (BN_bn2binpad(commitment->ciphertext, mta.commitment.data(), mta.commitment.size()) <= 0) + { + LOG_ERROR("Failed to encode mta commitment"); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } return mta; } -elliptic_curve_scalar answer_mta_request(const elliptic_curve256_algebra_ctx_t* algebra, const cmp_mta_message& request, const uint8_t* secret, uint32_t secret_size, const byte_vector_t& aad, - const std::shared_ptr& my_key, const std::shared_ptr& paillier, const std::shared_ptr& ring_pedersen, cmp_mta_message& response) +elliptic_curve_scalar answer_mta_request(const elliptic_curve256_algebra_ctx_t* algebra, + const cmp_mta_message& request, + const uint8_t* secret, + const uint32_t secret_size, + const byte_vector_t& aad, + const std::shared_ptr& my_key, + const std::shared_ptr& paillier, + const std::shared_ptr& ring_pedersen, + const uint32_t version, + cmp_mta_message& response) { if (!secret || !secret_size || !my_key || !paillier || !ring_pedersen) - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); byte_vector_t beta(secret_size * BETA_HIDING_FACTOR); + utils::byte_vector_cleaner beta_cleaner(beta); response.message.resize(BN_num_bytes(paillier->n2)); if (RAND_bytes(beta.data(), secret_size * BETA_HIDING_FACTOR) != 1) { LOG_ERROR("Failed to rand beta error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } uint32_t len = 0; auto status = paillier_mul(paillier.get(), request.message.data(), request.message.size(), secret, secret_size, response.message.data(), response.message.size(), &len); if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to mul ciphertext status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + paillier_ciphertext_t* ciphertext = NULL; status = paillier_encrypt_to_ciphertext(paillier.get(), beta.data(), beta.size(), &ciphertext); if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt beta status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } std::unique_ptr ciphertext_guard(ciphertext, paillier_free_ciphertext); byte_vector_t tmp(BN_num_bytes(ciphertext->ciphertext)); @@ -632,7 +737,7 @@ elliptic_curve_scalar answer_mta_request(const elliptic_curve256_algebra_ctx_t* if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to add beta from ciphertext status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } response.message.resize(len); @@ -641,30 +746,46 @@ elliptic_curve_scalar answer_mta_request(const elliptic_curve256_algebra_ctx_t* if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt commitment status: %ld", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } std::unique_ptr commitment_guard(commitment, paillier_free_ciphertext); response.commitment.resize(BN_num_bytes(commitment->ciphertext)); BN_bn2bin(commitment->ciphertext, response.commitment.data()); - + std::unique_ptr x(BN_bin2bn(secret, secret_size, NULL), BN_clear_free); std::unique_ptr y(BN_bin2bn(beta.data(), beta.size(), NULL), BN_clear_free); std::unique_ptr req(BN_bin2bn(request.message.data(), request.message.size(), NULL), BN_free); if (!x || !y || !req) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); + + response.proof = mta_range_generate_zkp(algebra, + ring_pedersen.get(), + my_key.get(), + paillier.get(), + aad, + x.get(), + y.get(), + req.get(), + ciphertext->r, + commitment, + version, + response); - response.proof = mta_range_generate_zkp(algebra, ring_pedersen.get(), my_key.get(), paillier.get(), aad, x.get(), y.get(), req.get(), ciphertext->r, commitment, response); const BIGNUM* q = algebra->order_internal(algebra); std::unique_ptr ctx(BN_CTX_new(), BN_CTX_free); if (!ctx) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); if (!BN_mod(y.get(), y.get(), q, ctx.get())) { LOG_ERROR("Failed to calc beta error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } elliptic_curve_scalar ret; - BN_bn2binpad(y.get(), ret.data, sizeof(elliptic_curve256_scalar_t)); + if (BN_bn2binpad(y.get(), ret.data, sizeof(elliptic_curve256_scalar_t)) <= 0) + { + LOG_ERROR("Failed to serialize y"); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } return ret; } @@ -673,25 +794,25 @@ elliptic_curve_scalar decrypt_mta_response(uint64_t other_id, const elliptic_cur std::unique_ptr ctx(BN_CTX_new(), BN_CTX_free); if (!ctx) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); bn_ctx_frame ctx_guard(ctx.get()); BIGNUM* resp = BN_CTX_get(ctx.get()); BIGNUM* tmp = BN_CTX_get(ctx.get()); std::unique_ptr alpha(BN_new(), BN_clear_free); if (!resp || !alpha || !tmp || !BN_bin2bn(response.data(), response.size(), resp)) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); response.clear(); - + auto status = paillier_decrypt_openssl_internal(my_key.get(), resp, alpha.get(), ctx.get()); if (status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to decrypt mta response from player %" PRIu64 ", error %ld", other_id, status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_rshift1(tmp, my_key->pub.n)) { LOG_ERROR("shift right failed error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } const BIGNUM* q = algebra->order_internal(algebra); if (BN_cmp(alpha.get(), tmp) > 0) @@ -699,52 +820,58 @@ elliptic_curve_scalar decrypt_mta_response(uint64_t other_id, const elliptic_cur if (!BN_mod_sub(alpha.get(), alpha.get(), my_key->pub.n, q, ctx.get())) { LOG_ERROR("Failed to calc alpha minus n error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } else if (!BN_mod(alpha.get(), alpha.get(), q, ctx.get())) { LOG_ERROR("Failed to calc alpha error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } ctx_guard.reset(); ctx.reset(); elliptic_curve_scalar ret; - BN_bn2binpad(alpha.get(), ret.data, sizeof(elliptic_curve256_scalar_t)); + if (BN_bn2binpad(alpha.get(), ret.data, sizeof(elliptic_curve256_scalar_t)) <= 0) + { + LOG_ERROR("Failed to serialize alpha"); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); + } return ret; } -base_response_verifier::base_response_verifier(const uint64_t other_id, - const elliptic_curve256_algebra_ctx_t* algebra, - const byte_vector_t& aad, - const std::shared_ptr& my_key, - const std::shared_ptr& paillier, - const std::shared_ptr& ring_pedersen) : - _other_id(other_id), - _algebra(algebra), - _aad(aad), - _my_paillier(my_key), - _my_ring_pedersen(ring_pedersen), +base_response_verifier::base_response_verifier(const uint32_t version, + const uint64_t other_id, + const elliptic_curve256_algebra_ctx_t* algebra, + const byte_vector_t& aad, + const std::shared_ptr& my_key, + const std::shared_ptr& paillier, + const std::shared_ptr& ring_pedersen) : + _version(version), + _other_id(other_id), + _algebra(algebra), + _aad(aad), + _my_paillier(my_key), + _my_ring_pedersen(ring_pedersen), _other_paillier(paillier), - _ctx(BN_CTX_new(), BN_CTX_free), - _my_mont(BN_MONT_CTX_new(), BN_MONT_CTX_free), + _ctx(BN_CTX_new(), BN_CTX_free), + _my_mont(BN_MONT_CTX_new(), BN_MONT_CTX_free), _other_mont(BN_MONT_CTX_new(), BN_MONT_CTX_free) { if (!_ctx || !_my_mont || !_other_mont) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); BN_CTX_start(_ctx.get()); if (!BN_MONT_CTX_set(_my_mont.get(), _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to init montgomery context, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } if (!BN_MONT_CTX_set(_other_mont.get(), _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to init montgomery context, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } } @@ -757,13 +884,14 @@ base_response_verifier::~base_response_verifier() } batch_response_verifier::batch_response_verifier( - uint64_t other_id, - const elliptic_curve256_algebra_ctx_t* algebra, - const byte_vector_t& aad, - const std::shared_ptr& my_key, - const std::shared_ptr& paillier, + const uint32_t version, + const uint64_t other_id, + const elliptic_curve256_algebra_ctx_t* algebra, + const byte_vector_t& aad, + const std::shared_ptr& my_key, + const std::shared_ptr& paillier, const std::shared_ptr& ring_pedersen) : - base_response_verifier(other_id, algebra, aad, my_key, paillier, ring_pedersen) + base_response_verifier(version, other_id, algebra, aad, my_key, paillier, ring_pedersen) { for (size_t i = 0; i < BATCH_STATISTICAL_SECURITY; i++) @@ -776,13 +904,29 @@ batch_response_verifier::batch_response_verifier( if (!_mta_ro[i] || !_mta_B[i] || !_commitment_ro[i] || !_commitment_B[i]) { LOG_ERROR("Failed to alloc batch bignums"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } - BN_one(_mta_ro[i]); - BN_one(_mta_B[i]); - BN_one(_commitment_ro[i]); - BN_one(_commitment_B[i]); + if (!BN_one(_mta_ro[i])) + { + LOG_ERROR("Failed to set BN_one for _mta_ro"); + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + if (!BN_one(_mta_B[i])) + { + LOG_ERROR("Failed to set BN_one for _mta_B"); + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + if (!BN_one(_commitment_ro[i])) + { + LOG_ERROR("Failed to set BN_one for _commitment_ro"); + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + if (!BN_one(_commitment_B[i])) + { + LOG_ERROR("Failed to set BN_one for _commitment_B"); + throw_cosigner_exception(cosigner_exception::NO_MEM); + } } _pedersen_t_exp = BN_CTX_get(_ctx.get()); @@ -791,15 +935,19 @@ batch_response_verifier::batch_response_verifier( if (!_pedersen_t_exp || !_pedersen_B) { LOG_ERROR("Failed to alloc pedersen bignums"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); + } + if (!BN_one(_pedersen_B)) + { + LOG_ERROR("Failed to set BN_one for _pedersen_B"); + throw_cosigner_exception(cosigner_exception::NO_MEM); } - BN_one(_pedersen_B); } void batch_response_verifier::process( const byte_vector_t& request, //this is mta_request from ecdsa_preprocessing_data, K sent by the other party - cmp_mta_message& response, + cmp_mta_message& response, const elliptic_curve_point& public_point) { bn_ctx_frame frame_guard(_ctx.get()); @@ -808,17 +956,17 @@ void batch_response_verifier::process( BIGNUM* mta_response = BN_CTX_get(_ctx.get()); BIGNUM* commitment = BN_CTX_get(_ctx.get()); if (!mta_request || !mta_response || !commitment || - !BN_bin2bn(request.data(), request.size(), mta_request) || - !BN_bin2bn(response.message.data(), response.message.size(), mta_response) || + !BN_bin2bn(request.data(), request.size(), mta_request) || + !BN_bin2bn(response.message.data(), response.message.size(), mta_response) || !BN_bin2bn(response.commitment.data(), response.commitment.size(), commitment)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } BIGNUM* e = BN_CTX_get(_ctx.get()); - + if (!e) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); mta_range_zkp proof(_ctx.get()); deserialize_mta_range_zkp(response.proof, &_my_ring_pedersen->pub, _my_paillier.get(), _other_paillier.get(), proof); response.proof.clear(); @@ -827,24 +975,31 @@ void batch_response_verifier::process( if ((size_t)BN_num_bytes(proof.z1) > sizeof(elliptic_curve256_scalar_t) + MTA_ZKP_EPSILON_SIZE) { LOG_ERROR("player %" PRIu64 " z1 (%d bits) is out of range", _other_id, BN_num_bits(proof.z1)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if ((size_t)BN_num_bytes(proof.z2) > sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + MTA_ZKP_EPSILON_SIZE) { LOG_ERROR("player %" PRIu64 " z2 (%d bits) is out of range", _other_id, BN_num_bits(proof.z2)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } // sample e uint8_t seed[SHA256_DIGEST_LENGTH]; - genarate_mta_range_zkp_seed(response, proof, _aad, seed); + if (_version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) + { + generate_mta_range_zkp_extended_seed(response, proof, _aad, &_my_ring_pedersen->pub, _other_paillier.get(), &_my_paillier->pub, seed); + } + else + { + generate_mta_range_zkp_seed(response, proof, _aad, seed); + } response.commitment.clear(); drng_t* rng = NULL; if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) { LOG_ERROR("Failed to create drng"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } std::unique_ptr drng_guard(rng, drng_free); @@ -856,7 +1011,7 @@ void batch_response_verifier::process( if (!BN_bin2bn(val, sizeof(elliptic_curve256_scalar_t), e)) { LOG_ERROR("Failed to load e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } } while (BN_cmp(e, q) >= 0); drng_guard.reset(); @@ -869,26 +1024,26 @@ void batch_response_verifier::process( if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc g^z1, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } auto status = _algebra->point_mul(_algebra, &p2, &public_point.data, &val); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc X^e, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } status = _algebra->add_points(_algebra, &p2, &proof.Bx, &p2); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc Bx*X^e, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (memcmp(p1, p2, sizeof(elliptic_curve256_point_t)) != 0) { LOG_ERROR("Failed to verify Bx*X^e == g^z1 for player %" PRIu64, _other_id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } process_paillier(e, mta_request, mta_response, commitment, proof); process_ring_pedersen(e, proof); @@ -899,9 +1054,9 @@ void batch_response_verifier::verify() bn_ctx_frame frame_guard(_ctx.get()); BIGNUM* tmp = BN_CTX_get(_ctx.get()); - + if (!tmp) - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); // verify paillier for (size_t i = 0; i < BATCH_STATISTICAL_SECURITY; i++) @@ -909,38 +1064,38 @@ void batch_response_verifier::verify() if (!BN_mod_exp_mont(tmp, _mta_ro[i], _my_paillier->pub.n, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc ro^N, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (BN_cmp(tmp, _mta_B[i]) != 0) { LOG_ERROR("Failed to verify mta ro^N == B for player %" PRIu64, _other_id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (!BN_mod_exp_mont(tmp, _commitment_ro[i], _other_paillier->n, _other_paillier->n2, _ctx.get(), _other_mont.get())) { LOG_ERROR("Failed to calc ro^N, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (BN_cmp(tmp, _commitment_B[i]) != 0) { LOG_ERROR("Failed to verify commitment ro^N == B for player %" PRIu64, _other_id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } // verify ring pedersen - if (!BN_mod(_pedersen_t_exp, _pedersen_t_exp, _my_ring_pedersen->phi_n, _ctx.get()) || + if (!BN_mod(_pedersen_t_exp, _pedersen_t_exp, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_exp_mont(_pedersen_t_exp, _my_ring_pedersen->pub.t, _pedersen_t_exp, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { LOG_ERROR("Failed to calc t^exp_t, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (BN_cmp(_pedersen_t_exp, _pedersen_B) != 0) { LOG_ERROR("Failed to verify commitment t^exp_t == B for player %" PRIu64, _other_id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } @@ -952,93 +1107,93 @@ void batch_response_verifier::process_paillier(const BIGNUM* e, const BIGNUM* re BIGNUM* tmp2 = BN_CTX_get(_ctx.get()); BIGNUM* B = BN_CTX_get(_ctx.get()); BIGNUM* gamma = BN_CTX_get(_ctx.get()); - + if (!tmp1 || !tmp2 || !B || !gamma) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } if (is_coprime_fast(response, _my_paillier->pub.n, _ctx.get()) != 1) { LOG_ERROR("response is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(proof.A, _my_paillier->pub.n, _ctx.get()) != 1) { LOG_ERROR("proof A is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(commitment, _other_paillier->n, _ctx.get()) != 1) { LOG_ERROR("commitment is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(proof.By, _other_paillier->n, _ctx.get()) != 1) { LOG_ERROR("proof By is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } uint8_t random[2 * BATCH_STATISTICAL_SECURITY]; - if (!RAND_bytes(random, 2 * BATCH_STATISTICAL_SECURITY * sizeof(uint8_t))) + if (RAND_bytes(random, 2 * BATCH_STATISTICAL_SECURITY * sizeof(uint8_t)) != 1) { LOG_ERROR("Failed to get random number, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + // first mta verification if (!BN_mod_inverse(tmp1, request, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc C^-1, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp2_mont(tmp1, tmp1, proof.z1, response, e, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc C^-z1*D^e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + if (!BN_mul(tmp2, _my_paillier->pub.n, proof.z2, _ctx.get()) || !BN_sub(tmp2, _my_paillier->pub.n2, tmp2) || !BN_add_word(tmp2, 1)) { LOG_ERROR("Failed to calc 1-N*z2, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(tmp1, tmp1, proof.A, _my_paillier->pub.n2, _ctx.get()) || !BN_mod_mul(B, tmp1, tmp2, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc D^e*C^-z1*A*(1-N*z2), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + for (size_t i = 0; i < BATCH_STATISTICAL_SECURITY; i++) { if (!BN_set_word(gamma, random[i * 2])) { LOG_ERROR("Failed to set random number, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } if (!BN_mod_exp_mont(tmp1, B, gamma, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc B, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_mta_B[i], _mta_B[i], tmp1, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc B product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp_mont(tmp2, proof.w, gamma, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc ro, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_mta_ro[i], _mta_ro[i], tmp2, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc ro product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } @@ -1046,17 +1201,17 @@ void batch_response_verifier::process_paillier(const BIGNUM* e, const BIGNUM* re if (!BN_mod_exp_mont(tmp1, commitment, e, _other_paillier->n2, _ctx.get(), _other_mont.get()) || !BN_mod_mul(tmp1, tmp1, proof.By, _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to calc Y^e*By, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul(tmp2, _other_paillier->n, proof.z2, _ctx.get()) || !BN_sub(tmp2, _other_paillier->n2, tmp2) || !BN_add_word(tmp2, 1)) { LOG_ERROR("Failed to calc 1-N*z2, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(B, tmp1, tmp2, _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to calc Y^e*By*(1-N*z2), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } for (size_t i = 0; i < BATCH_STATISTICAL_SECURITY; i++) @@ -1064,28 +1219,28 @@ void batch_response_verifier::process_paillier(const BIGNUM* e, const BIGNUM* re if (!BN_set_word(gamma, random[i * 2 + 1])) { LOG_ERROR("Failed to set random number, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } if (!BN_mod_exp_mont(tmp1, B, gamma, _other_paillier->n2, _ctx.get(), _other_mont.get())) { LOG_ERROR("Failed to calc B, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_commitment_B[i], _commitment_B[i], tmp1, _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to calc B product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp_mont(tmp2, proof.wy, gamma, _other_paillier->n2, _ctx.get(), _other_mont.get())) { LOG_ERROR("Failed to calc ro, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_commitment_ro[i], _commitment_ro[i], tmp2, _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to calc ro product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } } @@ -1096,93 +1251,93 @@ void batch_response_verifier::process_ring_pedersen(const BIGNUM* e, const mta_r BIGNUM* tmp1 = BN_CTX_get(_ctx.get()); BIGNUM* tmp2 = BN_CTX_get(_ctx.get()); - uint8_t gamma[2 * sizeof(uint64_t)]; + uint64_t gamma[2]; if (!tmp1 || !tmp2) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } - if (!RAND_bytes(gamma, 2 * sizeof(uint64_t))) + if (RAND_bytes(reinterpret_cast(&gamma[0]), 2 * sizeof(uint64_t)) != 1) { LOG_ERROR("Failed to get random number, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - gamma[0] &= 0xffffffffff; // 40bits - gamma[1] &= 0xffffffffff; // 40bits + gamma[0] &= 0xffffffffffULL; // 40bits + gamma[1] &= 0xffffffffffULL; // 40bits auto rp_status = ring_pedersen_init_montgomery(&_my_ring_pedersen->pub, _ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { - LOG_ERROR("Failed to init ring pedersen motgomery context, error %d", rp_status); - throw cosigner_exception(cosigner_exception::NO_MEM); + LOG_ERROR("Failed to init ring pedersen montgomery context, error %d", rp_status); + throw_cosigner_exception(cosigner_exception::NO_MEM); } // s^z1*t^z3 == E*S^e - if (!BN_mod_mul(tmp1, _my_ring_pedersen->lamda, proof.z1, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z3, _my_ring_pedersen->phi_n, _ctx.get())) + if (!BN_mod_mul(tmp1, _my_ring_pedersen->lambda, proof.z1, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z3, _my_ring_pedersen->phi_n, _ctx.get())) { - LOG_ERROR("Failed to calc lamda*x+r, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc lambda*x+r, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul_word(tmp1, gamma[0])) { - LOG_ERROR("Failed to calc (lamda*x+r)*random64, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc (lambda*x+r)*random64, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_add(_pedersen_t_exp, _pedersen_t_exp, tmp1)) { LOG_ERROR("Failed to calc sum t exponant, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_set_word(tmp1, gamma[0]) || !BN_mod_mul(tmp2, tmp1, e, _my_ring_pedersen->phi_n, _ctx.get())) { LOG_ERROR("Failed to calc e*gamma, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp2_mont(tmp1, proof.E, tmp1, proof.S, tmp2, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { LOG_ERROR("Failed to calc E^gamma*S^(e*gamma), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_pedersen_B, _pedersen_B, tmp1, _my_ring_pedersen->pub.n, _ctx.get())) { LOG_ERROR("Failed to calc ro product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } // s^z2*t^z4 == F*T^e - if (!BN_mod_mul(tmp1, _my_ring_pedersen->lamda, proof.z2, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z4, _my_ring_pedersen->phi_n, _ctx.get())) + if (!BN_mod_mul(tmp1, _my_ring_pedersen->lambda, proof.z2, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z4, _my_ring_pedersen->phi_n, _ctx.get())) { - LOG_ERROR("Failed to calc lamda*x+r, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc lambda*x+r, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mul_word(tmp1, gamma[1])) { - LOG_ERROR("Failed to calc (lamda*x+r)*random64, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc (lambda*x+r)*random64, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_add(_pedersen_t_exp, _pedersen_t_exp, tmp1)) { LOG_ERROR("Failed to calc sum t exponant, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_set_word(tmp1, gamma[1]) || !BN_mod_mul(tmp2, tmp1, e, _my_ring_pedersen->phi_n, _ctx.get())) { LOG_ERROR("Failed to calc e*gamma, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_exp2_mont(tmp1, proof.F, tmp1, proof.T, tmp2, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { LOG_ERROR("Failed to calc E^gamma*S^(e*gamma), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } if (!BN_mod_mul(_pedersen_B, _pedersen_B, tmp1, _my_ring_pedersen->pub.n, _ctx.get())) { LOG_ERROR("Failed to calc ro product, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } @@ -1195,251 +1350,258 @@ void single_response_verifier::process_ring_pedersen(const BIGNUM* e, const mta_ if (!tmp1 || !tmp2) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } auto rp_status = ring_pedersen_init_montgomery(&_my_ring_pedersen->pub, _ctx.get()); if (rp_status != RING_PEDERSEN_SUCCESS) { - LOG_ERROR("Failed to init ring pedersen motgomery context, error %d", rp_status); - throw cosigner_exception(cosigner_exception::NO_MEM); + LOG_ERROR("Failed to init ring pedersen montgomery context, error %d", rp_status); + throw_cosigner_exception(cosigner_exception::NO_MEM); } // s^z1*t^z3 == E*S^e - // tmp1 = z1* lamda + z3 - if (!BN_mod_mul(tmp1, _my_ring_pedersen->lamda, proof.z1, _my_ring_pedersen->phi_n, _ctx.get()) || + // tmp1 = z1* lambda + z3 + if (!BN_mod_mul(tmp1, _my_ring_pedersen->lambda, proof.z1, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z3, _my_ring_pedersen->phi_n, _ctx.get())) { - LOG_ERROR("Failed to calc lamda*z1+z3, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc lambda*z1+z3, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - //tmp1 = t^(lamda * z1 + z3) + //tmp1 = t^(lambda * z1 + z3) if (!BN_mod_exp_mont(tmp1, _my_ring_pedersen->pub.t, tmp1, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { - LOG_ERROR("Failed to calc t^(lamda * z1 + z3), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc t^(lambda * z1 + z3), error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = S^e if (!BN_mod_exp_mont(tmp2, proof.S, e, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { LOG_ERROR("Failed to calc S^e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = tmp2 * E = E * S^e if (!BN_mod_mul(tmp2, tmp2, proof.E, _my_ring_pedersen->pub.n, _ctx.get())) { LOG_ERROR("Failed to calc E * S^e), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //compare tmp1 and tmp2 if (0 != BN_cmp(tmp1, tmp2)) { LOG_ERROR("s^z1*t^z3 != E * S^e)"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } // s^z2*t^z4 == F*T^e - // tmp1 = z2* lamda + z4 - if (!BN_mod_mul(tmp1, _my_ring_pedersen->lamda, proof.z2, _my_ring_pedersen->phi_n, _ctx.get()) || + // tmp1 = z2* lambda + z4 + if (!BN_mod_mul(tmp1, _my_ring_pedersen->lambda, proof.z2, _my_ring_pedersen->phi_n, _ctx.get()) || !BN_mod_add(tmp1, tmp1, proof.z4, _my_ring_pedersen->phi_n, _ctx.get())) { - LOG_ERROR("Failed to calc z2* lamda + z4, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc z2* lambda + z4, error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - //tmp1 = t^(lamda * z2 + z4) + //tmp1 = t^(lambda * z2 + z4) if (!BN_mod_exp_mont(tmp1, _my_ring_pedersen->pub.t, tmp1, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { - LOG_ERROR("Failed to calc t^(lamda * z2 + z4), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + LOG_ERROR("Failed to calc t^(lambda * z2 + z4), error %lu", ERR_get_error()); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = T^e if (!BN_mod_exp_mont(tmp2, proof.T, e, _my_ring_pedersen->pub.n, _ctx.get(), _my_ring_pedersen->pub.mont)) { LOG_ERROR("Failed to calc T^e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = tmp2 * F = F * T^e if (!BN_mod_mul(tmp2, tmp2, proof.F, _my_ring_pedersen->pub.n, _ctx.get())) { LOG_ERROR("Failed to calc F * T^e), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //compare tmp1 and tmp2 if (0 != BN_cmp(tmp1, tmp2)) { LOG_ERROR("s^z2*t^z4 != F * T^e)"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } void single_response_verifier::process_paillier( - const BIGNUM* e, + const BIGNUM* e, const BIGNUM* request, //C in the document, actually here passed encrypted K - const BIGNUM* response, //D in the document, homomorphic calculation k*(x or gamma) + beta + const BIGNUM* response, //D in the document, homomorphic calculation k*(x or gamma) + beta const BIGNUM* commitment, //Y in the document, paillier encrypted my parties beta as commitment const mta_range_zkp& proof) { bn_ctx_frame frame_guard(_ctx.get()); - + BIGNUM* tmp1 = BN_CTX_get(_ctx.get()); BIGNUM* tmp2 = BN_CTX_get(_ctx.get()); - + if (!tmp1 || !tmp2) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } if (is_coprime_fast(response, _my_paillier->pub.n, _ctx.get()) != 1) { LOG_ERROR("response is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(proof.A, _my_paillier->pub.n, _ctx.get()) != 1) { LOG_ERROR("proof A is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(commitment, _other_paillier->n, _ctx.get()) != 1) { LOG_ERROR("commitment is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if (is_coprime_fast(proof.By, _other_paillier->n, _ctx.get()) != 1) { LOG_ERROR("proof By is not a valid ciphertext"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } //============ 1st MTA verification ============ if (!BN_mod_exp_mont(tmp1, request, proof.z1, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc C^z1, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } long paillier_status = paillier_encrypt_openssl_internal(&_my_paillier->pub, tmp2, proof.w, proof.z2, _ctx.get()); if (paillier_status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt z2 with my key during verify, error %ld", paillier_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //tmp1 holds the result if (!BN_mod_mul(tmp1, tmp1, tmp2, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc C^z1 * enc(z2, w), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //tmp2 = D^e if (!BN_mod_exp_mont(tmp2, response, e, _my_paillier->pub.n2, _ctx.get(), _my_mont.get())) { LOG_ERROR("Failed to calc D^e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = tmp2 * A = A * D^e if (!BN_mod_mul(tmp2, tmp2, proof.A, _my_paillier->pub.n2, _ctx.get())) { LOG_ERROR("Failed to calc (A * D^e), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //compare tmp1 and tmp2 if (0 != BN_cmp(tmp1, tmp2)) { LOG_ERROR("Failed check C^z1 * enc(z2, w) == A * D^e"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - //============ 2nd MTA verification ============ + //============ 2nd MTA verification ============ paillier_status = paillier_encrypt_openssl_internal(_other_paillier.get(), tmp1, proof.wy, proof.z2, _ctx.get()); if (paillier_status != PAILLIER_SUCCESS) { LOG_ERROR("Failed to encrypt z2 with my peer's key during verify, error %ld", paillier_status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = Y^e if (!BN_mod_exp_mont(tmp2, commitment, e, _other_paillier->n2, _ctx.get(), _other_mont.get())) { LOG_ERROR("Failed to calc Y^e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } //tmp2 = tmp2 * By = By * Y^e if (!BN_mod_mul(tmp2, proof.By, tmp2, _other_paillier->n2, _ctx.get())) { LOG_ERROR("Failed to calc (A * D^e), error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } - + //compare tmp1 and tmp2 if (0 != BN_cmp(tmp1, tmp2)) { LOG_ERROR("Failed check enc(z2, w) == By * Y^e"); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } } void single_response_verifier::process(const byte_vector_t& request, cmp_mta_message& response, const elliptic_curve_point& public_point) { bn_ctx_frame ctx_guard(_ctx.get()); - + BIGNUM* mta_request = BN_CTX_get(_ctx.get()); if (!mta_request || !BN_bin2bn(request.data(), request.size(), mta_request)) { - throw cosigner_exception(cosigner_exception::NO_MEM); - } + throw_cosigner_exception(cosigner_exception::NO_MEM); + } BIGNUM* mta_response = BN_CTX_get(_ctx.get()); //paillier encrypted minus my beta with my key BIGNUM* commitment = BN_CTX_get(_ctx.get()); //paillier encrypted my parties beta with his key if (!mta_response || !commitment || - !BN_bin2bn(response.message.data(), response.message.size(), mta_response) || + !BN_bin2bn(response.message.data(), response.message.size(), mta_response) || !BN_bin2bn(response.commitment.data(), response.commitment.size(), commitment)) { - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } - + BIGNUM* e = BN_CTX_get(_ctx.get()); - + if (!e) - throw cosigner_exception(cosigner_exception::NO_MEM); - + throw_cosigner_exception(cosigner_exception::NO_MEM); + mta_range_zkp proof(_ctx.get()); - + deserialize_mta_range_zkp(response.proof, &_my_ring_pedersen->pub, _my_paillier.get(), _other_paillier.get(), proof); // start with range check if ((size_t)BN_num_bytes(proof.z1) > sizeof(elliptic_curve256_scalar_t) + MTA_ZKP_EPSILON_SIZE) { - LOG_ERROR("player %lu z1 (%d bits) is out of range", _other_id, BN_num_bits(proof.z1)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("player %" PRIu64 " z1 (%d bits) is out of range", _other_id, BN_num_bits(proof.z1)); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } if ((size_t)BN_num_bytes(proof.z2) > sizeof(elliptic_curve256_scalar_t) * BETA_HIDING_FACTOR + MTA_ZKP_EPSILON_SIZE) { - LOG_ERROR("player %lu z2 (%d bits) is out of range", _other_id, BN_num_bits(proof.z2)); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("player %" PRIu64 " z2 (%d bits) is out of range", _other_id, BN_num_bits(proof.z2)); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } // sample e uint8_t seed[SHA256_DIGEST_LENGTH]; - genarate_mta_range_zkp_seed(response, proof, _aad, seed); - + if (_version >= fireblocks::common::cosigner::MPC_EXTENDED_MTA) + { + generate_mta_range_zkp_extended_seed(response, proof, _aad, &_my_ring_pedersen->pub, _other_paillier.get(), &_my_paillier->pub, seed); + } + else + { + generate_mta_range_zkp_seed(response, proof, _aad, seed); + } + response.commitment.clear(); response.proof.clear(); @@ -1447,7 +1609,7 @@ void single_response_verifier::process(const byte_vector_t& request, cmp_mta_mes if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) { LOG_ERROR("Failed to create drng"); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } std::unique_ptr drng_guard(rng, drng_free); @@ -1459,13 +1621,13 @@ void single_response_verifier::process(const byte_vector_t& request, cmp_mta_mes if (!BN_bin2bn(val, sizeof(elliptic_curve256_scalar_t), e)) { LOG_ERROR("Failed to load e, error %lu", ERR_get_error()); - throw cosigner_exception(cosigner_exception::NO_MEM); + throw_cosigner_exception(cosigner_exception::NO_MEM); } } while (BN_cmp(e, q) >= 0); drng_guard.reset(); elliptic_curve256_point_t p1, p2; - + //scope for bin variable and calculate p1=q^z1 { std::vector bin(BN_num_bytes(proof.z1)); @@ -1474,7 +1636,7 @@ void single_response_verifier::process(const byte_vector_t& request, cmp_mta_mes if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc g^z1, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } } @@ -1482,31 +1644,28 @@ void single_response_verifier::process(const byte_vector_t& request, cmp_mta_mes if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc X^e, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } status = _algebra->add_points(_algebra, &p2, &proof.Bx, &p2); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { LOG_ERROR("Failed to calc Bx*X^e, error %d", status); - throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); + throw_cosigner_exception(cosigner_exception::INTERNAL_ERROR); } // verify g^z1 == Bx*X^e if (memcmp(p1, p2, sizeof(elliptic_curve256_point_t)) != 0) { - LOG_ERROR("Failed to verify Bx*X^e == g^z1 for player %lu", _other_id); - throw cosigner_exception(cosigner_exception::INVALID_PARAMETERS); + LOG_ERROR("Failed to verify Bx*X^e == g^z1 for player %" PRIu64 "", _other_id); + throw_cosigner_exception(cosigner_exception::INVALID_PARAMETERS); } - process_paillier(e, - mta_request, - mta_response, - commitment, + process_paillier(e, + mta_request, + mta_response, + commitment, proof); process_ring_pedersen(e, proof); } -} -} -} -} +} //fireblocks::common::cosigner::mta diff --git a/src/common/cosigner/mta.h b/src/common/cosigner/mta.h index 5ef5f8d..716f099 100644 --- a/src/common/cosigner/mta.h +++ b/src/common/cosigner/mta.h @@ -39,11 +39,12 @@ cmp_mta_message request(const uint64_t my_id, elliptic_curve_scalar answer_mta_request(const elliptic_curve256_algebra_ctx_t* algebra, const cmp_mta_message& request, const uint8_t* secret, - uint32_t secret_size, + const uint32_t secret_size, const byte_vector_t& aad, const std::shared_ptr& my_key, const std::shared_ptr& paillier, const std::shared_ptr& ring_pedersen, + const uint32_t version, cmp_mta_message& response); elliptic_curve_scalar decrypt_mta_response(uint64_t other_id, @@ -56,7 +57,8 @@ struct mta_range_zkp; class base_response_verifier { public: - base_response_verifier(const uint64_t other_id, + base_response_verifier(const uint32_t version, + const uint64_t other_id, const elliptic_curve256_algebra_ctx_t* algebra, const byte_vector_t& aad, const std::shared_ptr& my_key, @@ -67,6 +69,7 @@ class base_response_verifier protected: + const uint32_t _version; const uint64_t _other_id; const elliptic_curve256_algebra_ctx_t* _algebra; const byte_vector_t _aad; @@ -90,7 +93,8 @@ class base_response_verifier class batch_response_verifier : public base_response_verifier { public: - batch_response_verifier(const uint64_t other_id, + batch_response_verifier(const uint32_t version, + const uint64_t other_id, const elliptic_curve256_algebra_ctx_t* algebra, const byte_vector_t& aad, const std::shared_ptr& my_key, @@ -153,6 +157,7 @@ class single_response_verifier : public base_response_verifier }; static inline std::unique_ptr new_response_verifier( + const uint32_t version, const size_t num_of_blocks, const uint64_t other_id, const elliptic_curve256_algebra_ctx_t* algebra, @@ -164,11 +169,11 @@ static inline std::unique_ptr new_response_verifier( { if (num_of_blocks >= min_batch_threshold) { - return std::unique_ptr(new batch_response_verifier(other_id, algebra, aad, my_key, paillier, ring_pedersen)); + return std::unique_ptr(new batch_response_verifier(version, other_id, algebra, aad, my_key, paillier, ring_pedersen)); } else { - return std::unique_ptr(new single_response_verifier(other_id, algebra, aad, my_key, paillier, ring_pedersen)); + return std::unique_ptr(new single_response_verifier(version, other_id, algebra, aad, my_key, paillier, ring_pedersen)); } } diff --git a/src/common/cosigner/timing_map.cpp b/src/common/cosigner/timing_map.cpp index 7fcc1a5..a93c4f1 100644 --- a/src/common/cosigner/timing_map.cpp +++ b/src/common/cosigner/timing_map.cpp @@ -16,7 +16,7 @@ void TimingMap::insert(const std::string& key) { std::unique_lock guard(_lock); #ifdef DEBUG - // Unit tests may call some phase handlers more then once to test replays + // Unit tests may call some phase handlers more than once to test replays if (_data.find(key) == _data.end()) #endif _data[key] = _time_service.now_msec(); diff --git a/src/common/cosigner/utils.cpp b/src/common/cosigner/utils.cpp index e9b220b..6a5e099 100644 --- a/src/common/cosigner/utils.cpp +++ b/src/common/cosigner/utils.cpp @@ -17,7 +17,7 @@ void verify_tenant_id(const platform_service& service, const cmp_key_persistency if (tenant_id.compare(key_persistency.get_tenantid_from_keyid(key_id)) != 0) { LOG_ERROR("key id %s is not part of tenant %s", key_id.c_str(), tenant_id.c_str()); - throw cosigner_exception(cosigner_exception::UNAUTHORIZED); + throw_cosigner_exception(cosigner_exception::UNAUTHORIZED); } } diff --git a/src/common/cosigner/utils.h b/src/common/cosigner/utils.h index 7727af8..220af34 100644 --- a/src/common/cosigner/utils.h +++ b/src/common/cosigner/utils.h @@ -14,6 +14,24 @@ class cmp_key_persistency; void verify_tenant_id(const platform_service& service, const cmp_key_persistency& key_persistency, const std::string& key_id); +template +std::string HexStr(const T itbegin, const T itend) +{ + std::string rv; + static const char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + rv.reserve((itend-itbegin)*3); + for(T it = itbegin; it < itend; ++it) + { + unsigned char val = (unsigned char)(*it); + rv.push_back(hexmap[val>>4]); + rv.push_back(hexmap[val&15]); + } + + return rv; +} + + } } } \ No newline at end of file diff --git a/src/common/crypto/GFp_curve_algebra/GFp_curve_algebra.c b/src/common/crypto/GFp_curve_algebra/GFp_curve_algebra.c index 8d2ad20..fdb91cb 100644 --- a/src/common/crypto/GFp_curve_algebra/GFp_curve_algebra.c +++ b/src/common/crypto/GFp_curve_algebra/GFp_curve_algebra.c @@ -877,7 +877,7 @@ elliptic_curve_algebra_status GFp_curve_algebra_verify_signature(const GFp_curve cmp = BN_cmp(u1, order); - if (cmp >= 0 && !BN_usub(u1, order, u1)) + if (cmp >= 0 && !BN_sub(u1, u1, order)) goto cleanup; /* if the signature is correct u1 is equal to sig_r */ @@ -922,6 +922,61 @@ static const elliptic_curve256_point_t *infinity_point(const struct elliptic_cur return &INFINITY; } +static elliptic_curve_algebra_status validate_non_infinity_point(const struct elliptic_curve256_algebra_ctx *ctx, const elliptic_curve256_point_t *p) +{ + GFp_curve_algebra_ctx_t* ctx_ = NULL; + BN_CTX* bn_ctx = NULL; + EC_POINT* point = NULL; + + if (!ctx || !p) + return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; + if (ctx->type != ELLIPTIC_CURVE_SECP256K1 && ctx->type != ELLIPTIC_CURVE_SECP256R1 && ctx->type != ELLIPTIC_CURVE_STARK) + return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; + + /* + * IMPORTANT: This file intentionally treats any encoding whose first byte is 0x00 + * as the point at infinity, by parsing it with length 1 via SIZEOF_POINT(p). + * That means that {0x00, } is still interpreted as infinity by + * add/mul/verify paths. `validate_non_infinity_point()` must reject these infinity encodings (while + * still matching the parsing semantics elsewhere). + */ + if ((*p)[0] == 0) + return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; // infinity is not a valid point for callers + + ctx_ = (GFp_curve_algebra_ctx_t*)ctx->ctx; + if (!ctx_ || !ctx_->curve) + return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; + + bn_ctx = BN_CTX_new(); + if (!bn_ctx) + return ELLIPTIC_CURVE_ALGEBRA_OUT_OF_MEMORY; + + point = EC_POINT_new(ctx_->curve); + if (!point) + { + BN_CTX_free(bn_ctx); + return ELLIPTIC_CURVE_ALGEBRA_OUT_OF_MEMORY; + } + + if (!EC_POINT_oct2point(ctx_->curve, point, *p, SIZEOF_POINT(*p), bn_ctx)) + { + EC_POINT_free(point); + BN_CTX_free(bn_ctx); + return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + } + + if (EC_POINT_is_at_infinity(ctx_->curve, point) == 1) + { + EC_POINT_free(point); + BN_CTX_free(bn_ctx); + return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + } + + EC_POINT_free(point); + BN_CTX_free(bn_ctx); + return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; +} + static const uint8_t *secp256k1_order(const elliptic_curve256_algebra_ctx_t *ctx) { (void)(ctx); @@ -1231,7 +1286,7 @@ static elliptic_curve_algebra_status simple_hash_to_curve(const struct elliptic_ // Convert hash to a big number and reduce modulo the field size if (!BN_bin2bn(hash, sizeof(hash), tmp) || !BN_mod(tmp, tmp, field_size, ctx) || - !BN_bn2binpad(tmp, point + 1, sizeof(point) - 1)) + BN_bn2binpad(tmp, point + 1, sizeof(point) - 1) <= 0) { goto cleanup; } @@ -1280,6 +1335,7 @@ elliptic_curve256_algebra_ctx_t* elliptic_curve256_new_secp256k1_algebra() ctx->order = secp256k1_order; ctx->point_size = point_size; ctx->infinity_point = infinity_point; + ctx->validate_non_infinity_point = validate_non_infinity_point; ctx->generator_mul_data = generate_proof_for_data; ctx->verify = verify; ctx->verify_linear_combination = verify_linear_combination; @@ -1309,6 +1365,7 @@ elliptic_curve256_algebra_ctx_t* elliptic_curve256_new_secp256r1_algebra() ctx->order = secp256r1_order; ctx->point_size = point_size; ctx->infinity_point = infinity_point; + ctx->validate_non_infinity_point = validate_non_infinity_point; ctx->generator_mul_data = generate_proof_for_data; ctx->verify = verify; ctx->verify_linear_combination = verify_linear_combination; @@ -1338,6 +1395,7 @@ elliptic_curve256_algebra_ctx_t* elliptic_curve256_new_stark_algebra() ctx->order = stark_order; ctx->point_size = point_size; ctx->infinity_point = infinity_point; + ctx->validate_non_infinity_point = validate_non_infinity_point; ctx->generator_mul_data = generate_proof_for_data; ctx->verify = verify; ctx->verify_linear_combination = verify_linear_combination; diff --git a/src/common/crypto/algebra_utils/algebra_utils.c b/src/common/crypto/algebra_utils/algebra_utils.c index 946ffbb..6e43435 100644 --- a/src/common/crypto/algebra_utils/algebra_utils.c +++ b/src/common/crypto/algebra_utils/algebra_utils.c @@ -1,4 +1,4 @@ -#include "algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" #include #include #include @@ -102,7 +102,7 @@ elliptic_curve_algebra_status generate_tough_prime(BIGNUM* p, const uint32_t bit } // Limit factors number to prevent excessive memory allocation - if (bitsize > (256 - PRIME_GENERATION_POOL_INCREASE) * subprimes_bitsize) + if (bitsize >= (256 - PRIME_GENERATION_POOL_INCREASE) * subprimes_bitsize) { return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; } @@ -205,7 +205,7 @@ elliptic_curve_algebra_status generate_tough_prime(BIGNUM* p, const uint32_t bit //NIST (FIPS 186-4) suggests 40 rounds for 3072-bit numbers, so 64 rounds is even stricter // probability of incorrect classification is 2^(-128) which is less than 10^(-39) - if (BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)) + if (BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1) { // Found a prime! ret = ELLIPTIC_CURVE_ALGEBRA_SUCCESS; @@ -250,7 +250,7 @@ elliptic_curve_algebra_status crt_recombine(BIGNUM* out, const BIGNUM* mod_p, co // the following validations added for sanity // the function will not return a meaningful result if violated - assert(is_coprime_fast(p, q, ctx)); // Ensure that p and q are coprime (i.e., gcd(p, q) == 1) + assert(is_coprime_fast(p, q, ctx) == 1); // Ensure that p and q are coprime (i.e., gcd(p, q) == 1) assert(BN_mod_mul(out, q, q_inv_p, p, ctx) && BN_is_one(out)); // Verify q * q_inv_p ≡ 1 mod p (ensuring q_inv_p is a valid modular inverse) assert(BN_mul(out, p, q, ctx) && 0 == BN_cmp(out, pq)); // Verify p * q == pq (ensuring pq is the product of p and q) assert(BN_cmp(mod_p, p) < 0); // Ensure mod_p is within the range [0, p) @@ -298,7 +298,7 @@ elliptic_curve_algebra_status crt_mod_exp(BIGNUM* out, const BIGNUM* base, const goto cleanup; } - assert(is_coprime_fast(p, q, ctx)); + assert(is_coprime_fast(p, q, ctx) == 1); assert(BN_mod_mul(mod_q, q, q_inv_p, p, ctx) && BN_is_one(mod_q)); assert(BN_mul(mod_p, p, q, ctx) && 0 == BN_cmp(mod_p, pq)); @@ -334,6 +334,12 @@ int is_coprime_fast(const BIGNUM *in_a, const BIGNUM *in_b, BN_CTX *ctx) return -1; } + // assume 0 is illegal + if (BN_is_zero(in_a) || BN_is_zero(in_b)) + { + return -1; + } + BN_CTX_start(ctx); a = BN_CTX_get(ctx); b = BN_CTX_get(ctx); diff --git a/src/common/crypto/algebra_utils/status_convert.c b/src/common/crypto/algebra_utils/status_convert.c index 9fc9b11..1004740 100644 --- a/src/common/crypto/algebra_utils/status_convert.c +++ b/src/common/crypto/algebra_utils/status_convert.c @@ -1,4 +1,4 @@ -#include "status_convert.h" +#include "crypto/algebra_utils/status_convert.h" ring_pedersen_status algebra_to_ring_pedersen_status(const elliptic_curve_algebra_status status) { @@ -23,7 +23,7 @@ zero_knowledge_proof_status convert_drng_to_zkp_status(const drng_status status) case DRNG_SUCCESS: return ZKP_SUCCESS; case DRNG_INVALID_PARAMETER: return ZKP_INVALID_PARAMETER; case DRNG_OUT_OF_MEMORY: return ZKP_OUT_OF_MEMORY; - case DRNG_INTERNAL_ERROR: // fallthough + case DRNG_INTERNAL_ERROR: // fallthrough default: return ZKP_UNKNOWN_ERROR; } } diff --git a/src/common/crypto/commitments/commitments.c b/src/common/crypto/commitments/commitments.c index 825736b..eff9739 100644 --- a/src/common/crypto/commitments/commitments.c +++ b/src/common/crypto/commitments/commitments.c @@ -17,7 +17,7 @@ commitments_status commitments_create_commitment_for_data(const uint8_t *data, u SHA256_CTX ctx; if (!data || !data_len || !commitment) return COMMITMENTS_INVALID_PARAMETER; - if (!RAND_bytes(commitment->salt, sizeof(commitments_sha256_t))) + if (RAND_bytes(commitment->salt, sizeof(commitments_sha256_t)) != 1) return COMMITMENTS_INTERNAL_ERROR; SHA256_Init(&ctx); SHA256_Update(&ctx, commitment->salt, sizeof(commitments_sha256_t)); @@ -49,7 +49,7 @@ commitments_status commitments_ctx_commitment_new(commitments_ctx_t **ctx) local_ctx = (commitments_ctx_t*)malloc(sizeof(commitments_ctx_t)); if (!local_ctx) return COMMITMENTS_OUT_OF_MEMORY; - if (!RAND_bytes(local_ctx->commitment, sizeof(commitments_sha256_t))) + if (RAND_bytes(local_ctx->commitment, sizeof(commitments_sha256_t)) != 1) { free(local_ctx); return COMMITMENTS_INTERNAL_ERROR; diff --git a/src/common/crypto/commitments/damgard_fujisaki.c b/src/common/crypto/commitments/damgard_fujisaki.c index 7489de6..a9cb25e 100644 --- a/src/common/crypto/commitments/damgard_fujisaki.c +++ b/src/common/crypto/commitments/damgard_fujisaki.c @@ -2,8 +2,8 @@ #include "crypto/commitments/damgard_fujisaki.h" #include "crypto/zero_knowledge_proof/range_proofs.h" #include "crypto/drng/drng.h" -#include "../algebra_utils/algebra_utils.h" -#include "../algebra_utils/status_convert.h" +#include "crypto/algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/status_convert.h" #include "../zero_knowledge_proof/zkp_constants_internal.h" #include #include @@ -61,15 +61,15 @@ static inline void damgard_fujisaki_cleanup_private(damgard_fujisaki_private_t* { if (priv) { - if (priv->lamda) + if (priv->lambda) { for (uint32_t i = 0; i < priv->pub.dimension; ++i) { - BN_clear_free(priv->lamda[i]); - priv->lamda[i] = NULL; + BN_clear_free(priv->lambda[i]); + priv->lambda[i] = NULL; } - free(priv->lamda); - priv->lamda = NULL; + free(priv->lambda); + priv->lambda = NULL; } BN_clear_free(priv->phi_n); priv->phi_n = NULL; @@ -205,7 +205,7 @@ static ring_pedersen_status damgard_fujisaki_create_commitment_with_private_inte for (uint32_t i = 0; i < batch_size; ++i) { - if (!BN_mod_mul(tmp2, priv->lamda[i], x[i], priv->phi_n, ctx)) + if (!BN_mod_mul(tmp2, priv->lambda[i], x[i], priv->phi_n, ctx)) { goto cleanup; } @@ -355,14 +355,14 @@ static ring_pedersen_status damgard_fujisaki_generate_key_inner(const uint32_t k for (uint32_t i = 0; i < dimension; ++i) { - // generate labda which will have twice security bits of the RSA key - if (!BN_rand(priv->lamda[i], 2 * get_min_secure_exponent_size((uint32_t)BN_num_bits(priv->pub.n)), BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) + // generate lambda which will have twice security bits of the RSA key + if (!BN_rand(priv->lambda[i], 2 * get_min_secure_exponent_size((uint32_t)BN_num_bits(priv->pub.n)), BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) { goto cleanup; } - //s[i] = t ^ lamda[i] - if (!BN_mod_exp(priv->pub.s[i], priv->pub.t, priv->lamda[i], priv->pub.n, ctx)) + //s[i] = t ^ lambda[i] + if (!BN_mod_exp(priv->pub.s[i], priv->pub.t, priv->lambda[i], priv->pub.n, ctx)) { goto cleanup; } @@ -382,6 +382,7 @@ static ring_pedersen_status damgard_fujisaki_generate_key_inner(const uint32_t k ret = RING_PEDERSEN_UNKNOWN_ERROR; } + BN_CTX_end(ctx); return ret; } @@ -411,9 +412,9 @@ ring_pedersen_status damgard_fujisaki_generate_private_key(const uint32_t key_le } local_priv->pub.s = (BIGNUM**) calloc(dimension, sizeof(BIGNUM*)); - local_priv->lamda = (BIGNUM**) calloc(dimension, sizeof(BIGNUM*)); + local_priv->lambda = (BIGNUM**) calloc(dimension, sizeof(BIGNUM*)); - if (!local_priv->pub.s || !local_priv->lamda) + if (!local_priv->pub.s || !local_priv->lambda) { goto cleanup; } @@ -442,19 +443,19 @@ ring_pedersen_status damgard_fujisaki_generate_private_key(const uint32_t key_le BN_set_flags(local_priv->q, BN_FLG_CONSTTIME); BN_set_flags(local_priv->qinvp, BN_FLG_CONSTTIME); - local_priv->pub.dimension = dimension; //must set dimention before allocating internal memory for free to work + local_priv->pub.dimension = dimension; //must set dimension before allocating internal memory for free to work for (uint32_t i = 0; i < dimension; ++i) { local_priv->pub.s[i] = BN_new(); - local_priv->lamda[i] = BN_new(); - if (!local_priv->pub.s[i] || !local_priv->lamda[i]) + local_priv->lambda[i] = BN_new(); + if (!local_priv->pub.s[i] || !local_priv->lambda[i]) { ret = RING_PEDERSEN_OUT_OF_MEMORY; goto cleanup; } - BN_set_flags(local_priv->lamda[i], BN_FLG_CONSTTIME); + BN_set_flags(local_priv->lambda[i], BN_FLG_CONSTTIME); } ret = damgard_fujisaki_generate_key_inner(key_len, dimension, local_priv, ctx); @@ -587,7 +588,7 @@ static inline uint32_t damgard_fujisaki_public_serialized_size(const damgard_fuj + size_n // n + sizeof(uint32_t) // dimension + size_n // t - + pub->dimension * size_n; // s times dimentions + + pub->dimension * size_n; // s times dimensions } uint8_t* damgard_fujisaki_public_serialize(const damgard_fujisaki_public_t* pub, uint8_t* buffer, const uint32_t buffer_len, uint32_t* real_buffer_len) @@ -694,7 +695,6 @@ static inline uint32_t damgard_fujisaki_public_deserialize_internal(damgard_fuji goto cleanup; } - assert(buffer_len >= 2 * sizeof(uint32_t) + (2 * n_len) + (pub->dimension * n_len)); if (buffer_len < (2 * sizeof(uint32_t)) + (2 * n_len) + (pub->dimension * n_len)) { goto cleanup; @@ -740,7 +740,8 @@ damgard_fujisaki_public_t* damgard_fujisaki_public_deserialize(const uint8_t* co goto cleanup; } - if ( 0 == damgard_fujisaki_public_deserialize_internal(pub, buffer, buffer_len)) + const uint32_t bytes_consumed = damgard_fujisaki_public_deserialize_internal(pub, buffer, buffer_len); + if (0 == bytes_consumed || bytes_consumed != buffer_len) { goto cleanup; } @@ -822,7 +823,7 @@ uint8_t *damgard_fujisaki_private_serialize(const damgard_fujisaki_private_t* pr // lambda_i for (uint32_t i = 0; i < priv->pub.dimension; ++i) { - if (BN_bn2binpad(priv->lamda[i], ptr, size_n) == -1) + if (BN_bn2binpad(priv->lambda[i], ptr, size_n) == -1) { return NULL; } @@ -865,9 +866,9 @@ damgard_fujisaki_private_t* damgard_fujisaki_private_deserialize(const uint8_t* priv->p = BN_new(); priv->q = BN_new(); priv->qinvp = BN_new(); - priv->lamda = (BIGNUM**) calloc(priv->pub.dimension, sizeof(BIGNUM*)); + priv->lambda = (BIGNUM**) calloc(priv->pub.dimension, sizeof(BIGNUM*)); - if (!priv->phi_n || !priv->p || !priv->q || !priv->qinvp || !priv->lamda) + if (!priv->phi_n || !priv->p || !priv->q || !priv->qinvp || !priv->lambda) { goto cleanup; } @@ -901,16 +902,16 @@ damgard_fujisaki_private_t* damgard_fujisaki_private_deserialize(const uint8_t* buffer_len -= size_n / 2; - // lamda array + // lambda array for (uint32_t i = 0; i < priv->pub.dimension; ++i) { - priv->lamda[i] = BN_new(); - if (!priv->lamda[i] || !BN_bin2bn(buffer, size_n, priv->lamda[i])) + priv->lambda[i] = BN_new(); + if (!priv->lambda[i] || !BN_bin2bn(buffer, size_n, priv->lambda[i])) { goto cleanup; } - BN_set_flags(priv->lamda[i], BN_FLG_CONSTTIME); + BN_set_flags(priv->lambda[i], BN_FLG_CONSTTIME); buffer += size_n; buffer_len -= size_n; @@ -918,7 +919,7 @@ damgard_fujisaki_private_t* damgard_fujisaki_private_deserialize(const uint8_t* assert(buffer_len == 0); - // computer qinvp + // compute qinvp if (!BN_mod_inverse(priv->qinvp, priv->q, priv->p, ctx)) { goto cleanup; diff --git a/src/common/crypto/commitments/damgard_fujisaki_internal.h b/src/common/crypto/commitments/damgard_fujisaki_internal.h index 65e5ad0..2168da2 100644 --- a/src/common/crypto/commitments/damgard_fujisaki_internal.h +++ b/src/common/crypto/commitments/damgard_fujisaki_internal.h @@ -16,15 +16,15 @@ struct damgard_fujisaki_public { uint32_t dimension; // number of secrets BIGNUM *n; // public part of p * q - BIGNUM **s; // for each secret labda holds it's public t^labda + BIGNUM **s; // for each secret lambda holds its public t^lambda BIGNUM *t; // single t used for all s - BN_MONT_CTX *mont; // montegomery context used for calculations + BN_MONT_CTX *mont; // montgomery context used for calculations }; struct damgard_fujisaki_private { struct damgard_fujisaki_public pub; - BIGNUM **lamda; // secrets, same count as pub->dimension + BIGNUM **lambda; // secrets, same count as pub->dimension BIGNUM *phi_n; // (p-1) * (q-1) BIGNUM *p; BIGNUM *q; diff --git a/src/common/crypto/commitments/pedersen.c b/src/common/crypto/commitments/pedersen.c index 23b823f..a3114fd 100644 --- a/src/common/crypto/commitments/pedersen.c +++ b/src/common/crypto/commitments/pedersen.c @@ -81,7 +81,7 @@ commitments_status pedersen_commitment_two_generators_create_commitment(elliptic } // trick to "transform" a variable-length scalar into the modular-reduced one. - // scanler will be (b + 0) in mod n which is b mod n + // scalar will be (b + 0) in mod n which is b mod n if (ctx->add_scalars(ctx, &scalar, b, b_len, &ZERO, 1) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS || ctx->point_mul(ctx, &tmp, &base->h, &scalar) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { diff --git a/src/common/crypto/commitments/ring_pedersen.c b/src/common/crypto/commitments/ring_pedersen.c index b30d2fb..a3aca4f 100644 --- a/src/common/crypto/commitments/ring_pedersen.c +++ b/src/common/crypto/commitments/ring_pedersen.c @@ -1,7 +1,7 @@ #include "crypto/commitments/ring_pedersen.h" #include "crypto/drng/drng.h" #include "ring_pedersen_internal.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" #include #include @@ -43,7 +43,7 @@ ring_pedersen_status ring_pedersen_init_montgomery(ring_pedersen_public_t *pub, ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pedersen_public_t **pub, ring_pedersen_private_t **priv) { ring_pedersen_status ret = RING_PEDERSEN_UNKNOWN_ERROR; - BIGNUM *p, *q, *tmp, *n, *lamda, *phi, *r, *s, *t; + BIGNUM *p, *q, *tmp, *n, *lambda, *phi, *r, *s, *t; BN_CTX *ctx = NULL; ring_pedersen_public_t *local_pub = NULL; ring_pedersen_private_t *local_priv = NULL; @@ -75,18 +75,18 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede q = BN_CTX_get(ctx); n = BN_new(); - lamda = BN_new(); + lambda = BN_new(); phi = BN_new(); s = BN_new(); t = BN_new(); - if (!p || !q || !tmp || !n || !phi || !lamda || !r || !s || !t) + if (!p || !q || !tmp || !n || !phi || !lambda || !r || !s || !t) goto cleanup; BN_set_flags(phi, BN_FLG_CONSTTIME); BN_set_flags(p, BN_FLG_CONSTTIME); BN_set_flags(q, BN_FLG_CONSTTIME); - BN_set_flags(lamda, BN_FLG_CONSTTIME); + BN_set_flags(lambda, BN_FLG_CONSTTIME); if (!BN_generate_prime_ex(p, key_len / 2, 1, NULL, NULL, NULL)) { @@ -121,7 +121,7 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede goto cleanup; } - if (!BN_rand_range(lamda, phi)) + if (!BN_rand_range(lambda, phi)) { goto cleanup; } @@ -132,8 +132,7 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede if (!BN_rand_range(r, n)) { goto cleanup; - } - + } } while (!BN_gcd(tmp, r, n, ctx) || !BN_is_one(tmp)); @@ -142,7 +141,7 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede goto cleanup; } - if (!BN_mod_exp(s, t, lamda, n, ctx)) + if (!BN_mod_exp(s, t, lambda, n, ctx)) { goto cleanup; } @@ -158,7 +157,7 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede local_priv->pub.s = s; local_priv->pub.t = t; local_priv->pub.mont = NULL; - local_priv->lamda = lamda; + local_priv->lambda = lambda; local_priv->phi_n = phi; local_pub = (ring_pedersen_public_t*)malloc(sizeof(ring_pedersen_public_t)); @@ -194,6 +193,10 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede { BN_clear(q); } + if (r) + { + BN_clear(r); + } BN_CTX_end(ctx); BN_CTX_free(ctx); } @@ -206,10 +209,10 @@ ring_pedersen_status ring_pedersen_generate_key_pair(uint32_t key_len, ring_pede free(local_priv); } - ring_pedersen_free_public(local_pub); // as the public key uses duplication of p, s and t it's not sefficent just to free it + ring_pedersen_free_public(local_pub); // as the public key uses duplication of p, s and t it's not sufficient just to free it BN_free(n); - BN_free(lamda); - BN_free(phi); + BN_clear_free(lambda); + BN_clear_free(phi); BN_free(s); BN_free(t); } @@ -399,7 +402,7 @@ const ring_pedersen_public_t* ring_pedersen_private_key_get_public(const ring_pe uint8_t *ring_pedersen_private_serialize(const ring_pedersen_private_t *priv, uint8_t *buffer, uint32_t buffer_len, uint32_t *real_buffer_len) { uint32_t needed_len = 0; - uint32_t lamda_len = 0; + uint32_t lambda_len = 0; uint32_t phi_len = 0; uint8_t *p = buffer; @@ -408,9 +411,9 @@ uint8_t *ring_pedersen_private_serialize(const ring_pedersen_private_t *priv, ui return NULL; } - lamda_len = BN_num_bytes(priv->lamda); + lambda_len = BN_num_bytes(priv->lambda); phi_len = BN_num_bytes(priv->phi_n); - needed_len = ring_pedersen_public_serialize_internal(&priv->pub, NULL, 0) + sizeof(uint32_t) * 2 + lamda_len + phi_len; + needed_len = ring_pedersen_public_serialize_internal(&priv->pub, NULL, 0) + sizeof(uint32_t) * 2 + lambda_len + phi_len; if (real_buffer_len) { *real_buffer_len = needed_len; @@ -422,10 +425,10 @@ uint8_t *ring_pedersen_private_serialize(const ring_pedersen_private_t *priv, ui } p += ring_pedersen_public_serialize_internal(&priv->pub, buffer, buffer_len); - *(uint32_t*)p = lamda_len; + *(uint32_t*)p = lambda_len; p += sizeof(uint32_t); - BN_bn2bin(priv->lamda, p); - p += lamda_len; + BN_bn2bin(priv->lambda, p); + p += lambda_len; *(uint32_t*)p = phi_len; p += sizeof(uint32_t); BN_bn2bin(priv->phi_n, p); @@ -469,8 +472,12 @@ ring_pedersen_private_t *ring_pedersen_private_deserialize(const uint8_t *buffer } buffer_len -= sizeof(uint32_t); - priv->lamda = BN_bin2bn(p, len, NULL); - BN_set_flags(priv->lamda, BN_FLG_CONSTTIME); + priv->lambda = BN_bin2bn(p, len, NULL); + if (!priv->lambda) + { + goto cleanup; + } + BN_set_flags(priv->lambda, BN_FLG_CONSTTIME); p += len; buffer_len -= len; @@ -483,14 +490,13 @@ ring_pedersen_private_t *ring_pedersen_private_deserialize(const uint8_t *buffer buffer_len -= sizeof(uint32_t); priv->phi_n = BN_bin2bn(p, len, NULL); - BN_set_flags(priv->phi_n, BN_FLG_CONSTTIME); - buffer_len -= len; - assert(buffer_len == 0); - - if (!priv->lamda || !priv->phi_n) + if (!priv->phi_n) { goto cleanup; } + BN_set_flags(priv->phi_n, BN_FLG_CONSTTIME); + buffer_len -= len; + assert(buffer_len == 0); return priv; @@ -510,7 +516,7 @@ void ring_pedersen_free_private(ring_pedersen_private_t *priv) BN_free(priv->pub.n); BN_free(priv->pub.s); BN_free(priv->pub.t); - BN_clear_free(priv->lamda); + BN_clear_free(priv->lambda); BN_clear_free(priv->phi_n); free(priv); } @@ -735,8 +741,8 @@ zero_knowledge_proof_status ring_pedersen_parameters_zkp_generate(const ring_ped if (e & 0x01) { - // both z and lamda are in Z(phi(n)) so the add_quick version can be used - if (!BN_mod_add_quick(proof.z[i], proof.z[i], priv->lamda, priv->phi_n)) + // both z and lambda are in Z(phi(n)) so the add_quick version can be used + if (!BN_mod_add_quick(proof.z[i], proof.z[i], priv->lambda, priv->phi_n)) { goto cleanup; } @@ -998,7 +1004,7 @@ static ring_pedersen_status ring_pedersen_verify_commitment_internal(const ring_ ring_pedersen_init_mont((ring_pedersen_public_t*)&priv->pub, ctx); status = RING_PEDERSEN_UNKNOWN_ERROR; - if (!BN_mod_mul(tmp, priv->lamda, x, priv->phi_n, ctx)) + if (!BN_mod_mul(tmp, priv->lambda, x, priv->phi_n, ctx)) { goto cleanup; } @@ -1119,7 +1125,10 @@ ring_pedersen_status ring_pedersen_verify_batch_commitments_internal(const ring_ goto cleanup; } - BN_one(B); + if (!BN_one(B)) + { + goto cleanup; + } ring_pedersen_init_mont((ring_pedersen_public_t*)&priv->pub, ctx); status = RING_PEDERSEN_UNKNOWN_ERROR; @@ -1133,7 +1142,7 @@ ring_pedersen_status ring_pedersen_verify_batch_commitments_internal(const ring_ } gamma &= 0xffffffffff; // 40bits - if (!BN_mod_mul(tmp1, priv->lamda, x[i], priv->phi_n, ctx)) + if (!BN_mod_mul(tmp1, priv->lambda, x[i], priv->phi_n, ctx)) { goto cleanup; } @@ -1230,7 +1239,10 @@ ring_pedersen_status ring_pedersen_verify_batch_commitments(const ring_pedersen_ } - BN_one(B); + if (!BN_one(B)) + { + goto cleanup; + } status = RING_PEDERSEN_UNKNOWN_ERROR; ring_pedersen_init_mont((ring_pedersen_public_t*) &priv->pub, ctx); @@ -1253,7 +1265,7 @@ ring_pedersen_status ring_pedersen_verify_batch_commitments(const ring_pedersen_ goto cleanup; } - if (!BN_mod_mul(tmp1, priv->lamda, tmp1, priv->phi_n, ctx)) + if (!BN_mod_mul(tmp1, priv->lambda, tmp1, priv->phi_n, ctx)) { goto cleanup; } diff --git a/src/common/crypto/commitments/ring_pedersen_internal.h b/src/common/crypto/commitments/ring_pedersen_internal.h index 796dcdb..280db66 100644 --- a/src/common/crypto/commitments/ring_pedersen_internal.h +++ b/src/common/crypto/commitments/ring_pedersen_internal.h @@ -22,7 +22,7 @@ struct ring_pedersen_public struct ring_pedersen_private { struct ring_pedersen_public pub; - BIGNUM *lamda; + BIGNUM *lambda; BIGNUM *phi_n; }; ring_pedersen_status ring_pedersen_init_montgomery(struct ring_pedersen_public *pub, BN_CTX *ctx); diff --git a/src/common/crypto/ed25519_algebra/ed25519_algebra.c b/src/common/crypto/ed25519_algebra/ed25519_algebra.c index bb36e99..a2c8684 100644 --- a/src/common/crypto/ed25519_algebra/ed25519_algebra.c +++ b/src/common/crypto/ed25519_algebra/ed25519_algebra.c @@ -16,9 +16,9 @@ const uint8_t ED25519_FIELD[] = 0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6, 0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed }; -static const uint8_t ED25519_FIELD_LITTLE_ENDIAN[] = +static const uint8_t ED25519_FIELD_LITTLE_ENDIAN[] = { - 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 }; @@ -26,7 +26,7 @@ static const uint8_t ED25519_FIELD_LITTLE_ENDIAN[] = struct ed25519_algebra_ctx { - BIGNUM *L; //order of the cuve + BIGNUM *L; //order of the curve BIGNUM *P; //order of the field of each coordinate }; @@ -36,13 +36,23 @@ ed25519_algebra_ctx_t *ed25519_algebra_ctx_new() if (ctx) { + ctx->P = BN_new(); ctx->L = BN_bin2bn(ED25519_FIELD, sizeof(ED25519_FIELD), NULL); - if (!ctx->L) + if (!ctx->L || !ctx->P) { ed25519_algebra_ctx_free(ctx); return NULL; } BN_set_flags(ctx->L, BN_FLG_CONSTTIME); + + if (!BN_set_bit(ctx->P, 255) || + !BN_sub_word(ctx->P, 19)) + { + ed25519_algebra_ctx_free(ctx); + return NULL; + } + BN_set_flags(ctx->P, BN_FLG_CONSTTIME); + } return ctx; } @@ -52,6 +62,7 @@ void ed25519_algebra_ctx_free(ed25519_algebra_ctx_t *ctx) if (ctx) { BN_free(ctx->L); + BN_free(ctx->P); free(ctx); } } @@ -259,6 +270,7 @@ elliptic_curve_algebra_status ed25519_algebra_generator_mul(const ed25519_algebr if (!ed25519_to_scalar(*exp, local_exp)) return ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR; ed25519_algebra_generator_mul_internal(res, &local_exp); + OPENSSL_cleanse(local_exp, sizeof(local_exp)); return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; } @@ -287,7 +299,7 @@ elliptic_curve_algebra_status ed25519_algebra_add_points(const ed25519_algebra_c elliptic_curve_algebra_status ed25519_algebra_point_mul(const ed25519_algebra_ctx_t *ctx, ed25519_point_t *res, const ed25519_point_t *p, const ed25519_scalar_t *exp) { ed25519_scalar_t local_exp; - + if (!ctx || !res || !p || !exp) return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; @@ -296,10 +308,12 @@ elliptic_curve_algebra_status ed25519_algebra_point_mul(const ed25519_algebra_ct if (!ed25519_to_scalar(*exp, local_exp)) return ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR; - if (ed25519_scalar_mult(*res, local_exp, *p)) - return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; - else - return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + + elliptic_curve_algebra_status ret = ed25519_scalar_mult(*res, local_exp, *p) ? ELLIPTIC_CURVE_ALGEBRA_SUCCESS : ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + + OPENSSL_cleanse(local_exp, sizeof(ed25519_scalar_t)); + + return ret; } elliptic_curve_algebra_status ed25519_algebra_add_scalars(const ed25519_algebra_ctx_t *ctx, ed25519_scalar_t *res, const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len) @@ -557,6 +571,7 @@ elliptic_curve_algebra_status ed25519_algebra_reduce(const ed25519_algebra_ctx_t memcpy(value, *s, sizeof(ed25519_le_large_scalar_t)); x25519_sc_reduce(value); memcpy(*res, value, sizeof(ed25519_le_scalar_t)); + OPENSSL_cleanse(value,sizeof(ed25519_le_large_scalar_t)); return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; } @@ -742,6 +757,8 @@ elliptic_curve_algebra_status ed25519_algebra_le_to_be(ed25519_scalar_t *res, co memcpy(&tmp, *n, (sizeof(ed25519_le_scalar_t))); bswap_256(tmp, *res); + OPENSSL_cleanse(tmp, sizeof(ed25519_le_scalar_t)); + return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; } @@ -753,10 +770,12 @@ elliptic_curve_algebra_status ed25519_algebra_be_to_le(ed25519_le_scalar_t *res, memcpy(&tmp, *n, (sizeof(ed25519_scalar_t))); bswap_256(tmp, *res); + OPENSSL_cleanse(tmp, sizeof(ed25519_scalar_t)); + return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; } -// elliptic_curve256_algebra_ctx_t interface implamantion needed as ed25519_point_t is smaller then elliptic_curve256_point_t +// elliptic_curve256_algebra_ctx_t interface implementation needed as ed25519_point_t is smaller than elliptic_curve256_point_t static int release(elliptic_curve256_algebra_ctx_t *ctx) { if (ctx) @@ -790,6 +809,45 @@ static const elliptic_curve256_point_t *infinity_point(const struct elliptic_cur return &INFINITY; } +static inline int ed25519_is_infinity_compat(const elliptic_curve256_point_t *p) +{ + /* + * Ed25519 identity (neutral element) in compressed form is: + * y = 1, x sign bit = 0 => 0x01 followed by 31 zero bytes. + * + * Note: `elliptic_curve256_point_t` is 33 bytes, while Ed25519 point + * serialization is 32 bytes. All Ed25519 operations in this file ignore + * the 33rd byte (tail) by passing only the first 32 bytes to the ref10 + * parser. Therefore we must treat any value in the tail byte as still + * representing infinity as long as the first 32 bytes encode the identity. + */ + /* We intentionally do NOT reuse the 33-byte `elliptic_curve256_point_t INFINITY = {1,0}` here: + * comparing 33 bytes would incorrectly reject valid "infinity" encodings where the tail byte is non-zero, + * which are still treated as infinity by all Ed25519 operations in this file (they ignore the tail byte). */ + static const uint8_t ED25519_INFINITY_COMPRESSED[ED25519_COMPRESSED_POINT_LEN] = {1, 0}; + return CRYPTO_memcmp(*p, ED25519_INFINITY_COMPRESSED, ED25519_COMPRESSED_POINT_LEN) == 0; +} + +static elliptic_curve_algebra_status validate_non_infinity_point(const struct elliptic_curve256_algebra_ctx *ctx, const elliptic_curve256_point_t *p) +{ + if (!ctx || !p || ctx->type != ELLIPTIC_CURVE_ED25519) + { + return ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER; + } + + /* Must be a valid encoding in the correct subgroup, and must not be infinity. */ + if (!ed25519_is_valid_point(*p)) + { + return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + } + if (ed25519_is_infinity_compat(p)) + { + return ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT; + } + return ELLIPTIC_CURVE_ALGEBRA_SUCCESS; +} + + static elliptic_curve_algebra_status generator_mul_data(const elliptic_curve256_algebra_ctx_t *ctx, const uint8_t *data, uint32_t data_len, elliptic_curve256_point_t *proof) { if (!ctx || !proof || ctx->type != ELLIPTIC_CURVE_ED25519) @@ -980,13 +1038,13 @@ static const struct bignum_st *order_internal(const elliptic_curve256_algebra_ct /** * @brief Maps a message to a valid Ed25519 elliptic curve point using a simple hash function. * - * This function attempts to map an arbitrary message to a point on the Ed25519 curve + * This function attempts to map an arbitrary message to a point on the Ed25519 curve * by using a hashing approach. It performs the following steps: * * 1. Computes a SHA-512 hash of the input message. * 2. Interprets the hash as a candidate point and checks if it is a valid Ed25519 point. * 3. If the point is invalid, it re-hashes the previous hash and repeats. - * 4. If a valid point is found within a maximum number of attempts (`max_retries`), + * 4. If a valid point is found within a maximum number of attempts (`max_retries`), * the function returns the point. * 5. If no valid point is found after `max_retries`, the function returns an error. * @@ -1030,12 +1088,12 @@ static elliptic_curve_algebra_status simple_hash_to_curve(const struct elliptic_ { // Check if the hashed value is a valid Ed25519 point if (ed25519_is_valid_point(hash)) - { + { memcpy(res, hash, sizeof(ed25519_point_t)); status = ELLIPTIC_CURVE_ALGEBRA_SUCCESS; break; } - + // If invalid, re-hash and try again SHA512_Init(&hash_ctx); SHA512_Update(&hash_ctx, hash, sizeof(hash)); @@ -1057,6 +1115,7 @@ elliptic_curve256_algebra_ctx_t* elliptic_curve256_new_ed25519_algebra() ctx->order = ed25519_order; ctx->point_size = ed25519_point_size; ctx->infinity_point = infinity_point; + ctx->validate_non_infinity_point = validate_non_infinity_point; ctx->generator_mul_data = generator_mul_data; ctx->verify = verify; ctx->verify_linear_combination = verify_linear_combination; @@ -1071,6 +1130,6 @@ elliptic_curve256_algebra_ctx_t* elliptic_curve256_new_ed25519_algebra() ctx->order_internal = order_internal; ctx->reduce = reduce; ctx->hash_on_curve = simple_hash_to_curve; - + return ctx; } diff --git a/src/common/crypto/paillier/paillier.c b/src/common/crypto/paillier/paillier.c index 72cae49..3f45c66 100644 --- a/src/common/crypto/paillier/paillier.c +++ b/src/common/crypto/paillier/paillier.c @@ -1,7 +1,7 @@ #include "paillier_internal.h" #include "crypto/zero_knowledge_proof/range_proofs.h" -#include "../algebra_utils/status_convert.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/status_convert.h" +#include "crypto/algebra_utils/algebra_utils.h" #include "../zero_knowledge_proof/zkp_constants_internal.h" #include @@ -38,7 +38,7 @@ uint64_t paillier_L(BIGNUM *res, const BIGNUM *x, const BIGNUM *n, BN_CTX *ctx) cleanup: if (ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } BN_clear_free(x_copy); @@ -50,7 +50,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * long ret = -1; BIGNUM *p = NULL, *q = NULL; BIGNUM *tmp = NULL, *n = NULL, *n2 = NULL; - BIGNUM *lamda = NULL, *mu = NULL; + BIGNUM *lambda = NULL, *mu = NULL; BIGNUM *three = NULL, *seven = NULL, *eight = NULL; BN_CTX *ctx = NULL; @@ -66,7 +66,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * ctx = BN_CTX_secure_new(); if (!ctx) { - //not jumping to cleanup to avoid initializating all local variables + //not jumping to cleanup to avoid initializing all local variables return PAILLIER_ERROR_OUT_OF_MEMORY; } @@ -81,10 +81,10 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * q = BN_new(); n = BN_new(); n2 = BN_new(); - lamda = BN_new(); + lambda = BN_new(); mu = BN_new(); - if (!p || !q || !tmp || !n || !n2 || !lamda || !mu || !three || !seven || !eight) + if (!p || !q || !tmp || !n || !n2 || !lambda || !mu || !three || !seven || !eight) { goto cleanup; } @@ -93,7 +93,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * BN_set_flags(n2, BN_FLG_CONSTTIME); BN_set_flags(p, BN_FLG_CONSTTIME); BN_set_flags(q, BN_FLG_CONSTTIME); - BN_set_flags(lamda, BN_FLG_CONSTTIME); + BN_set_flags(lambda, BN_FLG_CONSTTIME); BN_set_flags(mu, BN_FLG_CONSTTIME); if (!BN_set_word(three, 3)) @@ -140,22 +140,22 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * goto cleanup; } - if (!BN_sub(lamda, n, p)) + if (!BN_sub(lambda, n, p)) { goto cleanup; } - if (!BN_sub(lamda, lamda, q)) + if (!BN_sub(lambda, lambda, q)) { goto cleanup; } - if (!BN_add_word(lamda, 1)) + if (!BN_add_word(lambda, 1)) { goto cleanup; } } while (BN_cmp(p, q) == 0 || - !BN_gcd(tmp, lamda, n, ctx) || + !BN_gcd(tmp, lambda, n, ctx) || !BN_is_one(tmp)); if (!BN_sqr(n2, n, ctx)) @@ -164,7 +164,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * } // if num_bits(q) == num_bits(p), we can optimize g lambda and mu selection see https://en.wikipedia.org/wiki/Paillier_cryptosystem - if (!BN_mod_inverse(mu, lamda, n, ctx)) + if (!BN_mod_inverse(mu, lambda, n, ctx)) { goto cleanup; } @@ -173,7 +173,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * priv->pub.n2 = n2; priv->p = p; priv->q = q; - priv->lamda = lamda; + priv->lambda = lambda; priv->mu = mu; ret = PAILLIER_SUCCESS; @@ -181,7 +181,7 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -193,12 +193,12 @@ static long paillier_generate_private(uint32_t key_len, paillier_private_key_t * if (ret) { // handle errors - BN_free(p); - BN_free(q); + BN_clear_free(p); + BN_clear_free(q); BN_free(n); BN_free(n2); - BN_free(lamda); - BN_free(mu); + BN_clear_free(lambda); + BN_clear_free(mu); } return ret; @@ -286,7 +286,7 @@ long paillier_public_key_n(const paillier_public_key_t *pub, uint8_t *n, uint32_ return PAILLIER_ERROR_KEYLEN_TOO_SHORT; } - return BN_bn2bin(pub->n, n) > 0 ? PAILLIER_SUCCESS : ERR_get_error() * -1; + return BN_bn2bin(pub->n, n) > 0 ? PAILLIER_SUCCESS : paillier_error_from_openssl(); } uint32_t paillier_public_key_size(const paillier_public_key_t *pub) @@ -325,7 +325,10 @@ uint8_t *paillier_public_key_serialize(const paillier_public_key_t *pub, uint8_t memcpy(p, &n_len, sizeof(uint32_t)); p += sizeof(uint32_t); - BN_bn2bin(pub->n, p); + if (BN_bn2binpad(pub->n, p, n_len) <= 0) + { + return NULL; + } return buffer; } @@ -334,7 +337,9 @@ static inline void paillier_free_public_key_cleanup(paillier_public_key_t *pub) if (pub) { BN_free(pub->n); + pub->n = NULL; BN_free(pub->n2); + pub->n2 = NULL; } } @@ -399,8 +404,6 @@ paillier_public_key_t *paillier_public_key_deserialize(const uint8_t *buffer, ui buffer_len -= sizeof(uint32_t); buffer += sizeof(uint32_t); - assert(len == buffer_len); - if (len > buffer_len) { goto cleanup; @@ -410,6 +413,8 @@ paillier_public_key_t *paillier_public_key_deserialize(const uint8_t *buffer, ui { goto cleanup; } + + assert(len == buffer_len); // catch possible bugs in debug mode. BN_CTX_free(ctx); return pub; @@ -453,7 +458,7 @@ long paillier_private_key_n(const paillier_private_key_t *priv, uint8_t *n, uint return PAILLIER_ERROR_KEYLEN_TOO_SHORT; } - return BN_bn2bin(priv->pub.n, n) > 0 ? PAILLIER_SUCCESS : ERR_get_error() * -1; + return BN_bn2binpad(priv->pub.n, n, len) > 0 ? PAILLIER_SUCCESS : paillier_error_from_openssl(); } const paillier_public_key_t* paillier_private_key_get_public(const paillier_private_key_t *priv) @@ -511,10 +516,10 @@ static paillier_private_key_t* paillier_private_key_deserialize_internal(const u priv->q = BN_bin2bn(buffer, p_len, NULL); priv->pub.n = BN_new(); priv->pub.n2 = BN_new(); - priv->lamda = BN_new(); + priv->lambda = BN_new(); priv->mu = BN_new(); - if (!priv->p || !priv->q || !priv->lamda || !priv->mu || !priv->pub.n || !priv->pub.n2) + if (!priv->p || !priv->q || !priv->lambda || !priv->mu || !priv->pub.n || !priv->pub.n2) { goto cleanup; } @@ -523,7 +528,7 @@ static paillier_private_key_t* paillier_private_key_deserialize_internal(const u BN_set_flags(priv->q, BN_FLG_CONSTTIME); BN_set_flags(priv->pub.n, BN_FLG_CONSTTIME); BN_set_flags(priv->pub.n2, BN_FLG_CONSTTIME); - BN_set_flags(priv->lamda, BN_FLG_CONSTTIME); + BN_set_flags(priv->lambda, BN_FLG_CONSTTIME); BN_set_flags(priv->mu, BN_FLG_CONSTTIME); if (!BN_mul(priv->pub.n, priv->p, priv->q, ctx)) @@ -536,22 +541,22 @@ static paillier_private_key_t* paillier_private_key_deserialize_internal(const u goto cleanup; } - if (!BN_sub(priv->lamda, priv->pub.n, priv->p)) + if (!BN_sub(priv->lambda, priv->pub.n, priv->p)) { goto cleanup; } - if (!BN_sub(priv->lamda, priv->lamda, priv->q)) + if (!BN_sub(priv->lambda, priv->lambda, priv->q)) { goto cleanup; } - if (!BN_add_word(priv->lamda, 1)) + if (!BN_add_word(priv->lambda, 1)) { goto cleanup; } - if (!BN_mod_inverse(priv->mu, priv->lamda, priv->pub.n, ctx)) + if (!BN_mod_inverse(priv->mu, priv->lambda, priv->pub.n, ctx)) { goto cleanup; } @@ -589,8 +594,7 @@ paillier_private_key_t* paillier_private_key_deserialize(const uint8_t* buffer, buffer += sizeof(uint32_t); buffer_len -= sizeof(uint32_t); - assert(2 * len == buffer_len); - if (2 * len > buffer_len) + if (len > 65535 || 2 * len > buffer_len) { return NULL; } @@ -621,7 +625,7 @@ static inline void paillier_free_private_key_cleanup(paillier_private_key_t *pri paillier_free_public_key_cleanup(&priv->pub); BN_clear_free(priv->p); BN_clear_free(priv->q); - BN_clear_free(priv->lamda); + BN_clear_free(priv->lambda); BN_clear_free(priv->mu); } } @@ -676,9 +680,19 @@ long paillier_encrypt_openssl_internal(const paillier_public_key_t *key, BIGNUM cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); + } + // Clear sensitive intermediates unconditionally, e.g. if we error in any of the BN arithmetic steps. + // tmp1 = (1 + n * plaintext) - plaintext-derived + // tmp2 = r^n mod n^2 - randomness-derived + if (tmp1) + { + BN_clear(tmp1); + } + if (tmp2) + { + BN_clear(tmp2); } - BN_CTX_end(ctx); return ret; @@ -693,7 +707,7 @@ static inline long encrypt_openssl(const paillier_public_key_t *key, BIGNUM *cip if (!r) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } else { @@ -701,7 +715,7 @@ static inline long encrypt_openssl(const paillier_public_key_t *key, BIGNUM *cip { if (!BN_rand_range(r, key->n)) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); break; } @@ -709,7 +723,7 @@ static inline long encrypt_openssl(const paillier_public_key_t *key, BIGNUM *cip } while (ret == PAILLIER_ERROR_INVALID_RANDOMNESS); } - + BN_clear(r); BN_CTX_end(ctx); return ret; @@ -734,8 +748,8 @@ long paillier_decrypt_openssl_internal(const paillier_private_key_t *key, const goto cleanup; } - // Compute the plaintext = paillier_L(ciphertext^lamda mod n2)*mu mod n - if (!BN_mod_exp(tmp, ciphertext, key->lamda, key->pub.n2, ctx)) + // Compute the plaintext = paillier_L(ciphertext^lambda mod n2)*mu mod n + if (!BN_mod_exp(tmp, ciphertext, key->lambda, key->pub.n2, ctx)) { goto cleanup; } @@ -746,7 +760,7 @@ long paillier_decrypt_openssl_internal(const paillier_private_key_t *key, const goto cleanup; } - ret = -1; //revet to openssl error + ret = -1; //revert to openssl error if (!BN_mod_mul(plaintext, tmp, key->mu, key->pub.n, ctx)) { @@ -758,9 +772,9 @@ long paillier_decrypt_openssl_internal(const paillier_private_key_t *key, const cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } - + BN_clear(tmp); BN_CTX_end(ctx); return ret; } @@ -771,12 +785,14 @@ long paillier_encrypt(const paillier_public_key_t *key, const uint8_t *plaintext int len = 0; BIGNUM *msg = NULL, *c = NULL; BN_CTX *ctx = NULL; - + uint32_t n2_size; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } + n2_size = (uint32_t)BN_num_bytes(key->n2); + if (!plaintext || plaintext_len > (uint32_t)BN_num_bytes(key->n)) { return PAILLIER_ERROR_INVALID_PLAIN_TEXT; @@ -784,10 +800,10 @@ long paillier_encrypt(const paillier_public_key_t *key, const uint8_t *plaintext if (ciphertext_real_len) { - *ciphertext_real_len = (uint32_t)BN_num_bytes(key->n2); + *ciphertext_real_len = n2_size; } - if (!ciphertext || ciphertext_len < (uint32_t)BN_num_bytes(key->n2)) + if (!ciphertext || ciphertext_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -824,7 +840,7 @@ long paillier_encrypt(const paillier_public_key_t *key, const uint8_t *plaintext goto cleanup; } - len = BN_bn2bin(c, ciphertext); + len = BN_bn2binpad(c, ciphertext, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -839,11 +855,12 @@ long paillier_encrypt(const paillier_public_key_t *key, const uint8_t *plaintext cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) { + BN_clear(msg); BN_CTX_end(ctx); BN_CTX_free(ctx); } @@ -875,6 +892,8 @@ long paillier_encrypt_to_ciphertext(const paillier_public_key_t *key, const uint { return PAILLIER_ERROR_OUT_OF_MEMORY; } + + c->cipher_size = (uint32_t)BN_num_bytes(key->n2); if ((c->ciphertext = BN_new()) == NULL) { @@ -928,9 +947,13 @@ long paillier_encrypt_to_ciphertext(const paillier_public_key_t *key, const uint cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } + if (msg) + { + BN_clear(msg); + } if (ctx) { BN_CTX_end(ctx); @@ -946,18 +969,21 @@ long paillier_encrypt_integer(const paillier_public_key_t *key, uint64_t plainte int len = 0; BN_CTX *ctx = NULL; BIGNUM *msg = NULL, *c = NULL; + uint32_t n2_size = 0; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - + + n2_size = (uint32_t)BN_num_bytes(key->n2); + if (ciphertext_real_len) { - *ciphertext_real_len = (uint32_t)BN_num_bytes(key->n2); + *ciphertext_real_len = n2_size; } - if (!ciphertext || ciphertext_len < (uint32_t)BN_num_bytes(key->n2)) + if (!ciphertext || ciphertext_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -988,7 +1014,7 @@ long paillier_encrypt_integer(const paillier_public_key_t *key, uint64_t plainte goto cleanup; } - len = BN_bn2bin(c, ciphertext); + len = BN_bn2binpad(c, ciphertext, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -997,13 +1023,13 @@ long paillier_encrypt_integer(const paillier_public_key_t *key, uint64_t plainte if (ciphertext_real_len) { - *ciphertext_real_len = len; + *ciphertext_real_len = n2_size; } cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1077,7 +1103,12 @@ long paillier_decrypt(const paillier_private_key_t *key, const uint8_t *cipherte } len = BN_bn2bin(msg, plaintext); - if (len <= 0) + if (len == 0 && BN_is_zero(msg)) + { + plaintext[0] = 0; + len = 1; + } + else if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; goto cleanup; @@ -1091,7 +1122,7 @@ long paillier_decrypt(const paillier_private_key_t *key, const uint8_t *cipherte cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1170,7 +1201,7 @@ long paillier_decrypt_integer(const paillier_private_key_t *key, const uint8_t * cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1197,24 +1228,27 @@ long paillier_add(const paillier_public_key_t *key, BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2) || - !b_ciphertext || b_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size || + !b_ciphertext || b_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1259,7 +1293,7 @@ long paillier_add(const paillier_public_key_t *key, } - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1268,7 +1302,7 @@ long paillier_add(const paillier_public_key_t *key, if (result_real_len) { - *result_real_len = len; + *result_real_len = n2_size; } ret = PAILLIER_SUCCESS; @@ -1276,7 +1310,7 @@ long paillier_add(const paillier_public_key_t *key, cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1295,22 +1329,26 @@ long paillier_add_integer(const paillier_public_key_t *key, const uint8_t *a_cip BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1362,7 +1400,7 @@ long paillier_add_integer(const paillier_public_key_t *key, const uint8_t *a_cip } - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1379,7 +1417,7 @@ long paillier_add_integer(const paillier_public_key_t *key, const uint8_t *a_cip cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1405,24 +1443,27 @@ long paillier_sub(const paillier_public_key_t *key, BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2) || - !b_ciphertext || b_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size || + !b_ciphertext || b_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1468,7 +1509,7 @@ long paillier_sub(const paillier_public_key_t *key, if (!BN_mod_mul(res, a, b, key->n2, ctx)) goto cleanup; - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1485,7 +1526,7 @@ long paillier_sub(const paillier_public_key_t *key, cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1504,20 +1545,25 @@ long paillier_sub_integer(const paillier_public_key_t *key, const uint8_t *a_cip BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; + if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1573,7 +1619,7 @@ long paillier_sub_integer(const paillier_public_key_t *key, const uint8_t *a_cip goto cleanup; } - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1590,7 +1636,7 @@ long paillier_sub_integer(const paillier_public_key_t *key, const uint8_t *a_cip cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1616,12 +1662,16 @@ long paillier_mul(const paillier_public_key_t *key, BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; + if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1633,10 +1683,10 @@ long paillier_mul(const paillier_public_key_t *key, if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1679,7 +1729,7 @@ long paillier_mul(const paillier_public_key_t *key, goto cleanup; } - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1688,7 +1738,7 @@ long paillier_mul(const paillier_public_key_t *key, if (result_real_len) { - *result_real_len = len; + *result_real_len = n2_size; } ret = PAILLIER_SUCCESS; @@ -1696,7 +1746,7 @@ long paillier_mul(const paillier_public_key_t *key, cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1721,23 +1771,26 @@ long paillier_mul_integer(const paillier_public_key_t *key, BIGNUM *res = NULL; long ret = -1; int len = 0; + uint32_t n2_size; if (!key) { return PAILLIER_ERROR_INVALID_KEY; } - - if (!a_ciphertext || a_ciphertext_len > (uint32_t)BN_num_bytes(key->n2)) + + n2_size = (uint32_t)BN_num_bytes(key->n2); + + if (!a_ciphertext || a_ciphertext_len > n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } if (result_real_len) { - *result_real_len = (uint32_t)BN_num_bytes(key->n2); + *result_real_len = n2_size; } - if (!result || result_len < (uint32_t)BN_num_bytes(key->n2)) + if (!result || result_len < n2_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } @@ -1780,7 +1833,7 @@ long paillier_mul_integer(const paillier_public_key_t *key, } - len = BN_bn2bin(res, result); + len = BN_bn2binpad(res, result, n2_size); if (len <= 0) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1789,7 +1842,7 @@ long paillier_mul_integer(const paillier_public_key_t *key, if (result_real_len) { - *result_real_len = len; + *result_real_len = n2_size; } @@ -1798,7 +1851,7 @@ long paillier_mul_integer(const paillier_public_key_t *key, cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (ctx) @@ -1818,24 +1871,30 @@ long paillier_get_ciphertext(const paillier_ciphertext_t *ciphertext_object, { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } - if (!ciphertext && ciphertext_len) { return PAILLIER_ERROR_INVALID_PARAM; } - + + const uint32_t encrypted_bytes = (uint32_t)BN_num_bytes(ciphertext_object->ciphertext); + + if (encrypted_bytes > ciphertext_object->cipher_size) + { + return PAILLIER_ERROR_INVALID_CIPHER_TEXT; + } + if (ciphertext_real_len) { - *ciphertext_real_len = (uint32_t)BN_num_bytes(ciphertext_object->ciphertext); + *ciphertext_real_len = ciphertext_object->cipher_size; } - if (!ciphertext || ciphertext_len < (uint32_t)BN_num_bytes(ciphertext_object->ciphertext)) + if (!ciphertext || ciphertext_len < ciphertext_object->cipher_size) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } - if (BN_bn2bin(ciphertext_object->ciphertext, ciphertext) <= 0) + if (BN_bn2binpad(ciphertext_object->ciphertext, ciphertext, ciphertext_object->cipher_size) <= 0) { return PAILLIER_ERROR_UNKNOWN; } @@ -1848,8 +1907,18 @@ void paillier_free_ciphertext(paillier_ciphertext_t *ciphertext_object) if (ciphertext_object) { BN_free(ciphertext_object->ciphertext); - BN_free(ciphertext_object->r); + BN_clear_free(ciphertext_object->r); free(ciphertext_object); } } +long paillier_error_from_openssl() +{ + long ret = ERR_get_error() * -1; + if (0 == ret) + { + ret = PAILLIER_ERROR_UNKNOWN; + } + + return ret; +} \ No newline at end of file diff --git a/src/common/crypto/paillier/paillier_internal.h b/src/common/crypto/paillier/paillier_internal.h index 04811ed..520a016 100644 --- a/src/common/crypto/paillier/paillier_internal.h +++ b/src/common/crypto/paillier/paillier_internal.h @@ -23,7 +23,7 @@ struct paillier_private_key paillier_public_key_t pub; BIGNUM *p; BIGNUM *q; - BIGNUM *lamda; // phi(n) + BIGNUM *lambda; // phi(n) BIGNUM *mu; // phi(n) ^ (-1) in mod(n) }; @@ -31,11 +31,13 @@ struct paillier_ciphertext { BIGNUM *ciphertext; BIGNUM *r; + uint32_t cipher_size; //size in bytes of the ciphertext if serialized }; long paillier_encrypt_openssl_internal(const paillier_public_key_t *key, BIGNUM *ciphertext, const BIGNUM *r, const BIGNUM *plaintext, BN_CTX *ctx); long paillier_decrypt_openssl_internal(const paillier_private_key_t *key, const BIGNUM *ciphertext, BIGNUM *plaintext, BN_CTX *ctx); uint64_t paillier_L(BIGNUM *res, const BIGNUM *x, const BIGNUM *n, BN_CTX *ctx); +long paillier_error_from_openssl(); #ifdef __cplusplus } diff --git a/src/common/crypto/paillier/paillier_zkp.c b/src/common/crypto/paillier/paillier_zkp.c index c7d0145..3a2e172 100644 --- a/src/common/crypto/paillier/paillier_zkp.c +++ b/src/common/crypto/paillier/paillier_zkp.c @@ -1,5 +1,5 @@ #include "paillier_internal.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" #include "alpha.h" #include @@ -14,7 +14,7 @@ // this is the minimal required security // can be used if (pub->n mod 4) == 1 -#define PAILLIER_BLUM_STATISTICAL_SECURITY_MINIMAL_REQUIRED 64 +#define PAILLIER_BLUM_STATISTICAL_SECURITY_MINIMAL_REQUIRED 64 #define FACTORIZANTION_ZKP_SALT "factorization zkpok" #define COPRIME_ZKP_SALT "coprime zkp" @@ -80,12 +80,12 @@ static inline long update_with_bignum(SHA256_CTX *ctx, const BIGNUM *bn) return PAILLIER_SUCCESS; } -long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, - const uint8_t *aad, - uint32_t aad_len, - uint8_t x[PAILLIER_SHA256_LEN], - uint8_t *y, - uint32_t y_len, +long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, + const uint8_t *aad, + uint32_t aad_len, + uint8_t x[PAILLIER_SHA256_LEN], + uint8_t *y, + uint32_t y_len, uint32_t *y_real_len) { BN_CTX *ctx = NULL; @@ -128,7 +128,7 @@ long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, ctx = BN_CTX_new(); if (!ctx) { - return ERR_get_error() * -1; + return paillier_error_from_openssl(); } BN_CTX_start(ctx); @@ -214,7 +214,11 @@ long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, { do { - deterministic_rand(seed, n_len, z, &seed); + if (NULL == deterministic_rand(seed, n_len, z, &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } } while (BN_cmp(z, priv->pub.n) >= 0); if (!BN_bn2bin(z, tmp)) @@ -241,7 +245,7 @@ long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, goto cleanup; } - if (!BN_usub(bn_y, priv->pub.n, priv->lamda)) + if (!BN_sub(bn_y, priv->pub.n, priv->lambda)) { goto cleanup; } @@ -264,7 +268,7 @@ long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } free(n); free(tmp); @@ -274,11 +278,11 @@ long paillier_generate_factorization_zkpok(const paillier_private_key_t *priv, return ret; } -long paillier_verify_factorization_zkpok(const paillier_public_key_t *pub, - const uint8_t *aad, - uint32_t aad_len, - const uint8_t x[PAILLIER_SHA256_LEN], - const uint8_t *y, +long paillier_verify_factorization_zkpok(const paillier_public_key_t *pub, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t x[PAILLIER_SHA256_LEN], + const uint8_t *y, uint32_t y_len) { BN_CTX *ctx = NULL; @@ -323,7 +327,7 @@ long paillier_verify_factorization_zkpok(const paillier_public_key_t *pub, ctx = BN_CTX_new(); if (!ctx) { - return ERR_get_error() * -1; + return paillier_error_from_openssl(); } BN_CTX_start(ctx); @@ -384,7 +388,11 @@ long paillier_verify_factorization_zkpok(const paillier_public_key_t *pub, do { - deterministic_rand(seed, n_len, z[i], &seed); + if (NULL == deterministic_rand(seed, n_len, z[i], &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } } while (BN_cmp(z[i], pub->n) >= 0); if (!BN_bn2bin(z[i], tmp)) @@ -462,7 +470,7 @@ long paillier_verify_factorization_zkpok(const paillier_public_key_t *pub, cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } free(n); free(tmp); @@ -489,7 +497,7 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin { return PAILLIER_ERROR_INVALID_KEY; } - + if (!aad && aad_len) { return PAILLIER_ERROR_INVALID_PARAM; @@ -522,7 +530,7 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin ctx = BN_CTX_new(); if (!ctx) { - return ERR_get_error() * -1; + return paillier_error_from_openssl(); } BN_CTX_start(ctx); @@ -543,7 +551,7 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin } BN_set_flags(M, BN_FLG_CONSTTIME); - if (!BN_mod_inverse(M, priv->pub.n, priv->lamda, ctx)) + if (!BN_mod_inverse(M, priv->pub.n, priv->lambda, ctx)) { goto cleanup; } @@ -576,13 +584,18 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin { goto cleanup; } - + for (size_t i = 0; i < COPRIME_ZKP_K; ++i) { int is_coprime_res; do { - deterministic_rand(seed, n_len, x, &seed); + if (NULL == deterministic_rand(seed, n_len, x, &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } + is_coprime_res = is_coprime_fast(x, priv->pub.n, ctx); } while (is_coprime_res == 0); @@ -595,12 +608,12 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin { goto cleanup; } - - if (BN_bn2binpad(tmp, y_ptr, n_len) < 0) + + if (BN_bn2binpad(tmp, y_ptr, n_len) <= 0) { goto cleanup; } - + y_ptr += n_len; } @@ -609,9 +622,9 @@ long paillier_generate_coprime_zkp(const paillier_private_key_t *priv, const uin cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } - + free(n); BN_MONT_CTX_free(mont); BN_CTX_end(ctx); @@ -636,17 +649,17 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t { return PAILLIER_ERROR_INVALID_KEY; } - + if (!aad && aad_len) { return PAILLIER_ERROR_INVALID_PARAM; } - + if (!y && y_len) { return PAILLIER_ERROR_INVALID_PARAM; } - + n_len = BN_num_bytes(pub->n); @@ -665,7 +678,7 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t ctx = BN_CTX_new(); if (!ctx) { - return ERR_get_error() * -1; + return paillier_error_from_openssl(); } BN_CTX_start(ctx); @@ -700,7 +713,7 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t { goto cleanup; } - + if (is_coprime_fast(tmp, pub->n, ctx) != 1) { @@ -718,7 +731,7 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t { goto cleanup; } - + SHA256_Init(&sha256_ctx); SHA256_Update(&sha256_ctx, COPRIME_ZKP_SALT, sizeof(COPRIME_ZKP_SALT)); @@ -741,7 +754,11 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t int is_coprime_res; do { - deterministic_rand(seed, n_len, x, &seed); + if (NULL == deterministic_rand(seed, n_len, x, &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } is_coprime_res = is_coprime_fast(x, pub->n, ctx); } while (is_coprime_res == 0); @@ -754,17 +771,17 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t { goto cleanup; } - + if (!BN_bin2bn(y_ptr, n_len, bn_y)) { goto cleanup; } - + if (!BN_mod_exp_mont(tmp, bn_y, pub->n, pub->n, ctx, mont)) { goto cleanup; } - + if (BN_cmp(tmp, x) != 0) { ret = PAILLIER_ERROR_INVALID_PROOF; @@ -778,7 +795,7 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } free(n); BN_MONT_CTX_free(mont); @@ -787,8 +804,11 @@ long paillier_verify_coprime_zkp(const paillier_public_key_t *pub, const uint8_t return ret; } -static inline long init_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_t *proof, BN_CTX *ctx) +#define MINIMUM_NUMBER_OF_NTH_ROOTS (8) + +static inline long init_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_t *proof, uint8_t use_all_nth_roots, BN_CTX *ctx) { + OPENSSL_cleanse(proof, sizeof(zkp_paillier_blum_modulus_proof_t)); proof->w = BN_CTX_get(ctx); if (!proof->w) { @@ -798,42 +818,67 @@ static inline long init_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_t *pro for (size_t i = 0; i < PAILLIER_BLUM_STATISTICAL_SECURITY; i++) { proof->x[i] = BN_CTX_get(ctx); - proof->z[i] = BN_CTX_get(ctx); - if (!proof->x[i] || !proof->z[i]) + if (!proof->x[i]) { return PAILLIER_ERROR_OUT_OF_MEMORY; } + if (use_all_nth_roots || (i < MINIMUM_NUMBER_OF_NTH_ROOTS)) + { + proof->z[i] = BN_CTX_get(ctx); + if (!proof->z[i]) + { + return PAILLIER_ERROR_OUT_OF_MEMORY; + } + } + } return PAILLIER_SUCCESS; } -/* serialization format is sizeof(pub->n) || w || (x || z || a || b) * PAILLIER_BLUM_STATISTICAL_SECURITY */ -static inline uint32_t paillier_blum_zkp_serialized_size(const paillier_public_key_t *pub) +// serialization format is sizeof(pub->n) || w || (x || z || a || b) * PAILLIER_BLUM_STATISTICAL_SECURITY if serialize_all_nth_roots is true +// and sizeof(pub->n) || w || (x || z || a || b) * MINIMUM_NUMBER_OF_NTH_ROOTS + (x || a || b) * (PAILLIER_BLUM_STATISTICAL_SECURITY - MINIMUM_NUMBER_OF_NTH_ROOTS) otherwise +static inline uint32_t paillier_blum_zkp_serialized_size(const paillier_public_key_t *pub, const uint8_t use_all_nth_roots) { int n_len = BN_num_bytes(pub->n); - return sizeof(uint32_t) + n_len + (n_len * 2 + sizeof(uint8_t) * 2) * PAILLIER_BLUM_STATISTICAL_SECURITY; + return sizeof(uint32_t) + + n_len + + (n_len + sizeof(uint8_t) * 2) * PAILLIER_BLUM_STATISTICAL_SECURITY + + (MINIMUM_NUMBER_OF_NTH_ROOTS + use_all_nth_roots * (PAILLIER_BLUM_STATISTICAL_SECURITY - MINIMUM_NUMBER_OF_NTH_ROOTS)) * n_len; //this is for z which is sent at least MINIMUM_NUMBER_OF_NTH_ROOTS times } -static inline void serialize_paillier_blum_zkp(const zkp_paillier_blum_modulus_proof_t *proof, uint32_t n_len, uint8_t *serialized_proof) +static inline long serialize_paillier_blum_zkp(const zkp_paillier_blum_modulus_proof_t *proof, uint32_t n_len, uint8_t serialize_all_nth_roots, uint8_t *serialized_proof) { uint8_t *ptr = serialized_proof; *(uint32_t*)ptr = n_len; ptr += sizeof(uint32_t); - BN_bn2binpad(proof->w, ptr, n_len); + if (BN_bn2binpad(proof->w, ptr, n_len) <= 0) + { + return PAILLIER_ERROR_UNKNOWN; + } ptr += n_len; for (uint32_t i = 0; i < PAILLIER_BLUM_STATISTICAL_SECURITY; ++i) { - BN_bn2binpad(proof->x[i], ptr, n_len); - ptr += n_len; - BN_bn2binpad(proof->z[i], ptr, n_len); + if (BN_bn2binpad(proof->x[i], ptr, n_len) <= 0) + { + return PAILLIER_ERROR_UNKNOWN; + } ptr += n_len; + if (serialize_all_nth_roots || (i < MINIMUM_NUMBER_OF_NTH_ROOTS)) + { + if (BN_bn2binpad(proof->z[i], ptr, n_len) <= 0) + { + return PAILLIER_ERROR_UNKNOWN; + } + ptr += n_len; + } *ptr++ = proof->a[i]; *ptr++ = proof->b[i]; } + return PAILLIER_SUCCESS; } -static inline int deserialize_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_t *proof, uint32_t n_len, const uint8_t *serialized_proof) +static inline int deserialize_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_t *proof, uint32_t n_len, uint8_t deserialize_all_nth_roots, const uint8_t *serialized_proof) { uint32_t proof_n_len; const uint8_t *ptr = serialized_proof; @@ -844,13 +889,13 @@ static inline int deserialize_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_ { return 0; } - + if (!BN_bin2bn(ptr, n_len, proof->w)) { return 0; } - + ptr += n_len; for (uint32_t i = 0; i < PAILLIER_BLUM_STATISTICAL_SECURITY; ++i) @@ -859,35 +904,44 @@ static inline int deserialize_paillier_blum_zkp(zkp_paillier_blum_modulus_proof_ { return 0; } - + ptr += n_len; - if (!BN_bin2bn(ptr, n_len, proof->z[i])) + if (deserialize_all_nth_roots || (i < MINIMUM_NUMBER_OF_NTH_ROOTS)) { - return 0; + if (!BN_bin2bn(ptr, n_len, proof->z[i])) + { + return 0; + } + + ptr += n_len; } - - ptr += n_len; proof->a[i] = *ptr++; proof->b[i] = *ptr++; } return 1; } -static inline uint8_t get_2bit_number(const uint8_t* array, const uint32_t i) +static inline uint8_t get_2bit_number(const uint8_t* array, const uint32_t i) { // Calculate which byte the 2-bit number is in const uint32_t byte_index = i / 4; - + // Calculate the bit position within that byte (0, 2, 4, or 6) const uint32_t bit_position = (i % 4) * 2; - + // Extract the 2-bit number by shifting and masking return (uint8_t)(array[byte_index] >> bit_position) & 0x03; } -long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *proof_real_len) +long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, + uint8_t compute_all_nth_roots, + const uint8_t *aad, + uint32_t aad_len, + uint8_t *serialized_proof, + uint32_t proof_len, + uint32_t *proof_real_len) { BN_CTX *ctx = NULL; BIGNUM *p_remainder = NULL, *q_remainder = NULL; @@ -904,15 +958,15 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con sha256_md_t seed; uint32_t n_len; uint32_t needed_proof_len; - - // since it is needed to randomly choose one of the 4 roots + + // since it is needed to randomly choose one of the 4 roots // in a loop of PAILLIER_BLUM_STATISTICAL_SECURITY iterations - // there is a need for 2 * PAILLIER_BLUM_STATISTICAL_SECURITY bits. + // there is a need for 2 * PAILLIER_BLUM_STATISTICAL_SECURITY bits. // adding 7 will round up if PAILLIER_BLUM_STATISTICAL_SECURITY is not multiple of 8 uint8_t random_bytes[(PAILLIER_BLUM_STATISTICAL_SECURITY * 2 + 7) / 8]; long ret = -1; - + if (!priv) { return PAILLIER_ERROR_INVALID_KEY; @@ -928,7 +982,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con //assume that (BN_mod_word(priv->p, 8) == 3 && BN_mod_word(priv->q, 8) == 7) - needed_proof_len = paillier_blum_zkp_serialized_size(&priv->pub); + needed_proof_len = paillier_blum_zkp_serialized_size(&priv->pub, !!compute_all_nth_roots); if (proof_real_len) { *proof_real_len = needed_proof_len; @@ -938,7 +992,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con { return PAILLIER_ERROR_BUFFER_TOO_SHORT; } - + OPENSSL_cleanse(serialized_proof, proof_len); n_len = BN_num_bytes(priv->pub.n); @@ -950,15 +1004,15 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con } BN_CTX_start(ctx); - ret = init_paillier_blum_zkp(&proof, ctx); + ret = init_paillier_blum_zkp(&proof, !!compute_all_nth_roots, ctx); if (ret != PAILLIER_SUCCESS) { goto cleanup; } - - //reset return value so if following statements fail a propper error would be reported - ret = -1; + + //reset return value so if following statements fail a proper error would be reported + ret = -1; p_remainder = BN_CTX_get(ctx); q_remainder = BN_CTX_get(ctx); @@ -1000,7 +1054,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con // Satisfying w = -a^4 mod p and w = b^4 mod q // choose w to be 2^n and calculate a and b - // calculate p_remainder and q_remainder wich will be used to quickly find value in mod n if we know + // calculate p_remainder and q_remainder which will be used to quickly find value in mod n if we know // the value in mod p and mod q using Chinese remainder theorem if (!BN_mod_inverse(p_remainder, priv->p, priv->q, ctx)) // p_remainder = p^(-1) mod q { @@ -1011,7 +1065,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con goto cleanup; } - //since p and q are secret, their dericative require also const time calculations + //since p and q are secret, their derivative require also const time calculations BN_set_flags(p_remainder, BN_FLG_CONSTTIME); BN_set_flags(q_remainder, BN_FLG_CONSTTIME); @@ -1023,9 +1077,9 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con { goto cleanup; } - - if (!BN_mod_inverse(n_inverse_mod_phi_n, priv->pub.n, priv->lamda, ctx)) // To compute z[i] + + if (!BN_mod_inverse(n_inverse_mod_phi_n, priv->pub.n, priv->lambda, ctx)) // To compute z[i] { goto cleanup; } @@ -1043,7 +1097,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con goto cleanup; } - //since p and q are primes, the are odd. Clearing last bit is same as doing minus 1 + //since p and q are primes, they are odd. Clearing last bit is same as doing minus 1 // so p_minus_1 = p -1 , q_minus_1 = q - 1 if (!BN_clear_bit(p_minus_1, 0) || !BN_clear_bit(q_minus_1, 0)) { @@ -1065,16 +1119,16 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con BN_set_flags(p_exp_4th, BN_FLG_CONSTTIME); BN_set_flags(q_exp_4th, BN_FLG_CONSTTIME); - + // Since p = 3[4], then ((p+1)/4)^2 mod (p-1) = (2/4)^2 mod (p-1) = 1/4 mod (p-1) // So the exponent ((p+1)/4)^2 can be used to compute 4th root. - + if (!BN_add_word(p_exp_4th, 1)) //p_exp_4th = p + 1 { goto cleanup; } - if (!BN_rshift(p_exp_4th, p_exp_4th, 2)) //p_exp_4th = (p + 1) / 4 + if (!BN_rshift(p_exp_4th, p_exp_4th, 2)) //p_exp_4th = (p + 1) / 4 { goto cleanup; } @@ -1108,7 +1162,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con // so fulfills the above condition. For the security proof to hold, though, we need to set w = 2^N [N]. // Otherwise, we generate w with (-1, 1) Jacobi signs wrt (p,q) by Chinese remainder theorem // Satisfying w = -a^4 mod p and w = b^4 mod q for random a,b - // set w to 2 and computer (w=2^n % n) + // set w to 2 and compute (w=2^n % n) if (!BN_set_word(proof.w, 2) || !BN_mod_exp(proof.w, proof.w, priv->pub.n, priv->pub.n, ctx)) { goto cleanup; @@ -1118,7 +1172,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con // It mainly means that // | correction^4 = -w mod p // | correction^4 = w mod q - // reminder: p_exp_4th = ((p + 1) / 4) ^ 2 mod (p - 1) which is (2/4) ^2 mod (p - 1) = 1/4 mod (p - 1) + // reminder: p_exp_4th = ((p + 1) / 4) ^ 2 mod (p - 1) which is (2/4) ^2 mod (p - 1) = 1/4 mod (p - 1) // q_exp_4th = ((q + 1) / 4) ^ 2 mod (q - 1) which is (2/4) ^2 mod (q - 1) = 1/4 mod (q - 1) // and we need to calculate w ^ 1/4 mod (N) if (!BN_mod_exp(a, proof.w, p_exp_4th, priv->p, ctx) || // a = (w ^ ((2/4) ^2 mod (p - 1))) mod p @@ -1142,7 +1196,7 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con goto cleanup; } - // Calculate seed for deterministric rand which in turn will be used for generating y values using hash of the current state. + // Calculate seed for deterministic rand which in turn will be used for generating y values using hash of the current state. // This comes instead of receiving y values from the other party SHA256_Init(&sha256_ctx); @@ -1166,14 +1220,14 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con SHA256_Final(seed, &sha256_ctx); - //reset return value so if following statements fail a propper error would be reported + //reset return value so if following statements fail a proper error would be reported ret = -1; // The following randomization is needed for the security proof - if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) + if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) { goto cleanup; - } + } for (uint32_t i = 0; i < PAILLIER_BLUM_STATISTICAL_SECURITY; ++i) { @@ -1183,13 +1237,21 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con do { - deterministic_rand(seed, n_len, y, &seed); + if (NULL == deterministic_rand(seed, n_len, y, &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } } while (BN_cmp(y, priv->pub.n) >= 0); - //while we could do it only once, we still fill all z values for the backward compatibility - if (!BN_mod_exp(proof.z[i], y, n_inverse_mod_phi_n, priv->pub.n, ctx)) + // we compute it MINIMUM_NUMBER_OF_NTH_ROOTS times only if 'compute_all_nth_roots == 0' + // otherwise, we compute it everytime. + if (compute_all_nth_roots || (i < MINIMUM_NUMBER_OF_NTH_ROOTS)) { - goto cleanup; + if (!BN_mod_exp(proof.z[i], y, n_inverse_mod_phi_n, priv->pub.n, ctx)) + { + goto cleanup; + } } // Compute potential 4th root modulo prime, and get legendre symbol 0/1 using 4th power @@ -1239,9 +1301,9 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con { goto cleanup; } - + // We'll chose proof.x[i] randomly as +/- p_4th_root +/-q_4th_root - switch (get_2bit_number(random_bytes, i)) + switch (get_2bit_number(random_bytes, i)) { case 0: // p_4th_root + q_4th_root @@ -1288,17 +1350,16 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con } } - serialize_paillier_blum_zkp(&proof, n_len, serialized_proof); + ret = serialize_paillier_blum_zkp(&proof, n_len, compute_all_nth_roots, serialized_proof); - ret = PAILLIER_SUCCESS; cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } //where DUPed - need to be freed explicitly - BN_clear_free(p_minus_1); + BN_clear_free(p_minus_1); BN_clear_free(q_minus_1); BN_clear_free(p_exp_4th); BN_clear_free(q_exp_4th); @@ -1354,7 +1415,12 @@ long paillier_generate_paillier_blum_zkp(const paillier_private_key_t *priv, con return ret; } -long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len) +long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, + uint8_t use_all_nth_roots, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t *serialized_proof, + uint32_t proof_len) { BN_CTX *ctx = NULL; zkp_paillier_blum_modulus_proof_t proof; @@ -1370,28 +1436,28 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u { return PAILLIER_ERROR_INVALID_KEY; } - + if (!aad && aad_len) { return PAILLIER_ERROR_INVALID_PARAM; } - - if (!serialized_proof || proof_len != paillier_blum_zkp_serialized_size(pub)) + + if (!serialized_proof || proof_len != paillier_blum_zkp_serialized_size(pub, !!use_all_nth_roots)) { return PAILLIER_ERROR_INVALID_PARAM; } - + if (!BN_is_odd(pub->n)) // must be odd { return PAILLIER_ERROR_INVALID_KEY; } - + if (BN_is_bit_set(pub->n, 1) != 0) // should be even because n % 4 == 1 { return PAILLIER_ERROR_INVALID_KEY; } - + ctx = BN_CTX_new(); if (!ctx) @@ -1419,12 +1485,12 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u n_len = BN_num_bytes(pub->n); - if (init_paillier_blum_zkp(&proof, ctx) != PAILLIER_SUCCESS) + if (init_paillier_blum_zkp(&proof, use_all_nth_roots, ctx) != PAILLIER_SUCCESS) { goto cleanup; } - - if (!deserialize_paillier_blum_zkp(&proof, n_len, serialized_proof)) + + if (!deserialize_paillier_blum_zkp(&proof, n_len, use_all_nth_roots, serialized_proof)) { ret = PAILLIER_ERROR_INVALID_PROOF; goto cleanup; @@ -1436,19 +1502,19 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u { SHA256_Update(&sha256_ctx, aad, aad_len); } - + ret = update_with_bignum(&sha256_ctx, pub->n); if (ret != PAILLIER_SUCCESS) { goto cleanup; } - + ret = update_with_bignum(&sha256_ctx, proof.w); if (ret != PAILLIER_SUCCESS) { goto cleanup; } - + SHA256_Final(seed, &sha256_ctx); ret = -1; //reset return value so goto cleanup could be used @@ -1459,20 +1525,48 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u goto cleanup; } - - //prepare tmp for the 1st iteration to verify z - if (!BN_mod_exp(tmp, proof.z[0], pub->n, pub->n, ctx)) + if (use_all_nth_roots) { - goto cleanup; + // if we use all nth roots, then only a coprimality with 3 is required + BN_ULONG mod = BN_mod_word(pub->n, 3); + if (mod == (BN_ULONG) -1) + { + ret = PAILLIER_ERROR_UNKNOWN; + goto cleanup; + } + if (mod == 0) + { + ret = PAILLIER_ERROR_INVALID_PROOF; + goto cleanup; + } } + else + { + // coprimality test with the product of many small primes. + if (!BN_bin2bn(alpha_bin, alpha_bin_len, tmp)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } - // during development of 2 out of 2 MPC it was decided that + if (is_coprime_fast(tmp, pub->n, ctx) != 1) + { + ret = PAILLIER_ERROR_INVALID_PROOF; + goto cleanup; + } + } + + // during development of 2 out of 2 MPC it was decided that // PAILLIER_BLUM_STATISTICAL_SECURITY_MINIMAL_REQUIRED is enough for (uint32_t i = 0; i < PAILLIER_BLUM_STATISTICAL_SECURITY_MINIMAL_REQUIRED; ++i) { do { - deterministic_rand(seed, n_len, y, &seed); + if (NULL == deterministic_rand(seed, n_len, y, &seed)) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } } while (BN_cmp(y, pub->n) >= 0); if (is_coprime_fast(y, pub->n, ctx) != 1) @@ -1481,9 +1575,14 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u goto cleanup; } - // also z is enough to verify only once for the 1st y - if (0 == i) + // also z is enough to verify only once for the 1st y regardless of use_all_nth_roots + if (use_all_nth_roots || (i < MINIMUM_NUMBER_OF_NTH_ROOTS)) { + if (!BN_mod_exp(tmp, proof.z[i], pub->n, pub->n, ctx)) + { + goto cleanup; + } + if (BN_cmp(tmp, y) != 0) //ensure that y == z^n in mod n { ret = PAILLIER_ERROR_INVALID_PROOF; @@ -1495,7 +1594,7 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u ret = PAILLIER_ERROR_INVALID_PROOF; goto cleanup; } - + if (!BN_sub(tmp, pub->n, BN_value_one())) //tmp = n - 1 { goto cleanup; @@ -1512,12 +1611,12 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u { goto cleanup; } - + if (!BN_mod_sqr(tmp, tmp, pub->n, ctx)) { goto cleanup; } - + if (proof.b[i]) { if (!BN_mod_mul(y, proof.w, y, pub->n, ctx)) @@ -1546,9 +1645,9 @@ long paillier_verify_paillier_blum_zkp(const paillier_public_key_t *pub, const u cleanup: if (ret < 0) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } - + BN_CTX_end(ctx); BN_CTX_free(ctx); diff --git a/src/common/crypto/paillier_commitment/paillier_commitment.c b/src/common/crypto/paillier_commitment/paillier_commitment.c index 366abc7..60f9d13 100644 --- a/src/common/crypto/paillier_commitment/paillier_commitment.c +++ b/src/common/crypto/paillier_commitment/paillier_commitment.c @@ -2,7 +2,7 @@ #include "crypto/paillier_commitment/paillier_commitment.h" #include "crypto/commitments/damgard_fujisaki.h" #include "crypto/zero_knowledge_proof/range_proofs.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" #include "../zero_knowledge_proof/zkp_constants_internal.h" #include "../paillier/paillier_internal.h" #include "../commitments/damgard_fujisaki_internal.h" @@ -19,10 +19,10 @@ static inline uint32_t PAILLIER_COMMITMENTS_LAMBDA_BITSIZE(const uint32_t n_bitlen) { return (2 * ZKPOK_OPTIM_L_SIZE(n_bitlen) * 8); -} +} #define PAILLIER_COMMITMENTS_MIN_KEY_SIZE (PAILLIER_COMMITMENTS_MIN_KEY_BITSIZE / 8) -#define PAILLIER_COMMITMENTS_MAX_KEY_SIZE (8192) +#define PAILLIER_COMMITMENTS_MAX_KEY_SIZE (8192) #define PAILLIER_COMMITMENTS_MAX_SERIALIZED_SIZE (64 * 1024) static inline long paillier_commitment_init_montgomery(paillier_commitment_public_key_t *pub, BN_CTX *ctx) @@ -32,7 +32,7 @@ static inline long paillier_commitment_init_montgomery(paillier_commitment_publi { return PAILLIER_ERROR_OUT_OF_MEMORY; } - + if (!pub->mont_n2) { pub->mont_n2 = BN_MONT_CTX_new(); @@ -52,7 +52,7 @@ static inline long paillier_commitment_init_montgomery(paillier_commitment_publi local_ctx = NULL; } - return pub->mont_n2 ? PAILLIER_SUCCESS : PAILLIER_ERROR_OUT_OF_MEMORY; + return pub->mont_n2 ? PAILLIER_SUCCESS : PAILLIER_ERROR_OUT_OF_MEMORY; } static void paillier_commitment_cleanup_public_key(paillier_commitment_public_key_t *pub) @@ -61,19 +61,19 @@ static void paillier_commitment_cleanup_public_key(paillier_commitment_public_ke { BN_free(pub->n); pub->n = NULL; - + BN_free(pub->t); pub->t = NULL; - + BN_free(pub->s); pub->s = NULL; - + BN_free(pub->n2); pub->n2 = NULL; - + BN_free(pub->rho); pub->rho = NULL; - + BN_free(pub->sigma_0); pub->sigma_0 = NULL; @@ -93,25 +93,25 @@ static void paillier_commitment_cleanup_private_key(paillier_commitment_private_ BN_clear_free(priv->p); priv->p = NULL; - + BN_clear_free(priv->q); priv->q = NULL; - + BN_clear_free(priv->lambda); priv->lambda = NULL; - + BN_clear_free(priv->p2); priv->p2 = NULL; - + BN_clear_free(priv->q2); priv->q2 = NULL; - + BN_clear_free(priv->q2_inv_p2); priv->q2_inv_p2 = NULL; - + BN_clear_free(priv->phi_n); priv->phi_n = NULL; - + BN_clear_free(priv->phi_n_inv); priv->phi_n_inv = NULL; } @@ -135,11 +135,11 @@ uint32_t paillier_commitment_public_bitsize(const paillier_commitment_public_key { if (pub) { - // we use number of bytes to allwo some tolerance + // we use number of bytes to allow some tolerance // since we multiply to primes of half bitlength the result may be not exactly twice number of bits // the smallest product of multiplying two primes of size n bits will have 2n-2 bits - // This is why use use number of bytes to round up - return (uint32_t)BN_num_bytes(pub->n) * 8; + // This is why we use number of bytes to round up + return (uint32_t)BN_num_bytes(pub->n) * 8; } return 0; @@ -175,9 +175,9 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie { goto cleanup; } - - if (!BN_set_word(three, 3) || - !BN_set_word(seven, 7) || + + if (!BN_set_word(three, 3) || + !BN_set_word(seven, 7) || !BN_set_word(eight, 8)) { goto cleanup; @@ -191,13 +191,13 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie priv->q2_inv_p2 = BN_new(); priv->phi_n = BN_new(); priv->phi_n_inv = BN_new(); - if (!priv->p || - !priv->q || - !priv->lambda || - !priv->p2 || - !priv->q2 || - !priv->q2_inv_p2 || - !priv->phi_n || + if (!priv->p || + !priv->q || + !priv->lambda || + !priv->p2 || + !priv->q2 || + !priv->q2_inv_p2 || + !priv->phi_n || !priv->phi_n_inv) { goto cleanup; @@ -213,7 +213,7 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie if (!priv->pub.t || !priv->pub.s || - !priv->pub.n || + !priv->pub.n || !priv->pub.n2 || !priv->pub.rho || !priv->pub.sigma_0) @@ -229,11 +229,11 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie { // note - originally we had used p and q to be 4*k + 3. The new form keeps this requirement because // both p and q still satisfies 4 * k + 3 - // p needs to be in the form of p = 8 * k + 3 ( p = 3 mod 8) to allow efficient calculation off fourth roots + // p needs to be in the form of p = 8 * k + 3 ( p = 3 mod 8) to allow efficient calculation off fourth roots // (needed in paillier blum zkp) if (ELLIPTIC_CURVE_ALGEBRA_SUCCESS != generate_tough_prime(priv->p, key_len / 2, PAILLIER_COMMITMENTS_TOUGH_SUBPRIMES_BITSIZE, eight, three, ctx)) - { + { ret = PAILLIER_ERROR_UNKNOWN; goto cleanup; } @@ -249,7 +249,7 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie //because p and q are tough primes their length can differ in key_len / 2 * PAILLIER_COMMITMENTS_TOUGH_SUBPRIMES_BITSIZE bits assert((uint32_t)BN_num_bits(priv->q) == (key_len / 2)); - + // Compute n = pq if (!BN_mul(priv->pub.n, priv->p, priv->q, ctx)) { @@ -266,8 +266,8 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie } } while (paillier_commitment_public_bitsize(&priv->pub) != key_len || - BN_cmp(priv->p, priv->q) == 0 || - !BN_gcd(tmp, priv->phi_n, priv->pub.n, ctx) || + BN_cmp(priv->p, priv->q) == 0 || + !BN_gcd(tmp, priv->phi_n, priv->pub.n, ctx) || !BN_is_one(tmp)); if (!BN_mod_inverse(priv->phi_n_inv, priv->phi_n, priv->pub.n, ctx)) @@ -283,7 +283,7 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie { goto cleanup; } - + // generate random r in mod n do { @@ -292,7 +292,7 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie goto cleanup; } - } while (!BN_gcd(tmp, r, priv->pub.n, ctx) || + } while (!BN_gcd(tmp, r, priv->pub.n, ctx) || !BN_is_one(tmp)); // t = r ^ 2 in mod n @@ -301,7 +301,8 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie goto cleanup; } - if (!BN_rand(priv->lambda, PAILLIER_COMMITMENTS_LAMBDA_BITSIZE(key_len), BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) + // there is an assert in the serialization for sanity function that requires lambda to be of the right size + if (!BN_rand(priv->lambda, PAILLIER_COMMITMENTS_LAMBDA_BITSIZE(key_len), BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY)) { goto cleanup; } @@ -329,19 +330,19 @@ static long paillier_commitment_generate_private(const uint32_t key_len, paillie goto cleanup; } - + cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (r) { BN_clear(r); } - + BN_CTX_end(ctx); return ret; @@ -357,13 +358,13 @@ long paillier_commitment_generate_private_key(const uint32_t key_len, paillier_c { return PAILLIER_ERROR_INVALID_PARAM; } - + *priv = NULL; if (key_len < PAILLIER_COMMITMENTS_MIN_KEY_BITSIZE) { return PAILLIER_ERROR_KEYLEN_TOO_SHORT; - + } if (key_len % PAILLIER_COMMITMENTS_TOUGH_SUBPRIMES_BITSIZE != 0) { @@ -381,8 +382,8 @@ long paillier_commitment_generate_private_key(const uint32_t key_len, paillier_c ctx = BN_CTX_secure_new(); if (!ctx) { - //not jumping to cleanup to avoid initializating all local variables - return PAILLIER_ERROR_OUT_OF_MEMORY; + //not jumping to cleanup to avoid initializing all local variables + return PAILLIER_ERROR_OUT_OF_MEMORY; } local_private = (paillier_commitment_private_key_t*) calloc(1, sizeof(paillier_commitment_private_key_t)); @@ -400,7 +401,7 @@ long paillier_commitment_generate_private_key(const uint32_t key_len, paillier_c } cleanup: - + BN_CTX_free(ctx); if (ret) @@ -451,7 +452,7 @@ long paillier_commitment_public_key_serialize(const paillier_commitment_public_k { return PAILLIER_ERROR_BUFFER_TOO_SHORT; } - + if (!buffer) { return PAILLIER_ERROR_INVALID_PARAM; @@ -460,35 +461,35 @@ long paillier_commitment_public_key_serialize(const paillier_commitment_public_k *(uint32_t*)ptr = n_len; ptr += sizeof(uint32_t); - if (!BN_bn2binpad(pub->n, ptr, n_len)) + if (BN_bn2binpad(pub->n, ptr, n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += n_len; - - if (!BN_bn2binpad(pub->t, ptr, n_len)) + + if (BN_bn2binpad(pub->t, ptr, n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += n_len; - if (!BN_bn2binpad(pub->s, ptr, n_len)) + if (BN_bn2binpad(pub->s, ptr, n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += n_len; if (!is_reduced) { - if (!BN_bn2binpad(pub->rho, ptr, 2 * n_len)) + if (BN_bn2binpad(pub->rho, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; - - if (!BN_bn2binpad(pub->sigma_0, ptr, 2 * n_len)) + + if (BN_bn2binpad(pub->sigma_0, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; } @@ -539,7 +540,7 @@ paillier_commitment_public_key_t * paillier_commitment_public_key_deserialize(co if (buffer_len < needed_len) { goto cleanup; - } + } pub->n = BN_bin2bn(buffer, n_len, NULL); buffer += n_len; @@ -561,7 +562,7 @@ paillier_commitment_public_key_t * paillier_commitment_public_key_deserialize(co { goto cleanup; } - + if (PAILLIER_SUCCESS != paillier_commitment_init_montgomery(pub, ctx)) { goto cleanup; @@ -585,7 +586,7 @@ paillier_commitment_public_key_t * paillier_commitment_public_key_deserialize(co // calculate t_n and s_n pub->rho = BN_new(); pub->sigma_0 = BN_new(); - if (!pub->rho || + if (!pub->rho || !pub->sigma_0) { goto cleanup; @@ -611,14 +612,14 @@ paillier_commitment_public_key_t * paillier_commitment_public_key_deserialize(co return pub; cleanup: - + BN_CTX_free(ctx); paillier_commitment_free_public_key(pub); return NULL; } -#define NUM_OF_ADDITIONAL_COMMITMENT_VALUES (10) +#define NUM_OF_ADDITIONAL_COMMITMENT_VALUES (10) long paillier_commitment_private_key_serialize(const paillier_commitment_private_key_t *priv, uint8_t *buffer, @@ -641,11 +642,10 @@ long paillier_commitment_private_key_serialize(const paillier_commitment_private assert((uint32_t)BN_num_bytes(priv->lambda) == lambda_len); - // going to store p, q, lambda, t, s, rho, sigma_0, q2_inv_p2 and phi_n_inv needed_len = sizeof(uint32_t) + prime_len * 2 + // p and q - lambda_len + + lambda_len + n_len * NUM_OF_ADDITIONAL_COMMITMENT_VALUES; if (real_buffer_len) @@ -657,7 +657,7 @@ long paillier_commitment_private_key_serialize(const paillier_commitment_private { return PAILLIER_ERROR_BUFFER_TOO_SHORT; } - + if (!buffer) { return PAILLIER_ERROR_INVALID_PARAM; @@ -666,62 +666,62 @@ long paillier_commitment_private_key_serialize(const paillier_commitment_private *(uint32_t*)ptr = n_len; ptr += sizeof(uint32_t); - if (!BN_bn2binpad(priv->p, ptr, prime_len)) + if (BN_bn2binpad(priv->p, ptr, prime_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += prime_len; - if (!BN_bn2binpad(priv->q, ptr, prime_len)) + if (BN_bn2binpad(priv->q, ptr, prime_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += prime_len; - if (!BN_bn2binpad(priv->lambda, ptr, lambda_len)) + if (BN_bn2binpad(priv->lambda, ptr, lambda_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += lambda_len; - if (!BN_bn2binpad(priv->pub.t, ptr, n_len)) + if (BN_bn2binpad(priv->pub.t, ptr, n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += n_len; - if (!BN_bn2binpad(priv->pub.s, ptr, n_len)) + if (BN_bn2binpad(priv->pub.s, ptr, n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += n_len; - if (!BN_bn2binpad(priv->pub.rho, ptr, 2 * n_len)) + if (BN_bn2binpad(priv->pub.rho, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; - if (!BN_bn2binpad(priv->pub.sigma_0, ptr, 2 * n_len)) + if (BN_bn2binpad(priv->pub.sigma_0, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; - if (!BN_bn2binpad(priv->q2_inv_p2, ptr, 2 * n_len)) + if (BN_bn2binpad(priv->q2_inv_p2, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; - if (!BN_bn2binpad(priv->phi_n_inv, ptr, 2 * n_len)) + if (BN_bn2binpad(priv->phi_n_inv, ptr, 2 * n_len) <= 0) { - return -1 * ERR_get_error(); + return paillier_error_from_openssl(); } ptr += 2 * n_len; - + assert ( (uint32_t)(ptr - buffer) == needed_len ); return PAILLIER_SUCCESS; @@ -749,7 +749,7 @@ paillier_commitment_private_key_t * paillier_commitment_private_key_deserialize( { goto cleanup; } - + n_len = *(const uint32_t*)buffer; buffer += sizeof(uint32_t); prime_len = ((n_len * 8 / 2) + 7) / 8; @@ -793,14 +793,14 @@ paillier_commitment_private_key_t * paillier_commitment_private_key_deserialize( priv->phi_n_inv = BN_bin2bn(buffer, 2 * n_len, NULL); buffer += 2 * n_len; - if (!priv->p || - !priv->q || - !priv->lambda || - !priv->pub.t || - !priv->pub.s || - !priv->pub.rho || - !priv->pub.sigma_0 || - !priv->q2_inv_p2 || + if (!priv->p || + !priv->q || + !priv->lambda || + !priv->pub.t || + !priv->pub.s || + !priv->pub.rho || + !priv->pub.sigma_0 || + !priv->q2_inv_p2 || !priv->phi_n_inv) { goto cleanup; @@ -836,41 +836,41 @@ paillier_commitment_private_key_t * paillier_commitment_private_key_deserialize( { goto cleanup; } - + // calculate n^2, p^2, q^2 and 1/q^2 in mod p^2 if (!BN_sqr(priv->pub.n2, priv->pub.n, ctx) || !BN_sqr(priv->p2, priv->p, ctx) || !BN_sqr(priv->q2, priv->q, ctx)) { goto cleanup; - } - + } + paillier_commitment_set_consttime_flag(priv); if (PAILLIER_SUCCESS != paillier_commitment_init_montgomery(&priv->pub, ctx)) { goto cleanup; } - + BN_CTX_free(ctx); return priv; -cleanup: +cleanup: BN_CTX_free(ctx); paillier_commitment_free_private_key(priv); return NULL; } -// this is the paillir public key encryption with small group. -long paillier_commitment_encrypt_openssl_fixed_power_internal(const paillier_commitment_public_key_t *pub, - BIGNUM *ciphertext, - const BIGNUM *r_power, - const BIGNUM *message, +// this is the paillier public key encryption with small group. +long paillier_commitment_encrypt_openssl_fixed_power_internal(const paillier_commitment_public_key_t *pub, + BIGNUM *ciphertext, + const BIGNUM *r_power, + const BIGNUM *message, BN_CTX *ctx) { BIGNUM *tmp1 = NULL, *tmp2 = NULL; int ret = -1; - + if (!pub || !ciphertext || !message || !r_power || !ctx) { return PAILLIER_ERROR_INVALID_PARAM; @@ -886,13 +886,13 @@ long paillier_commitment_encrypt_openssl_fixed_power_internal(const paillier_com { goto cleanup; } - + if (!BN_mul(tmp1, pub->n, message, ctx) || // tmp1 = n * message !BN_add_word(tmp1, 1)) // tmp1 = (1 + n * message) { goto cleanup; } - + if (!BN_mod_exp_mont(tmp2, pub->rho, r_power, pub->n2, ctx, pub->mont_n2)) //tmp2 = (t^n)^r_power_local mod n2 { goto cleanup; @@ -902,43 +902,41 @@ long paillier_commitment_encrypt_openssl_fixed_power_internal(const paillier_com { goto cleanup; } - + ret = PAILLIER_SUCCESS; cleanup: - + if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } - + BN_CTX_end(ctx); return ret; } -// r_power is an optional output paramer -static long paillier_commitment_encrypt_openssl_internal(const paillier_commitment_public_key_t *pub, +// r_power is an optional output parameter +static long paillier_commitment_encrypt_openssl_internal(const paillier_commitment_public_key_t *pub, const uint32_t r_power_bitsize, - const BIGNUM *message, + const BIGNUM *message, BN_CTX *ctx, BIGNUM *ciphertext) { BIGNUM *r_power = NULL; int ret = -1; - + if (!pub || !ciphertext || !message || !r_power_bitsize || !ctx) { return PAILLIER_ERROR_INVALID_PARAM; } - - if (r_power_bitsize > paillier_commitment_public_bitsize(pub)) - { - return PAILLIER_ERROR_INVALID_PARAM; - } - + + // r_power_bitsize can be even bigger because it is used in pedersen commitment and used as a blinding factor + + BN_CTX_start(ctx); r_power = BN_CTX_get(ctx); @@ -947,53 +945,49 @@ static long paillier_commitment_encrypt_openssl_internal(const paillier_commitme { goto cleanup; } - - do + + // no need to loop and check that r_power is coprime because actual value being used + // as the blinding factor is rho^r_power. It is rho that has to be coprime with n and + // it is checked already when rho is generated + if (!BN_rand(r_power, r_power_bitsize, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) { - if (!BN_rand(r_power, r_power_bitsize, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) - { - goto cleanup; - } - } while (BN_cmp(r_power, pub->n) >= 0 || - 1 != is_coprime_fast(r_power, pub->n, ctx)); + goto cleanup; + } ret = paillier_commitment_encrypt_openssl_fixed_power_internal(pub, ciphertext, r_power, message, ctx); cleanup: - + if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } BN_clear(r_power); - + BN_CTX_end(ctx); return ret; } -// r_power is an optional output paramer -long paillier_commitment_encrypt_openssl_with_private_internal(const paillier_commitment_private_key_t *priv, +// r_power is an optional output parameter +long paillier_commitment_encrypt_openssl_with_private_internal(const paillier_commitment_private_key_t *priv, const uint32_t r_power_bitsize, - const BIGNUM *message, + const BIGNUM *message, BN_CTX *ctx, - BIGNUM *ciphertext, + BIGNUM *ciphertext, BIGNUM *r_power) { int ret = -1; BIGNUM *mod_p2, *mod_q2, *tmp; - + if (!priv || !ciphertext || !r_power_bitsize || !message || !ctx || !r_power) { return PAILLIER_ERROR_INVALID_PARAM; } - - if (r_power_bitsize > paillier_commitment_public_bitsize(&priv->pub)) - { - return PAILLIER_ERROR_INVALID_PARAM; - } - + + // r_power_bitsize can be even bigger because it is used in pedersen commitment and used as a blinding factor + BN_CTX_start(ctx); mod_p2 = BN_CTX_get(ctx); @@ -1005,20 +999,18 @@ long paillier_commitment_encrypt_openssl_with_private_internal(const paillier_co goto cleanup; } - do + // no need to loop and check that r_power is coprime because actual value being used + // as the blinding factor is rho^r_power. It is rho that has to be coprime with n and + // it is checked already when rho is generated + if (!BN_rand(r_power, r_power_bitsize, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) { - if (!BN_rand(r_power, r_power_bitsize, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) - { - goto cleanup; - } - } while (BN_cmp(r_power, priv->pub.n) >= 0 || - 1 != is_coprime_fast(r_power, priv->pub.n, ctx)); - - + goto cleanup; + } + // Compute ciphertext = g^message*(t^n)^r_power_local mod n^2 // as will select g=n+1 ciphertext = (1+n*message)*(t^n)^r_power_local mod n^2, see https://en.wikipedia.org/wiki/Paillier_cryptosystem // Computed using CRT. - + if (!BN_mod_mul(mod_p2, priv->pub.n, message, priv->p2, ctx) || // mod_p2 = n * message mod p^2 !BN_add_word(mod_p2, 1) || // mod_p2 = n * message + 1 mod p^2 !BN_mod_exp(tmp, priv->pub.rho, r_power, priv->p2, ctx) || // tmp = (t^n) ^ r_power_local mod p^2 @@ -1026,12 +1018,12 @@ long paillier_commitment_encrypt_openssl_with_private_internal(const paillier_co { goto cleanup; } - + if (!BN_mod_mul(mod_q2, priv->pub.n, message, priv->q2, ctx) || // mod_q2 = n * message mod q^2 !BN_add_word(mod_q2, 1) || // mod_q2 = n * message + 1 mod q^2 !BN_mod_exp(tmp, priv->pub.rho, r_power, priv->q2, ctx) || // tmp = (t^n) ^ r_power_local mod q^2 !BN_mod_mul(mod_q2, mod_q2, tmp, priv->q2, ctx)) // mod_q2 = mod_q2 * tmp = (n * message + 1) * (t^n)^r_power_local mod q^2 - { + { goto cleanup; } @@ -1040,13 +1032,13 @@ long paillier_commitment_encrypt_openssl_with_private_internal(const paillier_co ret = PAILLIER_ERROR_UNKNOWN; goto cleanup; } - + ret = PAILLIER_SUCCESS; cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } BN_clear(mod_p2); @@ -1073,12 +1065,12 @@ long paillier_commitment_encrypt(const paillier_commitment_public_key_t *pub, { return PAILLIER_ERROR_INVALID_KEY; } - + if (!plaintext || plaintext_len > (uint32_t)BN_num_bytes(pub->n)) { return PAILLIER_ERROR_INVALID_PLAIN_TEXT; } - + if (ciphertext_real_len) { *ciphertext_real_len = (uint32_t)BN_num_bytes(pub->n2); @@ -1100,14 +1092,14 @@ long paillier_commitment_encrypt(const paillier_commitment_public_key_t *pub, return PAILLIER_ERROR_OUT_OF_MEMORY; } BN_CTX_start(ctx); - + msg = BN_CTX_get(ctx); c = BN_CTX_get(ctx); if (!c || !msg) { goto cleanup; } - + if (!BN_bin2bn(plaintext, plaintext_len, msg)) { goto cleanup; @@ -1127,7 +1119,7 @@ long paillier_commitment_encrypt(const paillier_commitment_public_key_t *pub, goto cleanup; } - if (!BN_bn2binpad(c, ciphertext, (uint32_t)BN_num_bytes(pub->n2))) + if (BN_bn2binpad(c, ciphertext, (uint32_t)BN_num_bytes(pub->n2)) <= 0) { ret = -1; goto cleanup; @@ -1140,7 +1132,7 @@ long paillier_commitment_encrypt(const paillier_commitment_public_key_t *pub, { if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } } @@ -1150,9 +1142,9 @@ long paillier_commitment_encrypt(const paillier_commitment_public_key_t *pub, return ret; } -long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_private_key_t *priv, - const BIGNUM *ciphertext, - BIGNUM *plaintext, +long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_private_key_t *priv, + const BIGNUM *ciphertext, + BIGNUM *plaintext, BN_CTX *ctx) { int ret = -1; @@ -1160,12 +1152,12 @@ long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_priv { return PAILLIER_ERROR_INVALID_KEY; } - + if (!ciphertext) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } - + if (!plaintext) { return PAILLIER_ERROR_INVALID_PLAIN_TEXT; @@ -1193,7 +1185,7 @@ long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_priv goto cleanup; } - // Compute the plaintext = paillier_L(ciphertext^lamda mod n2)*mu mod n + // Compute the plaintext = paillier_L(ciphertext^lambda mod n2)*mu mod n if (ELLIPTIC_CURVE_ALGEBRA_SUCCESS != crt_mod_exp(tmp, ciphertext, priv->phi_n, priv->p2, priv->q2, priv->q2_inv_p2, priv->pub.n2, ctx)) { ret = PAILLIER_ERROR_UNKNOWN; @@ -1205,8 +1197,8 @@ long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_priv { goto cleanup; } - - ret = -1; //revet to openssl error + + ret = -1; //revert to openssl error if (!BN_mod_mul(plaintext, tmp, priv->phi_n_inv, priv->pub.n, ctx)) { @@ -1218,7 +1210,7 @@ long paillier_commitment_decrypt_openssl_internal(const paillier_commitment_priv cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } BN_CTX_end(ctx); @@ -1240,12 +1232,12 @@ long paillier_commitment_decrypt(const paillier_commitment_private_key_t *priv, { return PAILLIER_ERROR_INVALID_KEY; } - + if (!ciphertext || ciphertext_len > (uint32_t)BN_num_bytes(priv->pub.n2)) { return PAILLIER_ERROR_INVALID_CIPHER_TEXT; } - + if (plaintext_real_len) { *plaintext_real_len = (uint32_t)BN_num_bytes(priv->pub.n); @@ -1288,7 +1280,7 @@ long paillier_commitment_decrypt(const paillier_commitment_private_key_t *priv, goto cleanup; } - if (!BN_bn2binpad(msg, plaintext, (uint32_t)BN_num_bytes(priv->pub.n))) + if (BN_bn2binpad(msg, plaintext, (uint32_t)BN_num_bytes(priv->pub.n)) <= 0) { ret = PAILLIER_ERROR_UNKNOWN; goto cleanup; @@ -1343,7 +1335,7 @@ long paillier_commitment_commit_with_private_internal(const paillier_commitment_ goto cleanup; } - if (!BN_MONT_CTX_set(mont_p2, priv->p2, ctx) || + if (!BN_MONT_CTX_set(mont_p2, priv->p2, ctx) || !BN_MONT_CTX_set(mont_q2, priv->q2, ctx)) { goto cleanup; @@ -1381,7 +1373,7 @@ long paillier_commitment_commit_with_private_internal(const paillier_commitment_ cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } if (commitment_modP2) @@ -1396,8 +1388,8 @@ long paillier_commitment_commit_with_private_internal(const paillier_commitment_ BN_MONT_CTX_free(mont_p2); BN_MONT_CTX_free(mont_q2); - - BN_CTX_end(ctx); + + BN_CTX_end(ctx); return ret; @@ -1425,7 +1417,7 @@ long paillier_commitment_commit_internal(const paillier_commitment_public_key_t { return PAILLIER_ERROR_INVALID_PARAM; } - + BN_CTX_start(ctx); tmp = BN_CTX_get(ctx); if (!tmp) @@ -1452,10 +1444,10 @@ long paillier_commitment_commit_internal(const paillier_commitment_public_key_t cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } - BN_CTX_end(ctx); + BN_CTX_end(ctx); return ret; } @@ -1469,7 +1461,7 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, const uint32_t modifier_exp_size, paillier_commitment_with_randomizer_power_t** commitment) { - + long ret = -1; paillier_commitment_with_randomizer_power_t* local_commitment = NULL; BIGNUM *bn_commited = NULL, *bn_random_expo_val = NULL; @@ -1481,7 +1473,7 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, { return PAILLIER_ERROR_INVALID_PARAM; } - + *commitment = NULL; if (modifier || modifier_size || modifier_exp || modifier_exp_size) @@ -1509,11 +1501,17 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, local_commitment->randomizer_exponent = (uint8_t*)calloc(1, local_commitment->randomizer_exponent_size); local_commitment->commitment_size = (uint32_t)BN_num_bytes(pub->n2); local_commitment->commitment = (uint8_t*)calloc(1, local_commitment->commitment_size); - + if (!local_commitment->randomizer_exponent || !local_commitment->commitment) + { + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; + } + ctx = BN_CTX_new(); if (ctx == NULL) { - return PAILLIER_ERROR_OUT_OF_MEMORY; + ret = PAILLIER_ERROR_OUT_OF_MEMORY; + goto cleanup; } BN_CTX_start(ctx); @@ -1543,10 +1541,10 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, { goto cleanup; } - + if (modifier) { - if (!BN_bin2bn(modifier, modifier_size, bn_modifier) || + if (!BN_bin2bn(modifier, modifier_size, bn_modifier) || !BN_bin2bn(modifier_exp, modifier_exp_size, bn_modifier_expo) ) { goto cleanup; @@ -1559,8 +1557,8 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, goto cleanup; } - if (!BN_bn2binpad(bn_random_expo_val, local_commitment->randomizer_exponent, local_commitment->randomizer_exponent_size) || - !BN_bn2binpad(bn_commitment, local_commitment->commitment, local_commitment->commitment_size)) + if (BN_bn2binpad(bn_random_expo_val, local_commitment->randomizer_exponent, local_commitment->randomizer_exponent_size) <= 0 || + BN_bn2binpad(bn_commitment, local_commitment->commitment, local_commitment->commitment_size) <= 0) { ret = -1; goto cleanup; @@ -1571,7 +1569,7 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, { if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } paillier_commitment_commitment_free(local_commitment); } @@ -1581,8 +1579,11 @@ long paillier_commitment_commit(const paillier_commitment_public_key_t *pub, local_commitment = NULL; } - BN_CTX_end(ctx); - BN_CTX_free(ctx); + if (ctx) + { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } return ret; @@ -1604,7 +1605,7 @@ long paillier_commitment_verify(const paillier_commitment_public_key_t *pub, BIGNUM *bn_commitment = NULL, *bn_commitment_expected = NULL; BIGNUM *bn_modifier = NULL, *bn_modifier_expo = NULL; - if (!pub || !commited_value || !commited_value_len || !commitment || + if (!pub || !commited_value || !commited_value_len || !commitment || !commitment->randomizer_exponent_size || !commitment->randomizer_exponent || !commitment->commitment_size || !commitment->commitment) { @@ -1618,7 +1619,7 @@ long paillier_commitment_verify(const paillier_commitment_public_key_t *pub, return PAILLIER_ERROR_INVALID_PARAM; } } - + ctx = BN_CTX_new(); if (ctx == NULL) { @@ -1657,7 +1658,7 @@ long paillier_commitment_verify(const paillier_commitment_public_key_t *pub, if (modifier) { - if (!BN_bin2bn(modifier, modifier_size, bn_modifier) || + if (!BN_bin2bn(modifier, modifier_size, bn_modifier) || !BN_bin2bn(modifier_exp, modifier_exp_size, bn_modifier_expo) ) { goto cleanup; @@ -1673,7 +1674,7 @@ long paillier_commitment_verify(const paillier_commitment_public_key_t *pub, if (BN_cmp(bn_commitment, bn_commitment_expected) != 0) { ret = PAILLIER_ERROR_INVALID_PROOF; - goto cleanup; + goto cleanup; } ret = PAILLIER_SUCCESS; @@ -1681,7 +1682,7 @@ long paillier_commitment_verify(const paillier_commitment_public_key_t *pub, cleanup: if (-1 == ret) { - ret = ERR_get_error() * -1; + ret = paillier_error_from_openssl(); } BN_CTX_end(ctx); @@ -1703,9 +1704,9 @@ void paillier_commitment_commitment_free(paillier_commitment_with_randomizer_pow } -long paillier_commitment_commitment_serialize(const paillier_commitment_with_randomizer_power_t* commitment, - uint8_t *serialized_proof, - uint32_t proof_len, +long paillier_commitment_commitment_serialize(const paillier_commitment_with_randomizer_power_t* commitment, + uint8_t *serialized_proof, + uint32_t proof_len, uint32_t *real_proof_len) { if (!commitment || (!serialized_proof && proof_len)) @@ -1759,7 +1760,7 @@ paillier_commitment_with_randomizer_power_t* paillier_commitment_commitment_dese commitment->randomizer_exponent_size = *(const uint32_t*)serialized_proof; serialized_proof += sizeof(uint32_t); - //sanitfy + //sanity if (commitment->commitment_size > PAILLIER_COMMITMENTS_MAX_SERIALIZED_SIZE || commitment->randomizer_exponent_size > PAILLIER_COMMITMENTS_MAX_SERIALIZED_SIZE) { @@ -1789,76 +1790,76 @@ paillier_commitment_with_randomizer_power_t* paillier_commitment_commitment_dese return commitment; } - + long paillier_commitment_paillier_blum_zkp_generate(const paillier_commitment_private_key_t *priv, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *proof_real_len) { if (!priv) - { + { return PAILLIER_ERROR_INVALID_PARAM; } const paillier_private_key_t paillier_private = { { priv->pub.n, priv->pub.n2}, - priv->p, - priv->q, - priv->phi_n, + priv->p, + priv->q, + priv->phi_n, priv->phi_n_inv }; - return paillier_generate_paillier_blum_zkp(&paillier_private, aad, aad_len, serialized_proof, proof_len, proof_real_len); + return paillier_generate_paillier_blum_zkp(&paillier_private, 0 /* compute only the first nth root */, aad, aad_len, serialized_proof, proof_len, proof_real_len); } long paillier_commitment_paillier_blum_zkp_verify(const paillier_commitment_public_key_t *pub, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len) { if (!pub) - { + { return PAILLIER_ERROR_INVALID_PARAM; } const paillier_public_key_t paillier_public = { pub->n, pub->n2}; - return paillier_verify_paillier_blum_zkp(&paillier_public, aad, aad_len, serialized_proof, proof_len); + return paillier_verify_paillier_blum_zkp(&paillier_public, 0, aad, aad_len, serialized_proof, proof_len); } uint32_t range_proof_paillier_commitment_large_factors_zkp_compute_d_bitsize(const paillier_commitment_public_key_t* pub) { if (!pub) - { + { return 0; } const paillier_public_key_t paillier_public = { pub->n, pub->n2}; return range_proof_paillier_large_factors_quadratic_zkp_compute_d_bitsize(&paillier_public); } -zero_knowledge_proof_status range_proof_paillier_commitment_large_factors_zkp_generate(const paillier_commitment_private_key_t *priv, - const uint8_t *aad, - const uint32_t aad_len, +zero_knowledge_proof_status range_proof_paillier_commitment_large_factors_zkp_generate(const paillier_commitment_private_key_t *priv, + const uint8_t *aad, + const uint32_t aad_len, const uint8_t *d_prime, const uint32_t d_prime_len, - uint8_t *serialized_proof, - uint32_t proof_len, - uint32_t *real_proof_len) + uint8_t *serialized_proof, + uint32_t proof_len, + uint32_t *real_proof_len) { if (!priv) - { + { return ZKP_INVALID_PARAMETER; } const paillier_private_key_t paillier_private = { { priv->pub.n, priv->pub.n2}, - priv->p, - priv->q, - priv->phi_n, + priv->p, + priv->q, + priv->phi_n, priv->phi_n_inv }; return range_proof_paillier_large_factors_quadratic_zkp_generate(&paillier_private, aad, aad_len, d_prime, d_prime_len, serialized_proof, proof_len, real_proof_len); } -zero_knowledge_proof_status range_proof_paillier_commitment_large_factors_zkp_verify(const paillier_commitment_public_key_t *pub, - const uint8_t *aad, - const uint32_t aad_len, - const uint8_t *serialized_proof, - const uint32_t proof_len) +zero_knowledge_proof_status range_proof_paillier_commitment_large_factors_zkp_verify(const paillier_commitment_public_key_t *pub, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t *serialized_proof, + const uint32_t proof_len) { if (!pub) - { + { return ZKP_INVALID_PARAMETER; } const paillier_public_key_t paillier_public = { pub->n, pub->n2}; @@ -1866,12 +1867,11 @@ zero_knowledge_proof_status range_proof_paillier_commitment_large_factors_zkp_ve return range_proof_paillier_large_factors_quadratic_zkp_verify(&paillier_public, aad, aad_len, serialized_proof, proof_len); } -zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_generate(const paillier_commitment_private_key_t *priv, - const uint8_t* aad, - const uint32_t aad_len, - const uint32_t challenge_bitlength, - uint8_t* serialized_proof, - const uint32_t proof_len, +zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_generate(const paillier_commitment_private_key_t *priv, + const uint8_t* aad, + const uint32_t aad_len, + uint8_t* serialized_proof, + const uint32_t proof_len, uint32_t* proof_real_len) { zero_knowledge_proof_status ret = ZKP_OUT_OF_MEMORY; @@ -1881,7 +1881,7 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ { return ZKP_INVALID_PARAMETER; } - + ctx = BN_CTX_new(); if (!ctx) { @@ -1889,23 +1889,23 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ } BN_CTX_start(ctx); - const damgard_fujisaki_private_t damgard_fujisaki_priv = + const damgard_fujisaki_private_t damgard_fujisaki_priv = { { - 1, //dimentions; // number of secrets + 1, //dimensions; // number of secrets priv->pub.n, // public part of p * q - (BIGNUM**)&priv->pub.s, // for each secret labda holds it's public t^labda + (BIGNUM**)&priv->pub.s, // for each secret lambda holds its public t^lambda priv->pub.t, // single t used for all s - NULL // montegomery context used for calculations + NULL // montgomery context used for calculations }, - + (BIGNUM**)&priv->lambda, // secrets, same count as pub->dimension priv->phi_n, // (p-1) * (q-1) priv->p, priv->q, BN_CTX_get(ctx) }; - + if (!damgard_fujisaki_priv.qinvp) { goto cleanup; @@ -1926,7 +1926,7 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ } } - ret = damgard_fujisaki_parameters_zkp_generate(&damgard_fujisaki_priv, aad, aad_len, challenge_bitlength, serialized_proof, proof_len, proof_real_len); + ret = damgard_fujisaki_parameters_zkp_generate(&damgard_fujisaki_priv, aad, aad_len, 1, serialized_proof, proof_len, proof_real_len); cleanup: BN_MONT_CTX_free(damgard_fujisaki_priv.pub.mont); //must manually since manually initialized free @@ -1936,11 +1936,10 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ return ret; } -zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_verify(const paillier_commitment_public_key_t *pub, - const uint8_t* aad, - const uint32_t aad_len, - const uint32_t challenge_bitlen, - const uint8_t* serialized_proof, +zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_verify(const paillier_commitment_public_key_t *pub, + const uint8_t* aad, + const uint32_t aad_len, + const uint8_t* serialized_proof, const uint32_t proof_len) { zero_knowledge_proof_status ret = ZKP_OUT_OF_MEMORY; @@ -1950,7 +1949,7 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ { return ZKP_INVALID_PARAMETER; } - + ctx = BN_CTX_new(); if (!ctx) { @@ -1958,15 +1957,15 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ } BN_CTX_start(ctx); - const damgard_fujisaki_public_t damgard_fujisaki_pub = + const damgard_fujisaki_public_t damgard_fujisaki_pub = { 1, // number of secrets pub->n, // public part of p * q - (BIGNUM **)&pub->s, // for each secret labda holds it's public t^labda + (BIGNUM **)&pub->s, // for each secret lambda holds its public t^lambda pub->t, // single t used for all s - NULL // montegomery context used for calculations + NULL // montgomery context used for calculations }; - + if (serialized_proof && proof_len) { if (RING_PEDERSEN_SUCCESS != damgard_fujisaki_init_montgomery((damgard_fujisaki_public_t*)&damgard_fujisaki_pub, ctx)) @@ -1976,7 +1975,7 @@ zero_knowledge_proof_status paillier_commitment_damgard_fujisaki_parameters_zkp_ } } - ret = damgard_fujisaki_parameters_zkp_verify(&damgard_fujisaki_pub, aad, aad_len, challenge_bitlen, serialized_proof, proof_len); + ret = damgard_fujisaki_parameters_zkp_verify(&damgard_fujisaki_pub, aad, aad_len, 1, serialized_proof, proof_len); cleanup: BN_MONT_CTX_free(damgard_fujisaki_pub.mont); //must manually since manually initialized free diff --git a/src/common/crypto/paillier_commitment/paillier_commitment_internal.h b/src/common/crypto/paillier_commitment/paillier_commitment_internal.h index 49adc82..04a207f 100644 --- a/src/common/crypto/paillier_commitment/paillier_commitment_internal.h +++ b/src/common/crypto/paillier_commitment/paillier_commitment_internal.h @@ -10,11 +10,11 @@ struct paillier_commitment_public_key { BIGNUM* n; // public key n = p * q BIGNUM* t; // damgard fujisaki generator, also used as rho0 in paillier - BIGNUM* s; // damgard fujisaki t^lamda in mod n^2 public. Also used in paillier encryption - BIGNUM* n2; // calcualted n^2 + BIGNUM* s; // damgard fujisaki t^lambda in mod n^2 public. Also used in paillier encryption + BIGNUM* n2; // calculated n^2 BIGNUM* rho; // t^n mod n^2 - either restored or calculated. used as rho in paillier BIGNUM* sigma_0; // (1 + n) * s^n mod n^2 - either restored or calculated - BN_MONT_CTX* mont_n2; // montegomery context used for in mod n^2 + BN_MONT_CTX* mont_n2; // montgomery context used for in mod n^2 }; struct paillier_commitment_private_key @@ -26,7 +26,7 @@ struct paillier_commitment_private_key BIGNUM* p2; // calculated - holds (p^2) BIGNUM* q2; // calculated - holds (q^2) BIGNUM* q2_inv_p2; // calculated - holds (q^2^-1) mod (p^2) - BIGNUM* phi_n; // calculated - phi(n) also known as lamda in paillier + BIGNUM* phi_n; // calculated - phi(n) also known as lambda in paillier BIGNUM* phi_n_inv; // calculated - phi(n) ^ 1 in mod(n) also known as mu in paillier }; diff --git a/src/common/crypto/shamir_secret_sharing/verifiable_secret_sharing.c b/src/common/crypto/shamir_secret_sharing/verifiable_secret_sharing.c index 1a3db7d..c5093b1 100644 --- a/src/common/crypto/shamir_secret_sharing/verifiable_secret_sharing.c +++ b/src/common/crypto/shamir_secret_sharing/verifiable_secret_sharing.c @@ -95,7 +95,7 @@ static verifiable_secret_sharing_status create_shares(const elliptic_curve256_al goto cleanup; - // multiply the access matrix and the coefficient vector to get the sharws + // multiply the access matrix and the coefficient vector to get the shares for (size_t i = 0; i < n; ++i) { elliptic_curve_algebra_status status; @@ -145,7 +145,7 @@ static verifiable_secret_sharing_status verifiable_secret_sharing_split_impl(con if (!(bn_prime = BN_bin2bn(algebra->order(algebra), ELLIPTIC_CURVE_FIELD_SIZE, NULL))) goto cleanup; - assert(BN_is_prime_ex(bn_prime, 1000, ctx, NULL)); + assert(BN_is_prime_ex(bn_prime, 1000, ctx, NULL) == 1); BN_set_flags(bn_prime, BN_FLG_CONSTTIME); @@ -156,7 +156,13 @@ static verifiable_secret_sharing_status verifiable_secret_sharing_split_impl(con } for (size_t i = 0; i < t*n; ++i) - BN_mod(access_mat[i], access_mat[i], bn_prime, ctx); + { + if (!BN_mod(access_mat[i], access_mat[i], bn_prime, ctx)) + { + ret = VERIFIABLE_SECRET_SHARING_UNKNOWN_ERROR; + goto cleanup; + } + } shares_local->ids = ids; @@ -167,7 +173,7 @@ static verifiable_secret_sharing_status verifiable_secret_sharing_split_impl(con } else { - // NULL shares_local->ids so it want be freed by verifiable_secret_sharing_free_shares, as we didn't take ownership over it + // NULL shares_local->ids so it won't be freed by verifiable_secret_sharing_free_shares, as we didn't take ownership over it shares_local->ids = NULL; } @@ -206,7 +212,8 @@ verifiable_secret_sharing_status verifiable_secret_sharing_split(const elliptic_ one = BN_CTX_get(ctx); if (!one) goto cleanup; - BN_one(one); + if (!BN_one(one)) + goto cleanup; ids = (uint64_t*)calloc(n, sizeof(uint64_t)); if (!ids) @@ -277,7 +284,8 @@ verifiable_secret_sharing_status verifiable_secret_sharing_split_with_custom_ids one = BN_CTX_get(ctx); if (!one) goto cleanup; - BN_one(one); + if (!BN_one(one)) + goto cleanup; local_ids = (uint64_t*)calloc(n, sizeof(uint64_t)); if (!local_ids) diff --git a/src/common/crypto/zero_knowledge_proof/damgard_fujisaki_zkp.c b/src/common/crypto/zero_knowledge_proof/damgard_fujisaki_zkp.c index 667b4a0..2c5b40a 100644 --- a/src/common/crypto/zero_knowledge_proof/damgard_fujisaki_zkp.c +++ b/src/common/crypto/zero_knowledge_proof/damgard_fujisaki_zkp.c @@ -1,8 +1,8 @@ #include "crypto/commitments/damgard_fujisaki.h" #include "crypto/drng/drng.h" #include "../commitments/damgard_fujisaki_internal.h" -#include "../algebra_utils/status_convert.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/status_convert.h" +#include "crypto/algebra_utils/algebra_utils.h" #include "zkp_constants_internal.h" #include @@ -36,52 +36,134 @@ static inline long init_damgard_fujisaki_param_zkp(struct damgard_fujisaki_param return ZKP_SUCCESS; } -static inline uint32_t damgard_fujisaki_compute_required_repetitions(const uint32_t challenge_bitlength, const uint32_t dimension) +// Damgård–Fujisaki repetitions for batch Ring-Pedersen +// Goal: choose the smallest integer r so that the soundness error per batch +// is <= 2^{-Kbits}. Two analytic bounds give r; we take the larger: +// +// r >= max( +// K / ( t + n * log(1 - 2^{-l/n}) - n ), +// K / ( -log( 2^{-l} + 2^{-l/n} ) ) +// ) +// +// Mapping to code variables: +// l := challenge_bitlength (in bits) +// n := dimension (a small integer; this function supports n = 1, 2) +// K := target in the *same log base* as `log` (we work base-2, so K = Kbits) +// t := small scheme-specific constant (≈ 4 .. 4.3 in base-2 for these ranges) +// +// IMPORTANT: Throughout, log means log base 2 (log2). +// +// Below we keep the original table but explain (and recompute) each number from the formula. +// We assume a standard 128-bit statistical target (Kbits = 128). +// Notes on each case are right next to the constants. + +static inline uint32_t damgard_fujisaki_compute_required_repetitions( + const uint32_t challenge_bitlength /* l */, + const uint32_t dimension /* n */) { assert(dimension != 0); + + // Legacy fallback (unchanged): if l is tiny or n > 2, punt to a project-wide default. if (challenge_bitlength <= 4 || dimension > 2) { return DAMGARD_FUJISAKI_STATISTICAL_SECURITY; } + // Default result (start from project-wide fallback; we overwrite below). uint32_t repetitions = DAMGARD_FUJISAKI_STATISTICAL_SECURITY; + + // We treat the exact DF formula as the source of truth and show how each table + // constant arises from it for Kbits = 128 (log base 2), using the worst l in the range. + + // --- Helper comments used in the derivations below --- + // Bound B (usually dominant for l >= 8): + // denom_B(l,n) = -log2( 2^{-l} + 2^{-l/n} ) + // + // Bound A (matters for small l; depends on t): + // denom_A(l,n,t) = t + n * log2(1 - 2^{-l/n}) - n + // + // Required r = ceil( 128 / min(denom_A, denom_B) ), taken at the worst l in the range. + if (challenge_bitlength < 8) { switch (dimension) { - case 1: + case 1: + // Range: l ∈ {5,6,7} (since l <= 4 is handled above). + // Bound B at l = 5: denom_B = -log2(2^{-5} + 2^{-5}) = -log2(2 * 2^{-5}) = 4 → 128/4 = 32. + // For n = 1 the tighter driver is Bound A with a typical t ≈ 4.06 (base 2): + // denom_A(l=5, n=1, t≈4.06) = 4.06 + log2(1 - 2^{-5}) - 1 + // = 4.06 + log2(31/32) - 1 + // = 3.06 - 0.045757... ≈ 3.014243... + // r >= ceil(128 / 3.014243...) = ceil(42.44...) = 43 + // + // At l=6,7 this only drops slightly (still rounds to ≥ 42), so 43 covers the whole l<8, n=1 bucket. repetitions = 43; break; - case 2: + + case 2: + // Range: l ∈ {5,6,7}. Bound B alone is not enough at l=5 (it would give ~57), + // and Bound A is again the driver. Using a t ≈ 4.24 (base 2) appropriate for n=2, + // denom_A(l=5, n=2, t≈4.24) = 4.24 + 2*log2(1 - 2^{-5/2}) - 2 + // = 4.24 + 2*log2(1 - 1/sqrt(32)) - 2 + // ≈ 1.678711... + // r >= ceil(128 / 1.678711...) = ceil(76.2489...) = 77 + // + // At l=6,7 the requirement drops (≈70 and ≈65 respectively), so 77 safely covers the bucket. repetitions = 77; - break; + break; } } else if (challenge_bitlength < 16) { + // Here Bound B dominates cleanly and does not depend on t. switch (dimension) { - case 1: + case 1: + // Worst l = 8: + // denom_B = -log2(2^{-8} + 2^{-8}) = -log2(2 * 2^{-8}) = 7 + // r >= ceil(128 / 7) = ceil(18.2857...) = 19 repetitions = 19; break; - case 2: + + case 2: + // Worst l = 8: + // denom_B = -log2(2^{-8} + 2^{-8/2}) = -log2(1/256 + 1/16) + // = -log2(0.00390625 + 0.0625) = -log2(0.06640625) + // ≈ 3.918861... + // r >= ceil(128 / 3.918861...) = ceil(32.67...) = 33 repetitions = 33; - break; + break; } } - else // challenge_bitlength 16 + else // challenge_bitlength >= 16 { switch (dimension) { - case 1: + case 1: + // Worst l = 16: + // Bound B: denom_B = -log2(2^{-16} + 2^{-16}) = 15 → ceil(128 / 15) = 9. + // The table uses 14, which is strictly *more* conservative than needed for 128-bit target. + // Keeping 14 matches the legacy code and provides >128-bit margin. + // + // (If you want the *minimal* r that still meets 128 bits here, 9 can be used.) repetitions = 14; break; - case 2: - repetitions = 16; - break; + + case 2: + // Worst l = 16: + // Bound B: denom_B = -log2(2^{-16} + 2^{-8}) + // = -log2(2^{-8}(1 + 2^{-8})) = 8 - log2(1 + 1/256) + // ≈ 8 - 0.005619... = 7.994381... + // r >= 128 / 7.994381... = 16.0089... → ceil(...) = 17 (strictly for ≥128 bits) + // + // 16 corresponds to ~127.99-bit—off by ~0.01 bits. + // A strict ≥128-bit bound is 17. + repetitions = 17; + break; } } - + return repetitions; } @@ -246,7 +328,7 @@ zero_knowledge_proof_status damgard_fujisaki_parameters_zkp_generate(const damga key_bitsize = (uint32_t)BN_num_bits(priv->pub.n); answer_randomness_bitlen = (2 * ZKPOK_OPTIM_L_SIZE(key_bitsize) + ZKPOK_OPTIM_NU_SIZE(key_bitsize)) * 8 + challenge_bitlength ; - // each lambda has 2 * key_security_bits bits. So sum of all lambdas times challage is log2_floor(priv->pub.dimension + 1) + challenge_bitlength + // each lambda has 2 * key_security_bits bits. So sum of all lambdas times challenge is log2_floor(priv->pub.dimension + 1) + challenge_bitlength // we need it to be smaller than the randomness assert(ZKPOK_OPTIM_NU_SIZE(key_bitsize) * 8 > log2_floor(priv->pub.dimension + 1)); @@ -276,7 +358,7 @@ zero_knowledge_proof_status damgard_fujisaki_parameters_zkp_generate(const damga if (!priv->pub.mont) { - //check only here because mont is not required in there is no enough buffer + //check only here because mont is not required if there is no enough buffer return ZKP_INVALID_PARAMETER; } @@ -354,7 +436,7 @@ zero_knowledge_proof_status damgard_fujisaki_parameters_zkp_generate(const damga if (!BN_is_zero(challenge_bn)) { // after the modular multiplication, both are smaller than phi(n), BN_mod_add_quick is ok - if (!BN_mod_mul(challenge_bn, challenge_bn, priv->lamda[j], priv->phi_n, ctx)) + if (!BN_mod_mul(challenge_bn, challenge_bn, priv->lambda[j], priv->phi_n, ctx)) { goto cleanup; } @@ -414,7 +496,7 @@ zero_knowledge_proof_status damgard_fujisaki_parameters_zkp_verify(const damgard key_bitsize = (uint32_t)BN_num_bits(pub->n); answer_randomness_bitlen = (2 * ZKPOK_OPTIM_L_SIZE(key_bitsize) + ZKPOK_OPTIM_NU_SIZE(key_bitsize)) * 8 + challenge_bitlength ; - // each lambda has 2 * key_security_bits bits. So sum of all lambdas times challage is log2_floor(priv->pub.dimension + 1) + challenge_bitlength + // each lambda has 2 * key_security_bits bits. So sum of all lambdas times challenge is log2_floor(priv->pub.dimension + 1) + challenge_bitlength // we need it to be smaller than the randomness assert(ZKPOK_OPTIM_NU_SIZE(key_bitsize) * 8 > log2_floor(pub->dimension + 1)); @@ -474,7 +556,7 @@ zero_knowledge_proof_status damgard_fujisaki_parameters_zkp_verify(const damgard for (uint32_t i = 0; i < pub->dimension; ++i) { - if (!is_coprime_fast(pub->s[i], pub->n, ctx)) + if (is_coprime_fast(pub->s[i], pub->n, ctx) != 1) { goto cleanup; } diff --git a/src/common/crypto/zero_knowledge_proof/diffie_hellman_log.c b/src/common/crypto/zero_knowledge_proof/diffie_hellman_log.c index 6392cb8..b10dbd5 100644 --- a/src/common/crypto/zero_knowledge_proof/diffie_hellman_log.c +++ b/src/common/crypto/zero_knowledge_proof/diffie_hellman_log.c @@ -3,6 +3,7 @@ #include "crypto/common/byteswap.h" #include #include +#include #define LOG_ZKP_SALT "diffie hellman discrete log zkp" @@ -79,37 +80,43 @@ zero_knowledge_proof_status diffie_hellman_log_zkp_generate(const elliptic_curve return ZKP_INVALID_PARAMETER; if (algebra->rand(algebra, &d) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->rand(algebra, &y) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->generator_mul(algebra, &local_proof.D, &d) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->point_mul(algebra, &local_proof.Y, base_point, &y) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->mul_scalars(algebra, &tmp, *a, sizeof(elliptic_curve256_scalar_t), d, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), y, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->generator_mul(algebra, &local_proof.V, &tmp) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; status = diffie_hellman_log_generate_e(algebra, aad, aad_len, base_point, public_data, &local_proof, &e); if (status != ZKP_SUCCESS) - return status; + goto cleanup; status = ZKP_UNKNOWN_ERROR; if (algebra->mul_scalars(algebra, &local_proof.z, e, sizeof(elliptic_curve256_scalar_t), *b, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->add_scalars(algebra, &local_proof.z, local_proof.z, sizeof(elliptic_curve256_scalar_t), d, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->mul_scalars(algebra, &local_proof.w, e, sizeof(elliptic_curve256_scalar_t), *secret, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; if (algebra->add_scalars(algebra, &local_proof.w, local_proof.w, sizeof(elliptic_curve256_scalar_t), y, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return status; + goto cleanup; memcpy(proof, &local_proof, sizeof(diffie_hellman_log_zkp_t)); - return ZKP_SUCCESS; + status = ZKP_SUCCESS; + +cleanup: + OPENSSL_cleanse(d, sizeof(elliptic_curve256_scalar_t)); + OPENSSL_cleanse(y, sizeof(elliptic_curve256_scalar_t)); + OPENSSL_cleanse(tmp, sizeof(elliptic_curve256_scalar_t)); + return status; } static inline zero_knowledge_proof_status from_elliptic_curve_status(elliptic_curve_algebra_status status) diff --git a/src/common/crypto/zero_knowledge_proof/range_proofs.c b/src/common/crypto/zero_knowledge_proof/range_proofs.c index bff73b1..4937d22 100644 --- a/src/common/crypto/zero_knowledge_proof/range_proofs.c +++ b/src/common/crypto/zero_knowledge_proof/range_proofs.c @@ -1,8 +1,8 @@ #include "crypto/zero_knowledge_proof/range_proofs.h" #include "crypto/paillier_commitment/paillier_commitment.h" #include "crypto/drng/drng.h" -#include "../algebra_utils/algebra_utils.h" -#include "../algebra_utils/status_convert.h" +#include "crypto/algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/status_convert.h" #include "../paillier/paillier_internal.h" #include "../paillier_commitment/paillier_commitment_internal.h" #include "../commitments/ring_pedersen_internal.h" @@ -25,6 +25,9 @@ #define PAILLER_LARGE_FACTORS_QUADRATIC_ZKP_SEED "Range Proof Pailler Quadratic for G and H" + +#define MAX_D_SIZE (4096) + typedef struct { BIGNUM *S; @@ -38,7 +41,7 @@ typedef struct typedef struct { - range_proof_exponent_zkpok_t base; // diffie_hellman is extansion to the exponent zkpok where rddh.Z<->log.Y + range_proof_exponent_zkpok_t base; // diffie_hellman is extension to the exponent zkpok where rddh.Z<->log.Y elliptic_curve256_scalar_t w; elliptic_curve256_point_t Y; } range_proof_diffie_hellman_zkpok_t; @@ -58,7 +61,7 @@ typedef struct BIGNUM *v; } range_proof_paillier_large_factors_zkp_t; -typedef struct +typedef struct { BIGNUM* d; BIGNUM* d_minus_1_over_2; @@ -67,7 +70,7 @@ typedef struct BN_MONT_CTX *d_mont; // to accelerate further exponentiations mod d } paillier_large_factors_quadratic_setup_t; -typedef struct +typedef struct { paillier_large_factors_quadratic_setup_t setup; BIGNUM *A; @@ -91,16 +94,24 @@ static zero_knowledge_proof_status init_exponent_zkpok(range_proof_exponent_zkpo zkpok->z1 = BN_CTX_get(ctx); zkpok->z2 = BN_CTX_get(ctx); zkpok->z3 = BN_CTX_get(ctx); - + if (zkpok->S && zkpok->D && zkpok->T && zkpok->z1 && zkpok->z2 && zkpok->z3) { return ZKP_SUCCESS; } - + return ZKP_OUT_OF_MEMORY; } -static inline int genarate_zkpok_seed_internal(const range_proof_exponent_zkpok_t *proof, const BIGNUM *ciphertext, const elliptic_curve256_point_t *X, const uint8_t *aad, uint32_t aad_len, SHA256_CTX *ctx) +static inline int genarate_zkpok_seed_internal(const uint32_t paillier_n_size, + const uint32_t ring_pedersen_n_size, + const range_proof_exponent_zkpok_t *proof, + const BIGNUM *ciphertext, + const elliptic_curve256_point_t *X, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t use_extended_seed, + SHA256_CTX *ctx) { uint8_t *n = NULL; uint32_t max_size; @@ -110,50 +121,111 @@ static inline int genarate_zkpok_seed_internal(const range_proof_exponent_zkpok_ { SHA256_Update(ctx, aad, aad_len); } - max_size = MAX(BN_num_bytes(proof->D), BN_num_bytes(proof->S)); // we assume that the paillier n is larger then ring pedersen n - max_size = MAX(max_size, (uint32_t)BN_num_bytes(ciphertext)); + + assert( (uint32_t)BN_num_bytes(proof->D) <= 2 * paillier_n_size ); + assert( (uint32_t)BN_num_bytes(proof->S) <= ring_pedersen_n_size ); + assert( (uint32_t)BN_num_bytes(ciphertext) <= 2 * paillier_n_size ); + assert( (uint32_t)BN_num_bytes(proof->T) <= ring_pedersen_n_size ); + + + if (use_extended_seed) + { + max_size = MAX(ring_pedersen_n_size, 2U * paillier_n_size); + } + else + { + max_size = MAX((uint32_t)BN_num_bytes(proof->D), (uint32_t)BN_num_bytes(proof->S)); // we assume that the paillier n is larger then ring pedersen n + max_size = MAX(max_size, (uint32_t)BN_num_bytes(ciphertext)); + max_size = MAX(max_size, (uint32_t)BN_num_bytes(proof->T)); + } n = (uint8_t*)malloc(max_size); + if (!n) { return 0; } - - BN_bn2bin(ciphertext, n); - SHA256_Update(ctx, n, BN_num_bytes(ciphertext)); + + if (use_extended_seed) + { + if (BN_bn2binpad(ciphertext, n, 2U * paillier_n_size) != (int)(2U * paillier_n_size)) + { + goto cleanup; + } + SHA256_Update(ctx, n, 2U * paillier_n_size); + } + else + { + BN_bn2bin(ciphertext, n); + SHA256_Update(ctx, n, (size_t)BN_num_bytes(ciphertext)); + } + + SHA256_Update(ctx, *X, sizeof(elliptic_curve256_point_t)); - BN_bn2bin(proof->S, n); - SHA256_Update(ctx, n, BN_num_bytes(proof->S)); - BN_bn2bin(proof->D, n); - SHA256_Update(ctx, n, BN_num_bytes(proof->D)); + if (use_extended_seed) + { + + if (BN_bn2binpad(proof->S, n, ring_pedersen_n_size) != (int)ring_pedersen_n_size) + { + goto cleanup; + } + SHA256_Update(ctx, n, ring_pedersen_n_size); + if (BN_bn2binpad(proof->D, n, 2U * paillier_n_size) != (int)(2U * paillier_n_size)) + { + goto cleanup; + } + SHA256_Update(ctx, n, 2U * paillier_n_size); + } + else + { + BN_bn2bin(proof->S, n); + SHA256_Update(ctx, n, BN_num_bytes(proof->S)); + BN_bn2bin(proof->D, n); + SHA256_Update(ctx, n, BN_num_bytes(proof->D)); + + } + SHA256_Update(ctx, proof->Y, sizeof(elliptic_curve256_point_t)); - if ((uint32_t)BN_num_bytes(proof->T) > max_size) // should never happen + + if (use_extended_seed) { - //reallocate memory so n should not be freed - uint8_t *tmp = realloc(n, BN_num_bytes(proof->T)); - if (!tmp) + if (BN_bn2binpad(proof->T, n, ring_pedersen_n_size) != (int)ring_pedersen_n_size) { - free(n); - return 0; + goto cleanup; } - n = tmp; + SHA256_Update(ctx, n, ring_pedersen_n_size); + } + else + { + BN_bn2bin(proof->T, n); + SHA256_Update(ctx, n, BN_num_bytes(proof->T)); } - BN_bn2bin(proof->T, n); - SHA256_Update(ctx, n, BN_num_bytes(proof->T)); free(n); return 1; + +cleanup: + free(n); + return 0; } -static inline int genarate_exponent_zkpok_seed(const range_proof_exponent_zkpok_t *proof, const BIGNUM *ciphertext, const elliptic_curve256_point_t *X, const uint8_t *aad, uint32_t aad_len, uint8_t *seed) +static inline int genarate_exponent_zkpok_seed(const uint32_t paillier_n_size, + const uint32_t ring_pedersen_n_size, + const range_proof_exponent_zkpok_t *proof, + const BIGNUM *ciphertext, + const elliptic_curve256_point_t *X, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + uint8_t *seed) { SHA256_CTX ctx; - + SHA256_Init(&ctx); - if (!genarate_zkpok_seed_internal(proof, ciphertext, X, aad, aad_len, &ctx)) + if (!genarate_zkpok_seed_internal(paillier_n_size, ring_pedersen_n_size, proof, ciphertext, X, aad, aad_len, use_extended_seed, &ctx)) { return 0; } - + SHA256_Final(seed, &ctx); return 1; } @@ -163,7 +235,7 @@ static inline uint32_t exponent_zkpok_serialized_size_internal(const BIGNUM *ped const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(pedersen_n); const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); - return + return sizeof(uint32_t) + // sizeof(ring_pedersen->n) sizeof(uint32_t) + // sizeof(paillier->n) ring_pedersen_n_size + // sizeof(S) @@ -181,13 +253,13 @@ static inline uint32_t exponent_zkpok_serialized_size(const ring_pedersen_public return exponent_zkpok_serialized_size_internal(pub->n, paillier->n); } -static uint8_t* serialize_exponent_zkpok(const range_proof_exponent_zkpok_t* proof, - const BIGNUM* ring_pedersen_n, - const BIGNUM* paillier_n, +static uint8_t* serialize_exponent_zkpok(const range_proof_exponent_zkpok_t* proof, + const BIGNUM* ring_pedersen_n, + const BIGNUM* paillier_n, uint8_t* serialized_proof) { - const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen_n); - const uint32_t paillier_n_size = BN_num_bytes(paillier_n); + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); uint8_t *ptr = serialized_proof; *(uint32_t*)ptr = ring_pedersen_n_size; @@ -196,37 +268,55 @@ static uint8_t* serialize_exponent_zkpok(const range_proof_exponent_zkpok_t* pro *(uint32_t*)ptr = paillier_n_size; ptr += sizeof(uint32_t); - BN_bn2binpad(proof->S, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->S, ptr, ring_pedersen_n_size) <= 0) + { + return NULL; + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->D, ptr, paillier_n_size * 2); + if (BN_bn2binpad(proof->D, ptr, paillier_n_size * 2) <= 0) + { + return NULL; + } ptr += paillier_n_size * 2; memcpy(ptr, proof->Y, sizeof(elliptic_curve256_point_t)); ptr += sizeof(elliptic_curve256_point_t); - BN_bn2binpad(proof->T, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->T, ptr, ring_pedersen_n_size) <= 0) + { + return NULL; + } ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->z1, ptr, ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1); + if (BN_bn2binpad(proof->z1, ptr, ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1) <= 0) + { + return NULL; + } ptr += ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + 1; - BN_bn2binpad(proof->z2, ptr, paillier_n_size); + if (BN_bn2binpad(proof->z2, ptr, paillier_n_size) <= 0) + { + return NULL; + } ptr += paillier_n_size; - BN_bn2binpad(proof->z3, ptr, ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + ring_pedersen_n_size + 1); + if (BN_bn2binpad(proof->z3, ptr, ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + ring_pedersen_n_size + 1) <= 0) + { + return NULL; + } ptr += ZKPOK_EPSILON_SIZE + sizeof(elliptic_curve256_scalar_t) + ring_pedersen_n_size + 1; return ptr; } -static const uint8_t* deserialize_exponent_zkpok(range_proof_exponent_zkpok_t* proof, - const BIGNUM* ring_pedersen_n, - const BIGNUM* paillier_n, +static const uint8_t* deserialize_exponent_zkpok(range_proof_exponent_zkpok_t* proof, + const BIGNUM* ring_pedersen_n, + const BIGNUM* paillier_n, const uint8_t* serialized_proof) { - const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen_n); - const uint32_t paillier_n_size = BN_num_bytes(paillier_n); + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); const uint8_t *ptr = serialized_proof; if (*(const uint32_t*)ptr != ring_pedersen_n_size) @@ -246,7 +336,7 @@ static const uint8_t* deserialize_exponent_zkpok(range_proof_exponent_zkpok_t* p return NULL; } ptr += ring_pedersen_n_size; - + if (!BN_bin2bn(ptr, paillier_n_size * 2, proof->D)) { return NULL; @@ -255,7 +345,7 @@ static const uint8_t* deserialize_exponent_zkpok(range_proof_exponent_zkpok_t* p memcpy(proof->Y, ptr, sizeof(elliptic_curve256_point_t)); ptr += sizeof(elliptic_curve256_point_t); - + if (!BN_bin2bn(ptr, ring_pedersen_n_size, proof->T)) { return NULL; @@ -283,15 +373,16 @@ static const uint8_t* deserialize_exponent_zkpok(range_proof_exponent_zkpok_t* p return ptr; } -zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, - const paillier_public_key_t *paillier, - const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, - uint32_t aad_len, - const elliptic_curve256_scalar_t *secret, - const paillier_ciphertext_t *ciphertext, - uint8_t *serialized_proof, - uint32_t proof_len, +zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_scalar_t *secret, + const paillier_ciphertext_t *ciphertext, + const uint8_t use_extended_seed, + uint8_t *serialized_proof, + uint32_t proof_len, uint32_t *real_proof_len) { BN_CTX *ctx = NULL; @@ -323,13 +414,13 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r if (!ctx) return ZKP_OUT_OF_MEMORY; - + if (is_coprime_fast(ciphertext->r, paillier->n, ctx) != 1) { BN_CTX_free(ctx); return ZKP_INVALID_PARAMETER; } - + BN_CTX_start(ctx); alpha = BN_CTX_get(ctx); mu = BN_CTX_get(ctx); @@ -360,9 +451,9 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r // rand alpha if (!BN_rand_range(alpha, tmp)) goto cleanup; - + // rand mu - if (!BN_copy(tmp, ring_pedersen->n) || !BN_lshift(tmp, tmp, sizeof(elliptic_curve256_scalar_t))) + if (!BN_copy(tmp, ring_pedersen->n) || !BN_lshift(tmp, tmp, sizeof(elliptic_curve256_scalar_t) * 8)) { status = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -372,7 +463,7 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r goto cleanup; // rand gamma - if (!BN_lshift(tmp, tmp, ZKPOK_EPSILON_SIZE)) + if (!BN_lshift(tmp, tmp, ZKPOK_EPSILON_SIZE * 8)) { status = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -387,7 +478,7 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r goto cleanup; paillier_status = paillier_encrypt_openssl_internal(paillier, zkpok.D, r, alpha, ctx); } while (paillier_status == PAILLIER_ERROR_INVALID_RANDOMNESS); - + if (paillier_status != PAILLIER_SUCCESS) goto cleanup; @@ -397,15 +488,19 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r goto cleanup; if (!BN_mod(tmp, alpha, q, ctx)) goto cleanup; - BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)); + if (BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)) <= 0) + goto cleanup; if (algebra->generator_mul(algebra, &zkpok.Y, &alpha_bin) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; if (algebra->generator_mul(algebra, &public_point, secret) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen->n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->n); + // sample e - if (!genarate_exponent_zkpok_seed(&zkpok, ciphertext->ciphertext, &public_point, aad, aad_len, seed)) + if (!genarate_exponent_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, ciphertext->ciphertext, &public_point, aad, aad_len, use_extended_seed, seed)) goto cleanup; if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) { @@ -416,7 +511,7 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r do { elliptic_curve256_scalar_t val; - + if (drng_read_deterministic_rand(rng, val, sizeof(elliptic_curve256_scalar_t)) != DRNG_SUCCESS) { status = ZKP_UNKNOWN_ERROR; @@ -431,35 +526,70 @@ zero_knowledge_proof_status range_proof_paillier_exponent_zkpok_generate(const r goto cleanup; if (!BN_add(zkpok.z1, zkpok.z1, alpha)) goto cleanup; - + if (!BN_mod_exp(zkpok.z2, ciphertext->r, e, paillier->n, ctx)) goto cleanup; if (!BN_mod_mul(zkpok.z2, zkpok.z2, r, paillier->n, ctx)) goto cleanup; - + if (!BN_mul(zkpok.z3, e, mu, ctx)) goto cleanup; if (!BN_add(zkpok.z3, zkpok.z3, gamma)) goto cleanup; - - serialize_exponent_zkpok(&zkpok, ring_pedersen->n, paillier->n, serialized_proof); - status = ZKP_SUCCESS; + + status = serialize_exponent_zkpok(&zkpok, ring_pedersen->n, paillier->n, serialized_proof) != NULL ? ZKP_SUCCESS : ZKP_OUT_OF_MEMORY; + cleanup: + + if (alpha) + { + BN_clear(alpha); + } + + if (mu) + { + BN_clear(mu); + } + + if (r) + { + BN_clear(r); + } + + if (gamma) + { + BN_clear(gamma); + } + + if (e) + { + BN_clear(e); + } + if (x) + { BN_clear(x); + } + + if (tmp) + { + BN_clear(tmp); + } + + drng_free(rng); BN_CTX_end(ctx); BN_CTX_free(ctx); return status; } -zero_knowledge_proof_status range_proof_paillier_encrypt_with_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, paillier_with_range_proof_t **proof) +zero_knowledge_proof_status range_proof_paillier_encrypt_with_exponent_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const uint8_t use_extended_seed, paillier_with_range_proof_t **proof) { paillier_ciphertext_t *ciphertext = NULL; paillier_with_range_proof_t *local_proof = NULL; zero_knowledge_proof_status status = ZKP_OUT_OF_MEMORY; - + if (!paillier || !secret) return ZKP_INVALID_PARAMETER; @@ -473,17 +603,25 @@ zero_knowledge_proof_status range_proof_paillier_encrypt_with_exponent_zkpok_gen if (!local_proof) goto cleanup; - local_proof->ciphertext_len = BN_num_bytes(ciphertext->ciphertext); + local_proof->ciphertext_len = BN_num_bytes(paillier->n2); local_proof->ciphertext = (uint8_t*)malloc(local_proof->ciphertext_len); local_proof->proof_len = exponent_zkpok_serialized_size(ring_pedersen, paillier); local_proof->serialized_proof = (uint8_t*)malloc(local_proof->proof_len); if (!local_proof->ciphertext || !local_proof->serialized_proof) + { goto cleanup; + } + + + if (BN_bn2binpad(ciphertext->ciphertext, local_proof->ciphertext, local_proof->ciphertext_len) <= 0) + { + status = ZKP_UNKNOWN_ERROR; + goto cleanup; + } + + status = range_proof_paillier_exponent_zkpok_generate(ring_pedersen, paillier, algebra, aad, aad_len, secret, ciphertext, use_extended_seed, local_proof->serialized_proof, local_proof->proof_len, NULL); - BN_bn2bin(ciphertext->ciphertext, local_proof->ciphertext); - status = range_proof_paillier_exponent_zkpok_generate(ring_pedersen, paillier, algebra, aad, aad_len, secret, ciphertext, local_proof->serialized_proof, local_proof->proof_len, NULL); - if (status == ZKP_SUCCESS) { *proof = local_proof; @@ -496,8 +634,15 @@ zero_knowledge_proof_status range_proof_paillier_encrypt_with_exponent_zkpok_gen return status; } -zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_point_t *public_point, const paillier_with_range_proof_t *proof) +zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_point_t *public_point, + const paillier_with_range_proof_t *proof, + const uint8_t strict_ciphertext_length, + const uint8_t use_extended_seed) { BN_CTX *ctx = NULL; drng_t *rng = NULL; @@ -511,10 +656,15 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse elliptic_curve256_scalar_t z1; elliptic_curve256_point_t p1; elliptic_curve256_point_t p2; - + if (!ring_pedersen || !paillier || !algebra || !aad || !aad_len || !public_point || !proof || !proof->ciphertext || !proof->ciphertext_len || !proof->serialized_proof || !proof->proof_len) return ZKP_INVALID_PARAMETER; + if (strict_ciphertext_length && proof->ciphertext_len != (uint32_t)BN_num_bytes(paillier->n2)) + { + return ZKP_INVALID_PARAMETER; + } + needed_proof_len = exponent_zkpok_serialized_size(&ring_pedersen->pub, paillier); if (proof->proof_len < needed_proof_len) return ZKP_INVALID_PARAMETER; @@ -523,7 +673,7 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse if (!ctx) return ZKP_OUT_OF_MEMORY; - + BN_CTX_start(ctx); e = BN_CTX_get(ctx); @@ -562,15 +712,29 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse goto cleanup; } + if (is_coprime_fast(zkpok.S, ring_pedersen->pub.n, ctx) != 1) + { + status = ZKP_VERIFICATION_FAILED; + goto cleanup; + } + if (is_coprime_fast(zkpok.T, ring_pedersen->pub.n, ctx) != 1) + { + status = ZKP_VERIFICATION_FAILED; + goto cleanup; + } + + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen->pub.n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->n); + // sample e - if (!genarate_exponent_zkpok_seed(&zkpok, tmp1, public_point, aad, aad_len, seed)) + if (!genarate_exponent_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, tmp1, public_point, aad, aad_len, use_extended_seed, seed)) { status = ZKP_UNKNOWN_ERROR; goto cleanup; } if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) goto cleanup; - + if ((size_t)BN_num_bytes(zkpok.z1) > sizeof(elliptic_curve256_scalar_t) + ZKPOK_EPSILON_SIZE) { status = ZKP_VERIFICATION_FAILED; @@ -578,7 +742,7 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse } q = algebra->order_internal(algebra); - + do { if (drng_read_deterministic_rand(rng, val, sizeof(elliptic_curve256_scalar_t)) != DRNG_SUCCESS) @@ -619,8 +783,9 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse if (!BN_mod(zkpok.z1, zkpok.z1, q, ctx)) goto cleanup; - - BN_bn2binpad(zkpok.z1, z1, sizeof(elliptic_curve256_scalar_t)); + + if (BN_bn2binpad(zkpok.z1, z1, sizeof(elliptic_curve256_scalar_t)) <= 0) + goto cleanup; if (algebra->point_mul(algebra, &p1, public_point, &val) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; if (algebra->add_points(algebra, &p1, &p1, &zkpok.Y) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) @@ -636,170 +801,28 @@ zero_knowledge_proof_status range_proof_exponent_zkpok_verify(const ring_pederse return status; } -zero_knowledge_proof_status range_proof_exponent_zkpok_batch_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, uint32_t batch_size, const elliptic_curve256_point_t *public_points, const paillier_with_range_proof_t *proofs) -{ - BN_CTX *ctx = NULL; - drng_t *rng = NULL; - range_proof_exponent_zkpok_t zkpok; - uint32_t needed_proof_len; - zero_knowledge_proof_status status = ZKP_OUT_OF_MEMORY; - BIGNUM *e = NULL, *tmp1 = NULL, *tmp2 = NULL; - const BIGNUM *q; - uint8_t seed[SHA256_DIGEST_LENGTH]; - elliptic_curve256_scalar_t val; - elliptic_curve256_scalar_t z1; - elliptic_curve256_point_t p1; - elliptic_curve256_point_t p2; - - if (!ring_pedersen || !paillier || !algebra || !aad || !aad_len || !public_points || !proofs) - return ZKP_INVALID_PARAMETER; - - needed_proof_len = exponent_zkpok_serialized_size(&ring_pedersen->pub, paillier); - - for (size_t i = 0; i < batch_size; i++) - { - if (!proofs[i].ciphertext || !proofs[i].ciphertext_len || !proofs[i].serialized_proof || proofs[i].proof_len < needed_proof_len) - return ZKP_INVALID_PARAMETER; - } - - - ctx = BN_CTX_new(); - - if (!ctx) - return ZKP_OUT_OF_MEMORY; - - BN_CTX_start(ctx); - - e = BN_CTX_get(ctx); - tmp1 = BN_CTX_get(ctx); - tmp2 = BN_CTX_get(ctx); - - if (!e || !tmp1 || !tmp2) - goto cleanup; - - status = init_exponent_zkpok(&zkpok, ctx); - if (status != ZKP_SUCCESS) - goto cleanup; - - q = algebra->order_internal(algebra); - - status = ZKP_UNKNOWN_ERROR; - - for (size_t i = 0; i < batch_size; i++) - { - if (!deserialize_exponent_zkpok(&zkpok, ring_pedersen->pub.n, paillier->n, proofs[i].serialized_proof)) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - if (is_coprime_fast(zkpok.D, paillier->n, ctx) != 1) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - if (!BN_bin2bn(proofs[i].ciphertext, proofs[i].ciphertext_len, tmp1)) - { - status = ZKP_OUT_OF_MEMORY; - goto cleanup; - } - - if (is_coprime_fast(tmp1, paillier->n, ctx) != 1) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - // sample e - if (!genarate_exponent_zkpok_seed(&zkpok, tmp1, &public_points[i], aad, aad_len, seed)) - { - status = ZKP_UNKNOWN_ERROR; - goto cleanup; - } - - if ((size_t)BN_num_bytes(zkpok.z1) > sizeof(elliptic_curve256_scalar_t) + ZKPOK_EPSILON_SIZE) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) - goto cleanup; - do - { - if (drng_read_deterministic_rand(rng, val, sizeof(elliptic_curve256_scalar_t)) != DRNG_SUCCESS) - { - status = ZKP_UNKNOWN_ERROR; - goto cleanup; - } - if (!BN_bin2bn(val, sizeof(elliptic_curve256_scalar_t), e)) - goto cleanup; - } while (BN_cmp(e, q) >= 0); - drng_free(rng); - rng = NULL; - - if (paillier_encrypt_openssl_internal(paillier, tmp2, zkpok.z2, zkpok.z1, ctx) != PAILLIER_SUCCESS) - goto cleanup; - if (!BN_mod_exp(tmp1, tmp1, e, paillier->n2, ctx)) - goto cleanup; - if (!BN_mod_mul(tmp1, tmp1, zkpok.D, paillier->n2, ctx)) - goto cleanup; - - if (BN_cmp(tmp1, tmp2) != 0) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - if (ring_pedersen_create_commitment_internal(&ring_pedersen->pub, zkpok.z1, zkpok.z3, tmp2, ctx) != RING_PEDERSEN_SUCCESS) - goto cleanup; - - if (!BN_mod_exp(tmp1, zkpok.S, e, ring_pedersen->pub.n, ctx)) - goto cleanup; - if (!BN_mod_mul(tmp1, tmp1, zkpok.T, ring_pedersen->pub.n, ctx)) - goto cleanup; - - if (BN_cmp(tmp1, tmp2) != 0) - { - status = ZKP_VERIFICATION_FAILED; - goto cleanup; - } - - if (!BN_mod(zkpok.z1, zkpok.z1, q, ctx)) - goto cleanup; - - BN_bn2binpad(zkpok.z1, z1, sizeof(z1)); - if (algebra->point_mul(algebra, &p1, &public_points[i], &val) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - goto cleanup; - if (algebra->add_points(algebra, &p1, &p1, &zkpok.Y) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - goto cleanup; - if (algebra->generator_mul(algebra, &p2, &z1) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - goto cleanup; - - status = memcmp(p1, p2, sizeof(elliptic_curve256_point_t)) == 0 ? ZKP_SUCCESS : ZKP_VERIFICATION_FAILED; - } - -cleanup: - drng_free(rng); - BN_CTX_end(ctx); - BN_CTX_free(ctx); - return status; -} - static inline zero_knowledge_proof_status init_diffie_hellman_zkpok(range_proof_diffie_hellman_zkpok_t *zkpok, BN_CTX *ctx) { return init_exponent_zkpok(&zkpok->base, ctx); } -static inline int genarate_diffie_hellman_zkpok_seed(const range_proof_diffie_hellman_zkpok_t *proof, const BIGNUM *ciphertext, const elliptic_curve256_point_t *A, const elliptic_curve256_point_t *B, const elliptic_curve256_point_t *X, const uint8_t *aad, uint32_t aad_len, uint8_t *seed) +static inline int genarate_diffie_hellman_zkpok_seed(const uint32_t paillier_n_size, + const uint32_t ring_pedersen_n_size, + const range_proof_diffie_hellman_zkpok_t *proof, + const BIGNUM *ciphertext, + const elliptic_curve256_point_t *A, + const elliptic_curve256_point_t *B, + const elliptic_curve256_point_t *X, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + uint8_t *seed) { SHA256_CTX ctx; - + SHA256_Init(&ctx); - if (!genarate_zkpok_seed_internal(&proof->base, ciphertext, X, aad, aad_len, &ctx)) + if (!genarate_zkpok_seed_internal(paillier_n_size, ring_pedersen_n_size, &proof->base, ciphertext, X, aad, aad_len, use_extended_seed, &ctx)) return 0; SHA256_Update(&ctx, *A, sizeof(elliptic_curve256_point_t)); SHA256_Update(&ctx, *B, sizeof(elliptic_curve256_point_t)); @@ -810,16 +833,19 @@ static inline int genarate_diffie_hellman_zkpok_seed(const range_proof_diffie_he static inline uint32_t diffie_hellman_zkpok_serialized_size(const ring_pedersen_public_t *pub, const paillier_public_key_t *paillier) { - return exponent_zkpok_serialized_size(pub, paillier) + + return exponent_zkpok_serialized_size(pub, paillier) + sizeof(elliptic_curve256_point_t) + // sizeof(Y) sizeof(elliptic_curve256_scalar_t); // sizeof(w) } -static inline void serialize_diffie_hellman_zkpok(const range_proof_diffie_hellman_zkpok_t *proof, const BIGNUM *ring_pedersen_n, const BIGNUM *paillier_n, uint8_t *serialized_proof) +static inline uint8_t* serialize_diffie_hellman_zkpok(const range_proof_diffie_hellman_zkpok_t *proof, const BIGNUM *ring_pedersen_n, const BIGNUM *paillier_n, uint8_t *serialized_proof) { uint8_t *ptr = serialize_exponent_zkpok(&proof->base, ring_pedersen_n, paillier_n, serialized_proof); + if (!ptr) + return NULL; memcpy(ptr, proof->Y, sizeof(elliptic_curve256_point_t)); memcpy(ptr + sizeof(elliptic_curve256_point_t), proof->w, sizeof(elliptic_curve256_scalar_t)); + return ptr + sizeof(elliptic_curve256_point_t) + sizeof(elliptic_curve256_scalar_t); } static inline int deserialize_diffie_hellman_zkpok(range_proof_diffie_hellman_zkpok_t *proof, const BIGNUM *ring_pedersen_n, const BIGNUM *paillier_n, const uint8_t *serialized_proof) @@ -832,9 +858,19 @@ static inline int deserialize_diffie_hellman_zkpok(range_proof_diffie_hellman_zk return 1; } -zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, const paillier_ciphertext_t *ciphertext, - uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len) +zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_scalar_t *secret, + const elliptic_curve256_scalar_t *a, + const elliptic_curve256_scalar_t *b, const + paillier_ciphertext_t *ciphertext, + const uint8_t use_extended_seed, + uint8_t *serialized_proof, + uint32_t proof_len, + uint32_t *real_proof_len) { BN_CTX *ctx = NULL; drng_t *rng = NULL; @@ -866,13 +902,13 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring if (!ctx) return ZKP_OUT_OF_MEMORY; - + if (is_coprime_fast(ciphertext->r, paillier->n, ctx) != 1) { BN_CTX_free(ctx); return ZKP_INVALID_PARAMETER; } - + BN_CTX_start(ctx); alpha = BN_CTX_get(ctx); mu = BN_CTX_get(ctx); @@ -907,9 +943,9 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring // rand beta if (algebra->rand(algebra, &beta) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; - + // rand mu - if (!BN_copy(tmp, ring_pedersen->n) || !BN_lshift(tmp, tmp, sizeof(elliptic_curve256_scalar_t))) + if (!BN_copy(tmp, ring_pedersen->n) || !BN_lshift(tmp, tmp, sizeof(elliptic_curve256_scalar_t) * 8)) { status = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -919,7 +955,7 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring goto cleanup; // rand gamma - if (!BN_lshift(tmp, tmp, ZKPOK_EPSILON_SIZE)) + if (!BN_lshift(tmp, tmp, ZKPOK_EPSILON_SIZE * 8)) { status = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -934,7 +970,7 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring goto cleanup; paillier_status = paillier_encrypt_openssl_internal(paillier, zkpok.base.D, r, alpha, ctx); } while (paillier_status == PAILLIER_ERROR_INVALID_RANDOMNESS); - + if (paillier_status != PAILLIER_SUCCESS) goto cleanup; @@ -946,8 +982,9 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring goto cleanup; if (!BN_mod(tmp, alpha, q, ctx)) goto cleanup; - BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)); - + if (BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)) <= 0) + goto cleanup; + if (algebra->mul_scalars(algebra, &tmp_scalar, *a, sizeof(elliptic_curve256_scalar_t), beta, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; if (algebra->add_scalars(algebra, &tmp_scalar, alpha_bin, sizeof(elliptic_curve256_scalar_t), tmp_scalar, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) @@ -966,8 +1003,11 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring if (algebra->generator_mul(algebra, &X, &tmp_scalar) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) // Y = A^beta*g^alpha == g^(a*beta+alpha) goto cleanup; + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen->n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->n); + // sample e - if (!genarate_diffie_hellman_zkpok_seed(&zkpok, ciphertext->ciphertext, &A, &B, &X, aad, aad_len, seed)) + if (!genarate_diffie_hellman_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, ciphertext->ciphertext, &A, &B, &X, aad, aad_len, use_extended_seed, seed)) goto cleanup; if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) { @@ -988,12 +1028,12 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring goto cleanup; if (!BN_add(zkpok.base.z1, zkpok.base.z1, alpha)) goto cleanup; - + if (!BN_mod_exp(zkpok.base.z2, ciphertext->r, e, paillier->n, ctx)) goto cleanup; if (!BN_mod_mul(zkpok.base.z2, zkpok.base.z2, r, paillier->n, ctx)) goto cleanup; - + if (!BN_mul(zkpok.base.z3, e, mu, ctx)) goto cleanup; if (!BN_add(zkpok.base.z3, zkpok.base.z3, gamma)) @@ -1003,25 +1043,60 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_generate(const ring goto cleanup; if (algebra->add_scalars(algebra, &zkpok.w, zkpok.w, sizeof(elliptic_curve256_scalar_t), beta, sizeof(elliptic_curve256_scalar_t)) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; - - serialize_diffie_hellman_zkpok(&zkpok, ring_pedersen->n, paillier->n, serialized_proof); - status = ZKP_SUCCESS; + + status = serialize_diffie_hellman_zkpok(&zkpok, ring_pedersen->n, paillier->n, serialized_proof) != NULL ? ZKP_SUCCESS : ZKP_OUT_OF_MEMORY; + cleanup: + if (alpha) + { + BN_clear(alpha); + } + + if (mu) + { + BN_clear(mu); + } + + if (r) + { + BN_clear(r); + } + + if (gamma) + { + BN_clear(gamma); + } + + if (e) + { + BN_clear(e); + } + if (x) + { BN_clear(x); + } + + if (tmp) + { + BN_clear(tmp); + } + + OPENSSL_cleanse(alpha_bin, sizeof(elliptic_curve256_scalar_t)); + OPENSSL_cleanse(tmp_scalar, sizeof(elliptic_curve256_scalar_t)); drng_free(rng); BN_CTX_end(ctx); BN_CTX_free(ctx); return status; } -zero_knowledge_proof_status range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, paillier_with_range_proof_t **proof) +zero_knowledge_proof_status range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(const ring_pedersen_public_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_scalar_t *a, const elliptic_curve256_scalar_t *b, const uint8_t use_extended_seed, paillier_with_range_proof_t **proof) { paillier_ciphertext_t *ciphertext = NULL; paillier_with_range_proof_t *local_proof = NULL; zero_knowledge_proof_status status = ZKP_OUT_OF_MEMORY; - + if (!paillier || !secret) return ZKP_INVALID_PARAMETER; @@ -1035,17 +1110,24 @@ zero_knowledge_proof_status range_proof_paillier_encrypt_with_diffie_hellman_zkp if (!local_proof) goto cleanup; - local_proof->ciphertext_len = BN_num_bytes(ciphertext->ciphertext); + paillier_get_ciphertext(ciphertext, NULL, 0, &local_proof->ciphertext_len); local_proof->ciphertext = (uint8_t*)malloc(local_proof->ciphertext_len); + local_proof->proof_len = diffie_hellman_zkpok_serialized_size(ring_pedersen, paillier); local_proof->serialized_proof = (uint8_t*)malloc(local_proof->proof_len); if (!local_proof->ciphertext || !local_proof->serialized_proof) + { goto cleanup; + } + + if (paillier_get_ciphertext(ciphertext, local_proof->ciphertext, local_proof->ciphertext_len, NULL) != PAILLIER_SUCCESS) + { + goto cleanup; + } + + status = range_proof_diffie_hellman_zkpok_generate(ring_pedersen, paillier, algebra, aad, aad_len, secret, a, b, ciphertext, use_extended_seed, local_proof->serialized_proof, local_proof->proof_len, NULL); - BN_bn2bin(ciphertext->ciphertext, local_proof->ciphertext); - status = range_proof_diffie_hellman_zkpok_generate(ring_pedersen, paillier, algebra, aad, aad_len, secret, a, b, ciphertext, local_proof->serialized_proof, local_proof->proof_len, NULL); - if (status == ZKP_SUCCESS) { *proof = local_proof; @@ -1058,8 +1140,17 @@ zero_knowledge_proof_status range_proof_paillier_encrypt_with_diffie_hellman_zkp return status; } -zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, const paillier_public_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, uint32_t aad_len, const elliptic_curve256_point_t *public_point, const elliptic_curve256_point_t *A, const elliptic_curve256_point_t *B, const paillier_with_range_proof_t *proof) +zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_pedersen_private_t *ring_pedersen, + const paillier_public_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + uint32_t aad_len, + const elliptic_curve256_point_t *public_point, + const elliptic_curve256_point_t *A, + const elliptic_curve256_point_t *B, + const paillier_with_range_proof_t *proof, + const uint8_t strict_ciphertext_length, + const uint8_t use_extended_seed) { BN_CTX *ctx = NULL; drng_t *rng = NULL; @@ -1073,10 +1164,16 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_p elliptic_curve256_scalar_t z1; elliptic_curve256_point_t p1; elliptic_curve256_point_t p2; - + if (!ring_pedersen || !paillier || !algebra || !aad || !aad_len || !public_point || !A || !B || !proof || !proof->ciphertext || !proof->ciphertext_len || !proof->serialized_proof || !proof->proof_len) return ZKP_INVALID_PARAMETER; + + if (strict_ciphertext_length && proof->ciphertext_len != (uint32_t)BN_num_bytes(paillier->n2)) + { + return ZKP_INVALID_PARAMETER; + } + needed_proof_len = diffie_hellman_zkpok_serialized_size(&ring_pedersen->pub, paillier); if (proof->proof_len < needed_proof_len) return ZKP_INVALID_PARAMETER; @@ -1085,7 +1182,7 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_p if (!ctx) return ZKP_OUT_OF_MEMORY; - + BN_CTX_start(ctx); e = BN_CTX_get(ctx); @@ -1132,17 +1229,32 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_p goto cleanup; } + if (is_coprime_fast(zkpok.base.S, ring_pedersen->pub.n, ctx) != 1) + { + status = ZKP_VERIFICATION_FAILED; + goto cleanup; + } + + if (is_coprime_fast(zkpok.base.T, ring_pedersen->pub.n, ctx) != 1) + { + status = ZKP_VERIFICATION_FAILED; + goto cleanup; + } + + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen->pub.n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->n); + // sample e - if (!genarate_diffie_hellman_zkpok_seed(&zkpok, tmp1, A, B, public_point, aad, aad_len, seed)) + if (!genarate_diffie_hellman_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, tmp1, A, B, public_point, aad, aad_len, use_extended_seed, seed)) { status = ZKP_UNKNOWN_ERROR; goto cleanup; } if (drng_new(seed, SHA256_DIGEST_LENGTH, &rng) != DRNG_SUCCESS) goto cleanup; - + q = algebra->order_internal(algebra); - + do { if (drng_read_deterministic_rand(rng, val, sizeof(elliptic_curve256_scalar_t)) != DRNG_SUCCESS) @@ -1183,8 +1295,9 @@ zero_knowledge_proof_status range_proof_diffie_hellman_zkpok_verify(const ring_p if (!BN_mod(zkpok.base.z1, zkpok.base.z1, q, ctx)) goto cleanup; - - BN_bn2binpad(zkpok.base.z1, z1, sizeof(elliptic_curve256_scalar_t)); + + if (BN_bn2binpad(zkpok.base.z1, z1, sizeof(elliptic_curve256_scalar_t)) <= 0) + goto cleanup; if (algebra->point_mul(algebra, &p1, A, &zkpok.w) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) goto cleanup; if (algebra->generator_mul(algebra, &p2, &z1) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) @@ -1230,7 +1343,7 @@ void range_proof_free_paillier_with_range_proof(paillier_with_range_proof_t *pro // paillier large factors zkp static inline uint32_t paillier_large_factors_zkp_serialized_size(const ring_pedersen_public_t *pub, const paillier_public_key_t *paillier) { - return + return sizeof(uint32_t) + // sizeof(ring_pedersen->n) sizeof(uint32_t) + // sizeof(paillier->n) 5 * BN_num_bytes(pub->n) + // sizeof(P) + sizeof(Q) + sizeof(A) + sizeof(B) + sizeof(T) @@ -1243,8 +1356,8 @@ static inline uint32_t paillier_large_factors_zkp_serialized_size(const ring_ped // this function doesn't verify serialized_proof size as it's done in range_proof_paillier_large_factors_zkp_generate function static inline uint8_t* serialize_paillier_large_factors_zkp(const range_proof_paillier_large_factors_zkp_t *proof, const BIGNUM *ring_pedersen_n, const BIGNUM *paillier_n, uint8_t *serialized_proof) { - const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen_n); - const uint32_t paillier_n_size = BN_num_bytes(paillier_n); + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); const uint32_t lambda_size = ZKPOK_L_SIZE + ring_pedersen_n_size + paillier_n_size; const uint32_t z_size = ZKPOK_L_SIZE + ZKPOK_EPSILON_SIZE + paillier_n_size / 2; const uint32_t w_size = ZKPOK_L_SIZE + ZKPOK_EPSILON_SIZE + ring_pedersen_n_size; @@ -1264,39 +1377,50 @@ static inline uint8_t* serialize_paillier_large_factors_zkp(const range_proof_pa ptr += sizeof(uint32_t); *(uint32_t*)ptr = paillier_n_size; ptr += sizeof(uint32_t); - - BN_bn2binpad(proof->P, ptr, ring_pedersen_n_size); + + if (BN_bn2binpad(proof->P, ptr, ring_pedersen_n_size) <= 0) + return NULL; ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->Q, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->Q, ptr, ring_pedersen_n_size) <= 0) + return NULL; ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->A, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->A, ptr, ring_pedersen_n_size) <= 0) + return NULL; ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->B, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->B, ptr, ring_pedersen_n_size) <= 0) + return NULL; ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->T, ptr, ring_pedersen_n_size); + if (BN_bn2binpad(proof->T, ptr, ring_pedersen_n_size) <= 0) + return NULL; ptr += ring_pedersen_n_size; - BN_bn2binpad(proof->lambda, ptr, lambda_size); + if (BN_bn2binpad(proof->lambda, ptr, lambda_size) <= 0) + return NULL; ptr += lambda_size; - BN_bn2binpad(proof->z1, ptr, z_size); + if (BN_bn2binpad(proof->z1, ptr, z_size) <= 0) + return NULL; ptr += z_size; - BN_bn2binpad(proof->z2, ptr, z_size); + if (BN_bn2binpad(proof->z2, ptr, z_size) <= 0) + return NULL; ptr += z_size; - BN_bn2binpad(proof->w1, ptr, w_size); + if (BN_bn2binpad(proof->w1, ptr, w_size) <= 0) + return NULL; ptr += w_size; - BN_bn2binpad(proof->w2, ptr, w_size); + if (BN_bn2binpad(proof->w2, ptr, w_size) <= 0) + return NULL; ptr += w_size; - BN_bn2binpad(proof->v, ptr, v_size); + if (BN_bn2binpad(proof->v, ptr, v_size) <= 0) + return NULL; ptr += v_size; return ptr; } -static inline const uint8_t* deserialize_paillier_large_factors_zkp(range_proof_paillier_large_factors_zkp_t *proof, - const BIGNUM *ring_pedersen_n, - const BIGNUM *paillier_n, +static inline const uint8_t* deserialize_paillier_large_factors_zkp(range_proof_paillier_large_factors_zkp_t *proof, + const BIGNUM *ring_pedersen_n, + const BIGNUM *paillier_n, const uint8_t *serialized_proof) { - const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen_n); - const uint32_t paillier_n_size = BN_num_bytes(paillier_n); + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); const uint32_t lambda_size = ZKPOK_L_SIZE + ring_pedersen_n_size + paillier_n_size; const uint32_t z_size = ZKPOK_L_SIZE + ZKPOK_EPSILON_SIZE + paillier_n_size / 2; const uint32_t w_size = ZKPOK_L_SIZE + ZKPOK_EPSILON_SIZE + ring_pedersen_n_size; @@ -1358,28 +1482,118 @@ static zero_knowledge_proof_status init_paillier_large_factors_zkp(range_proof_p zkp->w1 = BN_CTX_get(ctx); zkp->w2 = BN_CTX_get(ctx); zkp->v = BN_CTX_get(ctx); - + if (zkp->P && zkp->Q && zkp->A && zkp->B && zkp->T && zkp->lambda && zkp->z1 && zkp->z2 && zkp->w1 && zkp->w2 && zkp->v) return ZKP_SUCCESS; return ZKP_OUT_OF_MEMORY; } -static inline int genarate_paillier_large_factors_zkp_seed(const range_proof_paillier_large_factors_zkp_t *proof, - const BIGNUM *ring_pedersen_n, - const BIGNUM *paillier_n, - const uint8_t *aad, - uint32_t aad_len, + +static inline int genarate_paillier_large_factors_zkp_seed_ex(const range_proof_paillier_large_factors_zkp_t *proof, + const BIGNUM *ring_pedersen_n, + const BIGNUM *paillier_n, + const uint8_t *aad, + uint32_t aad_len, + uint8_t *seed) +{ + SHA256_CTX ctx; + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); + const uint32_t lambda_size = ZKPOK_L_SIZE + ring_pedersen_n_size + paillier_n_size; + uint8_t *n = (uint8_t*)malloc(lambda_size); //lambda has the maximum size in the proof + + if (!n) + { + return 0; + } + + + SHA256_Init(&ctx); + SHA256_Update(&ctx, PAILLIER_LARGE_FACTORS_ZKP_SALT, sizeof(PAILLIER_LARGE_FACTORS_ZKP_SALT)); + if (aad) + { + SHA256_Update(&ctx, aad, aad_len); + } + + if (BN_bn2binpad(paillier_n, n, paillier_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, paillier_n_size); + + // hash P + assert ((uint32_t)BN_num_bytes(proof->P) <= ring_pedersen_n_size); + if (BN_bn2binpad(proof->P, n, ring_pedersen_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, ring_pedersen_n_size); + + // hash Q + assert ((uint32_t)BN_num_bytes(proof->Q) <= ring_pedersen_n_size); + if (BN_bn2binpad(proof->Q, n, ring_pedersen_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, ring_pedersen_n_size); + + // hash A + assert ((uint32_t)BN_num_bytes(proof->A) <= ring_pedersen_n_size); + if (BN_bn2binpad(proof->A, n, ring_pedersen_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, ring_pedersen_n_size); + + // hash B + assert ((uint32_t)BN_num_bytes(proof->B) <= ring_pedersen_n_size); + if (BN_bn2binpad(proof->B, n, ring_pedersen_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, ring_pedersen_n_size); + + // hash T + assert ((uint32_t)BN_num_bytes(proof->T) <= ring_pedersen_n_size); + if (BN_bn2binpad(proof->T, n, ring_pedersen_n_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, ring_pedersen_n_size); + + // hash lambda + assert ((uint32_t)BN_num_bytes(proof->lambda) <= lambda_size); + if (BN_bn2binpad(proof->lambda, n, lambda_size) <= 0) + { + goto error; + } + SHA256_Update(&ctx, n, lambda_size); + + free(n); + SHA256_Final(seed, &ctx); + return 1; + +error: + free(n); + return 0; +} + +static inline int genarate_paillier_large_factors_zkp_seed(const range_proof_paillier_large_factors_zkp_t *proof, + const BIGNUM *ring_pedersen_n, + const BIGNUM *paillier_n, + const uint8_t *aad, + uint32_t aad_len, uint8_t *seed) { SHA256_CTX ctx; - const uint32_t ring_pedersen_n_size = BN_num_bytes(ring_pedersen_n); - const uint32_t paillier_n_size = BN_num_bytes(paillier_n); + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(ring_pedersen_n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier_n); const uint32_t lambda_size = ZKPOK_L_SIZE + ring_pedersen_n_size + paillier_n_size; uint8_t *n = (uint8_t*)malloc(lambda_size); //lambda has the maximum size in the proof - + if (!n) return 0; - + SHA256_Init(&ctx); SHA256_Update(&ctx, PAILLIER_LARGE_FACTORS_ZKP_SALT, sizeof(PAILLIER_LARGE_FACTORS_ZKP_SALT)); if (aad) @@ -1403,7 +1617,14 @@ static inline int genarate_paillier_large_factors_zkp_seed(const range_proof_pai return 1; } -zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(const paillier_private_key_t *priv, const ring_pedersen_public_t *ring_pedersen, const uint8_t *aad, uint32_t aad_len, uint8_t *serialized_proof, uint32_t proof_len, uint32_t *real_proof_len) +zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(const paillier_private_key_t *priv, + const ring_pedersen_public_t *ring_pedersen, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + uint8_t *serialized_proof, + uint32_t proof_len, + uint32_t *real_proof_len) { BN_CTX *ctx = NULL; range_proof_paillier_large_factors_zkp_t zkp; @@ -1438,7 +1659,7 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(cons if (!ctx) return ZKP_OUT_OF_MEMORY; - + BN_CTX_start(ctx); alpha = BN_CTX_get(ctx); beta = BN_CTX_get(ctx); @@ -1474,7 +1695,7 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(cons goto cleanup; if (!BN_rand(y, w_size * 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) goto cleanup; - + if (RING_PEDERSEN_SUCCESS != ring_pedersen_create_commitment_internal(ring_pedersen, priv->p, mu, zkp.P, ctx)) goto cleanup; if (RING_PEDERSEN_SUCCESS != ring_pedersen_create_commitment_internal(ring_pedersen, priv->q, sigma, zkp.Q, ctx)) @@ -1486,15 +1707,27 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(cons if (!BN_mod_exp2_mont(zkp.T, zkp.Q, alpha, ring_pedersen->t, r, ring_pedersen->n, ctx, ring_pedersen->mont)) goto cleanup; - if (!genarate_paillier_large_factors_zkp_seed(&zkp, ring_pedersen->n, priv->pub.n, aad, aad_len, e_val)) + if (use_extended_seed) { - status = ZKP_OUT_OF_MEMORY; - goto cleanup; + if (!genarate_paillier_large_factors_zkp_seed_ex(&zkp, ring_pedersen->n, priv->pub.n, aad, aad_len, e_val)) + { + status = ZKP_OUT_OF_MEMORY; + goto cleanup; + } } + else + { + if (!genarate_paillier_large_factors_zkp_seed(&zkp, ring_pedersen->n, priv->pub.n, aad, aad_len, e_val)) + { + status = ZKP_OUT_OF_MEMORY; + goto cleanup; + } + } + if (!BN_bin2bn(e_val, sizeof(elliptic_curve256_scalar_t), e)) goto cleanup; - + if (!BN_mul(zkp.z1, e, priv->p, ctx) || !BN_add(zkp.z1, zkp.z1, alpha)) goto cleanup; if (!BN_mul(zkp.z2, e, priv->q, ctx) || !BN_add(zkp.z2, zkp.z2, beta)) @@ -1512,12 +1745,56 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_generate(cons status = serialize_paillier_large_factors_zkp(&zkp, ring_pedersen->n, priv->pub.n, serialized_proof) ? ZKP_SUCCESS : ZKP_INVALID_PARAMETER; cleanup: + if (alpha) + { + BN_clear(alpha); + } + if (beta) + { + BN_clear(beta); + } + if (mu) + { + BN_clear(mu); + } + if (sigma) + { + BN_clear(sigma); + } + if (r) + { + BN_clear(r); + } + if (e) + { + BN_clear(e); + } + if (x) + { + BN_clear(x); + } + if (y) + { + BN_clear(y); + } + if (tmp) + { + BN_clear(tmp); + } + OPENSSL_cleanse(e_val, sizeof(elliptic_curve256_scalar_t)); + BN_CTX_end(ctx); BN_CTX_free(ctx); return status; } -zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const paillier_public_key_t *pub, const ring_pedersen_private_t *ring_pedersen, const uint8_t *aad, uint32_t aad_len, const uint8_t *serialized_proof, uint32_t proof_len) +zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const paillier_public_key_t *pub, + const ring_pedersen_private_t *ring_pedersen, + const uint8_t *aad, + uint32_t aad_len, + const uint8_t use_extended_seed, + const uint8_t *serialized_proof, + uint32_t proof_len) { BN_CTX *ctx = NULL; range_proof_paillier_large_factors_zkp_t zkp; @@ -1538,9 +1815,9 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const if (!ctx) return ZKP_OUT_OF_MEMORY; - + BN_CTX_start(ctx); - + e = BN_CTX_get(ctx); R = BN_CTX_get(ctx); tmp1 = BN_CTX_get(ctx); @@ -1561,10 +1838,21 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const } // sample e - if (!genarate_paillier_large_factors_zkp_seed(&zkp, ring_pedersen->pub.n, pub->n, aad, aad_len, e_val)) + if (use_extended_seed) { - status = ZKP_UNKNOWN_ERROR; - goto cleanup; + if (!genarate_paillier_large_factors_zkp_seed_ex(&zkp, ring_pedersen->pub.n, pub->n, aad, aad_len, e_val)) + { + status = ZKP_UNKNOWN_ERROR; + goto cleanup; + } + } + else + { + if (!genarate_paillier_large_factors_zkp_seed(&zkp, ring_pedersen->pub.n, pub->n, aad, aad_len, e_val)) + { + status = ZKP_UNKNOWN_ERROR; + goto cleanup; + } } if (!BN_bin2bn(e_val, sizeof(elliptic_curve256_scalar_t), e)) goto cleanup; @@ -1625,15 +1913,14 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_zkp_verify(const * *********************************************************/ -// it is expected that the caller would call BN_CTX_start() +// it is expected that the caller would call BN_CTX_start() // before calling this function and BN_CTX_end() after calling it -static inline zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_initialize(range_proof_paillier_large_factors_quadratic_zkp_t* zkp, BN_CTX *ctx) +static inline zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_initialize(range_proof_paillier_large_factors_quadratic_zkp_t* zkp, BN_CTX *ctx) { if (!zkp || !ctx) { return ZKP_INVALID_PARAMETER; } - OPENSSL_cleanse(zkp, sizeof(range_proof_paillier_large_factors_quadratic_zkp_t)); zkp->setup.d_mont = BN_MONT_CTX_new(); @@ -1670,24 +1957,24 @@ static inline zero_knowledge_proof_status range_proof_paillier_large_factors_qua { return ZKP_SUCCESS; } - + BN_MONT_CTX_free(zkp->setup.d_mont); zkp->setup.d_mont = NULL; return ZKP_OUT_OF_MEMORY; } -// "nothing up my sleeve" refers to the principle of transparency -// in the generation of constants or parameters used in cryptographic algorithms. -// The phrase means that the creators of the cryptographic system are not hiding any +// "nothing up my sleeve" refers to the principle of transparency +// in the generation of constants or parameters used in cryptographic algorithms. +// The phrase means that the creators of the cryptographic system are not hiding any // hidden backdoors or weaknesses in the design by using arbitrary or suspicious values. // generates g and h as nothing-up-my-sleeve based on aad -static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate_basis(BIGNUM* g, - BIGNUM* h, - const BIGNUM* d, - const uint8_t* aad, - uint32_t aad_len, - BN_CTX *ctx) +static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate_basis(BIGNUM* g, + BIGNUM* h, + const BIGNUM* d, + const uint8_t* aad, + uint32_t aad_len, + BN_CTX *ctx) { drng_t* rng = NULL; long ret = -1; @@ -1696,6 +1983,11 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate const uint32_t salted_msg_len = (uint32_t)sizeof(PAILLER_LARGE_FACTORS_QUADRATIC_ZKP_SEED) + aad_len + d_size; #define BIAS_PROTECTION_BYTES 16 + #define MAX_AAD_LEN 4096 + if (d_size > MAX_D_SIZE || aad_len > MAX_AAD_LEN) + { + goto cleanup; //protect from stack overflow. Normally should be around 3K + } uint8_t* buffer = (uint8_t*)alloca(salted_msg_len + BIAS_PROTECTION_BYTES); @@ -1704,48 +1996,48 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate BN_bn2bin(d, buffer + sizeof(PAILLER_LARGE_FACTORS_QUADRATIC_ZKP_SEED) + aad_len ); ret = convert_drng_to_zkp_status(drng_new(buffer, salted_msg_len, &rng)); - if (ret != ZKP_SUCCESS) + if (ret != ZKP_SUCCESS) { goto cleanup; } // generate 'g' as nothing-up-my-sleeve ret = convert_drng_to_zkp_status(drng_read_deterministic_rand(rng, buffer, d_size + BIAS_PROTECTION_BYTES)); - if (ret != ZKP_SUCCESS) + if (ret != ZKP_SUCCESS) { goto cleanup; } - + ret = -1; //reset ret to openssl error - if (!BN_bin2bn(buffer, d_size + BIAS_PROTECTION_BYTES, g)) + if (!BN_bin2bn(buffer, d_size + BIAS_PROTECTION_BYTES, g)) { goto cleanup; } // make sure it's a square mod d - if (!BN_mod_sqr(g, g, d, ctx)) + if (!BN_mod_sqr(g, g, d, ctx)) { goto cleanup; } // generate 'h' as nothing-up-my-sleeve ret = convert_drng_to_zkp_status(drng_read_deterministic_rand(rng, buffer, d_size + BIAS_PROTECTION_BYTES)); - if (ret != ZKP_SUCCESS) + if (ret != ZKP_SUCCESS) { goto cleanup; } ret = -1; //reset ret to openssl error - if (!BN_bin2bn(buffer, d_size + BIAS_PROTECTION_BYTES, h)) + if (!BN_bin2bn(buffer, d_size + BIAS_PROTECTION_BYTES, h)) { - + goto cleanup; } // make sure it's a square mod d - if (!BN_mod_sqr(h, h, d, ctx)) + if (!BN_mod_sqr(h, h, d, ctx)) { goto cleanup; } @@ -1760,11 +2052,11 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate } drng_free(rng); - + return ret; } -uint32_t range_proof_paillier_large_factors_quadratic_zkp_compute_d_bitsize(const paillier_public_key_t* pub) +uint32_t range_proof_paillier_large_factors_quadratic_zkp_compute_d_bitsize(const paillier_public_key_t* pub) { if (!pub) { @@ -1780,16 +2072,16 @@ uint32_t range_proof_paillier_large_factors_quadratic_zkp_compute_d_bitsize(cons // Uses predefined or generates a prime d which is large enough to hold the result // when calculates P = g ^ p * h ^ r in mod d AND Q = g ^ q * h ^ s in mod d // where r and s are random -static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate_setup(paillier_large_factors_quadratic_setup_t* setup, - BIGNUM* r, - BIGNUM* s, - BIGNUM* g, - BIGNUM* h, +static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate_setup(paillier_large_factors_quadratic_setup_t* setup, + BIGNUM* r, + BIGNUM* s, + BIGNUM* g, + BIGNUM* h, const BIGNUM* d, - const paillier_private_key_t* priv, - const uint8_t* aad, - const uint32_t aad_len, - BN_CTX *ctx) + const paillier_private_key_t* priv, + const uint8_t* aad, + const uint32_t aad_len, + BN_CTX *ctx) { if (!setup || !r || !s || !g || !h || !priv || (aad && !aad_len) || (!aad && aad_len) || !ctx) { @@ -1803,7 +2095,7 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate if (d) { //verify that d is big enough and it is a prime - if ((uint32_t)BN_num_bits(d) < d_bitsize || !BN_is_prime_ex(d, BN_prime_checks, ctx, NULL)) + if ((uint32_t)BN_num_bits(d) < d_bitsize || BN_is_prime_ex(d, BN_prime_checks, ctx, NULL) != 1) { ret = ZKP_INVALID_PARAMETER; goto cleanup; @@ -1812,10 +2104,10 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate { goto cleanup; } - } - else + } + else { - do + do { //generate safe prime - VERY LARGE and slow if (!BN_generate_prime_ex(setup->d, d_bitsize, 1, NULL, NULL, NULL)) @@ -1832,7 +2124,7 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate } // if d was given, verify that it is a strong prime - if (d && !BN_is_prime_ex(setup->d_minus_1_over_2, BN_prime_checks, ctx, NULL)) + if (d && BN_is_prime_ex(setup->d_minus_1_over_2, BN_prime_checks, ctx, NULL) != 1) { ret = ZKP_INVALID_PARAMETER; goto cleanup; @@ -1848,28 +2140,28 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate { goto cleanup; } - + ret = -1; //cleanup ret for openssl errors // Generate P and Q - if (!BN_rand_range(r, setup->d_minus_1_over_2) || - !BN_rand_range(s, setup->d_minus_1_over_2) ) + if (!BN_rand(r, 2 * ZKPOK_OPTIM_L_SIZE(BN_num_bits(priv->pub.n)) * 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) || + !BN_rand(s, 2 * ZKPOK_OPTIM_L_SIZE(BN_num_bits(priv->pub.n)) * 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) ) { goto cleanup; } - // P = g ^ p * h ^ r in mod d + // P = g ^ p * h ^ r in mod d if (!BN_mod_exp2_mont(setup->P, g, priv->p, h, r, setup->d, ctx, setup->d_mont)) { goto cleanup; } - + // Q = g ^ q * h ^ s in mod d if (!BN_mod_exp2_mont(setup->Q, g, priv->q, h, s, setup->d, ctx, setup->d_mont)) { goto cleanup; } - + ret = ZKP_SUCCESS; cleanup: @@ -1882,187 +2174,197 @@ static inline zero_knowledge_proof_status range_proof_pailler_quadratic_generate return ret; } -static inline int generate_paillier_large_factors_quadratic_zkp_seed(const range_proof_paillier_large_factors_quadratic_zkp_t *proof, - const paillier_public_key_t *pub, - const uint8_t *aad, - const uint32_t aad_len, +static inline int generate_paillier_large_factors_quadratic_zkp_seed(const range_proof_paillier_large_factors_quadratic_zkp_t *proof, + const paillier_public_key_t *pub, + const uint8_t *aad, + const uint32_t aad_len, uint8_t *seed) { SHA256_CTX ctx; - const uint32_t d_size = BN_num_bytes(proof->setup.d); - if (d_size > 4096) + const uint32_t d_size = (uint32_t)BN_num_bytes(proof->setup.d); + const uint32_t n_size = (uint32_t)BN_num_bytes(pub->n); + + if (d_size > MAX_D_SIZE || + n_size > d_size) { return 0; //protect from stack overflow. Normally should be around 3K } uint8_t *tmp = (uint8_t*)alloca(d_size); //use alloca for fast allocation - + + SHA256_Init(&ctx); SHA256_Update(&ctx, PAILLIER_LARGE_FACTORS_ZKP_SALT, sizeof(PAILLIER_LARGE_FACTORS_ZKP_SALT)); if (aad) { SHA256_Update(&ctx, aad, aad_len); } - - if (!BN_bn2bin(pub->n, tmp)) + + if (BN_bn2binpad(pub->n, tmp, n_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(pub->n)); + SHA256_Update(&ctx, tmp, n_size); - if (!BN_bn2bin(proof->setup.d, tmp)) + if (BN_bn2binpad(proof->setup.d, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->setup.d)); + SHA256_Update(&ctx, tmp, d_size); - if (!BN_bn2bin(proof->setup.P, tmp)) + assert((uint32_t)BN_num_bytes(proof->setup.P) <= d_size); + if (BN_bn2binpad(proof->setup.P, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->setup.P)); + SHA256_Update(&ctx, tmp, d_size); - if (!BN_bn2bin(proof->setup.Q, tmp)) + assert((uint32_t)BN_num_bytes(proof->setup.Q) <= d_size); + if (BN_bn2binpad(proof->setup.Q, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->setup.Q)); + SHA256_Update(&ctx, tmp, d_size); - if (!BN_bn2bin(proof->A, tmp)) + assert((uint32_t)BN_num_bytes(proof->A) <= d_size); + if (BN_bn2binpad(proof->A, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->A)); + SHA256_Update(&ctx, tmp, d_size); - if (!BN_bn2bin(proof->B, tmp)) + assert((uint32_t)BN_num_bytes(proof->B) <= d_size); + if (BN_bn2binpad(proof->B, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->B)); - - if (!BN_bn2bin(proof->C, tmp)) + SHA256_Update(&ctx, tmp, d_size); + + assert((uint32_t)BN_num_bytes(proof->C) <= d_size); + if (BN_bn2binpad(proof->C, tmp, d_size) <= 0) { return 0; } - SHA256_Update(&ctx, tmp, BN_num_bytes(proof->C)); + SHA256_Update(&ctx, tmp, d_size); SHA256_Final(seed, &ctx); return 1; - -} + +} static inline uint32_t paillier_large_factors_quadratic_z_size_bytes(const uint32_t n_bitlsize) { return ((n_bitlsize + 1) / 2 + ((ZKPOK_OPTIM_L_SIZE(n_bitlsize) + ZKPOK_OPTIM_NU_SIZE(n_bitlsize)) * 8 ) + 7) / 8; } -static inline uint8_t* serialize_paillier_large_factors_quadratic(const range_proof_paillier_large_factors_quadratic_zkp_t* zkp, +static inline uint8_t* serialize_paillier_large_factors_quadratic(const range_proof_paillier_large_factors_quadratic_zkp_t* zkp, const uint32_t n_bitlsize, - uint8_t *serialized_proof) + uint8_t *serialized_proof) { uint8_t* ptr = serialized_proof; const uint32_t d_size = (uint32_t)BN_num_bytes(zkp->setup.d); const uint32_t z_size = paillier_large_factors_quadratic_z_size_bytes(n_bitlsize); + const uint32_t lambda_size = (3 * ZKPOK_OPTIM_L_SIZE(n_bitlsize)) + ZKPOK_OPTIM_NU_SIZE(n_bitlsize); *(uint32_t*)ptr = d_size; ptr += sizeof(uint32_t); - if (!BN_bn2binpad(zkp->setup.d, ptr, d_size)) + if (BN_bn2binpad(zkp->setup.d, ptr, d_size) <= 0) { return NULL; } ptr += d_size; // store sizeof(uint32_t) + 1 * d_size - if (!BN_bn2binpad(zkp->setup.P, ptr, d_size)) + if (BN_bn2binpad(zkp->setup.P, ptr, d_size) <= 0) { return NULL; } ptr += d_size; // store sizeof(uint32_t) + 2 * d_size - if (!BN_bn2binpad(zkp->setup.Q, ptr, d_size)) + if (BN_bn2binpad(zkp->setup.Q, ptr, d_size) <= 0) { return NULL; } ptr += d_size; // store sizeof(uint32_t) + 3 * d_size - if (!BN_bn2binpad(zkp->A, ptr, d_size)) + if (BN_bn2binpad(zkp->A, ptr, d_size) <= 0) { - return NULL; + return NULL; } ptr += d_size; // store sizeof(uint32_t) + 4 * d_size - if (!BN_bn2binpad(zkp->B, ptr, d_size)) + if (BN_bn2binpad(zkp->B, ptr, d_size) <= 0) { return NULL; } ptr += d_size; // store sizeof(uint32_t) + 5 * d_size - if (!BN_bn2binpad(zkp->C, ptr, d_size)) + if (BN_bn2binpad(zkp->C, ptr, d_size) <= 0) { return NULL; } ptr += d_size; // store sizeof(uint32_t) + 6 * d_size assert((uint32_t)BN_num_bytes(zkp->z1) <= z_size); //can be smaller because depends on a random value of alpha - if (!BN_bn2binpad(zkp->z1, ptr, z_size)) + if (BN_bn2binpad(zkp->z1, ptr, z_size) <= 0) { return NULL; } ptr += z_size; // store sizeof(uint32_t) + 6 * d_size + z_size assert((uint32_t)BN_num_bytes(zkp->z2) <= z_size); //can be smalle because depends on a random value of beta - if (!BN_bn2binpad(zkp->z2, ptr, z_size)) + if (BN_bn2binpad(zkp->z2, ptr, z_size) <= 0) { return NULL; } ptr += z_size; // store sizeof(uint32_t) + 6 * d_size + 2 * z_size - if (!BN_bn2binpad(zkp->lambda1, ptr, d_size)) + if (BN_bn2binpad(zkp->lambda1, ptr, lambda_size) <= 0) { return NULL; } - ptr += d_size; // store sizeof(uint32_t) + 7 * d_size + 2 * z_size + ptr += lambda_size; // store sizeof(uint32_t) + 6 * d_size + 2 * z_size + lambda_size - if (!BN_bn2binpad(zkp->lambda2, ptr, d_size)) + if (BN_bn2binpad(zkp->lambda2, ptr, lambda_size) <= 0) { return NULL; } - ptr += d_size; // store sizeof(uint32_t) + 8 * d_size + 2 * z_size + ptr += lambda_size; // store sizeof(uint32_t) + 6 * d_size + 2 * z_size + 2 * lambda_size - if (!BN_bn2binpad(zkp->w, ptr, d_size)) + if (BN_bn2binpad(zkp->w, ptr, d_size) <= 0) { return NULL; } - ptr += d_size; // store sizeof(uint32_t) + 9 * d_size + 2 * z_size + ptr += d_size; // store sizeof(uint32_t) + 7 * d_size + 2 * z_size + 2 * lambda_size return ptr; } -static inline uint32_t paillier_large_factors_quadratic_proof_size_from_dsize(const uint32_t d_size, const uint32_t z_size) +static inline uint32_t paillier_large_factors_quadratic_proof_size_from_dsize(const uint32_t d_size, const uint32_t n_bitlen) { - return sizeof(uint32_t) + 9 * d_size + 2 * z_size; + const uint32_t z_size = paillier_large_factors_quadratic_z_size_bytes(n_bitlen) ; + return sizeof(uint32_t) + 7 * d_size + 2 * z_size + 2 * (3 * ZKPOK_OPTIM_L_SIZE(n_bitlen) + ZKPOK_OPTIM_NU_SIZE(n_bitlen)); } -static inline uint32_t paillier_large_factors_quadratic_proof_size(const paillier_public_key_t* pub, const uint32_t d_prime_len) +static inline uint32_t paillier_large_factors_quadratic_proof_size(const paillier_public_key_t* pub, const uint32_t d_prime_len) { const uint32_t d_bitsize = d_prime_len ? d_prime_len * 8 : range_proof_paillier_large_factors_quadratic_zkp_compute_d_bitsize(pub); const uint32_t d_size = (d_bitsize + 7) / 8; //convert to bytes of d - - const uint32_t z_size = paillier_large_factors_quadratic_z_size_bytes(paillier_public_key_size(pub)) ; - + const uint32_t n_bitlen = paillier_public_key_size(pub); - return paillier_large_factors_quadratic_proof_size_from_dsize(d_size, z_size); + return paillier_large_factors_quadratic_proof_size_from_dsize(d_size, n_bitlen); } -static inline const uint8_t* deserialize_paillier_large_factors_quadratic(range_proof_paillier_large_factors_quadratic_zkp_t* zkp, - const uint8_t* serialized_proof, +static inline const uint8_t* deserialize_paillier_large_factors_quadratic(range_proof_paillier_large_factors_quadratic_zkp_t* zkp, + const uint8_t* serialized_proof, const uint32_t n_bitlsize, - uint32_t proof_len, - BN_CTX* ctx) + uint32_t proof_len, + BN_CTX* ctx) { const uint32_t z_size = paillier_large_factors_quadratic_z_size_bytes(n_bitlsize); + const uint32_t lambda_size = (3 * ZKPOK_OPTIM_L_SIZE(n_bitlsize)) + ZKPOK_OPTIM_NU_SIZE(n_bitlsize); const uint8_t* ptr = serialized_proof; uint32_t d_size; if (proof_len < sizeof(uint32_t)) @@ -2072,12 +2374,12 @@ static inline const uint8_t* deserialize_paillier_large_factors_quadratic(range_ d_size = *(const uint32_t*)ptr; // read sizeof(uint32_t) ptr += sizeof(uint32_t); - if (!d_size) + if (!d_size || d_size > MAX_D_SIZE) { return NULL; } - if (proof_len < paillier_large_factors_quadratic_proof_size_from_dsize(d_size, z_size)) + if (proof_len < paillier_large_factors_quadratic_proof_size_from_dsize(d_size, n_bitlsize)) { return NULL; } @@ -2140,35 +2442,35 @@ static inline const uint8_t* deserialize_paillier_large_factors_quadratic(range_ } ptr += z_size; // read sizeof(uint32_t) + 6 * d_size + 2* z_size - if (!BN_bin2bn(ptr, d_size, zkp->lambda1)) + if (!BN_bin2bn(ptr, lambda_size, zkp->lambda1)) { return NULL; } - ptr += d_size; // read sizeof(uint32_t) + 7 * d_size + 2* z_size + ptr += lambda_size; // read sizeof(uint32_t) + 6 * d_size + 2* z_size + lambda_size - if (!BN_bin2bn(ptr, d_size, zkp->lambda2)) + if (!BN_bin2bn(ptr, lambda_size, zkp->lambda2)) { return NULL; } - ptr += d_size;// read sizeof(uint32_t) + 8 * d_size + 2* z_size + ptr += lambda_size;// read sizeof(uint32_t) + 6 * d_size + 2* z_size + 2 * lambda_size if (!BN_bin2bn(ptr, d_size, zkp->w)) { return NULL; } - ptr += d_size; // read sizeof(uint32_t) + 9 * d_size + 2* z_size + ptr += d_size; // read sizeof(uint32_t) + 7 * d_size + 2* z_size + 2 * lambda_size return ptr; } -zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_generate(const paillier_private_key_t *priv, - const uint8_t *aad, - const uint32_t aad_len, +zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_generate(const paillier_private_key_t *priv, + const uint8_t *aad, + const uint32_t aad_len, const uint8_t *d_prime, const uint32_t d_prime_len, - uint8_t *serialized_proof, - uint32_t proof_len, + uint8_t *serialized_proof, + uint32_t proof_len, uint32_t *real_proof_len) { BN_CTX *ctx = NULL; @@ -2184,7 +2486,7 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen { return ZKP_INVALID_PARAMETER; } - + required_len = paillier_large_factors_quadratic_proof_size(&priv->pub, d_prime_len); if (real_proof_len) @@ -2198,7 +2500,7 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen } n_bitlsize = paillier_public_key_size(&priv->pub); - + if (ZKPOK_OPTIM_L_SIZE(n_bitlsize) > SHA256_DIGEST_LENGTH) { return ZKP_INVALID_PARAMETER; @@ -2209,14 +2511,19 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen { return ZKP_OUT_OF_MEMORY; } - + BN_CTX_start(ctx); + ret = range_proof_paillier_large_factors_quadratic_zkp_initialize(&zkp, ctx); + if (ZKP_SUCCESS != ret) + { + goto cleanup; + } g = BN_CTX_get(ctx); h = BN_CTX_get(ctx); r = BN_CTX_get(ctx); s = BN_CTX_get(ctx); - + if (!g || !h || !r || !s) { ret = ZKP_OUT_OF_MEMORY; @@ -2252,12 +2559,6 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen } } - ret = range_proof_paillier_large_factors_quadratic_zkp_initialize(&zkp, ctx); - if (ZKP_SUCCESS != ret) - { - goto cleanup; - } - ret = range_proof_pailler_quadratic_generate_setup(&zkp.setup, r, s, g, h, d, priv, aad, aad_len, ctx); if (ZKP_SUCCESS != ret) { @@ -2270,13 +2571,16 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen if (!BN_rshift(tmp, priv->pub.n, (n_bitlsize + 1) / 2 - ((ZKPOK_OPTIM_L_SIZE(n_bitlsize) + ZKPOK_OPTIM_NU_SIZE(n_bitlsize)) * 8 )) || !BN_rand_range(alpha, tmp) || !BN_rand_range(beta, tmp) || - !BN_rand_range(sigma, zkp.setup.d_minus_1_over_2) || - !BN_rand_range(rho, zkp.setup.d_minus_1_over_2) || - !BN_rand_range(mu, zkp.setup.d_minus_1_over_2) || + // sigma and rho must be 3l + nu bits + !BN_rand(sigma, ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(n_bitlsize) + ZKPOK_OPTIM_L_SIZE(n_bitlsize) * 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) || + !BN_rand(rho, ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(n_bitlsize) + ZKPOK_OPTIM_L_SIZE(n_bitlsize) * 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) || + // we now pick mu to be 3*l + n_bit/2 + nu bits long + !BN_lshift(tmp, tmp, (2 * ZKPOK_OPTIM_L_SIZE(n_bitlsize)) * 8) || + !BN_rand_range(mu, tmp) || !BN_mod_exp2_mont(zkp.A, g, alpha, h, rho, zkp.setup.d, ctx, zkp.setup.d_mont) || !BN_mod_exp2_mont(zkp.B, g, beta, h, sigma, zkp.setup.d, ctx, zkp.setup.d_mont) || !BN_mod_exp2_mont(zkp.C, zkp.setup.Q, alpha, h, mu, zkp.setup.d, ctx, zkp.setup.d_mont) - ) + ) { goto cleanup; } @@ -2287,8 +2591,8 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen ret = ZKP_UNKNOWN_ERROR; goto cleanup; } - - if (!BN_bin2bn(seed, ZKPOK_OPTIM_L_SIZE(n_bitlsize), e)) + + if (!BN_bin2bn(seed, ZKPOK_OPTIM_L_SIZE(n_bitlsize), e)) { goto cleanup; } @@ -2309,23 +2613,23 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen !BN_mod_mul(zkp.w, s, priv->p, zkp.setup.d_minus_1_over_2, ctx) || !BN_mod_mul(zkp.w, zkp.w, e, zkp.setup.d_minus_1_over_2, ctx) || !BN_mod_sub_quick(zkp.w, mu, zkp.w, zkp.setup.d_minus_1_over_2) - ) + ) { goto cleanup; } end_of_serialized_data = serialize_paillier_large_factors_quadratic(&zkp, n_bitlsize, serialized_proof); - - if (!end_of_serialized_data) + + if (!end_of_serialized_data) { ret = ZKP_UNKNOWN_ERROR; goto cleanup; - } + } // clean up all the remaining bytes // can happen if initial size of d was incorerct and real d takes less bytes OPENSSL_cleanse(end_of_serialized_data, proof_len - (end_of_serialized_data - serialized_proof)); - + if (real_proof_len) { *real_proof_len = (end_of_serialized_data - serialized_proof); @@ -2339,6 +2643,40 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen ERR_clear_error(); ret = ZKP_UNKNOWN_ERROR; } + if (r) + { + BN_clear(r); + } + if (s) + { + BN_clear(s); + } + + if (alpha) + { + BN_clear(alpha); + } + if (beta) + { + BN_clear(beta); + } + if (sigma) + { + BN_clear(sigma); + } + if (rho) + { + BN_clear(rho); + } + if (mu) + { + BN_clear(mu); + } + if (tmp) + { + BN_clear(tmp); + } + BN_CTX_end(ctx); BN_CTX_free(ctx); @@ -2351,8 +2689,8 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_gen return ret; } -static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_verify_setup(const paillier_large_factors_quadratic_setup_t* setup, const paillier_public_key_t* pub, BN_CTX* ctx) -{ +static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_verify_setup(const paillier_large_factors_quadratic_setup_t* setup, const paillier_public_key_t* pub, BN_CTX* ctx) +{ long ret = -1; BN_CTX_start(ctx); @@ -2375,14 +2713,14 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ { goto cleanup; } - + if (BN_cmp(tmp, setup->d) != 0 || BN_is_prime_ex(setup->d, BN_prime_checks, ctx, NULL) != 1 || BN_is_prime_ex(setup->d_minus_1_over_2, BN_prime_checks, ctx, NULL) != 1 || BN_cmp(setup->d, setup->P) <= 0 || // make sure P, Q are smaller than 'd' BN_cmp(setup->d, setup->Q) <= 0 || BN_kronecker(setup->P, setup->d, ctx) != 1 || // make sure that P, Q are indeed quadratic residues. - BN_kronecker(setup->Q, setup->d, ctx) != 1) + BN_kronecker(setup->Q, setup->d, ctx) != 1) { ret = ZKP_VERIFICATION_FAILED; goto cleanup; @@ -2396,19 +2734,19 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ ERR_clear_error(); ret = ZKP_UNKNOWN_ERROR; } - + BN_CTX_end(ctx); return ret; } -static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_verify_internal(const paillier_public_key_t *pub, - const uint8_t *aad, - const uint32_t aad_len, +static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_verify_internal(const paillier_public_key_t *pub, + const uint8_t *aad, + const uint32_t aad_len, const range_proof_paillier_large_factors_quadratic_zkp_t* zkp, - const BIGNUM* g, - const BIGNUM* h, - BN_CTX* ctx) + const BIGNUM* g, + const BIGNUM* h, + BN_CTX* ctx) { long ret = -1; uint8_t seed[SHA256_DIGEST_LENGTH]; @@ -2439,7 +2777,7 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ { goto cleanup; } - + //reset ret to handle OpenSSL errors ret = -1; @@ -2447,15 +2785,15 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ { goto cleanup; } - - if (BN_cmp(zkp->z1, upper_limit) >= 0 || + + if (BN_cmp(zkp->z1, upper_limit) >= 0 || BN_cmp(zkp->z2, upper_limit) >= 0 ) { ret = ZKP_VERIFICATION_FAILED; goto cleanup; } - + if (!generate_paillier_large_factors_quadratic_zkp_seed(zkp, pub, aad, aad_len, seed)) { ret = ZKP_UNKNOWN_ERROR; @@ -2491,7 +2829,7 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ ret = ZKP_VERIFICATION_FAILED; goto cleanup; } - + if (!BN_mul(e, e, pub->n, ctx) || !BN_mod_exp2_mont(lhs, zkp->setup.Q, zkp->z1, h, zkp->w, zkp->setup.d, ctx, zkp->setup.d_mont) || @@ -2499,13 +2837,13 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ { goto cleanup; } - + if (BN_cmp(lhs, rhs) != 0) { ret = ZKP_VERIFICATION_FAILED; goto cleanup; } - + ret = ZKP_SUCCESS; cleanup: @@ -2520,11 +2858,11 @@ static zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_ return ret; } -zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_verify(const paillier_public_key_t *pub, - const uint8_t *aad, - const uint32_t aad_len, - const uint8_t *serialized_proof, - const uint32_t proof_len) +zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_verify(const paillier_public_key_t *pub, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t *serialized_proof, + const uint32_t proof_len) { BN_CTX *ctx = NULL; BIGNUM *g = NULL, *h = NULL; @@ -2535,35 +2873,35 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_ver { return ZKP_INVALID_PARAMETER; } - + // at least the size of a uint32_t if (proof_len < sizeof(uint32_t)) { return ZKP_INSUFFICIENT_BUFFER; } - + ctx = BN_CTX_new(); if (!ctx) { return ZKP_OUT_OF_MEMORY; } - + BN_CTX_start(ctx); + ret = range_proof_paillier_large_factors_quadratic_zkp_initialize(&zkp, ctx); + if (ret != ZKP_SUCCESS) + { + goto cleanup; + } g = BN_CTX_get(ctx); h = BN_CTX_get(ctx); - if (!h || !h) + if (!g || !h) { ret = ZKP_OUT_OF_MEMORY; goto cleanup; } - ret = range_proof_paillier_large_factors_quadratic_zkp_initialize(&zkp, ctx); - if (ret != ZKP_SUCCESS) - { - goto cleanup; - } - - if (!deserialize_paillier_large_factors_quadratic(&zkp, serialized_proof, paillier_public_key_size(pub), proof_len, ctx)) + + if (!deserialize_paillier_large_factors_quadratic(&zkp, serialized_proof, paillier_public_key_size(pub), proof_len, ctx)) { ret = ZKP_UNKNOWN_ERROR; goto cleanup; @@ -2585,7 +2923,7 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_ver { BN_MONT_CTX_free(zkp.setup.d_mont); } - + return ret; } @@ -2594,15 +2932,16 @@ zero_knowledge_proof_status range_proof_paillier_large_factors_quadratic_zkp_ver // paillier small group with encrypted dlog // the serialize_proof should point to a buffer of size at least // exponent_zkpok_serialized_size_internal(damgard_fujisaki->n, paillier->paillier_public.n) -static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, - const paillier_commitment_private_key_t *paillier, +static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, + const paillier_commitment_private_key_t *paillier, const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, - const uint32_t aad_len, - const uint8_t* secret, - const uint32_t secret_len, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t* secret, + const uint32_t secret_len, const BIGNUM *ciphertext, const BIGNUM *r, + const uint8_t use_extended_seed, uint8_t *serialized_proof) { BN_CTX *ctx = NULL; @@ -2619,13 +2958,13 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { return ZKP_INVALID_PARAMETER; } - + // because we require two proofs if (damgard_fujisaki->dimension < 2) { return ZKP_INVALID_PARAMETER; } - + ctx = BN_CTX_new(); if (!ctx) { @@ -2644,23 +2983,23 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { goto cleanup; } - + // all members of zkpok are allocated from the ctx ret = init_exponent_zkpok(&zkpok, ctx); if (ret != ZKP_SUCCESS) { goto cleanup; } - + ret = -1; //reset for OpenSSL errors - + if (!BN_bin2bn(secret, secret_len, x)) { goto cleanup; } - + q = algebra->order_internal(algebra); - + paillier_n_bitsize = (uint32_t)paillier_commitment_public_bitsize(&paillier->pub); // generate S, D, Y, T @@ -2674,7 +3013,7 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { goto cleanup; } - + // rand mu if (!BN_copy(tmp, damgard_fujisaki->n) || !BN_lshift(tmp, tmp, ZKPOK_OPTIM_NU_SIZE(paillier_n_bitsize) * 8)) //tmp = n * 2^(ZKPOK_OPTIM_NU_SIZE*8) { @@ -2685,7 +3024,7 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { goto cleanup; } - + // rand mu_p if (!BN_lshift(tmp, tmp, ZKPOK_OPTIM_EPSILON_SIZE(paillier_n_bitsize) * 8))// tmp = n * 2^(ZKPOK_OPTIM_NU_SIZE*8 + ZKPOK_OPTIM_EPSILON_SIZE * 8) @@ -2693,75 +3032,82 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon goto cleanup; } - if (!BN_rand_range(mu_p, tmp)) + if (!BN_rand_range(mu_p, tmp)) { goto cleanup; } - - // the r_power_bitsize is called n(lamda prime) in the paper and it used for encryption of alpha - // which is bigger than private share, thus we use larger exponent as in range_proof_paillier_commitment_exponent_zkpok_generate - ret = convert_paillier_to_zkp_status(paillier_commitment_encrypt_openssl_with_private_internal(paillier, - ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(paillier_n_bitsize) + ZKPOK_OPTIM_EPSILON_SIZE(paillier_n_bitsize) * 8, - alpha, + + // the r_power_bitsize is called n(lambda prime) in the paper and it used for encryption of alpha + // which is bigger than private share, thus we use larger exponent as in paillier_commitment_encrypt_with_exponent_zkpok_generate + ret = convert_paillier_to_zkp_status(paillier_commitment_encrypt_openssl_with_private_internal(paillier, + ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(paillier_n_bitsize) + ZKPOK_OPTIM_EPSILON_SIZE(paillier_n_bitsize) * 8, + alpha, ctx, - zkpok.D, + zkpok.D, lambda_p)); if (ret != ZKP_SUCCESS) { goto cleanup; } - + const BIGNUM* first_ped_committed_values[] = {x, r}; - ret = convert_ring_pedersen_to_zkp_status(damgard_fujisaki_create_commitment_internal(damgard_fujisaki, - first_ped_committed_values, + ret = convert_ring_pedersen_to_zkp_status(damgard_fujisaki_create_commitment_internal(damgard_fujisaki, + first_ped_committed_values, 2, - mu, - zkpok.S, + mu, + zkpok.S, ctx)); if (ret != ZKP_SUCCESS) { goto cleanup; } - + const BIGNUM* second_ped_committed_values[] = {alpha, lambda_p}; - ret = convert_ring_pedersen_to_zkp_status(damgard_fujisaki_create_commitment_internal(damgard_fujisaki, - second_ped_committed_values, + ret = convert_ring_pedersen_to_zkp_status(damgard_fujisaki_create_commitment_internal(damgard_fujisaki, + second_ped_committed_values, 2, - mu_p, - zkpok.T, + mu_p, + zkpok.T, ctx)); if (ret != ZKP_SUCCESS) { goto cleanup; } - + if (!BN_mod(tmp, alpha, q, ctx)) { ret = -1; // OpenSSL error goto cleanup; } - - BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)); - + + if (BN_bn2binpad(tmp, alpha_bin, sizeof(elliptic_curve256_scalar_t)) <= 0) + { + ret = -1; + goto cleanup; + } + ret = convert_algebra_to_zkp_status(algebra->generator_mul(algebra, &zkpok.Y, &alpha_bin)); if (ret != ZKP_SUCCESS) { goto cleanup; } - + ret = convert_algebra_to_zkp_status(algebra->generator_mul_data(algebra, secret, secret_len, &public_point)); if (ret != ZKP_SUCCESS) { goto cleanup; } + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(damgard_fujisaki->n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->pub.n); + // sample e - if (!genarate_exponent_zkpok_seed(&zkpok, ciphertext, &public_point, aad, aad_len, seed)) + if (!genarate_exponent_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, ciphertext, &public_point, aad, aad_len, use_extended_seed, seed)) { ret = ZKP_UNKNOWN_ERROR; goto cleanup; } - + ret = convert_drng_to_zkp_status(drng_new(seed, SHA256_DIGEST_LENGTH, &rng)); if (ret != ZKP_SUCCESS) { @@ -2781,7 +3127,7 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { goto cleanup; } - + } while (BN_cmp(e, q) >= 0); // calc z1, z2, z3 @@ -2789,37 +3135,35 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon { goto cleanup; } - + if (!BN_add(zkpok.z1, zkpok.z1, alpha)) { goto cleanup; } - + if (!BN_mul(zkpok.z2, r, e, ctx)) { goto cleanup; } - + if (!BN_add(zkpok.z2, zkpok.z2, lambda_p)) { goto cleanup; } - + if (!BN_mul(zkpok.z3, e, mu, ctx)) { goto cleanup; } - + if (!BN_add(zkpok.z3, zkpok.z3, mu_p)) { goto cleanup; } - - serialize_exponent_zkpok(&zkpok, damgard_fujisaki->n, paillier->pub.n, serialized_proof); - ret = ZKP_SUCCESS; + ret = serialize_exponent_zkpok(&zkpok, damgard_fujisaki->n, paillier->pub.n, serialized_proof) != NULL ? ZKP_SUCCESS : ZKP_OUT_OF_MEMORY; cleanup: if (-1 == ret) @@ -2835,7 +3179,7 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon BN_clear(alpha); BN_clear(lambda_p); } - + drng_free(rng); BN_CTX_end(ctx); BN_CTX_free(ctx); @@ -2843,14 +3187,15 @@ static zero_knowledge_proof_status range_proof_paillier_commitment_encrypt_expon } -zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, - const paillier_commitment_private_key_t *paillier, - const elliptic_curve256_algebra_ctx_t *algebra, - const uint8_t *aad, - const uint32_t aad_len, - const uint8_t* secret, - const uint32_t secret_len, - paillier_with_range_proof_t **proof) +zero_knowledge_proof_status paillier_commitment_encrypt_with_exponent_zkpok_generate(const damgard_fujisaki_public_t *damgard_fujisaki, + const paillier_commitment_private_key_t *paillier, + const elliptic_curve256_algebra_ctx_t *algebra, + const uint8_t *aad, + const uint32_t aad_len, + const uint8_t* secret, + const uint32_t secret_len, + const uint8_t use_extended_seed, + paillier_with_range_proof_t **proof) { BIGNUM *ciphertext = NULL, *randomizer_power = NULL, *msg = NULL; BN_CTX * ctx = NULL; @@ -2861,7 +3206,7 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener { return ZKP_INVALID_PARAMETER; } - + if (damgard_fujisaki->dimension < 2) { return ZKP_INVALID_PARAMETER; @@ -2877,7 +3222,7 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener ciphertext = BN_CTX_get(ctx); randomizer_power = BN_CTX_get(ctx); msg = BN_CTX_get(ctx); - if (!ciphertext || !randomizer_power) + if (!ciphertext || !randomizer_power || !msg) { ret = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -2897,9 +3242,9 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener paillier_n_bitsize = paillier_commitment_public_bitsize(&paillier->pub); - // the r_power_bitsize is called n(lamda) in the paper and it used for encrypt of private share here - ret = convert_paillier_to_zkp_status(paillier_commitment_encrypt_openssl_with_private_internal(paillier, - ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(paillier_n_bitsize), + // the r_power_bitsize is called n(lambda) in the paper and it used for encrypt of private share here + ret = convert_paillier_to_zkp_status(paillier_commitment_encrypt_openssl_with_private_internal(paillier, + ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(paillier_n_bitsize), msg, ctx, ciphertext, @@ -2916,7 +3261,7 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener ret = ZKP_OUT_OF_MEMORY; goto cleanup; } - + local_proof->ciphertext_len = BN_num_bytes(ciphertext); local_proof->ciphertext = (uint8_t*)calloc(1, local_proof->ciphertext_len); @@ -2931,15 +3276,16 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener BN_bn2bin(ciphertext, local_proof->ciphertext); - ret = range_proof_paillier_commitment_encrypt_exponent_zkpok_generate(damgard_fujisaki, - paillier, - algebra, - aad, - aad_len, - secret, - secret_len, - ciphertext, + ret = range_proof_paillier_commitment_encrypt_exponent_zkpok_generate(damgard_fujisaki, + paillier, + algebra, + aad, + aad_len, + secret, + secret_len, + ciphertext, randomizer_power, + use_extended_seed, local_proof->serialized_proof); cleanup: @@ -2948,6 +3294,11 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener BN_clear(randomizer_power); } + if (msg) + { + BN_clear(msg); + } + BN_CTX_end(ctx); BN_CTX_free(ctx); @@ -2963,13 +3314,14 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_gener return ret; } -zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verify(const damgard_fujisaki_private_t* damgard_fujisaki, - const paillier_commitment_public_key_t* paillier, - const elliptic_curve256_algebra_ctx_t* algebra, - const uint8_t* aad, - const uint32_t aad_len, - const elliptic_curve256_point_t* public_point, - const const_paillier_with_range_proof_t* proof) +zero_knowledge_proof_status paillier_commitment_exponent_zkpok_verify(const damgard_fujisaki_private_t* damgard_fujisaki, + const paillier_commitment_public_key_t* paillier, + const elliptic_curve256_algebra_ctx_t* algebra, + const uint8_t* aad, + const uint32_t aad_len, + const elliptic_curve256_point_t* public_point, + const const_paillier_with_range_proof_t* proof, + const uint8_t use_extended_seed) { BN_CTX *ctx = NULL; drng_t *rng = NULL; @@ -2985,21 +3337,21 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif elliptic_curve256_point_t p2; uint32_t paillier_n_bitsize; - if (!damgard_fujisaki || - !paillier || - !algebra || - !aad || - !aad_len || - !public_point || - !proof || - !proof->ciphertext || - !proof->ciphertext_len || - !proof->serialized_proof || + if (!damgard_fujisaki || + !paillier || + !algebra || + !aad || + !aad_len || + !public_point || + !proof || + !proof->ciphertext || + !proof->ciphertext_len || + !proof->serialized_proof || !proof->proof_len) { return ZKP_INVALID_PARAMETER; } - + paillier_n_bitsize = paillier_commitment_public_bitsize(paillier); needed_proof_len = exponent_zkpok_serialized_size_internal(damgard_fujisaki->pub.n, paillier->n); if (proof->proof_len < needed_proof_len) @@ -3032,7 +3384,7 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif } ret = ZKP_VERIFICATION_FAILED; - + if (!deserialize_exponent_zkpok(&zkpok, damgard_fujisaki->pub.n, paillier->n, proof->serialized_proof)) { goto cleanup; @@ -3054,8 +3406,22 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif goto cleanup; } + // \tilde(P) in the paper + if (is_coprime_fast(zkpok.S, damgard_fujisaki->pub.n, ctx) != 1) + { + goto cleanup; + } + // \tilde(B) in the paper + if (is_coprime_fast(zkpok.T, damgard_fujisaki->pub.n, ctx) != 1) + { + goto cleanup; + } + + const uint32_t ring_pedersen_n_size = (uint32_t)BN_num_bytes(damgard_fujisaki->pub.n); + const uint32_t paillier_n_size = (uint32_t)BN_num_bytes(paillier->n); + // sample e - if (!genarate_exponent_zkpok_seed(&zkpok, tmp1, public_point, aad, aad_len, seed)) + if (!genarate_exponent_zkpok_seed(paillier_n_size, ring_pedersen_n_size, &zkpok, tmp1, public_point, aad, aad_len, use_extended_seed, seed)) { ret = ZKP_OUT_OF_MEMORY; goto cleanup; @@ -3095,7 +3461,7 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif { goto cleanup; } - + do { @@ -3125,13 +3491,13 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif { goto cleanup; } - + if (!BN_mod_mul(tmp1, tmp1, zkpok.D, paillier->n2, ctx)) { goto cleanup; } - + if (BN_cmp(tmp1, tmp2) != 0) { @@ -3143,13 +3509,13 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif { goto cleanup; } - + if (!BN_mod_mul(tmp1, tmp1, zkpok.T, damgard_fujisaki->pub.n, ctx)) { goto cleanup; } - + const BIGNUM* verification_pedersen_commitments[] = {zkpok.z1, zkpok.z2}; ret = damgard_fujisaki_verify_commitment_internal(damgard_fujisaki, verification_pedersen_commitments, 2, zkpok.z3, tmp1, ctx); @@ -3157,15 +3523,19 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif { goto cleanup; } - + //prepare z1 point if (!BN_mod(zkpok.z1, zkpok.z1, q, ctx)) { ret = -1; goto cleanup; } - - BN_bn2binpad(zkpok.z1, z1, sizeof(elliptic_curve256_scalar_t)); + + if (BN_bn2binpad(zkpok.z1, z1, sizeof(elliptic_curve256_scalar_t)) <= 0) + { + ret = -1; + goto cleanup; + } ret = ZKP_VERIFICATION_FAILED; @@ -3173,24 +3543,24 @@ zero_knowledge_proof_status range_proof_paillier_commitment_exponent_zkpok_verif { goto cleanup; } - + if (algebra->add_points(algebra, &p1, &p1, &zkpok.Y) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { goto cleanup; } - + if (algebra->generator_mul(algebra, &p2, &z1) != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { goto cleanup; } - if (0 == memcmp(p1, p2, sizeof(elliptic_curve256_point_t))) + if (0 == memcmp(p1, p2, sizeof(elliptic_curve256_point_t))) { ret = ZKP_SUCCESS; } cleanup: - + if (-1 == ret) { ERR_clear_error(); diff --git a/src/common/crypto/zero_knowledge_proof/schnorr.c b/src/common/crypto/zero_knowledge_proof/schnorr.c index 474a655..65c2d25 100644 --- a/src/common/crypto/zero_knowledge_proof/schnorr.c +++ b/src/common/crypto/zero_knowledge_proof/schnorr.c @@ -51,14 +51,17 @@ static zero_knowledge_proof_status schnorr_zkp_generate_impl(const elliptic_curv status = algebra->mul_scalars(algebra, &c, secret, secret_size, c, sizeof(c)); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return from_elliptic_curve_algebra_status(status); + goto cleanup; status = algebra->sub_scalars(algebra, &proof->s, k, sizeof(k), c, sizeof(c)); - OPENSSL_cleanse(k, sizeof(k)); if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) - return from_elliptic_curve_algebra_status(status); + goto cleanup; - return ZKP_SUCCESS; + status = ELLIPTIC_CURVE_ALGEBRA_SUCCESS; +cleanup: + OPENSSL_cleanse(k, sizeof(k)); + OPENSSL_cleanse(c, sizeof(c)); + return from_elliptic_curve_algebra_status(status); } zero_knowledge_proof_status schnorr_zkp_generate(const elliptic_curve256_algebra_ctx_t *algebra, const uint8_t *prover_id, uint32_t id_len, const elliptic_curve256_scalar_t *secret, const elliptic_curve256_point_t *public_data, schnorr_zkp_t *proof) diff --git a/src/common/crypto/zero_knowledge_proof/zkp_constants_internal.h b/src/common/crypto/zero_knowledge_proof/zkp_constants_internal.h index 7079e28..b81b86a 100644 --- a/src/common/crypto/zero_knowledge_proof/zkp_constants_internal.h +++ b/src/common/crypto/zero_knowledge_proof/zkp_constants_internal.h @@ -2,7 +2,7 @@ #define __ZKP_CONSTRAINTS_INTERNAL_H__ #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" -#include "../algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" // Parameter sizes for zero-knowledge proof optimizations @@ -18,7 +18,9 @@ static inline CONSTEXPR uint32_t ZKPOK_OPTIM_L_SIZE(const uint32_t n_bitlen) static inline CONSTEXPR uint32_t ZKPOK_OPTIM_NU_SIZE(const uint32_t n_bitlen) { - return (ZKPOK_OPTIM_L_SIZE(n_bitlen) + 1) / 2; + (void)n_bitlen; + // always return 8 bytes (ie 64 bits) + return 8; } static inline CONSTEXPR uint32_t ZKPOK_OPTIM_NX_SIZE(const uint32_t n_bitlen) @@ -33,7 +35,7 @@ static inline CONSTEXPR uint32_t ZKPOK_OPTIM_EPSILON_SIZE(const uint32_t n_bitle static inline CONSTEXPR uint32_t ZKPOK_OPTIM_SMALL_GROUP_EXPONENT_BITS(const uint32_t n_bitlen) { - return ((2*ZKPOK_OPTIM_L_SIZE(n_bitlen) + ZKPOK_OPTIM_NU_SIZE(n_bitlen))*8); + return ((2 * ZKPOK_OPTIM_L_SIZE(n_bitlen) + ZKPOK_OPTIM_NU_SIZE(n_bitlen)) * 8); } static inline CONSTEXPR uint32_t ZKPOK_OPTIM_NLAMBDA_SIZE(const uint32_t n_bitlen) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a1cf3bb..0ff34d7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,7 @@ target_link_libraries(tests_main PUBLIC cosigner) add_subdirectory(cosigner) add_subdirectory(crypto/drng) +add_subdirectory(crypto/entropy) add_subdirectory(crypto/ed25519_algebra) add_subdirectory(crypto/paillier) add_subdirectory(crypto/secp256k1_algebra) diff --git a/test/cosigner/CMakeLists.txt b/test/cosigner/CMakeLists.txt index b102191..3707bb8 100644 --- a/test/cosigner/CMakeLists.txt +++ b/test/cosigner/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(cosigner_test ecdsa_online_test.cpp eddsa_offline_test.cpp eddsa_online_test.cpp + bam_test.cpp setup_test.cpp ) diff --git a/test/cosigner/bam_test.cpp b/test/cosigner/bam_test.cpp new file mode 100644 index 0000000..262101c --- /dev/null +++ b/test/cosigner/bam_test.cpp @@ -0,0 +1,3842 @@ +#include +#include "cosigner/bam_ecdsa_cosigner_client.h" +#include "cosigner/bam_ecdsa_cosigner_server.h" +#include "cosigner/bam_key_persistency_client.h" +#include "cosigner/bam_key_persistency_server.h" +#include "cosigner/bam_tx_persistency_server.h" +#include "cosigner/bam_tx_persistency_client.h" +#include "cosigner/platform_service.h" +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "blockchain/mpc/hd_derive.h" +#include "../../src/common/cosigner/utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "crypto/paillier_commitment/paillier_commitment.h" +#include "crypto/commitments/damgard_fujisaki.h" +#ifndef UUID_STR_LEN +#define UUID_STR_LEN 37 +#endif + +namespace fbc = fireblocks::common::cosigner; + +static const constexpr uint64_t server_id = 3213454843; +static const constexpr uint64_t client_id = 45234523; + +static std::string get_tenant_id() +{ + static std::string _tenant_id; + if (_tenant_id.size() == 0) + { + uuid_t uid; + _tenant_id.resize(UUID_STR_LEN); + uuid_generate_random(uid); + uuid_unparse(uid, _tenant_id.data()); + } + + return _tenant_id; +} + +template +class test_bam_key_persistency +{ +public: + bool key_exist(const std::string& key_id) const + { + return _keyStore.count(key_id) != 0; + } + + void store_key(const std::string& key_id, const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key) + { + _keyStore[key_id].first = algorithm; + memcpy(_keyStore[key_id].second, private_key, sizeof(elliptic_curve256_scalar_t)); + } + + void load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const + { + const auto it = _keyStore.find(key_id); + if (it == _keyStore.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + + + memcpy(private_key, it->second.second, sizeof(elliptic_curve256_scalar_t)); + algorithm = it->second.first; + } + + void store_key_metadata(const std::string& key_id, const KeyMetadataType& metadata, const bool overwrite) + { + auto it = _metadata_store.find(key_id); + if (it != _metadata_store.end()) + { + if (!overwrite) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + it->second = metadata; + } + else + { + _metadata_store[key_id] = metadata; + } + } + void load_key_metadata(const std::string& key_id, KeyMetadataType& metadata) const + { + const auto it = _metadata_store.find(key_id); + if (it == _metadata_store.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + metadata = it->second; + } + + void store_temp_data(const std::string& key_id, const TempDataType& data) + { + auto it = _temp_data.find(key_id); + if (it != _temp_data.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + else + { + if constexpr (std::is_array_v) // For fixed-size arrays like uint8_t[32] + { + memcpy(_temp_data[key_id], data, sizeof(TempDataType)); + } + else + { + _temp_data[key_id] = data; // For structs like bam_signature_metadata_server + } + } + } + + void load_and_delete_temp_data(const std::string& key_id, TempDataType& data) + { + auto it = _temp_data.find(key_id); + if (it == _temp_data.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + + if constexpr (std::is_array_v) // If it's a fixed-size array + { + memcpy(data, it->second, sizeof(TempDataType)); + } + else // If it's a complex structure + { + data = it->second; + } + + _temp_data.erase(it); + } + + void store_tenant_id_for_setup(const std::string& setup_id, const std::string& tenant_id) + { + _setup_tenant[setup_id] = tenant_id; + } + void load_tenant_id_for_setup(const std::string& setup_id, std::string& tenant_id) const + { + const auto it = _setup_tenant.find(setup_id); + if (it == _setup_tenant.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + tenant_id = it->second; + } + + +private: + + std::map> _keyStore; + std::map _metadata_store; + std::map _temp_data; + std::map _setup_tenant; +}; + +class test_bam_key_persistency_client : public fbc::bam_key_persistency_client, public fbc::bam_tx_persistency_client +{ +public: + + virtual ~test_bam_key_persistency_client() = default; + + virtual void store_tenant_id_for_setup(const std::string& setup_id, const std::string& tenant_id) override + { + _store.store_tenant_id_for_setup(setup_id, tenant_id); + } + virtual void load_tenant_id_for_setup(const std::string& setup_id, std::string& tenant_id) const override + { + _store.load_tenant_id_for_setup(setup_id, tenant_id); + } + + virtual void store_key(const std::string& key_id, const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key) override + { + _store.store_key(key_id, algorithm, private_key); + } + + virtual void load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const override + { + _store.load_key(key_id, algorithm, private_key); + } + + virtual void store_key_metadata(const std::string& key_id, const fbc::bam_key_metadata_client& metadata, const bool overwrite) override + { + _store.store_key_metadata(key_id, metadata, overwrite); + } + + virtual void load_key_metadata(const std::string& key_id, fbc::bam_key_metadata_client& metadata) const override + { + _store.load_key_metadata(key_id, metadata); + } + + virtual void store_key_temp_data(const std::string& key_id, const fbc::bam_temp_key_data_client& auxilary_key_client) override + { + _store.store_temp_data(key_id, auxilary_key_client); + } + + virtual void load_key_temp_data_and_delete(const std::string& key_id, fbc::bam_temp_key_data_client& auxilary_key_client) override + { + _store.load_and_delete_temp_data(key_id, auxilary_key_client); + } + virtual void store_signature_data(const std::string& tx_id, const fbc::bam_client_signature_data& signature_data) override + { + const std::string data_key = "txid_" + tx_id; + + auto it = _sig_data.find(data_key); + if (it != _sig_data.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + else + { + _sig_data[data_key] = signature_data; + } + } + virtual void load_signature_data_and_delete(const std::string& tx_id, fbc::bam_client_signature_data& signature_data) override + { + const std::string data_key = "txid_" + tx_id; + auto it = _sig_data.find(data_key); + if (it == _sig_data.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + signature_data = it->second; + _sig_data.erase(it); + } + virtual bool delete_temporary_tx_data(const std::string& tx_id) override { return true; } + virtual bool delete_temporary_key_data(const std::string& key_id) override { return true; } + virtual bool delete_key_data(const std::string& key_id) override{ return true; } + virtual bool delete_setup_data(const std::string& setup_id) override { return true; } + virtual bool backup_key(const std::string&) const override { return true; } + +private: + test_bam_key_persistency _store; + std::map _sig_data; +}; + + +class test_bam_key_persistency_server : public fbc::bam_key_persistency_server, public fbc::bam_tx_persistency_server +{ +public: + + virtual ~test_bam_key_persistency_server() = default; + + virtual void store_tenant_id_for_setup(const std::string& setup_id, const std::string& tenant_id) override + { + _store.store_tenant_id_for_setup(setup_id, tenant_id); + } + virtual void load_tenant_id_for_setup(const std::string& setup_id, std::string& tenant_id) const override + { + _store.load_tenant_id_for_setup(setup_id, tenant_id); + } + + virtual void store_key(const std::string& key_id, const cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key) override + { + _store.store_key(key_id, algorithm, private_key); + } + + virtual void load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const override + { + _store.load_key(key_id, algorithm, private_key); + } + + virtual void store_key_metadata(const std::string& key_id, const fbc::bam_key_metadata_server& metadata, const bool overwrite) override + { + _store.store_key_metadata(key_id, metadata, overwrite); + } + + virtual void load_key_metadata(const std::string& key_id, fbc::bam_key_metadata_server& metadata) const override + { + _store.load_key_metadata(key_id, metadata); + } + + void store_signature_data(const std::string& tx_id, const std::shared_ptr& signature_data) override + { + _store.store_temp_data("txid_" + tx_id, *signature_data); + } + + std::shared_ptr load_signature_data_and_delete(const std::string& tx_id) override + { + auto signature_data_ptr = std::make_shared(); + auto &signature_data = *signature_data_ptr; + _store.load_and_delete_temp_data("txid_" + tx_id, signature_data); + return signature_data_ptr; + } + virtual bool delete_temporary_tx_data(const std::string& tx_id) override { return true; } + virtual bool delete_temporary_key_data(const std::string& key_id) override { return true; } + virtual bool delete_key_data(const std::string& key_id) override { return true; } + virtual bool delete_setup_data(const std::string& setup_id) override { return true; } + + virtual void store_setup_auxilary_key(const std::string& setup_id, const fbc::bam_setup_auxilary_key_server& setup_aux_key) override + { + auto it = _setup_aux_key_map.find(setup_id); + if (it != _setup_aux_key_map.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + else + { + _setup_aux_key_map[setup_id] = setup_aux_key; + } + + } + + void load_setup_auxilary_key(const std::string& setup_id, fbc::bam_setup_auxilary_key_server& setup_aux_key) const override + { + const auto it = _setup_aux_key_map.find(setup_id); + if (it == _setup_aux_key_map.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + setup_aux_key = it->second; + } + + virtual void store_setup_metadata(const std::string& setup_id, const fbc::bam_setup_metadata_server& setup_metadata) override + { + auto it = _setup_metadata.find(setup_id); + if (it != _setup_metadata.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + else + { + _setup_metadata[setup_id] = setup_metadata; + } + } + virtual void load_setup_metadata(const std::string& setup_id, fbc::bam_setup_metadata_server& setup_metadata) const override + { + const auto it = _setup_metadata.find(setup_id); + if (it == _setup_metadata.end()) + { + throw fbc::cosigner_exception(fbc::cosigner_exception::BAD_KEY); + } + setup_metadata = it->second; + } + + virtual bool backup_key(const std::string& key_id) const override { return true; } + +private: + test_bam_key_persistency _store; + std::map _setup_aux_key_map; + std::map _setup_metadata; +}; + + +class test_bam_platform_service : public fbc::platform_service +{ +public: + ~test_bam_platform_service() = default; + void gen_random(size_t len, uint8_t* random_data) const override + { + RAND_bytes(random_data, len); + } + bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const fbc::cmp_key_metadata& metadata, const fbc::auxiliary_keys& aux) override {return true;} + + void derive_initial_share(const fbc::share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override {assert(0);} + void on_start_signing(const std::string& key_id, const std::string& txid, const fbc::signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {}; + bool is_client_id(uint64_t player_id) const override {return false;} + virtual const std::string get_current_tenantid() const override + { + return get_tenant_id(); + } + + virtual uint64_t get_id_from_keyid(const std::string& key_id) const override + { + return 0; + } + + virtual void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const override + { + if (metadata != "") + { + flags[0] = fbc::POSITIVE_R; + } + } + void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + // Stub for tests + } + + void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + if (metadata != "") + { + for (auto& sig : info) + sig.flags = fbc::POSITIVE_R; + } + } + virtual fbc::byte_vector_t encrypt_for_player(const uint64_t id, const fbc::byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override { return data; } + virtual fbc::byte_vector_t decrypt_message(const fbc::byte_vector_t& encrypted_data) const override { return encrypted_data; } + virtual void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {}; + virtual void mark_key_setup_in_progress(const std::string& key_id) const override {}; + virtual void clear_key_setup_in_progress(const std::string& key_id) const override {}; + +}; + +void bam_key_generation(const std::string& setup_id, + const std::string& key_id, + uint64_t client_id, + uint64_t server_id, + fbc::bam_ecdsa_cosigner_server& server, + fbc::bam_ecdsa_cosigner_client& client, + const cosigner_sign_algorithm algorithm, + elliptic_curve256_point_t& client_share, + elliptic_curve256_point_t& server_share) +{ + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + // Step 1. + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + + + REQUIRE_NOTHROW(server.generate_setup_with_proof(setup_id, get_tenant_id(), algorithm, setup)); + + REQUIRE_NOTHROW(client.start_new_key_generation(setup_id, key_id, get_tenant_id(), server_id, client_id, algorithm)); + REQUIRE_NOTHROW(server.generate_share_and_commit(setup_id, key_id, server_id, client_id, algorithm, B)); + + // Step 2. + REQUIRE_NOTHROW(client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, key_id, server_id, setup, B, client_message)); + + // Step 3. + REQUIRE_NOTHROW(server.verify_client_proofs_and_decommit_share_with_proof(key_id, client_id, client_message, server_message)); + + // Step 4. + REQUIRE_NOTHROW(client.verify_key_decommitment_and_proofs(key_id, server_id, client_id, server_message, generated_public_key)); + + // return public key + memcpy(client_share, client_message.X, sizeof(elliptic_curve256_point_t)); + memcpy(server_share, server_message.server_public_share, sizeof(elliptic_curve256_point_t)); +} + +void bam_key_sign(const std::string& setup_id, + const std::string& key_id, + const std::string& tx_id, + const uint64_t client_id, + const fbc::signing_data& data_to_sign, + const std::string& metadata, + fbc::bam_ecdsa_cosigner_server& server, + fbc::bam_ecdsa_cosigner_client& client, + fbc::recoverable_signature& full_signature, + const cosigner_sign_algorithm algorithm) +{ + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(client.prepare_for_signature(key_id, tx_id, 0, server_id, client_id, data_to_sign, metadata, std::set())); + REQUIRE_NOTHROW(server.generate_signature_share(key_id, tx_id, 0, server_id, client_id, algorithm, data_to_sign, metadata, std::set(), server_shares)); + REQUIRE_NOTHROW(client.compute_partial_signature(tx_id, server_shares, partial_signatures)); + REQUIRE_NOTHROW(server.verify_partial_signature_and_output_signature(tx_id, client_id, partial_signatures, full_signatures, signature_algorithm)); + REQUIRE(signature_algorithm == algorithm); + memcpy(&full_signature, &full_signatures[0], sizeof(fbc::recoverable_signature)); +} + + +void verify_ecdsa_signature(const cosigner_sign_algorithm algorithm, + const elliptic_curve256_point_t& X1, + const elliptic_curve256_point_t& X2, + const fbc::recoverable_signature& signature, + const fbc::signing_data& data_to_sign, + const bool isPositiveR) +{ + elliptic_curve256_algebra_ctx_t *ctx = NULL; + + // Initialize context based on the curve specified by algorithm + switch (algorithm) { + case ECDSA_SECP256K1: + ctx = elliptic_curve256_new_secp256k1_algebra(); + break; + case ECDSA_SECP256R1: + ctx = elliptic_curve256_new_secp256r1_algebra(); + break; + case ECDSA_STARK: + ctx = elliptic_curve256_new_stark_algebra(); + break; + default: + throw std::runtime_error("unsupported algorithm"); // Unsupported algorithm + } + + if (!ctx) + { + throw std::runtime_error("Failed to initialize context"); + } + + // Declare necessary variables + elliptic_curve256_scalar_t s_inv; + elliptic_curve256_scalar_t u1; + elliptic_curve256_scalar_t u2; + elliptic_curve256_point_t U1, U2; + elliptic_curve256_point_t R; + elliptic_curve256_scalar_t r; + elliptic_curve256_point_t X; + + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->add_points(ctx, &X, &X1, &X2)); + + + REQUIRE(HD_DERIVE_SUCCESS == derive_public_key_generic(ctx, X, X, data_to_sign.chaincode, &data_to_sign.blocks[0].path[0], (uint32_t)data_to_sign.blocks[0].path.size())); + + // Step 1: Calculate s_inv, the modular inverse of signature.s + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->inverse(ctx, &s_inv, &signature.s)); + + // Step 2: Calculate u1 = (hash * s_inv) mod n + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->mul_scalars(ctx, &u1, data_to_sign.blocks[0].data.data(), data_to_sign.blocks[0].data.size(), s_inv, sizeof(elliptic_curve256_scalar_t))); + + // Step 3: Calculate u2 = (signature.r * s_inv) mod n + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->mul_scalars(ctx, &u2, signature.r, sizeof(elliptic_curve256_scalar_t), s_inv, sizeof(elliptic_curve256_scalar_t))); + + // Step 4: Calculate U2 = u2 * G (generator multiplication) + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->generator_mul(ctx, &U1, &u1)); + + // Step 5: Calculate U1 = u1 * Q (public key multiplication) + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->point_mul(ctx, &U2, &X, &u2)); + + // Step 6: Calculate R = U1 + U2 + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->add_points(ctx, &R, &U1, &U2)); + + // Step 7: Extract the x-coordinate of R and compare it with signature.r + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == GFp_curve_algebra_get_point_projection((const GFp_curve_algebra_ctx_t *)ctx->ctx, &r, &R, NULL)); + + const uint8_t* message = data_to_sign.blocks[0].data.data(); + + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == GFp_curve_algebra_verify_signature((const GFp_curve_algebra_ctx_t *)ctx->ctx, &X, reinterpret_cast(message), &signature.r, &signature.s)); + + REQUIRE(memcmp(signature.r, r, sizeof(elliptic_curve256_scalar_t)) == 0); + + if (isPositiveR) + { + REQUIRE(fbc::bam_ecdsa_cosigner::is_positive(algorithm, signature.r)); + } + + // Clean up context + elliptic_curve256_algebra_ctx_free(ctx); +} + + +std::ostream& operator<<(std::ostream& os, const fbc::bam_ecdsa_cosigner::client_key_shared_data& data) +{ + os << "client_key_shared_data {" + << "\n X: " << fbc::HexStr(std::begin(data.X), std::end(data.X)) + << "\n damgard_fujisaki_pub: " << fbc::HexStr(data.damgard_fujisaki_pub.begin(), data.damgard_fujisaki_pub.end()) + << "\n damgard_fujisaki_proof: " << fbc::HexStr(data.damgard_fujisaki_proof.begin(), data.damgard_fujisaki_proof.end()) + << "\n schnorr_proof: " << fbc::HexStr(data.schnorr_proof.begin(), data.schnorr_proof.end()) + << "\n}"; + return os; +} + + +std::ostream& operator<<(std::ostream& os, const fbc::bam_ecdsa_cosigner::server_key_shared_data& data) { + os << "server_key_shared_data {" + << "\n server_public_share: " << fbc::HexStr(std::begin(data.server_public_share), std::end(data.server_public_share)) + << "\n encrypted_server_share: " << fbc::HexStr(data.encrypted_server_share.begin(), data.encrypted_server_share.end()) + << "\n enc_dlog_proof: " << fbc::HexStr(data.enc_dlog_proof.begin(), data.enc_dlog_proof.end()) + << "\n}"; + return os; +} + + +struct TestSetup +{ + test_bam_key_persistency_client persistencyClient; + test_bam_key_persistency_server persistencyServer; + test_bam_platform_service platform; + fbc::bam_ecdsa_cosigner_server server{ platform, persistencyServer, persistencyServer}; + fbc::bam_ecdsa_cosigner_client client{ platform, persistencyClient, persistencyClient}; +}; + +TEST_CASE("bam_ecdsa") +{ + uuid_t ecdsa_keyid_uid ; + + SECTION("embedded cosigner secp256k1") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + fbc::byte_vector_t chaincode(32, '\0'); + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + fbc::recoverable_signature signature { 0 }; + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, "", testSetup.server, testSetup.client, signature, ECDSA_SECP256K1); + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, signature, data_to_sign, false); + } + + SECTION("attack: server sends alternative infinity R (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Craft an "alternate infinity" encoding for GFp curves: + // For these curves, the underlying algebra parses any encoding whose first byte is 0x00 as infinity, + // ignoring the remaining bytes. The current BAM check compares against all-zero 33 bytes, so we set + // the first byte to 0x00 (infinity) but poison another byte to bypass memcmp(). + memset(server_shares[0].R, 0, sizeof(elliptic_curve256_point_t)); + server_shares[0].R[0] = 0x00; // infinity marker + server_shares[0].R[1] = 0x01; // poison: bypasses memcmp() against canonical infinity + + // Keep Y consistent with how the algebra will canonicalize infinity internally (all-zero encoding). + memset(server_shares[0].Y, 0, sizeof(elliptic_curve256_point_t)); + + // Security expectation: the client MUST reject infinity (in any representation). + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + // Symmetric variant: malicious client sends alternative encoding of infinity in client_R. + SECTION("attack: client sends alternative infinity client_R (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Alternate infinity encoding for GFp curves: first byte 0x00 is infinity, remaining bytes are ignored by parsing. + // Poison a byte to bypass old memcmp()-against-canonical check. + memset(partial_signatures[0].client_R, 0, sizeof(elliptic_curve256_point_t)); + partial_signatures[0].client_R[0] = 0x00; + partial_signatures[0].client_R[1] = 0x01; + memset(partial_signatures[0].common_R, 0, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Nonce-reuse regression: ensure the client does not reuse its ephemeral client_R across two signing sessions. + SECTION("regression: client must not reuse client_R across sessions (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid1[UUID_STR_LEN] = {'\0'}; + char txid2[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid1); + uuid_generate_random(uid); + uuid_unparse(uid, txid2); + + elliptic_curve256_scalar_t hash1, hash2; + REQUIRE(RAND_bytes(hash1, sizeof(hash1))); + REQUIRE(RAND_bytes(hash2, sizeof(hash2))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data1 = {{0}, {{ fbc::byte_vector_t(&hash1[0], &hash1[sizeof(hash1)]), { 44, 0, 0, 0, 0} }}}; + fbc::signing_data data2 = {{0}, {{ fbc::byte_vector_t(&hash2[0], &hash2[sizeof(hash2)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares1, server_shares2; + std::vector partial1, partial2; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid1, 0, server_id, client_id, data1, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid1, 0, server_id, client_id, ECDSA_SECP256K1, data1, "", std::set(), server_shares1)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid1, server_shares1, partial1)); + REQUIRE(partial1.size() == 1); + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid2, 0, server_id, client_id, data2, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid2, 0, server_id, client_id, ECDSA_SECP256K1, data2, "", std::set(), server_shares2)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid2, server_shares2, partial2)); + REQUIRE(partial2.size() == 1); + + REQUIRE(memcmp(partial1[0].client_R, partial2[0].client_R, sizeof(elliptic_curve256_point_t)) != 0); + } + + SECTION("embedded cosigner stark") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + fbc::byte_vector_t chaincode(32, '\0'); + const std::string setup_id(keyid); + elliptic_curve256_point_t X1, X2; + fbc::recoverable_signature signature; + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_STARK, X1, X2); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + const std::string sign_metadata = "{ \"signInfo\":[{\"positiveR\":true}]}"; + bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, sign_metadata, testSetup.server, testSetup.client, signature, ECDSA_STARK); + verify_ecdsa_signature(ECDSA_STARK, X1, X2, signature, data_to_sign, true); + } + + // Attack: replay setup proof from one setup_id to a different setup_id (AAD mismatch). + // The setup proof is bound to the setup_id via AAD, so replaying to a different setup_id must fail. + SECTION("attack: setup proof replay to different setup_id") + { + TestSetup testSetup; + uuid_t uid; + char setup_id1_buf[UUID_STR_LEN] = {'\0'}; + char setup_id2_buf[UUID_STR_LEN] = {'\0'}; + char keyid[UUID_STR_LEN] = {'\0'}; + + uuid_generate_random(uid); + uuid_unparse(uid, setup_id1_buf); + uuid_generate_random(uid); + uuid_unparse(uid, setup_id2_buf); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + const std::string setup_id1(setup_id1_buf); + const std::string setup_id2(setup_id2_buf); + + // Generate setup proof for setup_id1 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup1; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id1, get_tenant_id(), ECDSA_SECP256K1, setup1)); + + // Generate a second setup for setup_id2 (so it exists on server side) + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup2; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id2, get_tenant_id(), ECDSA_SECP256K1, setup2)); + + // Client starts key gen with setup_id2 but receives proof from setup_id1 — should fail verification + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id2, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + + commitments_sha256_t B; + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id2, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + // Client verifies setup1's proof against setup_id2's commitment — AAD mismatch must cause failure + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id2, keyid, server_id, setup1, B, client_message)); + } + + // Attack: replay setup proof with wrong algorithm (secp256r1 proof used for secp256k1 key). + SECTION("attack: setup proof replay with different algorithm") + { + TestSetup testSetup; + uuid_t uid; + char setup_id_buf[UUID_STR_LEN] = {'\0'}; + char setup_id2_buf[UUID_STR_LEN] = {'\0'}; + char keyid[UUID_STR_LEN] = {'\0'}; + + uuid_generate_random(uid); + uuid_unparse(uid, setup_id_buf); + uuid_generate_random(uid); + uuid_unparse(uid, setup_id2_buf); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + const std::string setup_id_r1(setup_id_buf); + const std::string setup_id_k1(setup_id2_buf); + + // Generate setup for secp256r1 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_r1; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_r1, get_tenant_id(), ECDSA_SECP256R1, setup_r1)); + + // Generate setup for secp256k1 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_k1; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_k1, get_tenant_id(), ECDSA_SECP256K1, setup_k1)); + + // Client starts key gen for secp256k1 but receives secp256r1 setup proof — must fail + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_k1, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + + commitments_sha256_t B; + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_k1, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_k1, keyid, server_id, setup_r1, B, client_message)); + } + + // Attack: encrypted_partial_sig = all zeros (Paillier coprime check). + // Zero is not coprime to n, so Paillier decryption or well-formedness proof must reject. + SECTION("attack: encrypted_partial_sig all zeros") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with all zeros + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), partial_signatures[0].encrypted_partial_sig.end(), 0x00); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Attack: encrypted_partial_sig = 1 (trivial ciphertext). + // The value 1 is a trivial Paillier ciphertext — decrypts to 0, which should fail proof verification. + SECTION("attack: encrypted_partial_sig trivial value 1") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with value 1 (big-endian) + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), partial_signatures[0].encrypted_partial_sig.end(), 0x00); + partial_signatures[0].encrypted_partial_sig.back() = 0x01; + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Attack: encrypted_partial_sig = all 0xFF (large value, coprime check). + // A value of all 0xFF bytes is likely not coprime to n^2, so should be rejected. + SECTION("attack: encrypted_partial_sig all 0xFF") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with all 0xFF + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), partial_signatures[0].encrypted_partial_sig.end(), 0xFF); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Attack: server R replaced with client's public key X (DH bypass attempt). + // If the server's R = X_client, the DH consistency check should still catch tampering + // because the client's k is random and (X_client)^k != R_client^k_server. + SECTION("attack: server R = client public key X (DH bypass)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Replace server's R with client's public key share + memcpy(server_shares[0].R, X1, sizeof(elliptic_curve256_point_t)); + + // Client should detect the DH inconsistency or produce a bad partial that server rejects + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + // Attack: R = Y degenerate DH pair. + // If R == Y, this implies k_server * X_client == G^k_server, which is only true if X_client == G. + // The client must reject this because DH consistency breaks. + SECTION("attack: R = Y degenerate DH pair") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Set Y = R (degenerate DH pair) + memcpy(server_shares[0].Y, server_shares[0].R, sizeof(elliptic_curve256_point_t)); + + // Client should detect the DH inconsistency + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + // Attack: algorithm mismatch — request signing with wrong algorithm for key. + SECTION("attack: algorithm mismatch on generate_signature_share") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + + // Request signing with secp256r1 on a secp256k1 key — must be rejected + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256R1, data_to_sign, "", std::set(), server_shares)); + } + + // Attack: wrong client_id on verify_partial_signature_and_output_signature. + SECTION("attack: client_id mismatch on verify_partial_signature") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + + // Use a different client_id — must be rejected + const uint64_t wrong_client_id = client_id + 1; + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, wrong_client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Batch signing: duplicate hashes must not produce duplicate nonces. + SECTION("attack: batch signing duplicate hashes produce unique nonces") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Create batch with two identical hashes + fbc::signing_data data_to_sign = {{0}, { + { fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }, + { fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} } + }}; + const std::string sign_metadata = "{ \"signInfo\":[{}, {}]}"; + + std::vector server_shares; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, sign_metadata, std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, sign_metadata, std::set(), server_shares)); + REQUIRE(server_shares.size() == 2); + + // Even with identical messages, server nonces (R values) MUST differ + REQUIRE(memcmp(server_shares[0].R, server_shares[1].R, sizeof(elliptic_curve256_point_t)) != 0); + } +} + +TEST_CASE("bam_ecdsa_attacks") +{ + // Note: no init()/term() needed — these tests use in-process TestSetup, + // not the Thrift-based cloud-cosigner service. + + SECTION("attack: corrupted server_public_share in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Corrupt the server's public share + server_message.server_public_share[1] ^= 0xFF; + + // Client should reject corrupted share (proof won't match) + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: corrupted enc_dlog_proof in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Corrupt the enc_dlog_proof + if (!server_message.enc_dlog_proof.empty()) { + server_message.enc_dlog_proof[0] ^= 0xFF; + } + + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: corrupted encrypted_server_share in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Corrupt the encrypted server share + if (!server_message.encrypted_server_share.empty()) { + server_message.encrypted_server_share[server_message.encrypted_server_share.size() / 2] ^= 0xFF; + } + + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: corrupted schnorr_proof in client keygen message (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Corrupt the schnorr proof (byte_vector_t — flip a byte in the middle) + if (!client_message.schnorr_proof.empty()) { + client_message.schnorr_proof[client_message.schnorr_proof.size() / 2] ^= 0xFF; + } + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: infinity X in client keygen message (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Replace X with infinity point + memset(client_message.X, 0, sizeof(elliptic_curve256_point_t)); + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: alternative infinity X in client keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Replace X with alt infinity: 0x00 prefix but non-zero trailing byte + memset(client_message.X, 0, sizeof(elliptic_curve256_point_t)); + client_message.X[0] = 0x00; + client_message.X[1] = 0x01; + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: corrupted partial signature proof (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Corrupt the signature proof + if (!partial_signatures[0].sig_proof.empty()) { + partial_signatures[0].sig_proof[0] ^= 0xFF; + } + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: corrupted encrypted_partial_sig (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Corrupt the encrypted partial signature + if (!partial_signatures[0].encrypted_partial_sig.empty()) { + partial_signatures[0].encrypted_partial_sig[partial_signatures[0].encrypted_partial_sig.size() / 2] ^= 0xFF; + } + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: swapped client_R and common_R (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Swap client_R and common_R + elliptic_curve256_point_t tmp; + memcpy(tmp, partial_signatures[0].client_R, sizeof(elliptic_curve256_point_t)); + memcpy(partial_signatures[0].client_R, partial_signatures[0].common_R, sizeof(elliptic_curve256_point_t)); + memcpy(partial_signatures[0].common_R, tmp, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: generator point as server R (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Replace R with generator point G (known discrete log = 1) + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t one; + memset(one, 0, sizeof(one)); + one[sizeof(one) - 1] = 1; + algebra->generator_mul(algebra, &server_shares[0].R, &one); + elliptic_curve256_algebra_ctx_free(algebra); + + // Client should detect the known-DL point or the proof mismatch + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: replay server shares from session 1 in session 2 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid1[UUID_STR_LEN] = {'\0'}; + char txid2[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid1); + uuid_generate_random(uid); + uuid_unparse(uid, txid2); + + elliptic_curve256_scalar_t hash1, hash2; + REQUIRE(RAND_bytes(hash1, sizeof(hash1))); + REQUIRE(RAND_bytes(hash2, sizeof(hash2))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Session 1: get server shares + fbc::signing_data data1 = {{0}, {{ fbc::byte_vector_t(&hash1[0], &hash1[sizeof(hash1)]), { 44, 0, 0, 0, 0} }}}; + std::vector server_shares1; + std::vector partial1; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid1, 0, server_id, client_id, data1, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid1, 0, server_id, client_id, ECDSA_SECP256K1, data1, "", std::set(), server_shares1)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid1, server_shares1, partial1)); + + // Session 2: prepare new session but try to use replayed server shares from session 1 + fbc::signing_data data2 = {{0}, {{ fbc::byte_vector_t(&hash2[0], &hash2[sizeof(hash2)]), { 44, 0, 0, 0, 0} }}}; + std::vector server_shares2; + std::vector partial2; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid2, 0, server_id, client_id, data2, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid2, 0, server_id, client_id, ECDSA_SECP256K1, data2, "", std::set(), server_shares2)); + + // Client does not validate server shares against tx_id, so this won't throw. + // However, the resulting partial signature will be rejected by the server's + // verify_partial_signature_and_output_signature because the R values won't match. + // The server-side load_signature_data_and_delete prevents server-side replay. + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid2, server_shares1, partial2)); + } + + SECTION("attack: empty server shares vector") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + + // Empty server shares + std::vector empty_shares; + std::vector partial_signatures; + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, empty_shares, partial_signatures)); + } + + SECTION("attack: empty partial signatures vector") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + + // Empty partial signatures + std::vector empty_partials; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, empty_partials, full_signatures, signature_algorithm)); + } + + SECTION("attack: negated server_public_share in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Negate the server's public share (flip parity byte for compressed point) + if (server_message.server_public_share[0] == 0x02) { + server_message.server_public_share[0] = 0x03; + } else if (server_message.server_public_share[0] == 0x03) { + server_message.server_public_share[0] = 0x02; + } + + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: sign before keygen complete should fail") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + std::vector server_shares; + + // Try to sign without having generated a key - should throw + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + } + + // ======================================================================== + // A. Malicious Client — Keygen Attacks + // ======================================================================== + + SECTION("attack: zero scalar X (G^0 = identity) in client keygen (secp256k1)") + { + // Different from canonical infinity: X = G^0 encoded as the identity element. + // The generator_mul(0) returns the infinity point — test the server rejects it. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Compute G^0 = infinity point for secp256k1 + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t identity; + algebra->generator_mul(algebra, &identity, &zero); + memcpy(client_message.X, identity, sizeof(elliptic_curve256_point_t)); + elliptic_curve256_algebra_ctx_free(algebra); + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: generator point as X (private share = 1) in client keygen (secp256k1)") + { + // If client sends X = G, that implies private_share = 1, which is trivially known. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Replace X with generator point G + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t one; + memset(one, 0, sizeof(one)); + one[sizeof(one) - 1] = 1; + algebra->generator_mul(algebra, reinterpret_cast(&client_message.X), &one); + elliptic_curve256_algebra_ctx_free(algebra); + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + // Server should reject because Schnorr proof won't match the new X + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: corrupted Damgard-Fujisaki public key in client keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Flip bytes in the DF public key + for (size_t i = 0; i < client_message.damgard_fujisaki_pub.size() && i < 16; i++) { + client_message.damgard_fujisaki_pub[i] ^= 0xFF; + } + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + + SECTION("attack: truncated Damgard-Fujisaki public key in client keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Truncate DF pub to half its length + client_message.damgard_fujisaki_pub.resize(client_message.damgard_fujisaki_pub.size() / 2); + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: all-zeros Schnorr proof in client keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Zero out the entire Schnorr proof + std::fill(client_message.schnorr_proof.begin(), client_message.schnorr_proof.end(), 0); + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + SECTION("attack: swapped Schnorr proof fields (R and s) in client keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Swap the first half and second half of the Schnorr proof (approximates swapping R and s) + if (client_message.schnorr_proof.size() >= 2) { + size_t mid = client_message.schnorr_proof.size() / 2; + fbc::byte_vector_t swapped; + swapped.insert(swapped.end(), client_message.schnorr_proof.begin() + mid, client_message.schnorr_proof.end()); + swapped.insert(swapped.end(), client_message.schnorr_proof.begin(), client_message.schnorr_proof.begin() + mid); + client_message.schnorr_proof = swapped; + } + + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + // ======================================================================== + // B. Malicious Server — Keygen Attacks + // ======================================================================== + + SECTION("attack: zero-length encrypted_server_share in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Empty ciphertext + server_message.encrypted_server_share.clear(); + + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: all-zeros enc_dlog_proof in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Zero out the proof + std::fill(server_message.enc_dlog_proof.begin(), server_message.enc_dlog_proof.end(), 0); + + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + SECTION("attack: server public share = generator G in keygen (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key generated_public_key; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Replace server_public_share with G (server private = 1) + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t one; + memset(one, 0, sizeof(one)); + one[sizeof(one) - 1] = 1; + algebra->generator_mul(algebra, reinterpret_cast(&server_message.server_public_share), &one); + elliptic_curve256_algebra_ctx_free(algebra); + + // Client should reject because proof won't match the new public share + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, generated_public_key)); + } + + // ======================================================================== + // C. Malicious Client — Signing Attacks + // ======================================================================== + + SECTION("attack: all-zeros common_R in partial signature (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Zero out common_R (the DH product G^(k_client * k_server)) + memset(partial_signatures[0].common_R, 0, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: generator point as client_R (k_client = 1) in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace client_R with G (known nonce k_client = 1) + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t one; + memset(one, 0, sizeof(one)); + one[sizeof(one) - 1] = 1; + algebra->generator_mul(algebra, reinterpret_cast(&partial_signatures[0].client_R), &one); + elliptic_curve256_algebra_ctx_free(algebra); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: client_R from wrong curve (secp256r1 point in secp256k1 signing)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Generate a point on secp256r1 and inject it as client_R + elliptic_curve256_algebra_ctx_t* r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(r1 != nullptr); + elliptic_curve256_scalar_t three = {0}; + three[sizeof(three) - 1] = 3; + r1->generator_mul(r1, reinterpret_cast(&partial_signatures[0].client_R), &three); + elliptic_curve256_algebra_ctx_free(r1); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: random bytes as encrypted_partial_sig (valid length) in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with random bytes of same length + size_t orig_len = partial_signatures[0].encrypted_partial_sig.size(); + partial_signatures[0].encrypted_partial_sig.resize(orig_len); + RAND_bytes(partial_signatures[0].encrypted_partial_sig.data(), orig_len); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: zero-length sig_proof in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Empty proof + partial_signatures[0].sig_proof.clear(); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: oversized sig_proof (10x) in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // 10x oversized random proof data + size_t oversized = partial_signatures[0].sig_proof.size() * 10; + partial_signatures[0].sig_proof.resize(oversized); + RAND_bytes(partial_signatures[0].sig_proof.data(), oversized); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // ======================================================================== + // D. Malicious Server — Signing Attacks + // ======================================================================== + + SECTION("attack: negated server R (-R) in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Negate R by flipping parity byte + if (server_shares[0].R[0] == 0x02) { + server_shares[0].R[0] = 0x03; + } else if (server_shares[0].R[0] == 0x03) { + server_shares[0].R[0] = 0x02; + } + + // Client should reject the inconsistent DH pair (R, Y) + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: Y doesn't match R (inconsistent DH pair) in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Keep R valid, replace Y with a random valid point + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + elliptic_curve256_scalar_t rand_scalar = {0}; + rand_scalar[sizeof(rand_scalar) - 1] = 42; + algebra->generator_mul(algebra, reinterpret_cast(&server_shares[0].Y), &rand_scalar); + elliptic_curve256_algebra_ctx_free(algebra); + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: R and Y swapped in server signing shares (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Swap R and Y + elliptic_curve256_point_t tmp; + memcpy(tmp, server_shares[0].R, sizeof(elliptic_curve256_point_t)); + memcpy(server_shares[0].R, server_shares[0].Y, sizeof(elliptic_curve256_point_t)); + memcpy(server_shares[0].Y, tmp, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: zero-length server R and Y in signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Zero both R and Y + memset(server_shares[0].R, 0, sizeof(elliptic_curve256_point_t)); + memset(server_shares[0].Y, 0, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + // ======================================================================== + // E. Protocol State Attacks + // ======================================================================== + + SECTION("attack: double keygen with same key_id (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + // First keygen succeeds + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Second keygen with same key_id should fail + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup2; + commitments_sha256_t B2; + bool threw = false; + try { + testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup2); + testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1); + testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B2); + } catch (...) { + threw = true; + } + // Either throws (expected: key already exists) or we get a duplicate key error later + // The test verifies the system handles duplicate key IDs without undefined behavior + REQUIRE(threw); + } + + SECTION("attack: sign with derivation path after keygen without derivation (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + // Use a BIP44 path (must be exactly 5 elements per BIP44_PATH_LENGTH) with + // unusual values — high hardened indices to test edge cases in HD derivation. + // Note: assert(path.size() == BIP44_PATH_LENGTH) in bam_ecdsa_cosigner.cpp:210 + // would crash on non-5-element paths (same assert-before-error pattern as bugs #3,#6). + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44 | 0x80000000, 60 | 0x80000000, 0x80000000, 0, 0xFFFFFFFF} }}}; + + fbc::recoverable_signature signature { 0 }; + // The signing should still work (HD derivation is applied at signing time), but + // this tests that unusual hardened path values don't cause crashes + REQUIRE_NOTHROW(bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, "", testSetup.server, testSetup.client, signature, ECDSA_SECP256K1)); + } + + SECTION("attack: mixed valid/invalid messages in batch signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Duplicate the first partial signature and corrupt the second one + partial_signatures.push_back(partial_signatures[0]); + memset(partial_signatures[1].client_R, 0, sizeof(elliptic_curve256_point_t)); + partial_signatures[1].client_R[0] = 0x00; + partial_signatures[1].client_R[1] = 0x01; + + // Server should reject the batch (size mismatch or corrupted entry) + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: cross-algorithm - key generated with secp256k1, signing requested with secp256r1") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + // Request signing with secp256r1 on a key generated with secp256k1 — must reject + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256R1, data_to_sign, "", std::set(), server_shares)); + } + + SECTION("attack: cross-algorithm - key generated with secp256r1, signing requested with secp256k1") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256R1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + // Request signing with secp256k1 on a key generated with secp256r1 — must reject + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + } + + // ======================================================================== + // F. Fiat-Shamir Binding / Cross-Context Replay + // Academic ref: ePrint 2016/771 (Fiat-Shamir cross-context attacks) + // The Schnorr proof hash binds to seed = SHA256(SALT || key_id || ...), + // so proofs from one keygen are non-transferable to another. + // ======================================================================== + + SECTION("attack: Schnorr proof cross-key replay - proof from key A used for key B (secp256k1)") + { + // Attack: Do 2 keygens. Replay schnorr_proof + X from key A into key B's + // client_message. The Schnorr hash is bound to seed = SHA256(SALT || key_id || ...), + // so different key_id => different seed => hash mismatch => server rejects. + TestSetup testSetup; + uuid_t uid; + + // --- Key A: complete keygen up to step 2 to get client_message_A --- + char keyid_a[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_a); + const std::string setup_id_a(keyid_a); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_a; + commitments_sha256_t B_a; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_a; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_a, get_tenant_id(), ECDSA_SECP256K1, setup_a)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_a, keyid_a, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_a, keyid_a, server_id, client_id, ECDSA_SECP256K1, B_a)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_a, keyid_a, server_id, setup_a, B_a, client_message_a)); + + // --- Key B: complete keygen up to step 2, then swap in A's proof + X --- + char keyid_b[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_b); + const std::string setup_id_b(keyid_b); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_b; + commitments_sha256_t B_b; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_b; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_b, get_tenant_id(), ECDSA_SECP256K1, setup_b)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_b, keyid_b, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_b, keyid_b, server_id, client_id, ECDSA_SECP256K1, B_b)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_b, keyid_b, server_id, setup_b, B_b, client_message_b)); + + // Replay: inject A's schnorr_proof and X into B's client_message + client_message_b.schnorr_proof = client_message_a.schnorr_proof; + memcpy(client_message_b.X, client_message_a.X, sizeof(elliptic_curve256_point_t)); + + // Server must reject: Schnorr hash seed includes key_id, so proof doesn't verify for key B + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_b; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid_b, client_id, client_message_b, server_message_b)); + } + + SECTION("attack: enc-dlog proof cross-key replay - server message from key A given to key B client (secp256k1)") + { + // Attack: Complete keygen A fully, capture server_message_A. + // Start keygen B, feed server_message_A at step 4. + // Commitment check (B != expected) or enc-dlog AAD mismatch rejects it. + TestSetup testSetup; + uuid_t uid; + + // --- Key A: full keygen --- + char keyid_a[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_a); + const std::string setup_id_a(keyid_a); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_a; + commitments_sha256_t B_a; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_a; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_a; + fbc::bam_ecdsa_cosigner::generated_public_key gen_pubkey_a; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_a, get_tenant_id(), ECDSA_SECP256K1, setup_a)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_a, keyid_a, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_a, keyid_a, server_id, client_id, ECDSA_SECP256K1, B_a)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_a, keyid_a, server_id, setup_a, B_a, client_message_a)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid_a, client_id, client_message_a, server_message_a)); + REQUIRE_NOTHROW(testSetup.client.verify_key_decommitment_and_proofs(keyid_a, server_id, client_id, server_message_a, gen_pubkey_a)); + + // --- Key B: keygen up to step 3, then feed A's server_message --- + char keyid_b[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_b); + const std::string setup_id_b(keyid_b); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_b; + commitments_sha256_t B_b; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_b; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_b; + fbc::bam_ecdsa_cosigner::generated_public_key gen_pubkey_b; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_b, get_tenant_id(), ECDSA_SECP256K1, setup_b)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_b, keyid_b, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_b, keyid_b, server_id, client_id, ECDSA_SECP256K1, B_b)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_b, keyid_b, server_id, setup_b, B_b, client_message_b)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid_b, client_id, client_message_b, server_message_b)); + + // Feed A's server_message to B's client — commitment mismatch or enc-dlog AAD mismatch + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid_b, server_id, client_id, server_message_a, gen_pubkey_b)); + } + + SECTION("attack: setup proof replay to different setup_id (secp256k1)") + { + // Attack: Generate setup A, replay its proofs under setup_id B. + // AAD = setup_id || algorithm differs, so Damgard-Fujisaki ZKP rejects. + TestSetup testSetup; + uuid_t uid; + + // --- Setup A --- + char keyid_a[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_a); + const std::string setup_id_a(keyid_a); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_a; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_a, get_tenant_id(), ECDSA_SECP256K1, setup_a)); + + // --- Setup B: use A's setup data with B's identifiers --- + char keyid_b[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_b); + const std::string setup_id_b(keyid_b); + + commitments_sha256_t B_b; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_b; + + // Generate B's setup (stored server-side), but we'll feed A's proofs to the client + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_b; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id_b, get_tenant_id(), ECDSA_SECP256K1, setup_b)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id_b, keyid_b, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id_b, keyid_b, server_id, client_id, ECDSA_SECP256K1, B_b)); + + // Client verifies setup_a's proofs under setup_id_b — AAD mismatch should reject + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id_b, keyid_b, server_id, setup_a, B_b, client_message_b)); + } + + SECTION("attack: setup proof replay with different algorithm (secp256r1 proof for secp256k1 key)") + { + // Attack: Generate setup with secp256r1, use it for secp256k1 key. + // Algorithm is part of setup AAD, so proof verification fails. + TestSetup testSetup; + uuid_t uid; + + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + // Generate setup with secp256r1 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_r1; + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256R1, setup_r1)); + + // Try to use it for secp256k1 key generation + char keyid_k1[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid_k1); + + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid_k1, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid_k1, server_id, client_id, ECDSA_SECP256K1, B)); + + // Client verifies secp256r1 setup proofs for secp256k1 context — algorithm mismatch + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid_k1, server_id, setup_r1, B, client_message)); + } + + // ======================================================================== + // G. Paillier Ciphertext Boundary Values + // Academic ref: Paillier malleability / chosen-ciphertext attacks + // Tests boundary values for encrypted_partial_sig: zero, one, max. + // ======================================================================== + + SECTION("attack: encrypted_partial_sig = all zeros (Paillier coprime check) (secp256k1)") + { + // Attack: Set encrypted_partial_sig to all 0x00. + // gcd(0, N) = N != 1, so fails is_coprime_fast at bam_well_formed_proof.cpp:366 + // or fails proof verification. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with all zeros (same length) + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), + partial_signatures[0].encrypted_partial_sig.end(), 0x00); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: encrypted_partial_sig = 1 (trivial Paillier ciphertext) (secp256k1)") + { + // Attack: Set encrypted_partial_sig to big-endian 1. + // gcd(1, N) = 1 passes coprime check, but proof verification fails + // (wrong ciphertext for the committed values). + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with big-endian 1 (0x00...01) + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), + partial_signatures[0].encrypted_partial_sig.end(), 0x00); + partial_signatures[0].encrypted_partial_sig.back() = 0x01; + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + SECTION("attack: encrypted_partial_sig = all 0xFF (large value coprime check) (secp256k1)") + { + // Attack: Set all bytes to 0xFF. Value = 2^(8*len)-1. + // Either fails coprime check (if gcd(val, N) != 1) or proof mismatch. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // Replace encrypted_partial_sig with all 0xFF + std::fill(partial_signatures[0].encrypted_partial_sig.begin(), + partial_signatures[0].encrypted_partial_sig.end(), 0xFF); + + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // ======================================================================== + // H. DH Consistency with Degenerate Values + // Tests server providing crafted R or Y values that try to bypass + // the DH consistency check (R^x_client == Y). + // ======================================================================== + + SECTION("attack: server R = client public key X (DH bypass attempt) (secp256k1)") + { + // Attack: Set server R to client's X1 from keygen. + // For DH check to pass, Y = X1^x_client = X1 * x_client, but attacker doesn't know x_client. + // So Y (the original one from generate_signature_share) won't match. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Replace R with client's public key X1 — DH pair (X1, Y_original) is inconsistent + memcpy(server_shares[0].R, X1, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: R = Y (degenerate DH pair) in signing (secp256k1)") + { + // Attack: Set Y = R. DH check requires R^x_client == Y, i.e. R^x_client == R, + // which means x_client == 1. Since x_client is random, this fails. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 1); + + // Set Y = R (degenerate: would only be valid if x_client == 1) + memcpy(server_shares[0].Y, server_shares[0].R, sizeof(elliptic_curve256_point_t)); + + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + // ======================================================================== + // J. Batch Signing Edge Case + // Verify that signing the same message twice in one batch produces + // valid signatures with DIFFERENT nonces (nonce uniqueness). + // ======================================================================== + + SECTION("attack: batch signing with duplicate hashes (same message twice) (secp256k1)") + { + // Sign 2 copies of the same hash in one batch. Both should succeed, + // but they MUST use different nonces (different client_R values). + // If nonces are reused, the private key can be extracted. + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Create signing_data with 2 identical blocks (same hash, same path) + fbc::signing_data data_to_sign = {{0}, { + { fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }, + { fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} } + }}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE(server_shares.size() == 2); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 2); + + // Verify nonce uniqueness: client_R[0] != client_R[1] + // If nonces were reused, attacker could extract private key via s1-s2 / (k*(h1-h2)) + REQUIRE(memcmp(partial_signatures[0].client_R, partial_signatures[1].client_R, sizeof(elliptic_curve256_point_t)) != 0); + + // Both signatures should verify successfully + REQUIRE_NOTHROW(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + REQUIRE(full_signatures.size() == 2); + + // Verify both signatures are valid + // verify_ecdsa_signature uses blocks[0], so verify each by constructing single-block data + fbc::signing_data data_block0 = {{0}, { data_to_sign.blocks[0] }}; + memcpy(data_block0.chaincode, data_to_sign.chaincode, sizeof(HDChaincode)); + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, full_signatures[0], data_block0, false); + + fbc::signing_data data_block1 = {{0}, { data_to_sign.blocks[1] }}; + memcpy(data_block1.chaincode, data_to_sign.chaincode, sizeof(HDChaincode)); + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, full_signatures[1], data_block1, false); + } + + // ======================================================================== + // K. Key Size Validation + // Tests that undersized cryptographic keys are rejected during setup. + // Undersized Paillier key (< 3072 bits), undersized DF key (!= 2048 bits). + // ======================================================================== + + SECTION("attack: undersized Paillier key (2048-bit) in setup (secp256k1)") + { + // Attack: Server sends a 2048-bit Paillier key instead of the required 3072-bit. + // Client should reject it during verify_setup_proof. + // Ref: bam_ecdsa_cosigner_client.cpp check: paillier_public_key_size < PAILLIER_COMMITMENT_BITSIZE + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + // Generate legitimate setup with 3072-bit Paillier key + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + + // Generate a smaller 2048-bit Paillier key + paillier_commitment_private_key_t* small_paillier = nullptr; + REQUIRE(paillier_commitment_generate_private_key(2048, &small_paillier) == PAILLIER_SUCCESS); + const paillier_commitment_public_key_t* small_pub = paillier_commitment_private_cast_to_public(small_paillier); + REQUIRE(small_pub != nullptr); + + // Serialize the smaller key's public portion + uint32_t pub_size = 0; + paillier_commitment_public_key_serialize(small_pub, 1, nullptr, 0, &pub_size); + REQUIRE(pub_size > 0); + setup.paillier_commitment_pub.resize(pub_size); + REQUIRE(paillier_commitment_public_key_serialize(small_pub, 1, setup.paillier_commitment_pub.data(), pub_size, &pub_size) == PAILLIER_SUCCESS); + + paillier_commitment_free_private_key(small_paillier); + + // Client should reject the undersized Paillier key + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + } + + SECTION("attack: undersized Damgard-Fujisaki key (1024-bit) in client keygen (secp256k1)") + { + // Attack: Client sends a 1024-bit DF key instead of the required 2048-bit. + // Server should reject it during verify_client_proofs_and_decommit_share_with_proof. + // Ref: bam_ecdsa_cosigner_server.cpp check: damgard_fujisaki_public_size != TEMPORARY_DAMGARD_FUJISAKI_BITSIZE + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Generate a smaller 1024-bit DF key (dimension 2, same as BAM protocol) + damgard_fujisaki_private_t* small_df = nullptr; + REQUIRE(damgard_fujisaki_generate_private_key(1024, 2, &small_df) == RING_PEDERSEN_SUCCESS); + const damgard_fujisaki_public_t* small_df_pub = damgard_fujisaki_private_key_get_public(small_df); + REQUIRE(small_df_pub != nullptr); + + // Serialize the smaller DF public key and replace in client message + uint32_t df_size = 0; + damgard_fujisaki_public_serialize(small_df_pub, nullptr, 0, &df_size); + REQUIRE(df_size > 0); + client_message.damgard_fujisaki_pub.resize(df_size); + REQUIRE(damgard_fujisaki_public_serialize(small_df_pub, client_message.damgard_fujisaki_pub.data(), df_size, &df_size) != nullptr); + + damgard_fujisaki_free_private(small_df); + + // Server should reject the undersized DF key (size != 2048) + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + } + + // ======================================================================== + // L. Invalid Keys in Well-Formed Proof (Signing Phase) + // Tests that invalid setup parameters are caught during signing, + // not just during keygen setup. + // ======================================================================== + + SECTION("attack: corrupted well-formed proof structure in signing (secp256k1)") + { + // Do a legitimate keygen, then corrupt the proof's internal structure + // (not just random byte flips, but targeted corruption of the Paillier + // commitment D component within the proof). + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE(partial_signatures.size() == 1); + + // The well-formed proof structure is: [paillier_size(4) | D(2*paillier_size) | U(65) | V(65) | z1 | z2 | w0(32) | w2] + // Corrupt the D component (Paillier commitment) — starts at offset 4, length 2*paillier_size + size_t proof_size = partial_signatures[0].sig_proof.size(); + REQUIRE(proof_size > 100); + + // Read paillier_size from first 4 bytes, then corrupt D + uint32_t paillier_size = 0; + memcpy(&paillier_size, partial_signatures[0].sig_proof.data(), sizeof(uint32_t)); + size_t d_offset = sizeof(uint32_t); + size_t d_size = 2 * paillier_size; + if (d_offset + d_size <= proof_size) + { + // Flip bytes in D component + for (size_t i = d_offset; i < d_offset + 32 && i < d_offset + d_size; i++) + { + partial_signatures[0].sig_proof[i] ^= 0xFF; + } + } + + // Server should reject: Paillier commitment verification fails + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + } + + // Corrupt stored server key metadata between keygen and signing. + // After successful keygen, alter the server's stored public key. The server + // recomputes the derived public key during signing and verifies the final + // signature against it — a corrupted public key causes verification failure. + SECTION("attack: corrupted stored public key between keygen and signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Tamper: load server metadata, corrupt public key, store back + fbc::bam_key_metadata_server server_meta; + testSetup.persistencyServer.load_key_metadata(keyid, server_meta); + server_meta.public_key[5] ^= 0xFF; + testSetup.persistencyServer.store_key_metadata(keyid, server_meta, true); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + // Signing flow must fail somewhere — the corrupted public key may cause + // generate_signature_share to throw (invalid curve point) or + // compute_partial_signature to throw (inconsistent server data). + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + bool signing_failed = false; + try + { + testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares); + testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures); + } + catch (...) + { + signing_failed = true; + } + REQUIRE(signing_failed); + } + + // Item 24 variant: Corrupt stored client private key between keygen and signing. + // The client produces an incorrect partial signature, which the server detects + // during final signature verification. + SECTION("attack: corrupted stored private key between keygen and signing (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Tamper: load client's private key, corrupt it, store back + cosigner_sign_algorithm algo; + elliptic_curve256_scalar_t priv_key; + testSetup.persistencyClient.load_key(keyid, algo, priv_key); + priv_key[10] ^= 0xFF; + testSetup.persistencyClient.store_key(keyid, algo, priv_key); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + // Client uses its private key during partial signature — corrupted key + // causes invalid intermediate values, detected during computation. + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + } + + SECTION("attack: keygen replay - each step rejects second call (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key pub_key; + + // Step 1: generate_setup_with_proof is stateless — replay is OK (idempotent) + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + + // Step 2: start_new_key_generation + generate_share_and_commit + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + + // Replay: generate_share_and_commit for same key_id should fail (share already committed) + commitments_sha256_t B_replay; + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_replay)); + + // Step 3: verify_setup_proof_store_key_commitment_generate_key_proof + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Replay: second call should fail (paillier_commitment_pub already set) + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_replay; + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_replay)); + + // Step 4: verify_client_proofs_and_decommit_share_with_proof + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Replay: second call should fail (encrypted_server_share already set) + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_replay; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_replay)); + + // Step 5: verify_key_decommitment_and_proofs + REQUIRE_NOTHROW(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key)); + + // Replay: second call should fail (client temp data was load+deleted) + fbc::bam_ecdsa_cosigner::generated_public_key pub_key_replay; + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_replay)); + } + + // ======================================================================== + // Keygen backward replay: comprehensive window test + // ======================================================================== + + SECTION("attack: keygen backward replay - after step 0 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + + // Happy path: step 0 + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + + // Window [0]: replay step 0 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + } + + SECTION("attack: keygen backward replay - after step 1 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + + // Happy path: steps 0-1 + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + + // Window [1]: replay step 1 + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + + // Window [0,1]: replay steps 0,1 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + } + + SECTION("attack: keygen backward replay - after step 2 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + + // Happy path: steps 0-2 + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + + // Window [2]: replay step 2 + commitments_sha256_t B_back; + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + + // Window [1,2]: replay steps 1,2 + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + + // Window [0,1,2]: replay steps 0,1,2 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + } + + SECTION("attack: keygen backward replay - after step 3 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + + // Happy path: steps 0-3 + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + + // Window [3]: replay step 3 + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_back; + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + + // Window [2,3]: replay steps 2,3 + commitments_sha256_t B_back; + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + + // Window [1,2,3]: replay steps 1,2,3 + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + + // Window [0,1,2,3]: replay steps 0,1,2,3 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + } + + SECTION("attack: keygen backward replay - after step 4 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + + // Happy path: steps 0-4 + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + + // Window [4]: replay step 4 + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_back; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + + // Window [3,4]: replay steps 3,4 + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_back; + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + + // Window [2,3,4]: replay steps 2,3,4 + commitments_sha256_t B_back; + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + + // Window [1,2,3,4]: replay steps 1,2,3,4 + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + + // Window [0,1,2,3,4]: replay steps 0,1,2,3,4 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + } + + SECTION("attack: keygen backward replay - after step 5 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + const std::string setup_id(keyid); + + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup; + commitments_sha256_t B; + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message; + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message; + fbc::bam_ecdsa_cosigner::generated_public_key pub_key; + + // Happy path: steps 0-5 (full keygen) + REQUIRE_NOTHROW(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup)); + REQUIRE_NOTHROW(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_NOTHROW(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B)); + REQUIRE_NOTHROW(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message)); + REQUIRE_NOTHROW(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message)); + REQUIRE_NOTHROW(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key)); + + // Window [5]: replay step 5 + fbc::bam_ecdsa_cosigner::generated_public_key pub_key_back; + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + + // Window [4,5]: replay steps 4,5 + fbc::bam_ecdsa_cosigner::server_key_shared_data server_message_back; + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + + // Window [3,4,5]: replay steps 3,4,5 + fbc::bam_ecdsa_cosigner::client_key_shared_data client_message_back; + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + + // Window [2,3,4,5]: replay steps 2,3,4,5 + commitments_sha256_t B_back; + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + + // Window [1,2,3,4,5]: replay steps 1,2,3,4,5 + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + + // Window [0,1,2,3,4,5]: replay steps 0,1,2,3,4,5 + fbc::bam_ecdsa_cosigner::server_setup_shared_data setup_back; + REQUIRE_THROWS(testSetup.server.generate_setup_with_proof(setup_id, get_tenant_id(), ECDSA_SECP256K1, setup_back)); + REQUIRE_THROWS(testSetup.client.start_new_key_generation(setup_id, keyid, get_tenant_id(), server_id, client_id, ECDSA_SECP256K1)); + REQUIRE_THROWS(testSetup.server.generate_share_and_commit(setup_id, keyid, server_id, client_id, ECDSA_SECP256K1, B_back)); + REQUIRE_THROWS(testSetup.client.verify_setup_proof_store_key_commitment_generate_key_proof(setup_id, keyid, server_id, setup, B, client_message_back)); + REQUIRE_THROWS(testSetup.server.verify_client_proofs_and_decommit_share_with_proof(keyid, client_id, client_message, server_message_back)); + REQUIRE_THROWS(testSetup.client.verify_key_decommitment_and_proofs(keyid, server_id, client_id, server_message, pub_key_back)); + } + + // ======================================================================== + // Signing backward replay: comprehensive window test + // ======================================================================== + + SECTION("attack: signing replay - after steps 1,2 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + const std::string setup_id(keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + + // Happy path: steps 1,2 (parallel — either order works) + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + + // Window [1]: replay step 1 + REQUIRE_THROWS(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + + // Window [2]: replay step 2 + std::vector server_shares_back; + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_back)); + + // Window [1,2]: replay steps 1,2 + REQUIRE_THROWS(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_back)); + } + + SECTION("attack: signing replay - after step 3 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + const std::string setup_id(keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + + // Happy path: steps 1,2,3 + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + + // Window [3]: replay step 3 + std::vector partial_signatures_back; + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures_back)); + + // Window [2,3]: replay steps 2,3 + std::vector server_shares_back; + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_back)); + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures_back)); + + // Window [1,2,3] + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_THROWS(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_back)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures_back)); + } + + SECTION("attack: signing replay - after step 4 (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + uuid_generate_random(uid); + uuid_unparse(uid, txid); + const std::string setup_id(keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + std::vector server_shares; + std::vector partial_signatures; + std::vector full_signatures; + cosigner_sign_algorithm signature_algorithm; + + // Happy path: steps 1,2,3,4 (full signing) + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures)); + REQUIRE_NOTHROW(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures, signature_algorithm)); + + // Verify the original signature is valid + REQUIRE(signature_algorithm == ECDSA_SECP256K1); + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, full_signatures[0], data_to_sign, false); + + // Window [1,2,3,4]: full re-signing + std::vector server_shares_new; + std::vector partial_signatures_new; + std::vector full_signatures_new; + cosigner_sign_algorithm algo_new; + REQUIRE_NOTHROW(testSetup.client.prepare_for_signature(keyid, txid, 0, server_id, client_id, data_to_sign, "", std::set())); + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_new)); + REQUIRE_NOTHROW(testSetup.client.compute_partial_signature(txid, server_shares_new, partial_signatures_new)); + REQUIRE_NOTHROW(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures_new, full_signatures_new, algo_new)); + REQUIRE(algo_new == ECDSA_SECP256K1); + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, full_signatures_new[0], data_to_sign, false); + + // After re-signing, all data is consumed again. Now test partial replays. + + // Window [4]: replay step 4 + std::vector full_signatures_back; + cosigner_sign_algorithm algo_back; + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures_back, algo_back)); + + // Window [3,4]: replay steps 3,4 + std::vector partial_signatures_back; + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures_back)); + REQUIRE_THROWS(testSetup.server.verify_partial_signature_and_output_signature(txid, client_id, partial_signatures, full_signatures_back, algo_back)); + + // Window [2,3,4] + std::vector server_shares_back; + REQUIRE_NOTHROW(testSetup.server.generate_signature_share(keyid, txid, 0, server_id, client_id, ECDSA_SECP256K1, data_to_sign, "", std::set(), server_shares_back)); + REQUIRE_THROWS(testSetup.client.compute_partial_signature(txid, server_shares, partial_signatures_back)); + } +} + +// ============================================================================ +// BAM Nonce Uniqueness Test (Item 5) +// Statistical test: 1000+ signatures must have unique R-parts. +// R-reuse = private key leak (nonce-reuse attack). +// ============================================================================ + +TEST_CASE("bam_ecdsa_nonce_uniqueness") +{ + // Note: uses in-process TestSetup, no Thrift service needed. + // Generate 20 keys, sign 50 messages each = 1000 signatures. + // Collect all R-part x-coordinates and verify uniqueness. + + const int NUM_KEYS = 20; + const int SIGS_PER_KEY = 50; + + std::set all_r_values; + int total_sigs = 0; + + for (int key_idx = 0; key_idx < NUM_KEYS; key_idx++) + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + for (int sig_idx = 0; sig_idx < SIGS_PER_KEY; sig_idx++) + { + uuid_t txuid; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(txuid); + uuid_unparse(txuid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + fbc::recoverable_signature signature = { 0 }; + REQUIRE_NOTHROW(bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, "", testSetup.server, testSetup.client, signature, ECDSA_SECP256K1)); + + // Collect the R-part (r component of ECDSA signature = x-coordinate of nonce point) + std::string r_hex = fbc::HexStr(std::begin(signature.r), std::end(signature.r)); + all_r_values.insert(r_hex); + total_sigs++; + } + } + + INFO("Total signatures: " << total_sigs << ", unique R values: " << all_r_values.size()); + REQUIRE(total_sigs == NUM_KEYS * SIGS_PER_KEY); + // Every R must be unique — R-reuse would allow private key extraction + REQUIRE(all_r_values.size() == static_cast(total_sigs)); +} + +// ============================================================================ +// BAM Correctness Tests (Items 17, 18) +// Structural/algebraic correctness checks that aren't attack scenarios. +// ============================================================================ + +TEST_CASE("bam_ecdsa_correctness") +{ + // After keygen, explicitly verify X_client + X_server == stored joint public key. + // Currently this is only verified implicitly via signature verification. + SECTION("keygen shares recombine to stored public key (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Compute X1 + X2 using EC algebra + elliptic_curve256_algebra_ctx_t* ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != nullptr); + elliptic_curve256_point_t computed_pubkey; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->add_points(ctx, &computed_pubkey, &X1, &X2)); + + // Retrieve stored public key from server + fbc::bam_ecdsa_cosigner::generated_public_key pub_key_data; + testSetup.server.get_public_key(keyid, pub_key_data); + REQUIRE(pub_key_data.algorithm == ECDSA_SECP256K1); + REQUIRE(pub_key_data.pub_key.size() == sizeof(elliptic_curve256_point_t)); + + // X_client + X_server must equal the stored joint public key + REQUIRE(memcmp(computed_pubkey, pub_key_data.pub_key.data(), sizeof(elliptic_curve256_point_t)) == 0); + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("keygen shares recombine to stored public key (secp256r1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256R1, X1, X2); + + elliptic_curve256_algebra_ctx_t* ctx = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(ctx != nullptr); + elliptic_curve256_point_t computed_pubkey; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->add_points(ctx, &computed_pubkey, &X1, &X2)); + + fbc::bam_ecdsa_cosigner::generated_public_key pub_key_data; + testSetup.server.get_public_key(keyid, pub_key_data); + REQUIRE(pub_key_data.algorithm == ECDSA_SECP256R1); + REQUIRE(pub_key_data.pub_key.size() == sizeof(elliptic_curve256_point_t)); + REQUIRE(memcmp(computed_pubkey, pub_key_data.pub_key.data(), sizeof(elliptic_curve256_point_t)) == 0); + + elliptic_curve256_algebra_ctx_free(ctx); + } + + // s-part is always normalized to s <= q/2 (low-S convention). + // This normalization is applied unconditionally by make_sig_s_positive(). + // Verify using both the internal is_positive() check and an independent + // OpenSSL BigNum comparison against q/2. + SECTION("s-part always positive without positiveR flag (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + // Get secp256k1 group order for external check + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1); + REQUIRE(group != nullptr); + BIGNUM* order = BN_new(); + REQUIRE(EC_GROUP_get_order(group, order, nullptr)); + BIGNUM* half_order = BN_new(); + BN_rshift1(half_order, order); // half_order = order / 2 + + // Sign 10 messages without positiveR, verify s is always positive + for (int i = 0; i < 10; i++) + { + uuid_t txuid; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(txuid); + uuid_unparse(txuid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + fbc::recoverable_signature signature = { 0 }; + bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, "", testSetup.server, testSetup.client, signature, ECDSA_SECP256K1); + + // Internal check + REQUIRE(fbc::bam_ecdsa_cosigner::is_positive(ECDSA_SECP256K1, signature.s)); + + // External check: s <= q/2 + BIGNUM* s_bn = BN_bin2bn(signature.s, sizeof(elliptic_curve256_scalar_t), nullptr); + REQUIRE(s_bn != nullptr); + INFO("sig " << i << ": s = " << fbc::HexStr(std::begin(signature.s), std::end(signature.s))); + REQUIRE(BN_cmp(s_bn, half_order) <= 0); + BN_free(s_bn); + } + + BN_free(half_order); + BN_free(order); + EC_GROUP_free(group); + } + + // When positiveR metadata is set on secp256k1, both r and s must be positive. + // Currently positiveR is only tested on STARK — this extends coverage to secp256k1. + SECTION("positiveR flag normalizes both r and s (secp256k1)") + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_secp256k1); + REQUIRE(group != nullptr); + BIGNUM* order = BN_new(); + REQUIRE(EC_GROUP_get_order(group, order, nullptr)); + BIGNUM* half_order = BN_new(); + BN_rshift1(half_order, order); + + const std::string sign_metadata = "{ \"signInfo\":[{\"positiveR\":true}]}"; + + // Sign 10 messages with positiveR, verify both r and s are positive + for (int i = 0; i < 10; i++) + { + uuid_t txuid; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(txuid); + uuid_unparse(txuid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + fbc::recoverable_signature signature = { 0 }; + bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, sign_metadata, testSetup.server, testSetup.client, signature, ECDSA_SECP256K1); + + // Verify full signature correctness + verify_ecdsa_signature(ECDSA_SECP256K1, X1, X2, signature, data_to_sign, true); + + // Internal checks + REQUIRE(fbc::bam_ecdsa_cosigner::is_positive(ECDSA_SECP256K1, signature.r)); + REQUIRE(fbc::bam_ecdsa_cosigner::is_positive(ECDSA_SECP256K1, signature.s)); + + // External BN checks: both r and s <= q/2 + BIGNUM* r_bn = BN_bin2bn(signature.r, sizeof(elliptic_curve256_scalar_t), nullptr); + BIGNUM* s_bn = BN_bin2bn(signature.s, sizeof(elliptic_curve256_scalar_t), nullptr); + REQUIRE(r_bn != nullptr); + REQUIRE(s_bn != nullptr); + REQUIRE(BN_cmp(r_bn, half_order) <= 0); + REQUIRE(BN_cmp(s_bn, half_order) <= 0); + BN_free(r_bn); + BN_free(s_bn); + } + + BN_free(half_order); + BN_free(order); + EC_GROUP_free(group); + } +} + +// ============================================================================ +// BAM Extended Nonce Uniqueness Test (Item 25) +// Long-running statistical test: 10,000+ signatures (for CI; full 1M for monthly). +// Tagged [long] — excluded from normal test runs. +// Run with: ./test "[long]" +// ============================================================================ + +TEST_CASE("bam_ecdsa_nonce_uniqueness_extended", "[.][long]") +{ + // 200 keys x 50 sigs = 10,000 signatures (CI-friendly subset of 1M target). + // For monthly runs, increase NUM_KEYS to 20,000 for 1M total. + const int NUM_KEYS = 200; + const int SIGS_PER_KEY = 50; + + std::set all_r_values; + int total_sigs = 0; + + for (int key_idx = 0; key_idx < NUM_KEYS; key_idx++) + { + TestSetup testSetup; + uuid_t uid; + char keyid[UUID_STR_LEN] = {'\0'}; + const std::string setup_id(keyid); + + uuid_generate_random(uid); + uuid_unparse(uid, keyid); + + elliptic_curve256_point_t X1, X2; + bam_key_generation(setup_id, keyid, client_id, server_id, testSetup.server, testSetup.client, ECDSA_SECP256K1, X1, X2); + + for (int sig_idx = 0; sig_idx < SIGS_PER_KEY; sig_idx++) + { + uuid_t txuid; + char txid[UUID_STR_LEN] = {'\0'}; + uuid_generate_random(txuid); + uuid_unparse(txuid, txid); + + elliptic_curve256_scalar_t hash; + REQUIRE(RAND_bytes(hash, sizeof(hash))); + + fbc::signing_data data_to_sign = {{0}, {{ fbc::byte_vector_t(&hash[0], &hash[sizeof(hash)]), { 44, 0, 0, 0, 0} }}}; + + fbc::recoverable_signature signature = { 0 }; + REQUIRE_NOTHROW(bam_key_sign(setup_id, keyid, txid, client_id, data_to_sign, "", testSetup.server, testSetup.client, signature, ECDSA_SECP256K1)); + + std::string r_hex = fbc::HexStr(std::begin(signature.r), std::end(signature.r)); + all_r_values.insert(r_hex); + total_sigs++; + } + } + + INFO("Total signatures: " << total_sigs << ", unique R values: " << all_r_values.size()); + REQUIRE(total_sigs == NUM_KEYS * SIGS_PER_KEY); + REQUIRE(all_r_values.size() == static_cast(total_sigs)); +} diff --git a/test/cosigner/ecdsa_offline_test.cpp b/test/cosigner/ecdsa_offline_test.cpp index f6fdc10..7437908 100644 --- a/test/cosigner/ecdsa_offline_test.cpp +++ b/test/cosigner/ecdsa_offline_test.cpp @@ -8,6 +8,7 @@ #include "cosigner/cosigner_exception.h" #include "cosigner/cmp_signature_preprocessed_data.h" #include "cosigner/cmp_offline_refresh_service.h" +#include "cosigner/bam_ecdsa_cosigner.h" #include "test_common.h" #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" #include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" @@ -56,7 +57,7 @@ class sign_platform : public platform_service const std::string get_current_tenantid() const override {return TENANT_ID;} uint64_t get_id_from_keyid(const std::string& key_id) const override {return _id;} void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override {assert(0);} - byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const override {return data;} + byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override {return data;} byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {return encrypted_data;} bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) override {return true;} void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {} @@ -65,7 +66,22 @@ class sign_platform : public platform_service for (auto i = flags.begin(); i != flags.end(); ++i) *i = _positive_r ? POSITIVE_R : 0; } + virtual void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + + } + + virtual void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + if (metadata != "") + { + info[0].flags = POSITIVE_R; + } + } bool is_client_id(uint64_t player_id) const override {return false;} + void mark_key_setup_in_progress(const std::string& key_id) const override {} + void clear_key_setup_in_progress(const std::string& key_id) const override {} + void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {} const uint64_t _id; bool _positive_r; @@ -269,7 +285,12 @@ struct offline_siging_info cmp_ecdsa_offline_signing_service signing_service; }; -static void ecdsa_preprocess(std::map>& services, const std::string& keyid, uint32_t start, uint32_t count, uint32_t total) +static void ecdsa_preprocess(std::map>& services, + const std::string& keyid, + uint32_t start, + uint32_t count, + uint32_t total, + uint32_t version) { uuid_t uid; char request[37] = {0}; @@ -295,10 +316,10 @@ static void ecdsa_preprocess(std::mapfirst]; - REQUIRE_NOTHROW(i->second->signing_service.offline_mta_response(request, mta_requests, response)); + REQUIRE_NOTHROW(i->second->signing_service.offline_mta_response(request, mta_requests, version, response)); cmp_mta_responses repeat_response; - REQUIRE_THROWS_AS(i->second->signing_service.offline_mta_response(request, mta_requests, repeat_response), cosigner_exception); + REQUIRE_THROWS_AS(i->second->signing_service.offline_mta_response(request, mta_requests, version, repeat_response), cosigner_exception); } mta_requests.clear(); @@ -472,7 +493,7 @@ struct preprocess_thread_data static void* preprocess_thread(void* arg) { preprocess_thread_data* param = (preprocess_thread_data*)arg; - ecdsa_preprocess(*param->services, param->keyid, param->index * BLOCK_SIZE, BLOCK_SIZE, param->total_count); + ecdsa_preprocess(*param->services, param->keyid, param->index * BLOCK_SIZE, BLOCK_SIZE, param->total_count, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); return NULL; } @@ -491,7 +512,7 @@ TEST_CASE("cmp_offline_ecdsa") { players.clear(); players[1]; players[2]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; for (auto i = players.begin(); i != players.end(); ++i) @@ -501,7 +522,7 @@ TEST_CASE("cmp_offline_ecdsa") { } auto before = Clock::now(); - ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE); + ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); auto after = Clock::now(); std::cout << "ECDSA preprocessing took: " << std::chrono::duration_cast(after - before).count() << " ms" << std::endl; @@ -559,7 +580,7 @@ TEST_CASE("cmp_offline_ecdsa") { players.clear(); players[1]; players[2]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; for (auto i = players.begin(); i != players.end(); ++i) @@ -604,7 +625,7 @@ TEST_CASE("cmp_offline_ecdsa") { players.clear(); players[11]; players[12]; - create_secret(players, ECDSA_SECP256R1, keyid, pubkey); + create_secret(players, ECDSA_SECP256R1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; for (auto i = players.begin(); i != players.end(); ++i) @@ -613,7 +634,7 @@ TEST_CASE("cmp_offline_ecdsa") { services.emplace(i->first, std::move(info)); } - ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE); + ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); ecdsa_sign(services, ECDSA_SECP256R1, keyid, 0, 1, pubkey, chaincode, {path}); char new_keyid[37] = {0}; @@ -623,14 +644,14 @@ TEST_CASE("cmp_offline_ecdsa") { new_players[21]; new_players[22]; new_players[23]; - add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey); + add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> new_services; for (auto i = new_players.begin(); i != new_players.end(); ++i) { auto info = std::make_unique(i->first, i->second); new_services.emplace(i->first, std::move(info)); } - ecdsa_preprocess(new_services, new_keyid, 0, BLOCK_SIZE, BLOCK_SIZE); + ecdsa_preprocess(new_services, new_keyid, 0, BLOCK_SIZE, BLOCK_SIZE, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); ecdsa_sign(new_services, ECDSA_SECP256R1, new_keyid, 0, 1, pubkey, chaincode, {path}); } @@ -641,7 +662,7 @@ TEST_CASE("cmp_offline_ecdsa") { players.clear(); players[21]; players[22]; - create_secret(players, ECDSA_STARK, keyid, pubkey); + create_secret(players, ECDSA_STARK, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; for (auto i = players.begin(); i != players.end(); ++i) @@ -650,7 +671,7 @@ TEST_CASE("cmp_offline_ecdsa") { services.emplace(i->first, std::move(info)); } - ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE); + ecdsa_preprocess(services, keyid, 0, BLOCK_SIZE, BLOCK_SIZE, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); ecdsa_sign(services, ECDSA_STARK, keyid, 0, 1, pubkey, chaincode, {path}); } } diff --git a/test/cosigner/ecdsa_online_test.cpp b/test/cosigner/ecdsa_online_test.cpp index 7940418..56d51ca 100644 --- a/test/cosigner/ecdsa_online_test.cpp +++ b/test/cosigner/ecdsa_online_test.cpp @@ -5,6 +5,7 @@ #include "cosigner/cmp_ecdsa_online_signing_service.h" #include "cosigner/cosigner_exception.h" +#include "cosigner/bam_ecdsa_cosigner.h" #include "test_common.h" #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" #include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" @@ -52,8 +53,8 @@ class sign_platform : public platform_service const std::string get_current_tenantid() const override {return TENANT_ID;} uint64_t get_id_from_keyid(const std::string& key_id) const override {return _id;} void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override {assert(0);} - byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const override {assert(0);} - byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {assert(0);} + byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override {return data;} + byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {return encrypted_data;} bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) override {return true;} void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {} void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const override @@ -61,7 +62,22 @@ class sign_platform : public platform_service for (auto i = flags.begin(); i != flags.end(); ++i) *i = _positive_r ? POSITIVE_R : 0; } + virtual void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + + } + + virtual void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + if (metadata != "") + { + info[0].flags = POSITIVE_R; + } + } bool is_client_id(uint64_t player_id) const override {return false;} + void mark_key_setup_in_progress(const std::string& key_id) const override {} + void clear_key_setup_in_progress(const std::string& key_id) const override {} + void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {} const uint64_t _id; const bool _positive_r; @@ -118,8 +134,15 @@ struct siging_info cmp_ecdsa_online_signing_service signing_service; }; -static void ecdsa_sign(players_setup_info& players, cosigner_sign_algorithm type, const std::string& keyid, uint32_t count, const elliptic_curve256_point_t& pubkey, - const byte_vector_t& chaincode, const std::vector>& paths, bool positive_r = false) +static void ecdsa_sign(players_setup_info& players, + cosigner_sign_algorithm type, + const std::string& keyid, + uint32_t count, + const elliptic_curve256_point_t& pubkey, + const byte_vector_t& chaincode, + const std::vector>& paths, + bool positive_r, + uint32_t version) { uuid_t uid; char txid[37] = {0}; @@ -163,10 +186,10 @@ static void ecdsa_sign(players_setup_info& players, cosigner_sign_algorithm type for (auto i = services.begin(); i != services.end(); ++i) { auto& response = mta_responses[i->first]; - REQUIRE_NOTHROW(i->second->signing_service.mta_response(txid, mta_requests, MPC_CMP_ONLINE_VERSION, response)); + REQUIRE_NOTHROW(i->second->signing_service.mta_response(txid, mta_requests, version, response)); cmp_mta_responses repeat_response; - REQUIRE_THROWS_AS(i->second->signing_service.mta_response(txid, mta_requests, MPC_CMP_ONLINE_VERSION, repeat_response), cosigner_exception); + REQUIRE_THROWS_AS(i->second->signing_service.mta_response(txid, mta_requests, version, repeat_response), cosigner_exception); } mta_requests.clear(); @@ -179,7 +202,7 @@ static void ecdsa_sign(players_setup_info& players, cosigner_sign_algorithm type REQUIRE_NOTHROW(i->second->signing_service.mta_verify(txid, mta_responses, delta)); cmp_mta_responses repeat_response; - REQUIRE_THROWS_AS(i->second->signing_service.mta_response(txid, mta_requests, MPC_CMP_ONLINE_VERSION, repeat_response), cosigner_exception); + REQUIRE_THROWS_AS(i->second->signing_service.mta_response(txid, mta_requests, version, repeat_response), cosigner_exception); std::vector repeat_deltas; mta_responses = mta_responses_saved; @@ -266,7 +289,7 @@ static void* sign_thread(void* arg) byte_vector_t chaincode(32, '\0'); std::vector path = {44, 0, 0, 0, 0}; - ecdsa_sign(param->players, ECDSA_SECP256K1, param->keyid, 1, param->pubkey, chaincode, {path}); + ecdsa_sign(param->players, ECDSA_SECP256K1, param->keyid, 1, param->pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); return NULL; } @@ -286,12 +309,12 @@ TEST_CASE("cmp_ecdsa") { players.clear(); players[1]; players[2]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("sign") { auto before = Clock::now(); - ecdsa_sign(players, ECDSA_SECP256K1, keyid, 1, pubkey, chaincode, {path}); + ecdsa_sign(players, ECDSA_SECP256K1, keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); auto after = Clock::now(); std::cout << "ECDSA signing took: " << std::chrono::duration_cast(after - before).count() << " ms" << std::endl; } @@ -305,8 +328,8 @@ TEST_CASE("cmp_ecdsa") { new_players[11]; new_players[12]; new_players[13]; - add_user(players, new_players, ECDSA_SECP256K1, keyid, new_keyid, pubkey); - ecdsa_sign(new_players, ECDSA_SECP256K1, new_keyid, 1, pubkey, chaincode, {path}); + add_user(players, new_players, ECDSA_SECP256K1, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + ecdsa_sign(new_players, ECDSA_SECP256K1, new_keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("sign multiple") { @@ -319,7 +342,7 @@ TEST_CASE("cmp_ecdsa") { derivation_paths.push_back(derivation_path); ++derivation_path[2]; } - ecdsa_sign(players, ECDSA_SECP256K1, keyid, COUNT, pubkey, chaincode, derivation_paths); + ecdsa_sign(players, ECDSA_SECP256K1, keyid, COUNT, pubkey, chaincode, derivation_paths, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("MT") { @@ -341,7 +364,7 @@ TEST_CASE("cmp_ecdsa") { SECTION("sign positive R") { // run 4 times as R has 50% chance of being negative for (size_t i = 0; i < 8; ++i) - ecdsa_sign(players, ECDSA_SECP256K1, keyid, 1, pubkey, chaincode, {path}, true);; + ecdsa_sign(players, ECDSA_SECP256K1, keyid, 1, pubkey, chaincode, {path}, true, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } } @@ -355,8 +378,8 @@ TEST_CASE("cmp_ecdsa") { uuid_unparse(uid, keyid); players[1]; players[2]; - create_secret(players, ECDSA_SECP256R1, keyid, pubkey); - ecdsa_sign(players, ECDSA_SECP256R1, keyid, 1, pubkey, chaincode, {path}); + create_secret(players, ECDSA_SECP256R1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + ecdsa_sign(players, ECDSA_SECP256R1, keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); uuid_unparse(uid, new_keyid); @@ -364,8 +387,8 @@ TEST_CASE("cmp_ecdsa") { new_players[11]; new_players[12]; new_players[13]; - add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey); - ecdsa_sign(new_players, ECDSA_SECP256R1, new_keyid, 1, pubkey, chaincode, {path}); + add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + ecdsa_sign(new_players, ECDSA_SECP256R1, new_keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("stark") { @@ -377,8 +400,8 @@ TEST_CASE("cmp_ecdsa") { uuid_unparse(uid, keyid); players[1]; players[2]; - create_secret(players, ECDSA_STARK, keyid, pubkey); - ecdsa_sign(players, ECDSA_STARK, keyid, 1, pubkey, chaincode, {path}); + create_secret(players, ECDSA_STARK, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + ecdsa_sign(players, ECDSA_STARK, keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); uuid_unparse(uid, new_keyid); @@ -386,7 +409,7 @@ TEST_CASE("cmp_ecdsa") { new_players[11]; new_players[12]; new_players[13]; - add_user(players, new_players, ECDSA_STARK, keyid, new_keyid, pubkey); - ecdsa_sign(new_players, ECDSA_STARK, new_keyid, 1, pubkey, chaincode, {path}); + add_user(players, new_players, ECDSA_STARK, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + ecdsa_sign(new_players, ECDSA_STARK, new_keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } } diff --git a/test/cosigner/eddsa_offline_test.cpp b/test/cosigner/eddsa_offline_test.cpp index 8a5daa0..d270fd6 100644 --- a/test/cosigner/eddsa_offline_test.cpp +++ b/test/cosigner/eddsa_offline_test.cpp @@ -49,16 +49,28 @@ class asymmetric_eddsa_platform : public platform_service const std::string get_current_tenantid() const override {return TENANT_ID;} uint64_t get_id_from_keyid(const std::string& key_id) const override {return _id;} void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override { assert(0);} - byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const override {assert(0);} - byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {assert(0);} + byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override {return data;} + byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {return encrypted_data;} bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) override {return true;} void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {} void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const override { for (auto i = flags.begin(); i != flags.end(); ++i) *i = _use_keccak ? EDDSA_KECCAK : 0; + } + void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + for (auto i = info.begin(); i != info.end(); ++i) + i->flags = _use_keccak ? EDDSA_KECCAK : 0; + } + void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + } bool is_client_id(uint64_t player_id) const override {return CLIENT_ID == player_id;} + void mark_key_setup_in_progress(const std::string& key_id) const override {} + void clear_key_setup_in_progress(const std::string& key_id) const override {} + void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {} const uint64_t _id; bool _use_keccak; @@ -189,7 +201,7 @@ class server_persistency : public asymmetric_eddsa_cosigner_server::signing_pers data = it->second; } - void delete_temporary_signing_data(const std::string& txid) override + void delete_signing_data(const std::string& txid) override { std::unique_lock lock(_mutex); _signing_metadata.erase(txid); @@ -204,7 +216,7 @@ class server_persistency : public asymmetric_eddsa_cosigner_server::signing_pers struct client_info { - client_info(uint64_t player_id, const cmp_key_persistency& key_persistency) : id(player_id), platform_service(player_id), service(platform_service, key_persistency, persistency) {} + client_info(uint64_t player_id, cmp_key_persistency& key_persistency) : id(player_id), platform_service(player_id), service(platform_service, key_persistency, persistency) {} uint64_t id; asymmetric_eddsa_platform platform_service; client_persistency persistency; @@ -213,7 +225,7 @@ struct client_info struct server_info { - server_info(uint64_t id, const cmp_key_persistency& key_persistency) : platform_service(id), service(platform_service, key_persistency, persistency) {} + server_info(uint64_t id, cmp_key_persistency& key_persistency) : platform_service(id), service(platform_service, key_persistency, persistency) {} asymmetric_eddsa_platform platform_service; server_persistency persistency; asymmetric_eddsa_cosigner_server service; @@ -276,7 +288,7 @@ static void eddsa_sign(std::map>& servers } std::map> R_commitments; - std::map Rs_map; + std::map> Rs_map; for (auto i = servers.begin(); i != servers.end(); ++i) { auto& R_commitment = R_commitments[i->first]; @@ -284,22 +296,22 @@ static void eddsa_sign(std::map>& servers REQUIRE_NOTHROW(i->second->service.eddsa_sign_offline(keyid, txid, data, "", players_str, players_ids, start_index, R_commitment, R)); std::vector repeat_commitments; - Rs_and_commitments repeat_Rs; + std::vector repeat_Rs; REQUIRE_THROWS_AS(i->second->service.eddsa_sign_offline(keyid, txid, data, "", players_str, players_ids, start_index, repeat_commitments, repeat_Rs), cosigner_exception); if (servers.size() == 1) { REQUIRE(R_commitment.size() == 0); - REQUIRE(R.Rs.size() == count); + REQUIRE(R.size() == count); } else { REQUIRE(R_commitment.size() == count); - REQUIRE(R.Rs.size() == 0); + REQUIRE(R.size() == 0); } } - std::map server_Rs; + std::map> server_Rs; if (servers.size() == 1) { server_Rs = std::move(Rs_map); @@ -310,9 +322,9 @@ static void eddsa_sign(std::map>& servers for (auto i = servers.begin(); i != servers.end(); ++i) { auto& R = Rs_map[i->first]; - REQUIRE_NOTHROW(i->second->service.decommit_r(txid, R_commitments, R.Rs)); - Rs[i->first] = R.Rs; - REQUIRE(R.Rs.size() == count); + REQUIRE_NOTHROW(i->second->service.decommit_r(txid, R_commitments, R)); + Rs[i->first] = R; + REQUIRE(R.size() == count); std::vector repeat_Rs; REQUIRE_THROWS_AS(i->second->service.decommit_r(txid, R_commitments, repeat_Rs), cosigner_exception); @@ -326,7 +338,7 @@ static void eddsa_sign(std::map>& servers REQUIRE(send_to_id == CLIENT_ID); uint64_t repeat_send_to_id; - Rs_and_commitments repeat_R; + std::vector repeat_R; REQUIRE_THROWS_AS(i->second->service.broadcast_r(txid, Rs, repeat_R, repeat_send_to_id), cosigner_exception); } } @@ -411,7 +423,7 @@ TEST_CASE("asymmetric_eddsa") { uuid_unparse(uid, keyid); players[CLIENT_ID]; players[SERVER_ID]; - create_secret(players, EDDSA_ED25519, keyid, pubkey); + create_secret(players, EDDSA_ED25519, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; services.emplace(SERVER_ID, std::make_unique(SERVER_ID, players[SERVER_ID])); @@ -428,7 +440,7 @@ TEST_CASE("asymmetric_eddsa") { players[CLIENT_ID]; players[11]; players[12]; - create_secret(players, EDDSA_ED25519, keyid, pubkey); + create_secret(players, EDDSA_ED25519, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); std::map> services; for (auto i = players.begin(); i != players.end(); ++i) diff --git a/test/cosigner/eddsa_online_test.cpp b/test/cosigner/eddsa_online_test.cpp index ba35a99..c4bafa3 100644 --- a/test/cosigner/eddsa_online_test.cpp +++ b/test/cosigner/eddsa_online_test.cpp @@ -47,8 +47,8 @@ class eddsa_sign_platform : public platform_service const std::string get_current_tenantid() const override {return TENANT_ID;} uint64_t get_id_from_keyid(const std::string& key_id) const override {return _id;} void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override {assert(0);} - byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const override {assert(0);} - byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {assert(0);} + byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override {return data;} + byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {return encrypted_data;} bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) override {return true;} void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {} void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const override @@ -56,7 +56,20 @@ class eddsa_sign_platform : public platform_service for (auto i = flags.begin(); i != flags.end(); ++i) *i = _use_keccak ? EDDSA_KECCAK : 0; } + void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + for (auto& sig : info) + sig.flags = _use_keccak ? EDDSA_KECCAK : 0; + } + + void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override + { + // Stub for tests + } bool is_client_id(uint64_t player_id) const override {return false;} + void mark_key_setup_in_progress(const std::string& key_id) const override {} + void clear_key_setup_in_progress(const std::string& key_id) const override {} + void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {} const uint64_t _id; const bool _use_keccak; @@ -64,30 +77,30 @@ class eddsa_sign_platform : public platform_service class eddsa_signing_persistency : public eddsa_online_signing_service::signing_persistency { - void store_signing_data(const std::string& txid, const eddsa_signing_metadata& data) override + void store_eddsa_signing_data(const std::string& txid, const std::shared_ptr& data) override { std::unique_lock lock(_mutex); if (_metadata.find(txid) != _metadata.end()) throw cosigner_exception(cosigner_exception::INVALID_TRANSACTION); - _metadata[txid] = data; + _metadata[txid] = *data; } - void load_signing_data(const std::string& txid, eddsa_signing_metadata& data) const override + std::shared_ptr load_eddsa_signing_data(const std::string& txid) const override { std::shared_lock lock(_mutex); auto it = _metadata.find(txid); if (it == _metadata.end()) throw cosigner_exception(cosigner_exception::INVALID_TRANSACTION); - data = it->second; + return std::make_shared(it->second); } - void update_signing_data(const std::string& txid, const eddsa_signing_metadata& data) override + void update_eddsa_signing_data(const std::string& txid, const std::shared_ptr& data) override { std::unique_lock lock(_mutex); auto it = _metadata.find(txid); if (it == _metadata.end()) throw cosigner_exception(cosigner_exception::INVALID_TRANSACTION); - it->second = data; + it->second = *data; } void store_signing_commitments(const std::string& txid, const std::map>& commitments) override @@ -107,11 +120,12 @@ class eddsa_signing_persistency : public eddsa_online_signing_service::signing_p commitments = it->second; } - void delete_temporary_signing_data(const std::string& txid) override + bool delete_eddsa_signing_data(const std::string& txid) override { std::unique_lock lock(_mutex); _metadata.erase(txid); _commitments.erase(txid); + return true; } mutable std::shared_mutex _mutex; @@ -121,14 +135,20 @@ class eddsa_signing_persistency : public eddsa_online_signing_service::signing_p struct eddsa_siging_info { - eddsa_siging_info(uint64_t id, const cmp_key_persistency& persistency, bool positive_r) : platform_service(id, positive_r), signing_service(platform_service, persistency, signing_persistency) {} + eddsa_siging_info(uint64_t id, cmp_key_persistency& persistency, bool positive_r) : platform_service(id, positive_r), signing_service(platform_service, persistency, signing_persistency) {} eddsa_sign_platform platform_service; eddsa_signing_persistency signing_persistency; eddsa_online_signing_service signing_service; }; -static void eddsa_sign(players_setup_info& players, const std::string& keyid, uint32_t count, const elliptic_curve256_point_t& pubkey, - const byte_vector_t& chaincode, const std::vector>& paths, bool keccek = false) +static void eddsa_sign(players_setup_info& players, + const std::string& keyid, + uint32_t count, + const elliptic_curve256_point_t& pubkey, + const byte_vector_t& chaincode, + const std::vector>& paths, + bool keccek, + uint32_t version) { uuid_t uid; char txid[37] = {0}; @@ -172,10 +192,10 @@ static void eddsa_sign(players_setup_info& players, const std::string& keyid, ui for (auto i = services.begin(); i != services.end(); ++i) { auto& R = Rs[i->first]; - REQUIRE_NOTHROW(i->second->signing_service.store_commitments(txid, commitments, MPC_CMP_ONLINE_VERSION, R)); + REQUIRE_NOTHROW(i->second->signing_service.store_commitments(txid, commitments, version, R)); std::vector repeat_Rs; - REQUIRE_THROWS_AS(i->second->signing_service.store_commitments(txid, commitments, MPC_CMP_ONLINE_VERSION, repeat_Rs), cosigner_exception); + REQUIRE_THROWS_AS(i->second->signing_service.store_commitments(txid, commitments, version, repeat_Rs), cosigner_exception); } commitments.clear(); @@ -231,7 +251,7 @@ static void* sign_thread(void* arg) byte_vector_t chaincode(32, '\0'); std::vector path = {44, 0, 0, 0, 0}; - eddsa_sign(param->players, param->keyid, 1, param->pubkey, chaincode, {path}); + eddsa_sign(param->players, param->keyid, 1, param->pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); return NULL; } @@ -250,12 +270,12 @@ TEST_CASE("eddsa") { uuid_unparse(uid, keyid); players[1]; players[2]; - create_secret(players, EDDSA_ED25519, keyid, pubkey); + create_secret(players, EDDSA_ED25519, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("sign") { auto before = Clock::now(); - eddsa_sign(players, keyid, 1, pubkey, chaincode, {path}); + eddsa_sign(players, keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); auto after = Clock::now(); std::cout << "EDDSA signing took: " << std::chrono::duration_cast(after - before).count() << " ms" << std::endl; } @@ -269,8 +289,8 @@ TEST_CASE("eddsa") { new_players[11]; new_players[12]; new_players[13]; - add_user(players, new_players, EDDSA_ED25519, keyid, new_keyid, pubkey); - eddsa_sign(new_players, new_keyid, 1, pubkey, chaincode, {path}); + add_user(players, new_players, EDDSA_ED25519, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); + eddsa_sign(new_players, new_keyid, 1, pubkey, chaincode, {path}, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("sign multiple") { @@ -283,7 +303,7 @@ TEST_CASE("eddsa") { derivation_paths.push_back(derivation_path); ++derivation_path[2]; } - eddsa_sign(players, keyid, COUNT, pubkey, chaincode, derivation_paths); + eddsa_sign(players, keyid, COUNT, pubkey, chaincode, derivation_paths, false, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("MT") { @@ -302,9 +322,9 @@ TEST_CASE("eddsa") { std::cout << "Done in " << std::chrono::duration_cast(finish - start).count() << " ms" << std::endl; } - SECTION("keccek") { + SECTION("keccak") { // run 4 times as R has 50% chance of being negative for (size_t i = 0; i < 8; ++i) - eddsa_sign(players, keyid, 1, pubkey, chaincode, {path}, true);; + eddsa_sign(players, keyid, 1, pubkey, chaincode, {path}, true, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION);; } } diff --git a/test/cosigner/setup_test.cpp b/test/cosigner/setup_test.cpp index 702ada3..5b89ce0 100644 --- a/test/cosigner/setup_test.cpp +++ b/test/cosigner/setup_test.cpp @@ -6,7 +6,7 @@ #include "test_common.h" #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" #include "cosigner/cmp_key_persistency.h" - +#include "cosigner/mpc_globals.h" #include #include @@ -48,11 +48,13 @@ std::string setup_persistency::dump_key(const std::string& key_id) const bool setup_persistency::key_exist(const std::string& key_id) const { + std::shared_lock lock(_mutex); return _keys.find(key_id) != _keys.end(); } void setup_persistency::load_key(const std::string& key_id, cosigner_sign_algorithm& algorithm, elliptic_curve256_scalar_t& private_key) const { + std::shared_lock lock(_mutex); auto it = _keys.find(key_id); if (it == _keys.end()) throw cosigner_exception(cosigner_exception::BAD_KEY); @@ -67,6 +69,7 @@ const std::string setup_persistency::get_tenantid_from_keyid(const std::string& void setup_persistency::load_key_metadata(const std::string& key_id, cmp_key_metadata& metadata, bool full_load) const { + std::shared_lock lock(_mutex); auto it = _keys.find(key_id); if (it == _keys.end()) throw cosigner_exception(cosigner_exception::BAD_KEY); @@ -75,6 +78,7 @@ void setup_persistency::load_key_metadata(const std::string& key_id, cmp_key_met void setup_persistency::load_auxiliary_keys(const std::string& key_id, auxiliary_keys& aux) const { + std::shared_lock lock(_mutex); auto it = _keys.find(key_id); if (it == _keys.end()) throw cosigner_exception(cosigner_exception::BAD_KEY); @@ -83,6 +87,7 @@ void setup_persistency::load_auxiliary_keys(const std::string& key_id, auxiliary void setup_persistency::store_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, uint64_t ttl) { + std::unique_lock lock(_mutex); auto& info = _keys[key_id]; memcpy(info.private_key, private_key, sizeof(elliptic_curve256_scalar_t)); info.algorithm = algorithm; @@ -90,6 +95,7 @@ void setup_persistency::store_key(const std::string& key_id, cosigner_sign_algor void setup_persistency::store_key_metadata(const std::string& key_id, const cmp_key_metadata& metadata, bool allow_override) { + std::unique_lock lock(_mutex); auto& info = _keys[key_id]; if (!allow_override && info.metadata) throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); @@ -99,24 +105,30 @@ void setup_persistency::store_key_metadata(const std::string& key_id, const cmp_ void setup_persistency::store_auxiliary_keys(const std::string& key_id, const auxiliary_keys& aux) { + std::unique_lock lock(_mutex); auto& info = _keys[key_id]; info.aux_keys = aux; } void setup_persistency::store_keyid_tenant_id(const std::string& key_id, const std::string& tenant_id) {} -void setup_persistency::store_setup_data(const std::string& key_id, const setup_data& metadata) +void setup_persistency::store_setup_data(const std::string& key_id, const setup_data& metadata, bool override) { + std::unique_lock lock(_mutex); _setup_data[key_id] = metadata; } void setup_persistency::load_setup_data(const std::string& key_id, setup_data& metadata) { + std::shared_lock lock(_mutex); + if (_setup_data.find(key_id) == _setup_data.end()) + throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); metadata = _setup_data[key_id]; } void setup_persistency::store_setup_commitments(const std::string& key_id, const std::map& commitments) { + std::unique_lock lock(_mutex); if (_commitments.find(key_id) != _commitments.end()) throw cosigner_exception(cosigner_exception::INTERNAL_ERROR); @@ -125,11 +137,13 @@ void setup_persistency::store_setup_commitments(const std::string& key_id, const void setup_persistency::load_setup_commitments(const std::string& key_id, std::map& commitments) { + std::shared_lock lock(_mutex); commitments = _commitments[key_id]; } void setup_persistency::delete_temporary_key_data(const std::string& key_id, bool delete_key) { + std::unique_lock lock(_mutex); _setup_data.erase(key_id); _commitments.erase(key_id); if (delete_key) @@ -151,13 +165,17 @@ class platform : public platform_service const std::string get_current_tenantid() const override {return TENANT_ID;} uint64_t get_id_from_keyid(const std::string& key_id) const override {return _id;} void derive_initial_share(const share_derivation_args& derive_from, cosigner_sign_algorithm algorithm, elliptic_curve256_scalar_t* key) const override {assert(0);} - byte_vector_t encrypt_for_player(uint64_t id, const byte_vector_t& data) const override {return data;} + byte_vector_t encrypt_for_player(const uint64_t id, const byte_vector_t& data, const std::optional& verify_modulus = std::nullopt) const override {return data;} byte_vector_t decrypt_message(const byte_vector_t& encrypted_data) const override {return encrypted_data;} bool backup_key(const std::string& key_id, cosigner_sign_algorithm algorithm, const elliptic_curve256_scalar_t& private_key, const cmp_key_metadata& metadata, const auxiliary_keys& aux) override {return true;} void on_start_signing(const std::string& key_id, const std::string& txid, const signing_data& data, const std::string& metadata_json, const std::set& players, const signing_type signature_type) override {} void fill_signing_info_from_metadata(const std::string& metadata, std::vector& flags) const override {assert(0);} + void fill_eddsa_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override {assert(0);} + void fill_bam_signing_info_from_metadata(std::vector& info, const std::string& metadata) const override {assert(0);} bool is_client_id(uint64_t player_id) const override {return false;} - + void mark_key_setup_in_progress(const std::string& key_id) const override {} + void clear_key_setup_in_progress(const std::string& key_id) const override {} + void prepare_for_signing(const std::string& key_id, const std::string tx_id) override {} uint64_t _id; }; @@ -168,7 +186,11 @@ struct setup_info cmp_setup_service setup_service; }; -void create_secret(players_setup_info& players, cosigner_sign_algorithm type, const std::string& keyid, elliptic_curve256_point_t& pubkey) +void create_secret(players_setup_info& players, + cosigner_sign_algorithm type, + const std::string& keyid, + elliptic_curve256_point_t& pubkey, + uint32_t version) { std::unique_ptr algebra(create_algebra(type), elliptic_curve256_algebra_ctx_free); const size_t PUBKEY_SIZE = algebra->point_size(algebra.get()); @@ -198,10 +220,10 @@ void create_secret(players_setup_info& players, cosigner_sign_algorithm type, co for (auto i = services.begin(); i != services.end(); ++i) { setup_decommitment& decommitment = decommitments[i->first]; - REQUIRE_NOTHROW(i->second->setup_service.store_setup_commitments(keyid, commitments, decommitment)); + REQUIRE_NOTHROW(i->second->setup_service.store_setup_commitments(keyid, commitments, version, decommitment)); setup_decommitment repeat_decommitment; - REQUIRE_THROWS_AS(i->second->setup_service.store_setup_commitments(keyid, commitments, repeat_decommitment), cosigner_exception); + REQUIRE_THROWS_AS(i->second->setup_service.store_setup_commitments(keyid, commitments, version, repeat_decommitment), cosigner_exception); } commitments.clear(); @@ -247,7 +269,7 @@ void create_secret(players_setup_info& players, cosigner_sign_algorithm type, co std::string repeat_public_key; cosigner_sign_algorithm repeat_algorithm; - REQUIRE_NOTHROW(i->second->setup_service.create_secret(keyid, paillier_large_factor_proofs, repeat_public_key, repeat_algorithm)); + REQUIRE_THROWS_AS(i->second->setup_service.create_secret(keyid, paillier_large_factor_proofs, repeat_public_key, repeat_algorithm), cosigner_exception); } paillier_large_factor_proofs.clear(); @@ -258,7 +280,13 @@ void create_secret(players_setup_info& players, cosigner_sign_algorithm type, co } } -void add_user(players_setup_info& old_players, players_setup_info& new_players, cosigner_sign_algorithm type, const std::string& old_keyid, const std::string& new_keyid, const elliptic_curve256_point_t& pubkey) +void add_user(players_setup_info& old_players, + players_setup_info& new_players, + cosigner_sign_algorithm type, + const std::string& old_keyid, + const std::string& new_keyid, + const elliptic_curve256_point_t& pubkey, + uint32_t version) { std::unique_ptr algebra(create_algebra(type), elliptic_curve256_algebra_ctx_free); const size_t PUBKEY_SIZE = algebra->point_size(algebra.get()); @@ -297,7 +325,7 @@ void add_user(players_setup_info& old_players, players_setup_info& new_players, for (auto i = services.begin(); i != services.end(); ++i) { setup_decommitment& decommitment = decommitments[i->first]; - REQUIRE_NOTHROW(i->second->setup_service.store_setup_commitments(new_keyid, commitments, decommitment)); + REQUIRE_NOTHROW(i->second->setup_service.store_setup_commitments(new_keyid, commitments, version, decommitment)); } commitments.clear(); @@ -334,7 +362,6 @@ void add_user(players_setup_info& old_players, players_setup_info& new_players, } } -#if 0 TEST_CASE("setup") { SECTION("secp256k1") { uuid_t uid; @@ -345,7 +372,7 @@ TEST_CASE("setup") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("secp256r1") { @@ -357,7 +384,7 @@ TEST_CASE("setup") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_SECP256R1, keyid, pubkey); + create_secret(players, ECDSA_SECP256R1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("ed25519") { @@ -369,7 +396,7 @@ TEST_CASE("setup") { players_setup_info players; players[1]; players[2]; - create_secret(players, EDDSA_ED25519, keyid, pubkey); + create_secret(players, EDDSA_ED25519, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("stark") { @@ -381,7 +408,7 @@ TEST_CASE("setup") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_STARK, keyid, pubkey); + create_secret(players, ECDSA_STARK, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("3/3") { @@ -394,7 +421,7 @@ TEST_CASE("setup") { players[1]; players[2]; players[111]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } } @@ -408,7 +435,7 @@ TEST_CASE("add_user") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_SECP256K1, keyid, pubkey); + create_secret(players, ECDSA_SECP256K1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); @@ -417,7 +444,7 @@ TEST_CASE("add_user") { new_players[11]; new_players[12]; new_players[13]; - add_user(players, new_players, ECDSA_SECP256K1, keyid, new_keyid, pubkey); + add_user(players, new_players, ECDSA_SECP256K1, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("secp256r1") { @@ -429,7 +456,7 @@ TEST_CASE("add_user") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_SECP256R1, keyid, pubkey); + create_secret(players, ECDSA_SECP256R1, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); @@ -437,7 +464,7 @@ TEST_CASE("add_user") { players_setup_info new_players; new_players[11]; new_players[12]; - add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey); + add_user(players, new_players, ECDSA_SECP256R1, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("ed25519") { @@ -449,7 +476,7 @@ TEST_CASE("add_user") { players_setup_info players; players[1]; players[2]; - create_secret(players, EDDSA_ED25519, keyid, pubkey); + create_secret(players, EDDSA_ED25519, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); @@ -459,7 +486,7 @@ TEST_CASE("add_user") { new_players[12]; new_players[13]; new_players[14]; - add_user(players, new_players, EDDSA_ED25519, keyid, new_keyid, pubkey); + add_user(players, new_players, EDDSA_ED25519, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } SECTION("stark") { @@ -471,7 +498,7 @@ TEST_CASE("add_user") { players_setup_info players; players[1]; players[2]; - create_secret(players, ECDSA_STARK, keyid, pubkey); + create_secret(players, ECDSA_STARK, keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); char new_keyid[37] = {0}; uuid_generate_random(uid); @@ -479,7 +506,6 @@ TEST_CASE("add_user") { players_setup_info new_players; new_players[11]; new_players[12]; - add_user(players, new_players, ECDSA_STARK, keyid, new_keyid, pubkey); + add_user(players, new_players, ECDSA_STARK, keyid, new_keyid, pubkey, fireblocks::common::cosigner::MPC_PROTOCOL_VERSION); } } -#endif \ No newline at end of file diff --git a/test/cosigner/test_common.h b/test/cosigner/test_common.h index e6c362c..ea09d4e 100644 --- a/test/cosigner/test_common.h +++ b/test/cosigner/test_common.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "cosigner/cmp_setup_service.h" #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" @@ -23,7 +24,7 @@ class setup_persistency : public fireblocks::common::cosigner::cmp_setup_service void store_key_metadata(const std::string& key_id, const fireblocks::common::cosigner::cmp_key_metadata& metadata, bool allow_override) override; void store_auxiliary_keys(const std::string& key_id, const fireblocks::common::cosigner::auxiliary_keys& aux) override; void store_keyid_tenant_id(const std::string& key_id, const std::string& tenant_id) override; - void store_setup_data(const std::string& key_id, const fireblocks::common::cosigner::setup_data& metadata) override; + void store_setup_data(const std::string& key_id, const fireblocks::common::cosigner::setup_data& metadata, bool override) override; void load_setup_data(const std::string& key_id, fireblocks::common::cosigner::setup_data& metadata) override; void store_setup_commitments(const std::string& key_id, const std::map& commitments) override; void load_setup_commitments(const std::string& key_id, std::map& commitments) override; @@ -37,6 +38,7 @@ class setup_persistency : public fireblocks::common::cosigner::cmp_setup_service fireblocks::common::cosigner::auxiliary_keys aux_keys; }; + mutable std::shared_mutex _mutex; std::map _keys; std::map _setup_data; std::map> _commitments; @@ -71,5 +73,5 @@ inline std::string HexStr(const T& vch) return HexStr(vch.begin(), vch.end()); } -void create_secret(players_setup_info& players, cosigner_sign_algorithm type, const std::string& keyid, elliptic_curve256_point_t& pubkey); -void add_user(players_setup_info& old_players, players_setup_info& new_players, cosigner_sign_algorithm type, const std::string& old_keyid, const std::string& new_keyid, const elliptic_curve256_point_t& pubkey); +void create_secret(players_setup_info& players, cosigner_sign_algorithm type, const std::string& keyid, elliptic_curve256_point_t& pubkey, uint32_t version); +void add_user(players_setup_info& old_players, players_setup_info& new_players, cosigner_sign_algorithm type, const std::string& old_keyid, const std::string& new_keyid, const elliptic_curve256_point_t& pubkey, uint32_t version); diff --git a/test/crypto/algebra_utils/Makefile b/test/crypto/algebra_utils/Makefile new file mode 100644 index 0000000..43a8d5b --- /dev/null +++ b/test/crypto/algebra_utils/Makefile @@ -0,0 +1,27 @@ +App_Name := test + +App_Common_Flags := -DOPENSSL_API_COMPAT=0x10101000L +App_C_Flags := -g -Wall -Wextra -I../../../../include $(App_Common_Flags) +App_Cpp_Flags := $(App_C_Flags) -std=c++17 $(App_Common_Flags) +App_Link_Flags := -Wl,-Bstatic -lcrypto -Wl,-Bdynamic -pthread -ldl + +all: $(App_Name) + +main.o: main.cpp + @$(CXX) $(App_Cpp_Flags) -c $< -o $@ + @echo "CXX <= $<" + +algebra_utils.o: ../../../crypto/algebra_utils/algebra_utils.c + @$(CC) $(App_C_Flags) -c $< -o $@ + @echo "CC <= $<" + + +$(App_Name): main.o algebra_utils.o + @$(CXX) $^ -o $@ $(App_Link_Flags) + @echo "LINK => $@" + +clean: + @rm -rf $(App_Name) *.o + +run: + ./$(App_Name) \ No newline at end of file diff --git a/test/crypto/algebra_utils/tests.cpp b/test/crypto/algebra_utils/tests.cpp index ce48c4f..018d7ae 100644 --- a/test/crypto/algebra_utils/tests.cpp +++ b/test/crypto/algebra_utils/tests.cpp @@ -1,4 +1,4 @@ -#include "../../../src/common/crypto/algebra_utils/algebra_utils.h" +#include "crypto/algebra_utils/algebra_utils.h" #include "crypto/elliptic_curve_algebra/elliptic_curve_algebra_status.h" #include @@ -44,7 +44,7 @@ TEST_CASE("is_coprime_fast") REQUIRE(BN_gcd(tmp, q, p, ctx)); } while(!BN_is_one(tmp)); - REQUIRE(is_coprime_fast(p, q, ctx)); + REQUIRE(is_coprime_fast(p, q, ctx) == 1); } SECTION("verify positive") @@ -55,7 +55,7 @@ TEST_CASE("is_coprime_fast") REQUIRE(BN_generate_prime_ex(p, 1024, 0, NULL, NULL, NULL)); REQUIRE(BN_mul(q, p, val, ctx)); - REQUIRE(!is_coprime_fast(p, q, ctx)); + REQUIRE(is_coprime_fast(p, q, ctx) == 0); } BN_CTX_end(ctx); @@ -82,7 +82,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 1024, 256, NULL, NULL, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 4) == 3); } } @@ -92,7 +92,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 1536, 256, NULL, NULL, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 4) == 3); } } @@ -102,7 +102,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 1024, 256, sixteen, seven, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 16) == 7); } } @@ -112,7 +112,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 1536, 256, sixteen, seven, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 16) == 7); } } @@ -123,7 +123,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 384, 192, sixteen, seven, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 16) == 7); } } @@ -134,7 +134,7 @@ TEST_CASE("tough_prime_generation") for (int i = 0; i < 5; ++ i) { REQUIRE(generate_tough_prime(p, 384, 192, NULL, NULL, ctx) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL)); + REQUIRE(BN_is_prime_fasttest_ex(p, 64, ctx, 1, NULL) == 1); REQUIRE(BN_mod_word(p, 4) == 3); } } diff --git a/test/crypto/drng/tests.cpp b/test/crypto/drng/tests.cpp index 4c48f7e..7bad867 100644 --- a/test/crypto/drng/tests.cpp +++ b/test/crypto/drng/tests.cpp @@ -69,3 +69,38 @@ TEST_CASE("schnorr", "verify") { drng_free(rng); } } + +TEST_CASE("drng_seed_sensitivity", "[correctness]") +{ + SECTION("1-bit difference in seed produces completely different output") + { + uint8_t seed1[32]; + memset(seed1, 0xAB, sizeof(seed1)); + uint8_t seed2[32]; + memcpy(seed2, seed1, sizeof(seed1)); + seed2[0] ^= 0x01; // Flip one bit + + drng_t *rng1, *rng2; + REQUIRE(drng_new(seed1, sizeof(seed1), &rng1) == DRNG_SUCCESS); + REQUIRE(drng_new(seed2, sizeof(seed2), &rng2) == DRNG_SUCCESS); + + uint8_t out1[256], out2[256]; + REQUIRE(drng_read_deterministic_rand(rng1, out1, 256) == DRNG_SUCCESS); + REQUIRE(drng_read_deterministic_rand(rng2, out2, 256) == DRNG_SUCCESS); + + // Outputs must differ + REQUIRE(memcmp(out1, out2, 256) != 0); + + // Count differing bytes - should be roughly half (128 out of 256) + int diff_count = 0; + for (int i = 0; i < 256; i++) + { + if (out1[i] != out2[i]) diff_count++; + } + // At minimum 25% of bytes should differ (conservative threshold) + REQUIRE(diff_count >= 64); + + drng_free(rng1); + drng_free(rng2); + } +} diff --git a/test/crypto/ed25519_algebra/tests.cpp b/test/crypto/ed25519_algebra/tests.cpp index 1052eef..68649cd 100644 --- a/test/crypto/ed25519_algebra/tests.cpp +++ b/test/crypto/ed25519_algebra/tests.cpp @@ -563,7 +563,7 @@ TEST_CASE( "sign", "ed25519") { REQUIRE(ED25519_verify(msg, 32, sig, pub)); } - SECTION("keccac") { + SECTION("keccak") { ed25519_scalar_t priv; ed25519_point_t pub; uint8_t msg[] = "00000000000000000000000000000000"; @@ -648,6 +648,252 @@ TEST_CASE( "hash_on_curve" ) { REQUIRE(ed25519->hash_on_curve(ed25519, &res, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); } - + + elliptic_curve256_algebra_ctx_free(ed25519); +} + +TEST_CASE("hash_on_curve_test_vectors") { + /* 11 precomputed test vectors for ed25519. + * If the hash-to-curve implementation changes, these will fail — that is + * intentional. Update the vectors only after reviewing the change. */ + + struct test_input { + const uint8_t *data; + uint32_t len; + const char *label; + }; + + const uint8_t byte_00 = 0x00; + const uint8_t byte_01 = 0x01; + const uint8_t byte_ff = 0xff; + const uint8_t abc[] = "abc"; + const uint8_t test_msg[] = "test"; + const uint8_t example[] = "Some example message"; + uint8_t zeros32[32]; memset(zeros32, 0x00, 32); + uint8_t ffs32[32]; memset(ffs32, 0xff, 32); + uint8_t incr32[32]; for (int i = 0; i < 32; i++) incr32[i] = (uint8_t)i; + const uint8_t fox[] = "The quick brown fox jumps over the lazy dog"; + + const test_input inputs[] = { + { NULL, 0, "NULL_empty" }, + { &byte_00, 1, "byte_0x00" }, + { &byte_01, 1, "byte_0x01" }, + { &byte_ff, 1, "byte_0xFF" }, + { abc, sizeof(abc), "abc_with_null" }, + { test_msg, sizeof(test_msg), "test_with_null" }, + { example, sizeof(example), "example_msg" }, + { zeros32, 32, "32_zero_bytes" }, + { ffs32, 32, "32_0xFF_bytes" }, + { incr32, 32, "32_incr_bytes" }, + { fox, sizeof(fox), "fox_with_null" }, + }; + const int N = sizeof(inputs) / sizeof(inputs[0]); + + static const elliptic_curve256_point_t expected[] = { + {0x97, 0x2f, 0x26, 0x99, 0x88, 0xd5, 0xf5, 0x3d, 0x96, 0xfa, 0xbb, + 0x04, 0xb0, 0x7c, 0x38, 0xb3, 0x5f, 0xb3, 0xaf, 0xa1, 0x8e, 0x34, + 0x94, 0x7a, 0x1d, 0x98, 0x2f, 0x1b, 0x9a, 0x57, 0xc9, 0x48, 0x00}, + {0xcb, 0x7c, 0xd9, 0xa7, 0x33, 0xe4, 0x88, 0x80, 0xa5, 0xc4, 0x69, + 0x31, 0x2d, 0x4c, 0x0e, 0x6d, 0xb2, 0xf8, 0xa2, 0x05, 0x6b, 0x89, + 0xb0, 0x8c, 0x60, 0x81, 0xd8, 0x80, 0x8c, 0xdb, 0x3d, 0xdb, 0x00}, + {0x62, 0x7e, 0x51, 0xd2, 0x13, 0x98, 0xf1, 0x9b, 0x33, 0x4d, 0x29, + 0xe2, 0x0b, 0x74, 0x90, 0x39, 0x26, 0xf9, 0xd2, 0x54, 0x29, 0x96, + 0x68, 0xf8, 0xb3, 0xf8, 0xb0, 0x48, 0xe7, 0xa6, 0x31, 0xd9, 0x00}, + {0xad, 0x02, 0xdb, 0x95, 0x51, 0xf7, 0x8a, 0x4f, 0x4c, 0xdb, 0x6b, + 0xdd, 0xb5, 0xa6, 0x42, 0x7a, 0x8a, 0xee, 0x12, 0x65, 0x2f, 0xe8, + 0xf2, 0xa7, 0x6b, 0x02, 0xf8, 0xb4, 0xb4, 0x3b, 0x63, 0x3d, 0x00}, + {0xe2, 0x18, 0xce, 0xff, 0xe4, 0x36, 0x67, 0x08, 0x2e, 0xfa, 0xb6, + 0x17, 0x1e, 0x95, 0xb5, 0xe3, 0x52, 0xc2, 0xc7, 0x9f, 0xe2, 0x19, + 0xc1, 0xf2, 0xe4, 0x6f, 0xf1, 0x4f, 0x5c, 0x39, 0xfd, 0x70, 0x00}, + {0x11, 0x8a, 0x9c, 0x8b, 0x57, 0x0f, 0x45, 0xb3, 0x27, 0x00, 0xc3, + 0x7e, 0x69, 0x1c, 0xef, 0x5d, 0x1a, 0x76, 0x49, 0x81, 0x2f, 0x02, + 0x47, 0x6c, 0xa8, 0x64, 0x00, 0x92, 0x94, 0x4e, 0x56, 0xbe, 0x00}, + {0x58, 0x9d, 0x77, 0x83, 0x19, 0xc6, 0x67, 0x84, 0x55, 0x0f, 0xc5, + 0x7c, 0x04, 0x0b, 0xb3, 0xa5, 0x18, 0x8a, 0x1b, 0x51, 0xb6, 0x8e, + 0xc4, 0x36, 0x6d, 0x7e, 0xe5, 0xd1, 0xaa, 0x15, 0x37, 0x06, 0x00}, + {0x6d, 0x78, 0xa6, 0x2a, 0x93, 0x62, 0xb6, 0x17, 0x00, 0x1a, 0xcb, + 0x6d, 0x28, 0x0a, 0xd5, 0xba, 0x7a, 0xc4, 0xa2, 0x88, 0xa8, 0x7e, + 0xa2, 0x2b, 0x36, 0x59, 0x2d, 0x82, 0x52, 0xe7, 0x28, 0xa9, 0x00}, + {0x0f, 0xe1, 0xdd, 0xb7, 0xc1, 0xe5, 0x26, 0x0c, 0x5b, 0xa7, 0xa7, + 0xeb, 0xf2, 0xef, 0x87, 0x88, 0x49, 0xfc, 0x7b, 0xd5, 0x51, 0x36, + 0x02, 0x56, 0xee, 0xf3, 0xbb, 0xb7, 0xb1, 0x7f, 0xfe, 0x76, 0x00}, + {0xda, 0xdc, 0xe1, 0x20, 0xb6, 0xd5, 0x39, 0xb3, 0x87, 0xc7, 0xa3, + 0x96, 0xae, 0x96, 0x7d, 0x9d, 0x30, 0x13, 0x85, 0x05, 0x5a, 0x51, + 0x96, 0x17, 0xd6, 0x71, 0xa5, 0x53, 0x03, 0xc1, 0x4b, 0xd6, 0x00}, + {0xc2, 0xb5, 0x2e, 0xbb, 0x4e, 0x38, 0xa0, 0x73, 0x54, 0xf3, 0x20, + 0x8e, 0xa0, 0x36, 0xa7, 0x89, 0x25, 0x4a, 0x51, 0xc7, 0x4a, 0xab, + 0x89, 0x91, 0xf7, 0x8f, 0xf5, 0x08, 0x53, 0xf7, 0xe5, 0xba, 0x00}, + }; + + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_ed25519_algebra(); + REQUIRE(ctx); + elliptic_curve256_point_t result; + for (int i = 0; i < N; i++) { + INFO("ed25519 vector " << i << " (" << inputs[i].label << ")"); + REQUIRE(ctx->hash_on_curve(ctx, &result, inputs[i].data, inputs[i].len) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, expected[i], sizeof(elliptic_curve256_point_t)) == 0); + } + elliptic_curve256_algebra_ctx_free(ctx); +} + +TEST_CASE("validate_non_infinity_point (ed25519)", "ed25519") { + elliptic_curve256_algebra_ctx_t* ed25519 = elliptic_curve256_new_ed25519_algebra(); + REQUIRE(ed25519); + REQUIRE(ed25519->validate_non_infinity_point); + + SECTION("infinity (canonical + tail variations) is rejected") { + const elliptic_curve256_point_t* inf = ed25519->infinity_point(ed25519); + REQUIRE(inf); + REQUIRE(ed25519->validate_non_infinity_point(ed25519, inf) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_point_t p; + memcpy(p, *inf, sizeof(p)); + p[32] = 0xAA; // tail byte is ignored by Ed25519 operations + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &p) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + SECTION("valid non-infinity point is accepted") { + elliptic_curve256_point_t p; + elliptic_curve256_scalar_t one = {0}; + one[31] = 1; + REQUIRE(ed25519->generator_mul(ed25519, &p, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &p) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("generator_mul by zero returns infinity (rejected)") { + elliptic_curve256_point_t p; + elliptic_curve256_scalar_t zero = {0}; + REQUIRE(ed25519->generator_mul(ed25519, &p, &zero) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &p) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + SECTION("sign-bit-toggled identity encoding is invalid (rejected)") { + const elliptic_curve256_point_t* inf = ed25519->infinity_point(ed25519); + REQUIRE(inf); + elliptic_curve256_point_t p; + memcpy(p, *inf, sizeof(p)); + p[31] |= 0x80; // invalid encoding (x sign bit set for x=0) + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &p) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + SECTION("obviously invalid encoding (y = 2^255-1) is rejected") { + elliptic_curve256_point_t p; + memset(p, 0xFF, sizeof(p)); + p[31] &= 0x7F; // clear x sign bit, keep y as 2^255-1 (not a valid field element for Ed25519) + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &p) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + elliptic_curve256_algebra_ctx_free(ed25519); +} + +// ============================================================================ +// Ed25519 Attack Tests via Generic Interface +// Tests boundary conditions used by EdDSA BAM variant through the +// elliptic_curve256 abstraction layer. +// ============================================================================ + +TEST_CASE("ed25519_attacks", "[attack][ed25519]") { + elliptic_curve256_algebra_ctx_t* ed25519 = elliptic_curve256_new_ed25519_algebra(); + REQUIRE(ed25519); + + SECTION("generator_mul with zero scalar") { + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = ed25519->generator_mul(ed25519, &result, &zero); + // G^0 should return identity or fail + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = ed25519->infinity_point(ed25519); + REQUIRE(inf); + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &result) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("generator_mul with group order scalar") { + elliptic_curve256_scalar_t order_scalar; + const uint8_t* order_bytes = ed25519->order(ed25519); + REQUIRE(order_bytes); + memcpy(order_scalar, order_bytes, sizeof(elliptic_curve256_scalar_t)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = ed25519->generator_mul(ed25519, &result, &order_scalar); + // G^n = identity, should either fail or return identity + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &result) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("point_mul with identity point") { + // Get identity point by multiplying by zero + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t identity; + elliptic_curve_algebra_status status = ed25519->generator_mul(ed25519, &identity, &zero); + if (status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + // If we can't get identity this way, use the infinity_point function + const elliptic_curve256_point_t* inf = ed25519->infinity_point(ed25519); + REQUIRE(inf); + memcpy(identity, *inf, sizeof(identity)); + } + + elliptic_curve256_scalar_t five = {0}; + five[sizeof(five) - 1] = 5; + elliptic_curve256_point_t result; + status = ed25519->point_mul(ed25519, &result, &identity, &five); + // identity * 5 should be identity or error + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &result) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT || + status == ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER || + status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("add_points P + (-P) = identity") { + // Compute G and -G + elliptic_curve256_scalar_t one = {0}; + one[sizeof(one) - 1] = 1; + elliptic_curve256_point_t gen_point; + REQUIRE(ed25519->generator_mul(ed25519, &gen_point, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // -G = G^(order-1) + elliptic_curve256_scalar_t order_minus_1; + const uint8_t* order_bytes = ed25519->order(ed25519); + REQUIRE(order_bytes); + memcpy(order_minus_1, order_bytes, sizeof(elliptic_curve256_scalar_t)); + // Subtract 1 from big-endian + for (int i = sizeof(order_minus_1) - 1; i >= 0; --i) { + if (order_minus_1[i] > 0) { + order_minus_1[i]--; + break; + } + order_minus_1[i] = 0xFF; + } + elliptic_curve256_point_t neg_gen; + REQUIRE(ed25519->generator_mul(ed25519, &neg_gen, &order_minus_1) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t sum; + REQUIRE(ed25519->add_points(ed25519, &sum, &gen_point, &neg_gen) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + // Result should be identity + REQUIRE(ed25519->validate_non_infinity_point(ed25519, &sum) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + } + + SECTION("inverse of zero scalar") { + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_scalar_t result; + elliptic_curve_algebra_status status = ed25519->inverse(ed25519, &result, &zero); + // Inverse of zero is undefined — must fail + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + // NOTE: cross-curve test (secp256k1 point in ed25519 context) lives in + // secp256k1_algebra/main.cpp TEST_CASE("multi_curve_attacks") because that + // test binary links both curve libraries. + elliptic_curve256_algebra_ctx_free(ed25519); } \ No newline at end of file diff --git a/test/crypto/entropy/CMakeLists.txt b/test/crypto/entropy/CMakeLists.txt new file mode 100644 index 0000000..57dedf1 --- /dev/null +++ b/test/crypto/entropy/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(entropy_test + tests.cpp + entropy_test_framework.cpp +) + +target_compile_options(entropy_test PRIVATE -Wall -Wextra) +target_link_libraries(entropy_test PRIVATE tests_main) + +add_test(NAME entropy_test COMMAND entropy_test) diff --git a/test/crypto/entropy/entropy_test_framework.cpp b/test/crypto/entropy/entropy_test_framework.cpp new file mode 100644 index 0000000..3351cd1 --- /dev/null +++ b/test/crypto/entropy/entropy_test_framework.cpp @@ -0,0 +1,338 @@ +/// @file entropy_test_framework.cpp +/// @brief Implementation of the entropy test suite. +/// +/// All statistical tests operate on raw byte buffers. The typical data flow is: +/// 1. Caller generates random bytes (via loop_test or manually). +/// 2. analyze_bytes() or individual passes_*() functions test the byte stream. +/// 3. Results indicate whether the RNG output is statistically consistent +/// with a uniform random source at conservative significance levels. +/// +/// The test battery includes: chi-squared (byte distribution), monobit frequency, +/// runs (byte-level), serial correlation (lag-1), mean value, bit bias (per-position), +/// and Monte Carlo Pi estimation. Scalar-specific helpers (uniqueness, range) are +/// also provided for elliptic curve scalar testing. + +#include "entropy_test_framework.h" +#include +#include +#include +#include + +namespace entropy_tests { + +// Regularized upper incomplete gamma function Q(s, x). +// +// Two computation branches: +// x < s+1: Series expansion for P(s,x), return 1-P. +// P(s,x) = e^(-x) * x^s / Gamma(s) * sum_{n=0}^{inf} x^n / (s*(s+1)*...*(s+n)) +// x >= s+1: Lentz's continued fraction for Q(s,x) directly. +// Q(s,x) = e^(-x) * x^s / Gamma(s) * CF(x, s) +// +// Both branches converge within 200 iterations to 1e-12 relative tolerance. +// Uses lgamma() to avoid overflow in Gamma(s) for large s (e.g., s=127.5). +double incomplete_gamma_upper(double s, double x) { + if (x < 0.0) return 1.0; + if (x == 0.0) return 1.0; + + // Use regularized gamma function: Q(s,x) = 1 - P(s,x) + // For P(s,x), use series expansion when x < s+1 + // For Q(s,x), use continued fraction when x >= s+1 + + if (x < s + 1.0) { + // Series expansion for P(s,x), then return 1-P + double term = 1.0 / s; + double sum = term; + for (int n = 1; n < 200; n++) { + term *= x / (s + n); + sum += term; + if (std::abs(term) < 1e-12 * std::abs(sum)) break; + } + double log_gamma_s = std::lgamma(s); + double p = sum * std::exp(-x + s * std::log(x) - log_gamma_s); + return 1.0 - p; + } else { + // Continued fraction for Q(s,x) using Lentz's method + double f = 1.0; + double c = 1.0; + double d = x - s + 1.0; + if (std::abs(d) < 1e-30) d = 1e-30; + d = 1.0 / d; + f = d; + + for (int i = 1; i < 200; i++) { + double an = -i * (i - s); + double bn = x - s + 1.0 + 2.0 * i; + d = bn + an * d; + if (std::abs(d) < 1e-30) d = 1e-30; + c = bn + an / c; + if (std::abs(c) < 1e-30) c = 1e-30; + d = 1.0 / d; + double delta = c * d; + f *= delta; + if (std::abs(delta - 1.0) < 1e-12) break; + } + double log_gamma_s = std::lgamma(s); + return f * std::exp(-x + s * std::log(x) - log_gamma_s); + } +} + +// Comprehensive randomness analysis: runs all byte-level tests and populates +// every field of EntropyStats. The four tests contributing to passed_all are: +// 1. Chi-squared byte distribution (256-bin, alpha=0.01) +// 2. Serial correlation (lag-1, threshold=0.05) +// 3. Mean value (tolerance=5.0 from 127.5) +// 4. Frequency / monobit (z < 4.0) +// Monte Carlo Pi is computed but does NOT affect passed_all. +EntropyStats analyze_bytes(const uint8_t* data, size_t len) { + EntropyStats stats{}; + stats.sample_count = len; + + if (len == 0) { + stats.passed_all = false; + return stats; + } + + // Pass 1: Count byte values and accumulate sum for mean + size_t freq[256] = {}; + double sum = 0.0; + for (size_t i = 0; i < len; i++) { + freq[data[i]]++; + sum += data[i]; + } + + stats.mean = sum / len; + + // Chi-squared statistic: sum((observed - expected)^2 / expected) + // For 256 bins with uniform distribution, expected count = len / 256. + // Degrees of freedom = 255. P-value via Q(127.5, chi_sq/2). + double expected = (double)len / 256.0; + stats.chi_squared = 0.0; + for (int i = 0; i < 256; i++) { + double diff = (double)freq[i] - expected; + stats.chi_squared += (diff * diff) / expected; + } + stats.chi_squared_p_value = incomplete_gamma_upper(127.5, stats.chi_squared / 2.0); + + // Lag-1 serial correlation: r = Cov(x_i, x_{i+1}) / Var(x_i) + // Numerator: sum of (x_i - mean)(x_{i+1} - mean) for i in [0, len-2] + // Denominator: sum of (x_i - mean)^2 for all i in [0, len-1] + if (len > 1) { + double mean_val = stats.mean; + double num = 0.0, denom = 0.0; + for (size_t i = 0; i < len - 1; i++) { + num += ((double)data[i] - mean_val) * ((double)data[i + 1] - mean_val); + denom += ((double)data[i] - mean_val) * ((double)data[i] - mean_val); + } + denom += ((double)data[len - 1] - mean_val) * ((double)data[len - 1] - mean_val); + stats.serial_correlation = (denom > 0) ? (num / denom) : 0.0; + } + + // Monte Carlo Pi: map consecutive byte pairs to (x,y) in [-1,1]x[-1,1] + // by centering on 127.5 and normalizing. Count fraction inside unit circle, + // multiply by 4 to estimate Pi. For reference: Pi ≈ 3.14159. + size_t inside = 0; + size_t pairs = len / 2; + for (size_t i = 0; i + 1 < len; i += 2) { + double x = ((double)data[i] - 127.5) / 127.5; + double y = ((double)data[i + 1] - 127.5) / 127.5; + if (x * x + y * y <= 1.0) inside++; + } + stats.monte_carlo_pi = (pairs > 0) ? (4.0 * inside / pairs) : 0.0; + + // Overall verdict: all four core tests must pass with standard thresholds + stats.passed_all = passes_chi_squared(data, len, CHI_SQUARED_ALPHA) && + passes_serial_correlation(data, len, SERIAL_CORRELATION_THRESHOLD) && + passes_mean_test(data, len, MEAN_TOLERANCE) && + passes_frequency_test(data, len); + + return stats; +} + +// NIST SP 800-22 monobit frequency test. +// +// Counts total 1-bits using Brian Kernighan's algorithm (byte &= byte - 1 +// clears the lowest set bit each iteration). For N total bits, the proportion +// of ones should be ≈ 0.5. The z-score is: +// z = |proportion - 0.5| / (0.5 / sqrt(N)) +// We use a 4-sigma threshold (P ≈ 6.3e-5) which is very conservative — +// NIST uses ~1.96 sigma (alpha=0.05). +bool passes_frequency_test(const uint8_t* data, size_t len) { + if (len == 0) return false; + + size_t ones = 0; + for (size_t i = 0; i < len; i++) { + uint8_t byte = data[i]; + while (byte) { + ones++; + byte &= byte - 1; + } + } + + size_t total_bits = len * 8; + double proportion = (double)ones / total_bits; + + double expected = 0.5; + double std_dev = 0.5 / std::sqrt((double)total_bits); + double z = std::abs(proportion - expected) / std_dev; + + return z < 4.0; +} + +// Byte-level runs test. +// +// A "run" is a maximal sequence of identical consecutive bytes. For n IID +// Uniform(0..255) bytes, each adjacent pair has P(different) = 255/256, so: +// E[runs] = 1 + (n-1) * 255/256 +// Var[runs] = (n-1) * (255/256) * (1/256) +// A z-score > 4.0 indicates non-random clustering or spreading of byte values. +bool passes_runs_test(const uint8_t* data, size_t len) { + if (len < 2) return false; + + size_t runs = 1; + for (size_t i = 1; i < len; i++) { + if (data[i] != data[i - 1]) runs++; + } + + double p_transition = 255.0 / 256.0; + double expected_runs = 1.0 + (len - 1.0) * p_transition; + double variance = (len - 1.0) * p_transition * (1.0 / 256.0); + double std_dev = std::sqrt(variance); + + if (std_dev == 0) return false; + double z = std::abs((double)runs - expected_runs) / std_dev; + + return z < 4.0; +} + +// Chi-squared goodness-of-fit on 256 byte-value bins. +// +// The chi-squared statistic measures how far the observed byte distribution +// deviates from uniform. For k=256 bins: +// chi_sq = sum_i((freq_i - expected)^2 / expected), expected = len / 256 +// Degrees of freedom = 255. P-value = Q(127.5, chi_sq/2). +// Reject uniformity (return false) if p-value <= alpha. +// Requires >= 256 bytes so each bin has expected count >= 1. +bool passes_chi_squared(const uint8_t* data, size_t len, double alpha) { + if (len < 256) return false; + + size_t freq[256] = {}; + for (size_t i = 0; i < len; i++) { + freq[data[i]]++; + } + + double expected = (double)len / 256.0; + double chi_sq = 0.0; + for (int i = 0; i < 256; i++) { + double diff = (double)freq[i] - expected; + chi_sq += (diff * diff) / expected; + } + + double p_value = incomplete_gamma_upper(127.5, chi_sq / 2.0); + + return p_value > alpha; +} + +// Lag-1 serial (auto)correlation test. +// +// Measures linear dependence between consecutive bytes: +// r = sum((x_i - mean)(x_{i+1} - mean)) / sum((x_i - mean)^2) +// For IID data, r ≈ 0. A nonzero r indicates sequential patterns. +// Note: the denominator includes ALL elements (including the last), making +// it the full variance sum, while the numerator only covers pairs [0, len-2]. +bool passes_serial_correlation(const uint8_t* data, size_t len, double threshold) { + if (len < 2) return false; + + double sum = 0.0; + for (size_t i = 0; i < len; i++) sum += data[i]; + double mean = sum / len; + + double num = 0.0, denom = 0.0; + for (size_t i = 0; i < len - 1; i++) { + num += ((double)data[i] - mean) * ((double)data[i + 1] - mean); + denom += ((double)data[i] - mean) * ((double)data[i] - mean); + } + denom += ((double)data[len - 1] - mean) * ((double)data[len - 1] - mean); + + double corr = (denom > 0) ? (num / denom) : 0.0; + return std::abs(corr) < threshold; +} + +// Mean byte value test. +// +// For Uniform(0..255), E[byte] = 127.5. A skewed mean indicates bias in the +// byte distribution. Default tolerance of 5.0 corresponds to roughly ±3.9% +// of the [0,255] range, which is generous for N >= 10000*32 = 320000 bytes. +bool passes_mean_test(const uint8_t* data, size_t len, double tolerance) { + if (len == 0) return false; + + double sum = 0.0; + for (size_t i = 0; i < len; i++) sum += data[i]; + double mean = sum / len; + + return std::abs(mean - 127.5) < tolerance; +} + +// Per-bit-position bias test. +// +// Checks each of the 8 bit positions independently. For each bit position b +// (0=LSB, 7=MSB), counts how many of the N bytes have bit b set. For uniform +// bytes, P(bit b set) = 0.5 exactly. If any position has proportion more than +// max_deviation from 0.5, the test fails. This catches biases that might be +// masked in byte-level tests (e.g., a stuck MSB would only shift the mean +// by ~64 but would show a 1.0 deviation in bit position 7). +bool passes_bit_bias_test(const uint8_t* data, size_t len, double max_deviation) { + if (len == 0) return false; + + size_t bit_counts[8] = {}; + for (size_t i = 0; i < len; i++) { + for (int bit = 0; bit < 8; bit++) { + if (data[i] & (1 << bit)) bit_counts[bit]++; + } + } + + for (int bit = 0; bit < 8; bit++) { + double proportion = (double)bit_counts[bit] / len; + if (std::abs(proportion - 0.5) > max_deviation) return false; + } + + return true; +} + +// Scalar uniqueness test via set insertion. +// For 256-bit scalars, any duplicate in N=10000 samples indicates a catastrophic +// RNG failure (birthday collision probability ≈ N^2 / 2^256 ≈ 10^-69). +size_t count_scalar_duplicates(const std::vector>& scalars) { + std::set> unique_set(scalars.begin(), scalars.end()); + return scalars.size() - unique_set.size(); +} + +// Range validation: every scalar must be < curve order (big-endian). +// memcmp returns <0 if scalar < order, 0 if equal, >0 if greater. +// We require strictly less-than (cmp < 0). Also rejects wrong-length scalars. +bool all_scalars_less_than_order(const std::vector>& scalars, + const uint8_t* order, size_t order_len) { + for (const auto& scalar : scalars) { + if (scalar.size() != order_len) return false; + int cmp = memcmp(scalar.data(), order, order_len); + if (cmp >= 0) return false; + } + return true; +} + +// Main test driver: calls generator() N times, concatenates all output into +// one contiguous buffer, then runs the full analysis suite via analyze_bytes(). +// The generator is called with a pointer into the pre-allocated buffer, so +// there is no extra copying — each call writes directly at its offset. +EntropyStats loop_test(std::function generator, + size_t sample_size, + size_t iterations) { + std::vector combined(sample_size * iterations); + + for (size_t i = 0; i < iterations; i++) { + generator(combined.data() + i * sample_size, sample_size); + } + + return analyze_bytes(combined.data(), combined.size()); +} + +} // namespace entropy_tests diff --git a/test/crypto/entropy/entropy_test_framework.h b/test/crypto/entropy/entropy_test_framework.h new file mode 100644 index 0000000..35019b3 --- /dev/null +++ b/test/crypto/entropy/entropy_test_framework.h @@ -0,0 +1,212 @@ +#ifndef __ENTROPY_TEST_FRAMEWORK_H__ +#define __ENTROPY_TEST_FRAMEWORK_H__ + +/// @file entropy_test_framework.h +/// @brief Statistical test suite for validating randomness quality of cryptographic RNGs. +/// +/// This framework provides a battery of statistical tests based on NIST SP 800-22 +/// recommendations and classical randomness metrics. It is designed to validate that +/// the RNG implementations used in BAM (secp256k1, secp256r1, ed25519, STARK curve +/// algebra->rand(), OpenSSL BN_rand, BN_rand_range) produce output with adequate +/// entropy for cryptographic operations. +/// +/// ## Typical usage (see tests.cpp): +/// +/// 1. **Baseline validation** — Run loop_test() with OpenSSL RAND_bytes to confirm +/// the test framework itself works with a known-good source. +/// +/// 2. **Per-curve scalar tests** — For each elliptic curve algebra: +/// - `loop_test()` with a lambda that calls algebra->rand() and extracts the +/// lower bytes (skipping SCALAR_STAT_OFFSET top bytes to avoid order-bias). +/// - `count_scalar_duplicates()` to verify uniqueness over N iterations. +/// - `all_scalars_less_than_order()` to verify range correctness. +/// - Individual passes_*() tests on the collected byte stream. +/// +/// 3. **DRNG sanity** — Verify that the deterministic RNG (drng) is NOT random: +/// same seed → same output, different seed → different output. +/// +/// 4. **Cross-curve independence** — Interleave scalar generation from two curves +/// and verify each stream maintains statistical quality independently. +/// +/// ## Important notes on scalar testing: +/// +/// Elliptic curve scalars are sampled uniformly from [0, order). Since order < 2^256 +/// for all supported curves, the most-significant bytes of a 32-byte scalar are NOT +/// uniformly distributed over [0, 255]. For example, ed25519 order ≈ 2^252 means +/// byte 0 is always in [0x00, 0x10]. Byte-level tests (chi-squared, mean, runs) +/// would fail on these top bytes even with a perfect RNG. Therefore, per-scalar +/// byte-level tests should skip the top SCALAR_STAT_OFFSET bytes (typically 8) and +/// only analyze bytes [offset, 32). The uniqueness and range tests use the full +/// 32-byte scalar. + +#include +#include +#include +#include +#include +#include +#include + +namespace entropy_tests { + +// Standard threshold constants for statistical tests. +// Use these when calling the individual passes_*() functions. +static constexpr double CHI_SQUARED_ALPHA = 0.01; // 99% confidence +static constexpr double SERIAL_CORRELATION_THRESHOLD = 0.05; +static constexpr double MEAN_TOLERANCE = 5.0; // |mean - 127.5| < 5.0 +static constexpr double BIT_BIAS_MAX_DEVIATION = 0.05; // |proportion - 0.5| < 0.05 + +/// Aggregated randomness statistics for a byte stream. +/// +/// Populated by analyze_bytes(). The passed_all field is true only if ALL of +/// chi-squared, serial correlation, mean, and frequency tests pass simultaneously. +/// Monte Carlo Pi is computed for informational purposes but does not affect passed_all. +struct EntropyStats { + double chi_squared; // Chi-squared statistic over 256 byte bins + double chi_squared_p_value; // P-value from incomplete upper gamma; >0.01 = pass + double serial_correlation; // Lag-1 autocorrelation; |r| < 0.05 = pass + double monte_carlo_pi; // Pi estimate from byte-pair circle test (informational) + double mean; // Mean byte value; expect ~127.5 for uniform bytes + size_t sample_count; // Total bytes analyzed + bool passed_all; // True iff chi_squared, serial_corr, mean, freq all pass +}; + +/// Run all statistical tests on a raw byte stream and return aggregated results. +/// +/// Computes: byte-frequency chi-squared + p-value, lag-1 serial correlation, +/// Monte Carlo Pi estimate, mean byte value, and an overall pass/fail verdict. +/// The verdict (passed_all) requires passing chi-squared, serial correlation, +/// mean, AND frequency tests simultaneously. +/// +/// @param data Pointer to the byte buffer to analyze. +/// @param len Number of bytes. Must be >= 256 for chi-squared to be meaningful. +/// @return EntropyStats with all fields populated. +EntropyStats analyze_bytes(const uint8_t* data, size_t len); + +/// NIST SP 800-22 monobit frequency test. +/// +/// Counts total 1-bits across all bytes (Brian Kernighan's algorithm). Computes +/// a z-score against expected proportion 0.5. Fails if z > 4.0 standard deviations +/// (very conservative — NIST recommends ~1.96 for alpha=0.05). +/// +/// @param data Byte buffer. +/// @param len Buffer length. Returns false if 0. +/// @return True if bit proportion is within 4 sigma of 0.5. +bool passes_frequency_test(const uint8_t* data, size_t len); + +/// NIST SP 800-22 runs test (byte-level variant). +/// +/// Counts runs of consecutive identical bytes. For n IID Uniform(0..255) bytes, +/// P(transition) = 255/256, so E[runs] = 1 + (n-1)*255/256 and +/// Var[runs] = (n-1)*(255/256)*(1/256). Fails if z-score > 4.0. +/// +/// @param data Byte buffer. +/// @param len Buffer length. Must be >= 2. +/// @return True if run count is within 4 sigma of expected. +bool passes_runs_test(const uint8_t* data, size_t len); + +/// Chi-squared goodness-of-fit test on byte distribution (256 bins). +/// +/// Computes sum_i((observed_i - expected)^2 / expected) with expected = len/256. +/// Converts to p-value via regularized upper incomplete gamma Q(127.5, chi_sq/2) +/// where 127.5 = (256-1)/2 degrees of freedom. Rejects if p-value <= alpha. +/// +/// @param data Byte buffer. +/// @param len Buffer length. Must be >= 256 (returns false otherwise). +/// @param alpha Significance level (e.g., CHI_SQUARED_ALPHA = 0.01 for 99% confidence). +bool passes_chi_squared(const uint8_t* data, size_t len, double alpha); + +/// Lag-1 serial correlation coefficient test. +/// +/// Computes r = sum((x_i - mean)(x_{i+1} - mean)) / sum((x_i - mean)^2). +/// For truly random data, r ≈ 0. Fails if |r| >= threshold. +/// +/// @param data Byte buffer. +/// @param len Buffer length. Must be >= 2. +/// @param threshold Maximum acceptable |correlation| (e.g., SERIAL_CORRELATION_THRESHOLD = 0.05). +bool passes_serial_correlation(const uint8_t* data, size_t len, double threshold); + +/// Mean byte value test. +/// +/// For uniformly distributed bytes in [0, 255], expected mean = 127.5. +/// Fails if observed mean deviates by more than tolerance. +/// +/// @param data Byte buffer. +/// @param len Buffer length. Returns false if 0. +/// @param tolerance Maximum acceptable |mean - 127.5| (e.g., MEAN_TOLERANCE = 5.0). +bool passes_mean_test(const uint8_t* data, size_t len, double tolerance); + +/// Per-bit-position bias test. +/// +/// For each of 8 bit positions, counts how many bytes have that bit set. +/// For uniform random bytes, each bit has P(set) = 0.5. Fails if any bit +/// position's proportion deviates from 0.5 by more than max_deviation. +/// +/// @param data Byte buffer. +/// @param len Buffer length. Returns false if 0. +/// @param max_deviation Maximum acceptable |proportion - 0.5| per bit +/// (e.g., BIT_BIAS_MAX_DEVIATION = 0.05). +bool passes_bit_bias_test(const uint8_t* data, size_t len, double max_deviation); + +/// Count duplicate scalars in a collection. +/// +/// Inserts all scalars into a std::set to find unique entries. Returns +/// (total - unique). For a proper 256-bit RNG with N=10000, duplicates +/// should be 0 (collision probability ≈ N^2 / 2^256 ≈ 0). +/// +/// @param scalars Vector of byte vectors, each representing one scalar. +/// @return Number of duplicates (0 = all unique). +size_t count_scalar_duplicates(const std::vector>& scalars); + +/// Verify all scalars are strictly less than a curve order (big-endian comparison). +/// +/// Uses memcmp for big-endian byte-by-byte comparison. Also checks that each +/// scalar has exactly order_len bytes. Validates that algebra->rand() correctly +/// reduces output modulo the curve order — a scalar >= order would break ECDSA. +/// +/// @param scalars Vector of byte vectors to check. +/// @param order Curve order in big-endian format. +/// @param order_len Length of order in bytes (typically 32). +/// @return True if ALL scalars have correct length and are < order. +bool all_scalars_less_than_order(const std::vector>& scalars, + const uint8_t* order, size_t order_len); + +/// Generate N random samples, concatenate, and run analyze_bytes(). +/// +/// Primary entry point for testing an RNG. The caller provides a generator +/// function that fills a buffer of sample_size bytes. loop_test() calls it +/// `iterations` times into a single (sample_size * iterations)-byte buffer, +/// then passes it to analyze_bytes(). +/// +/// Example: +/// auto stats = loop_test( +/// [](uint8_t* buf, size_t len) { RAND_bytes(buf, len); }, +/// 32, 10000); +/// REQUIRE(stats.passed_all); +/// +/// @param generator Callable: fills buf[0..len) with random bytes. +/// @param sample_size Bytes per generator invocation. +/// @param iterations Number of times to call generator. +/// @return EntropyStats for the combined byte stream. +EntropyStats loop_test(std::function generator, + size_t sample_size, + size_t iterations); + +/// Regularized upper incomplete gamma function Q(s, x) = 1 - P(s, x). +/// +/// Used internally by passes_chi_squared() and analyze_bytes() to convert +/// chi-squared statistics to p-values. For k=255 degrees of freedom: +/// p-value = Q(127.5, chi_sq / 2) +/// +/// Algorithm: series expansion for P(s,x) when x < s+1 (returns 1-P), +/// Lentz's continued fraction for Q(s,x) when x >= s+1. Both converge +/// to 1e-12 relative tolerance within 200 iterations. +/// +/// @param s Shape parameter (= degrees_of_freedom / 2). +/// @param x Integration bound (= chi_squared / 2). +/// @return Q(s, x) in [0, 1]. Returns 1.0 for x <= 0. +double incomplete_gamma_upper(double s, double x); + +} // namespace entropy_tests + +#endif // __ENTROPY_TEST_FRAMEWORK_H__ diff --git a/test/crypto/entropy/tests.cpp b/test/crypto/entropy/tests.cpp new file mode 100644 index 0000000..d31dd51 --- /dev/null +++ b/test/crypto/entropy/tests.cpp @@ -0,0 +1,481 @@ +#include "entropy_test_framework.h" +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include "crypto/drng/drng.h" + +#include +#include + +#include +#include +#include + +#include + +using namespace entropy_tests; + +// Number of samples for statistical tests +static constexpr size_t ENTROPY_ITERATIONS = 10000; +static constexpr size_t SCALAR_SIZE = 32; // ELLIPTIC_CURVE_FIELD_SIZE +// Skip top N bytes for byte-level stats on curve scalars. +// Scalars are uniform in [0, order) — for curves where order < 2^256, +// the top bytes are biased by the order constraint, making byte-level +// chi-squared/runs/mean tests invalid on those bytes. +// Skipping 4 bytes is safe for all supported curves: +// secp256k1 order ≈ 2^256 (negligible bias, but skipping is harmless) +// secp256r1 order has 0x00 bytes at positions 4-7 (need skip >= 8) +// ed25519 order ≈ 2^252 (top bytes highly constrained) +// STARK order ≈ 2^251 (top bytes highly constrained) +// Using 8 to cover secp256r1's unusual order structure. +static constexpr size_t SCALAR_STAT_OFFSET = 8; +static constexpr size_t SCALAR_STAT_SIZE = SCALAR_SIZE - SCALAR_STAT_OFFSET; + +// ============================================================================ +// Helper: Generate a random scalar using algebra->rand() +// ============================================================================ +static void generate_random_scalar(elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_scalar_t* out) { + auto status = algebra->rand(algebra, out); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); +} + +// ============================================================================ +// Helper: Generate random bytes using OpenSSL RAND_bytes +// ============================================================================ +static void generate_openssl_rand(uint8_t* buf, size_t len) { + REQUIRE(RAND_bytes(buf, (int)len) == 1); +} + +// ============================================================================ +// TEST CASE: OpenSSL RAND_bytes baseline +// Validates that the testing framework itself works with a known-good RNG +// ============================================================================ +TEST_CASE("entropy_baseline_openssl", "[entropy][baseline]") +{ + SECTION("RAND_bytes statistical quality") { + auto stats = loop_test(generate_openssl_rand, 32, ENTROPY_ITERATIONS); + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + REQUIRE(std::abs(stats.serial_correlation) < 0.05); + REQUIRE(std::abs(stats.mean - 127.5) < 5.0); + } + + SECTION("RAND_bytes frequency test") { + std::vector data(ENTROPY_ITERATIONS * 32); + RAND_bytes(data.data(), (int)data.size()); + REQUIRE(passes_frequency_test(data.data(), data.size())); + } + + SECTION("RAND_bytes runs test") { + std::vector data(ENTROPY_ITERATIONS * 32); + RAND_bytes(data.data(), (int)data.size()); + REQUIRE(passes_runs_test(data.data(), data.size())); + } + + SECTION("RAND_bytes bit bias test") { + std::vector data(ENTROPY_ITERATIONS * 32); + RAND_bytes(data.data(), (int)data.size()); + REQUIRE(passes_bit_bias_test(data.data(), data.size(), BIT_BIAS_MAX_DEVIATION)); + } +} + +// ============================================================================ +// TEST CASE: secp256k1 algebra->rand() entropy +// RNG site #1 in BAM: Private key share generation +// ============================================================================ +TEST_CASE("entropy_secp256k1_rand", "[entropy][secp256k1]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + + SECTION("statistical quality - lower bytes") { + // Test only bytes [SCALAR_STAT_OFFSET:32] per scalar to avoid + // order-constraint bias in the top bytes (see SCALAR_STAT_OFFSET comment). + auto stats = loop_test( + [&](uint8_t* buf, size_t len) { + REQUIRE(len == SCALAR_STAT_SIZE); + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + memcpy(buf, scalar + SCALAR_STAT_OFFSET, SCALAR_STAT_SIZE); + }, + SCALAR_STAT_SIZE, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } + + SECTION("uniqueness - no duplicate scalars") { + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(count_scalar_duplicates(scalars) == 0); + } + + SECTION("range - all scalars less than curve order") { + const uint8_t* order = algebra->order(algebra); + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(all_scalars_less_than_order(scalars, order, SCALAR_SIZE)); + } + + SECTION("bit bias - lower bytes") { + std::vector all_bytes; + all_bytes.reserve(ENTROPY_ITERATIONS * SCALAR_STAT_SIZE); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + all_bytes.insert(all_bytes.end(), scalar + SCALAR_STAT_OFFSET, scalar + SCALAR_SIZE); + } + REQUIRE(passes_bit_bias_test(all_bytes.data(), all_bytes.size(), BIT_BIAS_MAX_DEVIATION)); + } + + SECTION("frequency test - lower bytes") { + std::vector all_bytes; + all_bytes.reserve(ENTROPY_ITERATIONS * SCALAR_STAT_SIZE); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + all_bytes.insert(all_bytes.end(), scalar + SCALAR_STAT_OFFSET, scalar + SCALAR_SIZE); + } + REQUIRE(passes_frequency_test(all_bytes.data(), all_bytes.size())); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// TEST CASE: secp256r1 algebra->rand() entropy +// ============================================================================ +TEST_CASE("entropy_secp256r1_rand", "[entropy][secp256r1]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(algebra != nullptr); + + SECTION("statistical quality - lower bytes") { + // secp256r1 order: FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + // Bytes 4-7 are 0x00 in the order, causing heavy bias in the top 8 bytes. + auto stats = loop_test( + [&](uint8_t* buf, size_t len) { + REQUIRE(len == SCALAR_STAT_SIZE); + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + memcpy(buf, scalar + SCALAR_STAT_OFFSET, SCALAR_STAT_SIZE); + }, + SCALAR_STAT_SIZE, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } + + SECTION("uniqueness") { + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(count_scalar_duplicates(scalars) == 0); + } + + SECTION("range") { + const uint8_t* order = algebra->order(algebra); + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(all_scalars_less_than_order(scalars, order, SCALAR_SIZE)); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// TEST CASE: ed25519 algebra->rand() entropy +// ============================================================================ +TEST_CASE("entropy_ed25519_rand", "[entropy][ed25519]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_ed25519_algebra(); + REQUIRE(algebra != nullptr); + + SECTION("statistical quality - lower bytes") { + // ed25519 order: 1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED + // Order ≈ 2^252, so bytes 0-3 are heavily constrained (byte0 = 0x00..0x10). + auto stats = loop_test( + [&](uint8_t* buf, size_t len) { + REQUIRE(len == SCALAR_STAT_SIZE); + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + memcpy(buf, scalar + SCALAR_STAT_OFFSET, SCALAR_STAT_SIZE); + }, + SCALAR_STAT_SIZE, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } + + SECTION("uniqueness") { + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(count_scalar_duplicates(scalars) == 0); + } + + SECTION("range") { + const uint8_t* order = algebra->order(algebra); + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(all_scalars_less_than_order(scalars, order, SCALAR_SIZE)); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// TEST CASE: STARK algebra->rand() entropy +// RNG site #2 in BAM: Ephemeral key k (ECDSA nonce) +// ============================================================================ +TEST_CASE("entropy_stark_rand", "[entropy][stark]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_stark_algebra(); + REQUIRE(algebra != nullptr); + + SECTION("statistical quality - lower bytes") { + // STARK order ≈ 2^251, so bytes 0-3 are heavily constrained. + auto stats = loop_test( + [&](uint8_t* buf, size_t len) { + REQUIRE(len == SCALAR_STAT_SIZE); + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + memcpy(buf, scalar + SCALAR_STAT_OFFSET, SCALAR_STAT_SIZE); + }, + SCALAR_STAT_SIZE, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } + + SECTION("uniqueness") { + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(count_scalar_duplicates(scalars) == 0); + } + + SECTION("range") { + const uint8_t* order = algebra->order(algebra); + std::vector> scalars; + scalars.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + elliptic_curve256_scalar_t scalar; + generate_random_scalar(algebra, &scalar); + scalars.emplace_back(scalar, scalar + SCALAR_SIZE); + } + REQUIRE(all_scalars_less_than_order(scalars, order, SCALAR_SIZE)); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// TEST CASE: BN_rand entropy (used for Paillier blinding and ZKP parameters) +// RNG sites #3,6,7,8 in BAM: lambda0, alpha, beta, lambda_p +// ============================================================================ +TEST_CASE("entropy_bn_rand", "[entropy][bn_rand]") +{ + SECTION("BN_rand 256-bit statistical quality") { + auto stats = loop_test( + [](uint8_t* buf, size_t len) { + REQUIRE(len == 32); + BIGNUM* bn = BN_new(); + REQUIRE(bn != nullptr); + REQUIRE(BN_rand(bn, 256, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 1); + // Pad to 32 bytes big-endian + memset(buf, 0, 32); + int num_bytes = BN_num_bytes(bn); + if (num_bytes > 0 && num_bytes <= 32) { + BN_bn2bin(bn, buf + (32 - num_bytes)); + } + BN_free(bn); + }, + 32, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } + + SECTION("BN_rand uniqueness") { + std::vector> values; + values.reserve(ENTROPY_ITERATIONS); + for (size_t i = 0; i < ENTROPY_ITERATIONS; i++) { + BIGNUM* bn = BN_new(); + REQUIRE(bn != nullptr); + REQUIRE(BN_rand(bn, 256, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 1); + std::vector val(32, 0); + int num_bytes = BN_num_bytes(bn); + if (num_bytes > 0 && num_bytes <= 32) { + BN_bn2bin(bn, val.data() + (32 - num_bytes)); + } + values.push_back(std::move(val)); + BN_free(bn); + } + REQUIRE(count_scalar_duplicates(values) == 0); + } + + SECTION("BN_rand 2048-bit statistical quality") { + // Tests larger BN_rand used for Paillier encryption blinding + auto stats = loop_test( + [](uint8_t* buf, size_t len) { + REQUIRE(len == 256); // 2048 bits + BIGNUM* bn = BN_new(); + REQUIRE(bn != nullptr); + REQUIRE(BN_rand(bn, 2048, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 1); + memset(buf, 0, 256); + int num_bytes = BN_num_bytes(bn); + if (num_bytes > 0 && num_bytes <= 256) { + BN_bn2bin(bn, buf + (256 - num_bytes)); + } + BN_free(bn); + }, + 256, 1000); // Fewer iterations for larger values + + REQUIRE(stats.passed_all); + REQUIRE(stats.chi_squared_p_value > 0.01); + } +} + +// ============================================================================ +// TEST CASE: BN_rand_range entropy (used internally by algebra->rand()) +// ============================================================================ +TEST_CASE("entropy_bn_rand_range", "[entropy][bn_rand_range]") +{ + SECTION("BN_rand_range statistical quality - lower bytes") { + // Simulate what GFp_curve_algebra_rand does: BN_rand_range(order) + // Only test lower bytes to avoid order-constraint bias in top bytes. + BIGNUM* order = BN_new(); + REQUIRE(order != nullptr); + // secp256k1 order + REQUIRE(BN_hex2bn(&order, + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") > 0); + + auto stats = loop_test( + [&](uint8_t* buf, size_t len) { + REQUIRE(len == SCALAR_STAT_SIZE); + uint8_t full_scalar[32]; + BIGNUM* r = BN_new(); + REQUIRE(r != nullptr); + REQUIRE(BN_rand_range(r, order) == 1); + memset(full_scalar, 0, 32); + int num_bytes = BN_num_bytes(r); + if (num_bytes > 0 && num_bytes <= 32) { + BN_bn2bin(r, full_scalar + (32 - num_bytes)); + } + memcpy(buf, full_scalar + SCALAR_STAT_OFFSET, SCALAR_STAT_SIZE); + BN_free(r); + }, + SCALAR_STAT_SIZE, ENTROPY_ITERATIONS); + + REQUIRE(stats.passed_all); + BN_free(order); + } +} + +// ============================================================================ +// TEST CASE: DRNG (Deterministic RNG) - should be deterministic, NOT random +// This is a sanity check that DRNG is NOT used for entropy-critical operations +// ============================================================================ +TEST_CASE("entropy_drng_determinism", "[entropy][drng]") +{ + SECTION("DRNG is deterministic - same seed produces same output") { + drng_t* rng1; + drng_t* rng2; + uint8_t buf1[256], buf2[256]; + + REQUIRE(drng_new((uint8_t*)"test seed", 9, &rng1) == DRNG_SUCCESS); + REQUIRE(drng_new((uint8_t*)"test seed", 9, &rng2) == DRNG_SUCCESS); + + REQUIRE(drng_read_deterministic_rand(rng1, buf1, 256) == DRNG_SUCCESS); + REQUIRE(drng_read_deterministic_rand(rng2, buf2, 256) == DRNG_SUCCESS); + + REQUIRE(memcmp(buf1, buf2, 256) == 0); + + drng_free(rng1); + drng_free(rng2); + } + + SECTION("DRNG with different seeds produces different output") { + drng_t* rng1; + drng_t* rng2; + uint8_t buf1[256], buf2[256]; + + REQUIRE(drng_new((uint8_t*)"seed A", 6, &rng1) == DRNG_SUCCESS); + REQUIRE(drng_new((uint8_t*)"seed B", 6, &rng2) == DRNG_SUCCESS); + + REQUIRE(drng_read_deterministic_rand(rng1, buf1, 256) == DRNG_SUCCESS); + REQUIRE(drng_read_deterministic_rand(rng2, buf2, 256) == DRNG_SUCCESS); + + REQUIRE(memcmp(buf1, buf2, 256) != 0); + + drng_free(rng1); + drng_free(rng2); + } +} + +// ============================================================================ +// TEST CASE: Cross-curve scalar independence +// Verify that scalars from different curves don't correlate +// ============================================================================ +TEST_CASE("entropy_cross_curve_independence", "[entropy][cross_curve]") +{ + elliptic_curve256_algebra_ctx_t* secp256k1 = elliptic_curve256_new_secp256k1_algebra(); + elliptic_curve256_algebra_ctx_t* secp256r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(secp256k1 != nullptr); + REQUIRE(secp256r1 != nullptr); + + SECTION("interleaved generation maintains quality") { + // Generate alternating scalars from two different curves + // and verify each stream maintains statistical quality. + // Use only lower bytes to avoid order-constraint bias. + std::vector k1_bytes, r1_bytes; + k1_bytes.reserve(5000 * SCALAR_STAT_SIZE); + r1_bytes.reserve(5000 * SCALAR_STAT_SIZE); + + for (size_t i = 0; i < 5000; i++) { + elliptic_curve256_scalar_t s1, s2; + generate_random_scalar(secp256k1, &s1); + generate_random_scalar(secp256r1, &s2); + k1_bytes.insert(k1_bytes.end(), s1 + SCALAR_STAT_OFFSET, s1 + SCALAR_SIZE); + r1_bytes.insert(r1_bytes.end(), s2 + SCALAR_STAT_OFFSET, s2 + SCALAR_SIZE); + } + + REQUIRE(passes_chi_squared(k1_bytes.data(), k1_bytes.size(), CHI_SQUARED_ALPHA)); + REQUIRE(passes_chi_squared(r1_bytes.data(), r1_bytes.size(), CHI_SQUARED_ALPHA)); + } + + elliptic_curve256_algebra_ctx_free(secp256k1); + elliptic_curve256_algebra_ctx_free(secp256r1); +} diff --git a/test/crypto/paillier/CMakeLists.txt b/test/crypto/paillier/CMakeLists.txt index 62720e5..6d307c0 100644 --- a/test/crypto/paillier/CMakeLists.txt +++ b/test/crypto/paillier/CMakeLists.txt @@ -1,5 +1,6 @@ add_executable(paillier_test tests.cpp + ${PROJECT_SOURCE_DIR}/src/common/crypto/paillier/paillier.c ) target_compile_options(paillier_test PRIVATE -Wall -Wextra) diff --git a/test/crypto/paillier/tests.cpp b/test/crypto/paillier/tests.cpp index 86145eb..7b1da3d 100644 --- a/test/crypto/paillier/tests.cpp +++ b/test/crypto/paillier/tests.cpp @@ -1,4 +1,5 @@ #include "crypto/paillier/paillier.h" +#include "../../../src/common/crypto/paillier/paillier_internal.h" #include #include @@ -6,9 +7,9 @@ #include -TEST_CASE( "gen_key", "paillier") +TEST_CASE( "gen_key", "paillier") { - SECTION("gen_key") + SECTION("gen_key") { paillier_public_key_t* pub; paillier_private_key_t* priv; @@ -36,7 +37,7 @@ TEST_CASE( "gen_key", "paillier") // paillier_free_private_key(priv); // } - SECTION("too small key") + SECTION("too small key") { paillier_public_key_t* pub = NULL; paillier_private_key_t* priv = NULL; @@ -44,7 +45,7 @@ TEST_CASE( "gen_key", "paillier") REQUIRE(res == PAILLIER_ERROR_KEYLEN_TOO_SHORT); } - SECTION("strange key") + SECTION("strange key") { paillier_public_key_t* pub; paillier_private_key_t* priv; @@ -54,7 +55,7 @@ TEST_CASE( "gen_key", "paillier") paillier_free_private_key(priv); } - SECTION("invalid") + SECTION("invalid") { paillier_public_key_t* pub; paillier_private_key_t* priv; @@ -66,13 +67,13 @@ TEST_CASE( "gen_key", "paillier") } -TEST_CASE( "basic", "paillier") +TEST_CASE( "basic", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); - - SECTION("enc") + + SECTION("enc") { uint8_t* data = NULL; uint32_t data_len = 0; @@ -97,7 +98,7 @@ TEST_CASE( "basic", "paillier") delete[] text; } - SECTION("enc int") + SECTION("enc int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 1234567; @@ -137,14 +138,14 @@ TEST_CASE( "basic", "paillier") paillier_free_private_key(priv); } -TEST_CASE( "serialize pub", "paillier") +TEST_CASE( "serialize pub", "paillier") { paillier_public_key_t* pub = NULL; paillier_public_key_t* local_pub = NULL; paillier_private_key_t* priv = NULL; long res = paillier_generate_key_pair(2048, &pub, &priv); - - SECTION("enc") + + SECTION("enc") { uint8_t* data = NULL; uint32_t data_len = 0; @@ -176,7 +177,7 @@ TEST_CASE( "serialize pub", "paillier") delete[] text; } - SECTION("invalid buffer") + SECTION("invalid buffer") { uint8_t* data = NULL; uint32_t data_len = 0; @@ -196,14 +197,14 @@ TEST_CASE( "serialize pub", "paillier") paillier_free_private_key(priv); } -TEST_CASE( "serialize priv", "paillier") +TEST_CASE( "serialize priv", "paillier") { paillier_public_key_t* pub = NULL; paillier_private_key_t* priv = NULL; paillier_private_key_t* local_priv = NULL; long res = paillier_generate_key_pair(2048, &pub, &priv); - - SECTION("enc") + + SECTION("enc") { uint8_t* data = NULL; uint32_t data_len = 0; @@ -235,7 +236,7 @@ TEST_CASE( "serialize priv", "paillier") delete[] text; } - SECTION("invalid buffer") + SECTION("invalid buffer") { uint8_t* data = NULL; uint32_t data_len = 0; @@ -255,13 +256,13 @@ TEST_CASE( "serialize priv", "paillier") paillier_free_private_key(local_priv); } -TEST_CASE( "add", "paillier") +TEST_CASE( "add", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); - SECTION("add") + SECTION("add") { REQUIRE(res == PAILLIER_SUCCESS); uint8_t msg[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18}; @@ -278,15 +279,15 @@ TEST_CASE( "add", "paillier") uint32_t data_len2 = 0; res = paillier_encrypt(pub, msg, sizeof(msg), data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - + res = paillier_add(pub, data, data_len, data2, data_len2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data3 = new uint8_t[len]; uint32_t data_len3 = 0; res = paillier_add(pub, data, data_len, data2, data_len2, data3, len, &data_len3); REQUIRE(res == PAILLIER_SUCCESS); - - + + res = paillier_decrypt(priv, data3, data_len3, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); uint8_t* decrypted = new uint8_t[len]; @@ -301,7 +302,7 @@ TEST_CASE( "add", "paillier") delete[] decrypted; } - SECTION("add int") + SECTION("add int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 1234567; @@ -319,15 +320,15 @@ TEST_CASE( "add", "paillier") uint32_t data_len2 = 0; res = paillier_encrypt_integer(pub, msg2, data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - + res = paillier_add(pub, data, data_len, data2, data_len2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data3 = new uint8_t[len]; uint32_t data_len3 = 0; res = paillier_add(pub, data, data_len, data2, data_len2, data3, len, &data_len3); REQUIRE(res == PAILLIER_SUCCESS); - - + + uint64_t decrypted = 0; res = paillier_decrypt_integer(priv, data3, data_len3, &decrypted); REQUIRE(res == PAILLIER_SUCCESS); @@ -337,7 +338,7 @@ TEST_CASE( "add", "paillier") delete[] data3; } - SECTION("add int") + SECTION("add int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 1234567; @@ -349,15 +350,15 @@ TEST_CASE( "add", "paillier") res = paillier_encrypt_integer(pub, msg, data, len, &data_len); REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg2 = 7654321; - + res = paillier_add_integer(pub, data, data_len, msg2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data2 = new uint8_t[len]; uint32_t data_len2 = 0; res = paillier_add_integer(pub, data, data_len, msg2, data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - - + + uint64_t decrypted = 0; res = paillier_decrypt_integer(priv, data2, data_len2, &decrypted); REQUIRE(res == PAILLIER_SUCCESS); @@ -365,18 +366,18 @@ TEST_CASE( "add", "paillier") delete[] data; delete[] data2; } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } -TEST_CASE( "sub", "paillier") +TEST_CASE( "sub", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); - - SECTION("sub") + + SECTION("sub") { REQUIRE(res == PAILLIER_SUCCESS); uint8_t msg[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18}; @@ -394,15 +395,15 @@ TEST_CASE( "sub", "paillier") uint32_t data_len2 = 0; res = paillier_encrypt(pub, msg2, sizeof(msg2), data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - + res = paillier_sub(pub, data, data_len, data2, data_len2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data3 = new uint8_t[len]; uint32_t data_len3 = 0; res = paillier_sub(pub, data, data_len, data2, data_len2, data3, len, &data_len3); REQUIRE(res == PAILLIER_SUCCESS); - - + + res = paillier_decrypt(priv, data3, data_len3, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); uint8_t* decrypted = new uint8_t[len]; @@ -426,7 +427,7 @@ TEST_CASE( "sub", "paillier") delete[] decrypted; } - SECTION("sub int") + SECTION("sub int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 7654321; @@ -444,15 +445,15 @@ TEST_CASE( "sub", "paillier") uint32_t data_len2 = 0; res = paillier_encrypt_integer(pub, msg2, data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - + res = paillier_sub(pub, data, data_len, data2, data_len2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data3 = new uint8_t[len]; uint32_t data_len3 = 0; res = paillier_sub(pub, data, data_len, data2, data_len2, data3, len, &data_len3); REQUIRE(res == PAILLIER_SUCCESS); - - + + uint64_t decrypted = 0; res = paillier_decrypt_integer(priv, data3, data_len3, &decrypted); REQUIRE(res == PAILLIER_SUCCESS); @@ -462,7 +463,7 @@ TEST_CASE( "sub", "paillier") delete[] data3; } - SECTION("sub int") + SECTION("sub int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 7654321; @@ -474,15 +475,15 @@ TEST_CASE( "sub", "paillier") res = paillier_encrypt_integer(pub, msg, data, len, &data_len); REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg2 = 1234567; - + res = paillier_sub_integer(pub, data, data_len, msg2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data2 = new uint8_t[len]; uint32_t data_len2 = 0; res = paillier_sub_integer(pub, data, data_len, msg2, data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - - + + uint64_t decrypted = 0; res = paillier_decrypt_integer(priv, data2, data_len2, &decrypted); REQUIRE(res == PAILLIER_SUCCESS); @@ -490,18 +491,18 @@ TEST_CASE( "sub", "paillier") delete[] data; delete[] data2; } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } -TEST_CASE( "mul", "paillier") +TEST_CASE( "mul", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); - - SECTION("mul") + + SECTION("mul") { REQUIRE(res == PAILLIER_SUCCESS); uint8_t msg[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18}; @@ -512,15 +513,15 @@ TEST_CASE( "mul", "paillier") uint32_t data_len = 0; res = paillier_encrypt(pub, msg, sizeof(msg), data, len, &data_len); REQUIRE(res == PAILLIER_SUCCESS); - + res = paillier_mul(pub, data, data_len, msg, sizeof(msg), NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data3 = new uint8_t[len]; uint32_t data_len3 = 0; res = paillier_mul(pub, data, data_len, msg, sizeof(msg), data3, len, &data_len3); REQUIRE(res == PAILLIER_SUCCESS); - - + + res = paillier_decrypt(priv, data3, data_len3, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); uint8_t* decrypted = new uint8_t[len]; @@ -544,7 +545,7 @@ TEST_CASE( "mul", "paillier") delete[] decrypted; } - SECTION("mul int") + SECTION("mul int") { REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg = 1234567; @@ -556,15 +557,15 @@ TEST_CASE( "mul", "paillier") res = paillier_encrypt_integer(pub, msg, data, len, &data_len); REQUIRE(res == PAILLIER_SUCCESS); uint64_t msg2 = 7654321; - + res = paillier_mul_integer(pub, data, data_len, msg2, NULL, 0, &len); REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); uint8_t* data2 = new uint8_t[len]; uint32_t data_len2 = 0; res = paillier_mul_integer(pub, data, data_len, msg2, data2, len, &data_len2); REQUIRE(res == PAILLIER_SUCCESS); - - + + uint64_t decrypted = 0; res = paillier_decrypt_integer(priv, data2, data_len2, &decrypted); REQUIRE(res == PAILLIER_SUCCESS); @@ -572,45 +573,45 @@ TEST_CASE( "mul", "paillier") delete[] data; delete[] data2; } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } -TEST_CASE( "zkpok", "paillier") +TEST_CASE( "zkpok", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); REQUIRE(res == PAILLIER_SUCCESS); - SECTION("valid") + SECTION("valid") { unsigned char x[PAILLIER_SHA256_LEN]; unsigned char y[256]; - + res = paillier_generate_factorization_zkpok(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 256, NULL); REQUIRE(res == PAILLIER_SUCCESS); res = paillier_verify_factorization_zkpok(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 256); REQUIRE(res == PAILLIER_SUCCESS); } - SECTION("invalid aad") + SECTION("invalid aad") { unsigned char x[PAILLIER_SHA256_LEN]; unsigned char y[256]; - + res = paillier_generate_factorization_zkpok(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 256, NULL); REQUIRE(res == PAILLIER_SUCCESS); res = paillier_verify_factorization_zkpok(pub, (const unsigned char*)"gello world", sizeof("hello world") - 1, x, y, 256); REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - - SECTION("invalid x") + + SECTION("invalid x") { unsigned char x[PAILLIER_SHA256_LEN]; unsigned char y[256]; - + res = paillier_generate_factorization_zkpok(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 256, NULL); REQUIRE(res == PAILLIER_SUCCESS); x[4]++; @@ -618,11 +619,11 @@ TEST_CASE( "zkpok", "paillier") REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - SECTION("invalid y") + SECTION("invalid y") { unsigned char x[PAILLIER_SHA256_LEN]; unsigned char y[256]; - + res = paillier_generate_factorization_zkpok(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 256, NULL); REQUIRE(res == PAILLIER_SUCCESS); y[4]++; @@ -630,7 +631,7 @@ TEST_CASE( "zkpok", "paillier") REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - SECTION("invalid param") + SECTION("invalid param") { unsigned char x[PAILLIER_SHA256_LEN]; unsigned char y[256]; @@ -656,42 +657,42 @@ TEST_CASE( "zkpok", "paillier") res = paillier_verify_factorization_zkpok(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, x, y, 0); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } -TEST_CASE( "zkp", "paillier") +TEST_CASE( "zkp", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); REQUIRE(res == PAILLIER_SUCCESS); - SECTION("valid") + SECTION("valid") { unsigned char y[4096]; - + res = paillier_generate_coprime_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, y, sizeof(y), NULL); REQUIRE(res == PAILLIER_SUCCESS); res = paillier_verify_coprime_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, y, sizeof(y)); REQUIRE(res == PAILLIER_SUCCESS); } - SECTION("invalid aad") + SECTION("invalid aad") { unsigned char y[4096]; - + res = paillier_generate_coprime_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, y, sizeof(y), NULL); REQUIRE(res == PAILLIER_SUCCESS); res = paillier_verify_coprime_zkp(pub, (const unsigned char*)"gello world", sizeof("hello world") - 1, y, sizeof(y)); REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - - SECTION("invalid y") + + SECTION("invalid y") { unsigned char y[4096]; - + res = paillier_generate_coprime_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, y, sizeof(y), NULL); REQUIRE(res == PAILLIER_SUCCESS); y[4]++; @@ -699,7 +700,7 @@ TEST_CASE( "zkp", "paillier") REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - SECTION("invalid param") + SECTION("invalid param") { unsigned char y[4096]; unsigned int y_len; @@ -722,88 +723,1115 @@ TEST_CASE( "zkp", "paillier") res = paillier_verify_coprime_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 4096); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } -TEST_CASE( "paillier_blum_zkp", "paillier") +TEST_CASE( "paillier_blum_zkp", "paillier") { paillier_public_key_t* pub; paillier_private_key_t* priv; long res = paillier_generate_key_pair(2048, &pub, &priv); REQUIRE(res == PAILLIER_SUCCESS); - SECTION("valid") - { + SECTION("valid all nth root") + { + uint32_t proof_len; + res = paillier_generate_paillier_blum_zkp(priv, 1, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); + REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); + std::unique_ptr proof(new uint8_t[proof_len]); + res = paillier_generate_paillier_blum_zkp(priv, 1, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len -1, &proof_len); + REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); + res = paillier_generate_paillier_blum_zkp(priv, 1, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); + REQUIRE(res == PAILLIER_SUCCESS); + res = paillier_verify_paillier_blum_zkp(pub, 1, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); + REQUIRE(res == PAILLIER_SUCCESS); + } + + SECTION("valid only first nth root") + { uint32_t proof_len; - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); std::unique_ptr proof(new uint8_t[proof_len]); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len -1, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len -1, &proof_len); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); REQUIRE(res == PAILLIER_SUCCESS); - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_SUCCESS); } - SECTION("invalid aad") + SECTION("invalid aad") { uint32_t proof_len; - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); std::unique_ptr proof(new uint8_t[proof_len]); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); REQUIRE(res == PAILLIER_SUCCESS); - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"gello world", sizeof("hello world") - 1, proof.get(), proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"gello world", sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - - SECTION("invalid proof") + + SECTION("invalid proof") { uint32_t proof_len; - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); std::unique_ptr proof(new uint8_t[proof_len]); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, &proof_len); REQUIRE(res == PAILLIER_SUCCESS); (*(uint32_t*)proof.get())++; - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); (*(uint32_t*)proof.get())--; proof.get()[32]++; - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_PROOF); } - SECTION("invalid param") + SECTION("invalid param") { uint32_t proof_len; - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &proof_len); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); std::unique_ptr proof(new uint8_t[proof_len]); - res = paillier_generate_paillier_blum_zkp(NULL, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, NULL); + res = paillier_generate_paillier_blum_zkp(NULL, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len, NULL); REQUIRE(res == PAILLIER_ERROR_INVALID_KEY); - res = paillier_generate_paillier_blum_zkp(priv, NULL, sizeof("hello world") - 1, proof.get(), proof_len, NULL); + res = paillier_generate_paillier_blum_zkp(priv, 0, NULL, sizeof("hello world") - 1, proof.get(), proof_len, NULL); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, proof_len, NULL); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, proof_len, NULL); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); - res = paillier_generate_paillier_blum_zkp(priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), 0, NULL); + res = paillier_generate_paillier_blum_zkp(priv, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), 0, NULL); REQUIRE(res == PAILLIER_ERROR_BUFFER_TOO_SHORT); - - res = paillier_verify_paillier_blum_zkp(NULL, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); + + res = paillier_verify_paillier_blum_zkp(NULL, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_KEY); - res = paillier_verify_paillier_blum_zkp(pub, 0, sizeof("hello world") - 1, proof.get(), proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, 0, sizeof("hello world") - 1, proof.get(), proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, proof_len); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, proof_len); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); - res = paillier_verify_paillier_blum_zkp(pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), 7); + res = paillier_verify_paillier_blum_zkp(pub, 0, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof.get(), 7); REQUIRE(res == PAILLIER_ERROR_INVALID_PARAM); } - + paillier_free_public_key(pub); paillier_free_private_key(priv); } +TEST_CASE( "paillier_attacks", "paillier") +{ + paillier_public_key_t* pub = NULL; + paillier_private_key_t* priv = NULL; + long res = paillier_generate_key_pair(2048, &pub, &priv); + REQUIRE(res == PAILLIER_SUCCESS); + + SECTION("encrypt with zero plaintext") + { + uint8_t zero_msg[] = {0}; + uint32_t len = 0; + res = paillier_encrypt(pub, zero_msg, sizeof(zero_msg), NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + REQUIRE(len > 0); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, zero_msg, sizeof(zero_msg), ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t pt_len = 0; + long dec_res = paillier_decrypt(priv, ciphertext, ct_len, NULL, 0, &pt_len); + REQUIRE(dec_res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + REQUIRE(pt_len > 0); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + dec_res = paillier_decrypt(priv, ciphertext, ct_len, plaintext, pt_len, &pt_real_len); + REQUIRE(dec_res == PAILLIER_SUCCESS); + REQUIRE(pt_real_len == 1); + REQUIRE(plaintext[0] == 0); + + delete[] plaintext; + delete[] ciphertext; + } + + SECTION("encrypt with max value plaintext") + { + // Encrypt N-1 (the largest valid plaintext for Paillier) + // Get N from the public key + uint32_t n_len = 0; + res = paillier_public_key_n(pub, NULL, 0, &n_len); + REQUIRE(n_len > 0); + uint8_t* n_buf = new uint8_t[n_len]; + uint32_t n_real_len = 0; + res = paillier_public_key_n(pub, n_buf, n_len, &n_real_len); + + // Construct N-1 using OpenSSL BIGNUM + BIGNUM* bn_n = BN_bin2bn(n_buf, n_real_len, NULL); + REQUIRE(bn_n != NULL); + BIGNUM* bn_one = BN_new(); + BN_one(bn_one); + BIGNUM* bn_max = BN_new(); + BN_sub(bn_max, bn_n, bn_one); + + uint8_t* max_buf = new uint8_t[BN_num_bytes(bn_max)]; + int max_len = BN_bn2bin(bn_max, max_buf); + + // Encrypt N-1 + uint32_t len = 0; + res = paillier_encrypt(pub, max_buf, max_len, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, max_buf, max_len, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt and verify + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, plaintext, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + BIGNUM* bn_decrypted = BN_bin2bn(plaintext, pt_real_len, NULL); + REQUIRE(bn_decrypted != NULL); + REQUIRE(BN_cmp(bn_decrypted, bn_max) == 0); + + BN_free(bn_n); + BN_free(bn_one); + BN_free(bn_max); + BN_free(bn_decrypted); + delete[] n_buf; + delete[] max_buf; + delete[] ciphertext; + delete[] plaintext; + } + + SECTION("decrypt with wrong key") + { + // Generate a second keypair + paillier_public_key_t* pub2 = NULL; + paillier_private_key_t* priv2 = NULL; + res = paillier_generate_key_pair(2048, &pub2, &priv2); + REQUIRE(res == PAILLIER_SUCCESS); + + // Encrypt with first public key + uint64_t msg = 42; + uint32_t len = 0; + res = paillier_encrypt_integer(pub, msg, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt_integer(pub, msg, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt with second (wrong) private key - should either fail or produce wrong result + uint64_t wrong_result = 0; + res = paillier_decrypt_integer(priv2, ciphertext, ct_len, &wrong_result); + // Either decryption fails or gives a different value + if (res == PAILLIER_SUCCESS) + { + REQUIRE(wrong_result != msg); + } + // If res != PAILLIER_SUCCESS, that's also acceptable (decryption error with wrong key) + + paillier_free_public_key(pub2); + paillier_free_private_key(priv2); + delete[] ciphertext; + } + + SECTION("homomorphic add overflow") + { + // Encrypt two values whose sum exceeds N (result wraps mod N) + // Get N from the public key + uint32_t n_len = 0; + res = paillier_public_key_n(pub, NULL, 0, &n_len); + REQUIRE(n_len > 0); + uint8_t* n_buf = new uint8_t[n_len]; + uint32_t n_real_len = 0; + res = paillier_public_key_n(pub, n_buf, n_len, &n_real_len); + BIGNUM* bn_n = BN_bin2bn(n_buf, n_real_len, NULL); + REQUIRE(bn_n != NULL); + + // Create value = N - 1 + BIGNUM* bn_one = BN_new(); + BN_one(bn_one); + BIGNUM* bn_val = BN_new(); + BN_sub(bn_val, bn_n, bn_one); + + uint8_t* val_buf = new uint8_t[BN_num_bytes(bn_val)]; + int val_len = BN_bn2bin(bn_val, val_buf); + + // Encrypt (N-1) twice + uint32_t len = 0; + res = paillier_encrypt(pub, val_buf, val_len, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ct1 = new uint8_t[len]; + uint32_t ct1_len = 0; + res = paillier_encrypt(pub, val_buf, val_len, ct1, len, &ct1_len); + REQUIRE(res == PAILLIER_SUCCESS); + + res = paillier_encrypt(pub, val_buf, val_len, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ct2 = new uint8_t[len]; + uint32_t ct2_len = 0; + res = paillier_encrypt(pub, val_buf, val_len, ct2, len, &ct2_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Homomorphic add: (N-1) + (N-1) = 2N - 2 mod N = N - 2 + res = paillier_add(pub, ct1, ct1_len, ct2, ct2_len, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ct_sum = new uint8_t[len]; + uint32_t ct_sum_len = 0; + res = paillier_add(pub, ct1, ct1_len, ct2, ct2_len, ct_sum, len, &ct_sum_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt the result + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ct_sum, ct_sum_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ct_sum, ct_sum_len, plaintext, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Expected: N - 2 + BIGNUM* bn_two = BN_new(); + BN_set_word(bn_two, 2); + BIGNUM* bn_expected = BN_new(); + BN_sub(bn_expected, bn_n, bn_two); + + BIGNUM* bn_result = BN_bin2bn(plaintext, pt_real_len, NULL); + REQUIRE(bn_result != NULL); + REQUIRE(BN_cmp(bn_result, bn_expected) == 0); + + BN_free(bn_n); + BN_free(bn_one); + BN_free(bn_val); + BN_free(bn_two); + BN_free(bn_expected); + BN_free(bn_result); + delete[] n_buf; + delete[] val_buf; + delete[] ct1; + delete[] ct2; + delete[] ct_sum; + delete[] plaintext; + } + + SECTION("tampered ciphertext - bit flip") + { + uint64_t msg = 9999999; + uint32_t len = 0; + res = paillier_encrypt_integer(pub, msg, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt_integer(pub, msg, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Make a copy and flip a bit in the middle + uint8_t* tampered = new uint8_t[ct_len]; + memcpy(tampered, ciphertext, ct_len); + tampered[ct_len / 2] ^= 0x01; + + // Decrypt tampered ciphertext + uint64_t tampered_result = 0; + res = paillier_decrypt_integer(priv, tampered, ct_len, &tampered_result); + if (res == PAILLIER_SUCCESS) + { + // If decryption succeeds, the result must differ from original + REQUIRE(tampered_result != msg); + } + // If decryption fails, that is also an acceptable outcome for tampered data + + delete[] ciphertext; + delete[] tampered; + } + + SECTION("tampered ciphertext - all zeros") + { + // First encrypt something to get the correct ciphertext length + uint64_t msg = 12345; + uint32_t len = 0; + res = paillier_encrypt_integer(pub, msg, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt_integer(pub, msg, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Create all-zero ciphertext of the same length + uint8_t* zeros = new uint8_t[ct_len]; + memset(zeros, 0, ct_len); + + uint64_t result = 0; + res = paillier_decrypt_integer(priv, zeros, ct_len, &result); + // All-zero ciphertext should either fail or not decrypt to original message + if (res == PAILLIER_SUCCESS) + { + REQUIRE(result != msg); + } + + delete[] ciphertext; + delete[] zeros; + } + + SECTION("tampered ciphertext - all ones") + { + // First encrypt something to get the correct ciphertext length + uint64_t msg = 12345; + uint32_t len = 0; + res = paillier_encrypt_integer(pub, msg, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt_integer(pub, msg, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Create all-0xFF ciphertext of the same length + uint8_t* ones = new uint8_t[ct_len]; + memset(ones, 0xFF, ct_len); + + uint64_t result = 0; + res = paillier_decrypt_integer(priv, ones, ct_len, &result); + // All-0xFF ciphertext should either fail or not decrypt to original message + if (res == PAILLIER_SUCCESS) + { + REQUIRE(result != msg); + } + + delete[] ciphertext; + delete[] ones; + } + + + + SECTION("deserialize truncated public key") + { + uint32_t key_len = 0; + paillier_public_key_serialize(pub, NULL, 0, &key_len); + REQUIRE(key_len > 0); + uint8_t* key_buf = new uint8_t[key_len]; + REQUIRE(paillier_public_key_serialize(pub, key_buf, key_len, &key_len)); + paillier_public_key_t* bad_pub = paillier_public_key_deserialize(key_buf, key_len / 2); + REQUIRE(bad_pub == NULL); + delete[] key_buf; + } + + SECTION("deserialize garbage public key") + { + uint32_t garbage_len = 512; + uint8_t* garbage = new uint8_t[garbage_len]; + for (uint32_t i = 0; i < garbage_len; i++) + garbage[i] = (uint8_t)((i * 137 + 43) & 0xFF); + paillier_public_key_t* bad_pub = paillier_public_key_deserialize(garbage, garbage_len); + REQUIRE(bad_pub == NULL); + delete[] garbage; + } + SECTION("deserialize truncated public key 2") + { + uint32_t key_len = 0; + paillier_public_key_serialize(pub, NULL, 0, &key_len); + REQUIRE(key_len > 0); + uint8_t* key_buf = new uint8_t[key_len]; + REQUIRE(paillier_public_key_serialize(pub, key_buf, key_len, &key_len)); + paillier_public_key_t* bad_pub = paillier_public_key_deserialize(key_buf, key_len / 2); + REQUIRE(bad_pub == NULL); + delete[] key_buf; + } + SECTION("deserialize truncated private key") { + uint32_t key_len = 0; + paillier_private_key_serialize(priv, NULL, 0, &key_len); + REQUIRE(key_len > 0); + uint8_t* key_buf = new uint8_t[key_len]; + REQUIRE(paillier_private_key_serialize(priv, key_buf, key_len, &key_len)); + paillier_private_key_t* bad_priv = paillier_private_key_deserialize(key_buf, key_len / 2); + REQUIRE(bad_priv == NULL); + delete[] key_buf; + } + + SECTION("deserialize with very small buffer returns NULL") + { + // Buffers smaller than sizeof(uint32_t) + MIN_KEY_LEN_IN_BITS/8 = 36 + // are safely rejected by the min-length check at paillier.c:383. + uint8_t small_buf[4] = {0x01, 0x02, 0x03, 0x04}; + paillier_public_key_t* bad_pub = paillier_public_key_deserialize(small_buf, 4); + REQUIRE(bad_pub == NULL); + + // NULL buffer + bad_pub = paillier_public_key_deserialize(NULL, 100); + REQUIRE(bad_pub == NULL); + } + + SECTION("encrypt plaintext = 1 (smallest positive)") + { + uint8_t one_msg[] = {1}; + uint32_t len = 0; + res = paillier_encrypt(pub, one_msg, sizeof(one_msg), NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + REQUIRE(len > 0); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, one_msg, sizeof(one_msg), ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, plaintext, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + REQUIRE(pt_real_len == 1); + REQUIRE(plaintext[0] == 1); + + delete[] ciphertext; + delete[] plaintext; + } + + SECTION("encrypt plaintext = N (rejected)") + { + // Paillier plaintext space is [0, N). N itself must be rejected. + uint32_t n_len = 0; + res = paillier_public_key_n(pub, NULL, 0, &n_len); + REQUIRE(n_len > 0); + uint8_t* n_buf = new uint8_t[n_len]; + uint32_t n_real_len = 0; + res = paillier_public_key_n(pub, n_buf, n_len, &n_real_len); + + uint32_t len = 0; + res = paillier_encrypt(pub, n_buf, n_real_len, NULL, 0, &len); + // N has n_real_len bytes. paillier_encrypt rejects if plaintext_len > BN_num_bytes(N) + // OR if BN_cmp(msg, N) >= 0. Either way, it should not succeed. + if (len > 0) + { + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, n_buf, n_real_len, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + delete[] ciphertext; + } + else + { + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + } + + delete[] n_buf; + } + + SECTION("encrypt plaintext = N+1 (rejected)") + { + uint32_t n_len = 0; + res = paillier_public_key_n(pub, NULL, 0, &n_len); + REQUIRE(n_len > 0); + uint8_t* n_buf = new uint8_t[n_len]; + uint32_t n_real_len = 0; + res = paillier_public_key_n(pub, n_buf, n_len, &n_real_len); + + BIGNUM* bn_n = BN_bin2bn(n_buf, n_real_len, NULL); + REQUIRE(bn_n != NULL); + BIGNUM* bn_one = BN_new(); + BN_one(bn_one); + BIGNUM* bn_np1 = BN_new(); + BN_add(bn_np1, bn_n, bn_one); + + int np1_bytes = BN_num_bytes(bn_np1); + uint8_t* np1_buf = new uint8_t[np1_bytes]; + BN_bn2bin(bn_np1, np1_buf); + + // N+1 has the same byte length as N (N = p*q can never be all-FF). + // The byte-length check (plaintext_len > BN_num_bytes(N)) passes, + // so the first call with NULL buffer returns INVALID_CIPHER_TEXT (buffer query). + // The actual BN_cmp rejection happens only when a real buffer is provided. + REQUIRE(np1_bytes == (int)n_real_len); + uint32_t len = 0; + res = paillier_encrypt(pub, np1_buf, np1_bytes, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + REQUIRE(len > 0); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, np1_buf, np1_bytes, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + delete[] ciphertext; + + BN_free(bn_n); + BN_free(bn_one); + BN_free(bn_np1); + delete[] n_buf; + delete[] np1_buf; + } + + SECTION("encrypt plaintext = p (prime factor, round-trip)") + { + // p < N, so p is a valid plaintext — encrypt/decrypt should round-trip + int p_bytes = BN_num_bytes(priv->p); + REQUIRE(p_bytes > 0); + uint8_t* p_buf = new uint8_t[p_bytes]; + BN_bn2bin(priv->p, p_buf); + + uint32_t len = 0; + res = paillier_encrypt(pub, p_buf, p_bytes, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, p_buf, p_bytes, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, plaintext, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + BIGNUM* bn_decrypted = BN_bin2bn(plaintext, pt_real_len, NULL); + REQUIRE(bn_decrypted != NULL); + REQUIRE(BN_cmp(bn_decrypted, priv->p) == 0); + + BN_free(bn_decrypted); + delete[] p_buf; + delete[] ciphertext; + delete[] plaintext; + } + + SECTION("encrypt plaintext = q (prime factor, round-trip)") + { + // q < N, so q is a valid plaintext — encrypt/decrypt should round-trip + int q_bytes = BN_num_bytes(priv->q); + REQUIRE(q_bytes > 0); + uint8_t* q_buf = new uint8_t[q_bytes]; + BN_bn2bin(priv->q, q_buf); + + uint32_t len = 0; + res = paillier_encrypt(pub, q_buf, q_bytes, NULL, 0, &len); + REQUIRE(res == PAILLIER_ERROR_INVALID_CIPHER_TEXT); + uint8_t* ciphertext = new uint8_t[len]; + uint32_t ct_len = 0; + res = paillier_encrypt(pub, q_buf, q_bytes, ciphertext, len, &ct_len); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + uint8_t* plaintext = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ciphertext, ct_len, plaintext, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + BIGNUM* bn_decrypted = BN_bin2bn(plaintext, pt_real_len, NULL); + REQUIRE(bn_decrypted != NULL); + REQUIRE(BN_cmp(bn_decrypted, priv->q) == 0); + + BN_free(bn_decrypted); + delete[] q_buf; + delete[] ciphertext; + delete[] plaintext; + } + + paillier_free_public_key(pub); + paillier_free_private_key(priv); +} + +TEST_CASE("paillier_homomorphic_overflow", "[correctness]") +{ + paillier_public_key_t* pub = NULL; + paillier_private_key_t* priv = NULL; + long res = paillier_generate_key_pair(2048, &pub, &priv); + REQUIRE(res == PAILLIER_SUCCESS); + + // Get N for boundary value construction + uint32_t n_len = 0; + paillier_public_key_n(pub, NULL, 0, &n_len); + REQUIRE(n_len > 0); + uint8_t* n_buf = new uint8_t[n_len]; + uint32_t n_real_len = 0; + paillier_public_key_n(pub, n_buf, n_len, &n_real_len); + BIGNUM* bn_n = BN_bin2bn(n_buf, n_real_len, NULL); + REQUIRE(bn_n != NULL); + + // Helper: get ciphertext buffer size + uint32_t ct_size = 0; + { + uint8_t one_msg[] = {1}; + paillier_encrypt(pub, one_msg, 1, NULL, 0, &ct_size); + } + REQUIRE(ct_size > 0); + + SECTION("add overflow: enc(N-1) + enc(1) decrypts to 0") + { + BN_CTX* ctx = BN_CTX_new(); + + // Construct N-1 + BIGNUM* bn_nm1 = BN_dup(bn_n); + BIGNUM* bn_one = BN_new(); + BN_one(bn_one); + BN_sub(bn_nm1, bn_nm1, bn_one); + int nm1_bytes = BN_num_bytes(bn_nm1); + uint8_t* nm1_buf = new uint8_t[nm1_bytes]; + BN_bn2bin(bn_nm1, nm1_buf); + + // Encrypt N-1 + uint8_t* ct_nm1 = new uint8_t[ct_size]; + uint32_t ct_nm1_len = 0; + res = paillier_encrypt(pub, nm1_buf, nm1_bytes, ct_nm1, ct_size, &ct_nm1_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Encrypt 1 + uint8_t one_msg[] = {1}; + uint8_t* ct_one = new uint8_t[ct_size]; + uint32_t ct_one_len = 0; + res = paillier_encrypt(pub, one_msg, 1, ct_one, ct_size, &ct_one_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Homomorphic add: enc(N-1) + enc(1) = enc((N-1+1) mod N) = enc(0) + uint8_t* ct_sum = new uint8_t[ct_size]; + uint32_t ct_sum_len = 0; + res = paillier_add(pub, ct_nm1, ct_nm1_len, ct_one, ct_one_len, ct_sum, ct_size, &ct_sum_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt — should get 0 + uint32_t pt_len = 0; + res = paillier_decrypt(priv, ct_sum, ct_sum_len, NULL, 0, &pt_len); + REQUIRE(res == PAILLIER_ERROR_INVALID_PLAIN_TEXT); + REQUIRE(pt_len > 0); + uint8_t* pt_buf = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ct_sum, ct_sum_len, pt_buf, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + REQUIRE(pt_real_len == 1); + REQUIRE(pt_buf[0] == 0); + delete[] pt_buf; + + BN_free(bn_nm1); + BN_free(bn_one); + BN_CTX_free(ctx); + delete[] nm1_buf; + delete[] ct_nm1; + delete[] ct_one; + delete[] ct_sum; + } + + SECTION("sub underflow: enc(1) - enc(2) decrypts to N-1") + { + // Encrypt 1 + uint8_t one_msg[] = {1}; + uint8_t* ct_one = new uint8_t[ct_size]; + uint32_t ct_one_len = 0; + res = paillier_encrypt(pub, one_msg, 1, ct_one, ct_size, &ct_one_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Encrypt 2 + uint8_t two_msg[] = {2}; + uint8_t* ct_two = new uint8_t[ct_size]; + uint32_t ct_two_len = 0; + res = paillier_encrypt(pub, two_msg, 1, ct_two, ct_size, &ct_two_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Homomorphic sub: enc(1) - enc(2) = enc((1-2) mod N) = enc(N-1) + uint8_t* ct_diff = new uint8_t[ct_size]; + uint32_t ct_diff_len = 0; + res = paillier_sub(pub, ct_one, ct_one_len, ct_two, ct_two_len, ct_diff, ct_size, &ct_diff_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt — should get N-1 + uint32_t pt_len = 0; + paillier_decrypt(priv, ct_diff, ct_diff_len, NULL, 0, &pt_len); + REQUIRE(pt_len > 0); + uint8_t* pt_buf = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ct_diff, ct_diff_len, pt_buf, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + BIGNUM* bn_result = BN_bin2bn(pt_buf, pt_real_len, NULL); + REQUIRE(bn_result != NULL); + + // Expected: N-1 + BIGNUM* bn_expected = BN_dup(bn_n); + BIGNUM* bn_one_val = BN_new(); + BN_one(bn_one_val); + BN_sub(bn_expected, bn_expected, bn_one_val); + REQUIRE(BN_cmp(bn_result, bn_expected) == 0); + + BN_free(bn_result); + BN_free(bn_expected); + BN_free(bn_one_val); + delete[] ct_one; + delete[] ct_two; + delete[] ct_diff; + delete[] pt_buf; + } + + SECTION("mul overflow: enc(N-1) * 2 decrypts to N-2") + { + // Construct N-1 + BIGNUM* bn_nm1 = BN_dup(bn_n); + BIGNUM* bn_one_val = BN_new(); + BN_one(bn_one_val); + BN_sub(bn_nm1, bn_nm1, bn_one_val); + int nm1_bytes = BN_num_bytes(bn_nm1); + uint8_t* nm1_buf = new uint8_t[nm1_bytes]; + BN_bn2bin(bn_nm1, nm1_buf); + + // Encrypt N-1 + uint8_t* ct_nm1 = new uint8_t[ct_size]; + uint32_t ct_nm1_len = 0; + res = paillier_encrypt(pub, nm1_buf, nm1_bytes, ct_nm1, ct_size, &ct_nm1_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Homomorphic mul: enc(N-1) * 2 = enc((N-1)*2 mod N) = enc(N-2) + uint8_t* ct_prod = new uint8_t[ct_size]; + uint32_t ct_prod_len = 0; + uint8_t two_plaintext[] = {2}; + res = paillier_mul(pub, ct_nm1, ct_nm1_len, two_plaintext, 1, ct_prod, ct_size, &ct_prod_len); + REQUIRE(res == PAILLIER_SUCCESS); + + // Decrypt — should get N-2 + uint32_t pt_len = 0; + paillier_decrypt(priv, ct_prod, ct_prod_len, NULL, 0, &pt_len); + REQUIRE(pt_len > 0); + uint8_t* pt_buf = new uint8_t[pt_len]; + uint32_t pt_real_len = 0; + res = paillier_decrypt(priv, ct_prod, ct_prod_len, pt_buf, pt_len, &pt_real_len); + REQUIRE(res == PAILLIER_SUCCESS); + + BIGNUM* bn_result = BN_bin2bn(pt_buf, pt_real_len, NULL); + REQUIRE(bn_result != NULL); + + // Expected: N-2 + BIGNUM* bn_expected = BN_dup(bn_n); + BIGNUM* bn_two = BN_new(); + BN_set_word(bn_two, 2); + BN_sub(bn_expected, bn_expected, bn_two); + REQUIRE(BN_cmp(bn_result, bn_expected) == 0); + + BN_free(bn_result); + BN_free(bn_expected); + BN_free(bn_two); + BN_free(bn_nm1); + BN_free(bn_one_val); + delete[] nm1_buf; + delete[] ct_nm1; + delete[] ct_prod; + delete[] pt_buf; + } + + BN_free(bn_n); + delete[] n_buf; + paillier_free_public_key(pub); + paillier_free_private_key(priv); +} + +TEST_CASE("paillier_key_validity", "[correctness]") +{ + paillier_public_key_t* pub = NULL; + paillier_private_key_t* priv = NULL; + long res = paillier_generate_key_pair(2048, &pub, &priv); + REQUIRE(res == PAILLIER_SUCCESS); + + SECTION("N equals p times q") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* pq = BN_new(); + BN_mul(pq, priv->p, priv->q, ctx); + REQUIRE(BN_cmp(pq, pub->n) == 0); + BN_free(pq); + BN_CTX_free(ctx); + } + + SECTION("N has requested bit size") + { + int n_bits = BN_num_bits(pub->n); + // 2048-bit key: N should be 2047 or 2048 bits + REQUIRE(n_bits >= 2047); + REQUIRE(n_bits <= 2048); + } + + SECTION("p and q are distinct primes") + { + REQUIRE(BN_cmp(priv->p, priv->q) != 0); + // BN_is_prime_ex is probabilistic — 64 rounds gives negligible error + REQUIRE(BN_is_prime_ex(priv->p, 64, NULL, NULL) == 1); + REQUIRE(BN_is_prime_ex(priv->q, 64, NULL, NULL) == 1); + } + + SECTION("GCD(N, phi(N)) equals 1") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* p_minus_1 = BN_dup(priv->p); + BIGNUM* q_minus_1 = BN_dup(priv->q); + BIGNUM* one = BN_new(); + BN_one(one); + BN_sub(p_minus_1, p_minus_1, one); + BN_sub(q_minus_1, q_minus_1, one); + + BIGNUM* phi = BN_new(); + BN_mul(phi, p_minus_1, q_minus_1, ctx); + + BIGNUM* gcd = BN_new(); + BN_gcd(gcd, pub->n, phi, ctx); + REQUIRE(BN_is_one(gcd)); + + BN_free(p_minus_1); + BN_free(q_minus_1); + BN_free(one); + BN_free(phi); + BN_free(gcd); + BN_CTX_free(ctx); + } + + SECTION("lambda equals phi(N)") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* p_minus_1 = BN_dup(priv->p); + BIGNUM* q_minus_1 = BN_dup(priv->q); + BIGNUM* one = BN_new(); + BN_one(one); + BN_sub(p_minus_1, p_minus_1, one); + BN_sub(q_minus_1, q_minus_1, one); + + BIGNUM* phi = BN_new(); + BN_mul(phi, p_minus_1, q_minus_1, ctx); + + REQUIRE(BN_cmp(priv->lambda, phi) == 0); + + BN_free(p_minus_1); + BN_free(q_minus_1); + BN_free(one); + BN_free(phi); + BN_CTX_free(ctx); + } + + SECTION("mu equals lambda^(-1) mod N") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* product = BN_new(); + BN_mod_mul(product, priv->lambda, priv->mu, pub->n, ctx); + REQUIRE(BN_is_one(product)); + BN_free(product); + BN_CTX_free(ctx); + } + + paillier_free_public_key(pub); + paillier_free_private_key(priv); +} + +TEST_CASE("paillier_fixed_randomness", "[correctness]") +{ + paillier_public_key_t* pub = NULL; + paillier_private_key_t* priv = NULL; + long res = paillier_generate_key_pair(2048, &pub, &priv); + REQUIRE(res == PAILLIER_SUCCESS); + + SECTION("same randomness produces identical ciphertext") + { + // Use internal API to encrypt with explicit randomness + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* msg = BN_new(); + BN_set_word(msg, 42); + + BIGNUM* r = BN_new(); + BN_rand_range(r, pub->n); + // Ensure r is coprime to N + BIGNUM* gcd = BN_new(); + BN_gcd(gcd, r, pub->n, ctx); + while (!BN_is_one(gcd)) + { + BN_rand_range(r, pub->n); + BN_gcd(gcd, r, pub->n, ctx); + } + + BIGNUM* ct1 = BN_new(); + BIGNUM* ct2 = BN_new(); + res = paillier_encrypt_openssl_internal(pub, ct1, r, msg, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + res = paillier_encrypt_openssl_internal(pub, ct2, r, msg, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + + REQUIRE(BN_cmp(ct1, ct2) == 0); + + BN_free(msg); + BN_free(r); + BN_free(gcd); + BN_free(ct1); + BN_free(ct2); + BN_CTX_free(ctx); + } + + SECTION("different randomness produces different ciphertext") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* msg = BN_new(); + BN_set_word(msg, 42); + + BIGNUM* r1 = BN_new(); + BIGNUM* r2 = BN_new(); + BIGNUM* gcd = BN_new(); + + // Generate two different valid randomness values + BN_rand_range(r1, pub->n); + BN_gcd(gcd, r1, pub->n, ctx); + while (!BN_is_one(gcd)) + { + BN_rand_range(r1, pub->n); + BN_gcd(gcd, r1, pub->n, ctx); + } + + do { + BN_rand_range(r2, pub->n); + BN_gcd(gcd, r2, pub->n, ctx); + } while (!BN_is_one(gcd) || BN_cmp(r1, r2) == 0); + + BIGNUM* ct1 = BN_new(); + BIGNUM* ct2 = BN_new(); + res = paillier_encrypt_openssl_internal(pub, ct1, r1, msg, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + res = paillier_encrypt_openssl_internal(pub, ct2, r2, msg, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + + REQUIRE(BN_cmp(ct1, ct2) != 0); + + // Both should decrypt to the same value + BIGNUM* pt1 = BN_new(); + BIGNUM* pt2 = BN_new(); + res = paillier_decrypt_openssl_internal(priv, ct1, pt1, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + res = paillier_decrypt_openssl_internal(priv, ct2, pt2, ctx); + REQUIRE(res == PAILLIER_SUCCESS); + REQUIRE(BN_cmp(pt1, pt2) == 0); + REQUIRE(BN_cmp(pt1, msg) == 0); + + BN_free(msg); + BN_free(r1); + BN_free(r2); + BN_free(gcd); + BN_free(ct1); + BN_free(ct2); + BN_free(pt1); + BN_free(pt2); + BN_CTX_free(ctx); + } + + paillier_free_public_key(pub); + paillier_free_private_key(priv); +} + +TEST_CASE("paillier_keys_no_common_factors", "[statistical]") +{ + // Generate multiple Paillier keys and verify GCD(N_i, N_j) == 1 for all pairs. + // Using 50 keys (1225 pairs) as a practical trade-off — full 1000 keys takes too long. + const int NUM_KEYS = 50; + BIGNUM* moduli[NUM_KEYS]; + + SECTION("50 keys have pairwise coprime moduli") + { + for (int i = 0; i < NUM_KEYS; i++) + { + paillier_public_key_t* pub_i = NULL; + paillier_private_key_t* priv_i = NULL; + REQUIRE(paillier_generate_key_pair(2048, &pub_i, &priv_i) == PAILLIER_SUCCESS); + moduli[i] = BN_dup(pub_i->n); + REQUIRE(moduli[i] != NULL); + paillier_free_public_key(pub_i); + paillier_free_private_key(priv_i); + } + + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* gcd = BN_new(); + + for (int i = 0; i < NUM_KEYS; i++) + { + for (int j = i + 1; j < NUM_KEYS; j++) + { + BN_gcd(gcd, moduli[i], moduli[j], ctx); + REQUIRE(BN_is_one(gcd)); + } + } + + BN_free(gcd); + BN_CTX_free(ctx); + for (int i = 0; i < NUM_KEYS; i++) + { + BN_free(moduli[i]); + } + } +} + +TEST_CASE("paillier_invalid_randomness", "[correctness]") +{ + paillier_public_key_t* pub = NULL; + paillier_private_key_t* priv = NULL; + long res = paillier_generate_key_pair(2048, &pub, &priv); + REQUIRE(res == PAILLIER_SUCCESS); + + SECTION("encrypt with r=0 fails or produces invalid ciphertext") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* msg = BN_new(); + BN_set_word(msg, 42); + BIGNUM* r_zero = BN_new(); + BN_zero(r_zero); + + BIGNUM* ct = BN_new(); + res = paillier_encrypt_openssl_internal(pub, ct, r_zero, msg, ctx); + // r=0 is not coprime to N (gcd(0,N) = N != 1), so encrypt should fail or + // produce a ciphertext that decrypts incorrectly. + if (res == PAILLIER_SUCCESS) + { + // If it doesn't fail, verify that it decrypts incorrectly + BIGNUM* pt = BN_new(); + long dec_res = paillier_decrypt_openssl_internal(priv, ct, pt, ctx); + if (dec_res == PAILLIER_SUCCESS) + { + // r=0 means ciphertext = g^m * 0^N mod N^2 = 0, which shouldn't decrypt to m + // Either way, the test documents the behavior + INFO("r=0 encryption: decrypt returned " << BN_bn2dec(pt) << " for message 42"); + } + BN_free(pt); + } + // If it fails, that's the correct behavior + + BN_free(msg); + BN_free(r_zero); + BN_free(ct); + BN_CTX_free(ctx); + } + + SECTION("encrypt with r=p (not coprime to N) produces bad ciphertext") + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* msg = BN_new(); + BN_set_word(msg, 42); + + // r = p is not coprime to N (gcd(p, N) = p != 1) + BIGNUM* r_p = BN_dup(priv->p); + + BIGNUM* ct = BN_new(); + res = paillier_encrypt_openssl_internal(pub, ct, r_p, msg, ctx); + if (res == PAILLIER_SUCCESS) + { + // Decryption with non-coprime r may return wrong value + BIGNUM* pt = BN_new(); + long dec_res = paillier_decrypt_openssl_internal(priv, ct, pt, ctx); + if (dec_res == PAILLIER_SUCCESS) + { + // With r=p, the Paillier decryption may still work since the L function + // only depends on m, not on r. But this is implementation-specific. + INFO("r=p encryption: decrypt returned " << BN_bn2dec(pt) << " for message 42"); + } + BN_free(pt); + } + + BN_free(msg); + BN_free(r_p); + BN_free(ct); + BN_CTX_free(ctx); + } + + paillier_free_public_key(pub); + paillier_free_private_key(priv); +} diff --git a/test/crypto/paillier_commitment/tests.cpp b/test/crypto/paillier_commitment/tests.cpp index 62caf7c..26500ad 100644 --- a/test/crypto/paillier_commitment/tests.cpp +++ b/test/crypto/paillier_commitment/tests.cpp @@ -367,17 +367,17 @@ TEST_CASE( "ZKP", "paillier_commitment") const uint8_t aad[] = "SOME AAD"; uint32_t real_proof_len = 0; - auto res = paillier_commitment_damgard_fujisaki_parameters_zkp_generate(priv, aad, sizeof(aad), 25, NULL, 0, &real_proof_len); + auto res = paillier_commitment_damgard_fujisaki_parameters_zkp_generate(priv, aad, sizeof(aad), NULL, 0, &real_proof_len); REQUIRE(ZKP_INSUFFICIENT_BUFFER == res); uint8_t* serialized_proof = (uint8_t*)malloc(real_proof_len); uint32_t actual_proff_len = 0; - res = paillier_commitment_damgard_fujisaki_parameters_zkp_generate(priv, aad, sizeof(aad), 25, serialized_proof, real_proof_len, &actual_proff_len); + res = paillier_commitment_damgard_fujisaki_parameters_zkp_generate(priv, aad, sizeof(aad), serialized_proof, real_proof_len, &actual_proff_len); REQUIRE(ZKP_SUCCESS == res); REQUIRE(actual_proff_len == real_proof_len); - res = paillier_commitment_damgard_fujisaki_parameters_zkp_verify(pub, aad, sizeof(aad), 25, serialized_proof, real_proof_len); + res = paillier_commitment_damgard_fujisaki_parameters_zkp_verify(pub, aad, sizeof(aad), serialized_proof, real_proof_len); REQUIRE(ZKP_SUCCESS == res); free(serialized_proof); } diff --git a/test/crypto/pedersen_commitment/CMakeLists.txt b/test/crypto/pedersen_commitment/CMakeLists.txt index fa71be8..6797a12 100644 --- a/test/crypto/pedersen_commitment/CMakeLists.txt +++ b/test/crypto/pedersen_commitment/CMakeLists.txt @@ -1,5 +1,6 @@ add_executable(pedersen_commitment_test tests.cpp + ${PROJECT_SOURCE_DIR}/src/common/crypto/commitments/ring_pedersen.c ) target_compile_options(pedersen_commitment_test PRIVATE -Wall -Wextra) diff --git a/test/crypto/pedersen_commitment/tests.cpp b/test/crypto/pedersen_commitment/tests.cpp index 879a46f..d18ebfb 100644 --- a/test/crypto/pedersen_commitment/tests.cpp +++ b/test/crypto/pedersen_commitment/tests.cpp @@ -1,10 +1,15 @@ #include "crypto/commitments/pedersen.h" +#include "crypto/commitments/ring_pedersen.h" +#include "../../../src/common/crypto/commitments/ring_pedersen_internal.h" #include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include +#include +#include #include -TEST_CASE("test_pedersen_commitmnet") +TEST_CASE("test_pedersen_commitment") { const uint8_t aad[] = "SOME RANDOM AAD"; const uint32_t aad_len = sizeof(aad); @@ -107,3 +112,447 @@ TEST_CASE("test_pedersen_commitmnet") ctx->release(ctx); } } + +TEST_CASE("pedersen_commitment_attacks") +{ + const uint8_t aad[] = "SOME RANDOM AAD"; + const uint32_t aad_len = sizeof(aad); + + SECTION("zero scalar a") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + memset(a, 0, sizeof(a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + commitments_status create_ret = pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx); + if (create_ret == COMMITMENTS_SUCCESS) + { + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("zero scalar b") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &a)); + memset(b, 0, sizeof(b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + commitments_status create_ret = pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx); + if (create_ret == COMMITMENTS_SUCCESS) + { + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("both zero scalars") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + memset(a, 0, sizeof(a)); + memset(b, 0, sizeof(b)); + memset(c, 0, sizeof(c)); + + elliptic_curve_commitment_t commitment; + commitments_status create_ret = pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx); + if (create_ret == COMMITMENTS_SUCCESS) + { + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("order scalar a") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + const uint8_t *order = ctx->order(ctx); + memcpy(a, order, sizeof(a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + commitments_status create_ret = pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx); + // Scalar equal to the curve order is equivalent to zero mod n. + // Either the implementation rejects it or it produces a valid commitment for zero. + if (create_ret == COMMITMENTS_SUCCESS) + { + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("infinity point in base h") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + // Create a valid commitment first + elliptic_curve_commitment_t commitment; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + // Tamper base.h with the infinity point + pedersen_commitment_two_generators_t tampered_base; + memcpy(&tampered_base, &base, sizeof(base)); + const elliptic_curve256_point_t *inf = ctx->infinity_point(ctx); + memcpy(tampered_base.h, *inf, sizeof(elliptic_curve256_point_t)); + + // Verification with tampered base must fail + REQUIRE(COMMITMENTS_SUCCESS != pedersen_commitment_two_generators_verify_commitment(&commitment, &tampered_base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("infinity point in base f") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + // Create a valid commitment first + elliptic_curve_commitment_t commitment; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + // Tamper base.f with the infinity point + pedersen_commitment_two_generators_t tampered_base; + memcpy(&tampered_base, &base, sizeof(base)); + const elliptic_curve256_point_t *inf = ctx->infinity_point(ctx); + memcpy(tampered_base.f, *inf, sizeof(elliptic_curve256_point_t)); + + // Verification with tampered base must fail + REQUIRE(COMMITMENTS_SUCCESS != pedersen_commitment_two_generators_verify_commitment(&commitment, &tampered_base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("negated commitment") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + // Verify the original commitment is valid + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + // Flip the parity byte (0x02 <-> 0x03) to negate the point + elliptic_curve_commitment_t negated_commitment; + memcpy(negated_commitment, commitment, sizeof(negated_commitment)); + if (negated_commitment[0] == 0x02) + negated_commitment[0] = 0x03; + else if (negated_commitment[0] == 0x03) + negated_commitment[0] = 0x02; + + REQUIRE(COMMITMENTS_SUCCESS != pedersen_commitment_two_generators_verify_commitment(&negated_commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("cross-curve verification") + { + elliptic_curve256_algebra_ctx_t *ctx_k1 = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx_k1 != NULL); + elliptic_curve256_algebra_ctx_t *ctx_r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(ctx_r1 != NULL); + + pedersen_commitment_two_generators_t base_k1; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base_k1, aad, aad_len, ctx_k1)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx_k1->rand(ctx_k1, &a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx_k1->rand(ctx_k1, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx_k1->rand(ctx_k1, &c)); + + elliptic_curve_commitment_t commitment; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_create_commitment(&commitment, &base_k1, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx_k1)); + + // Generate base on secp256r1 with the same AAD + pedersen_commitment_two_generators_t base_r1; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base_r1, aad, aad_len, ctx_r1)); + + // Verification on a different curve must fail + REQUIRE(COMMITMENTS_SUCCESS != pedersen_commitment_two_generators_verify_commitment(&commitment, &base_r1, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx_r1)); + + elliptic_curve256_algebra_ctx_free(ctx_k1); + elliptic_curve256_algebra_ctx_free(ctx_r1); + } + + SECTION("random byte corruption in commitment") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + + // Corrupt multiple random bytes and check each time + uint8_t point_size = ctx->point_size(ctx); + for (int trial = 0; trial < 10; trial++) + { + elliptic_curve_commitment_t corrupted; + memcpy(corrupted, commitment, sizeof(corrupted)); + + // Pick a random byte position within the point and XOR with a non-zero value + uint32_t pos; + RAND_bytes((uint8_t *)&pos, sizeof(pos)); + pos %= point_size; + uint8_t xor_val = 0; + while (xor_val == 0) + { + RAND_bytes(&xor_val, 1); + } + corrupted[pos] ^= xor_val; + + REQUIRE(COMMITMENTS_SUCCESS != pedersen_commitment_two_generators_verify_commitment(&corrupted, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("STARK curve zero scalar") + { + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_stark_algebra(); + REQUIRE(ctx != NULL); + + pedersen_commitment_two_generators_t base; + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_base_generate(&base, aad, aad_len, ctx)); + + elliptic_curve256_scalar_t a, b, c; + memset(a, 0, sizeof(a)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &b)); + REQUIRE(ELLIPTIC_CURVE_ALGEBRA_SUCCESS == ctx->rand(ctx, &c)); + + elliptic_curve_commitment_t commitment; + commitments_status create_ret = pedersen_commitment_two_generators_create_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx); + if (create_ret == COMMITMENTS_SUCCESS) + { + REQUIRE(COMMITMENTS_SUCCESS == pedersen_commitment_two_generators_verify_commitment(&commitment, &base, a, sizeof(a), b, sizeof(b), c, sizeof(c), ctx)); + } + + elliptic_curve256_algebra_ctx_free(ctx); + } +} + +TEST_CASE("ring_pedersen_key_validity", "[correctness]") +{ + ring_pedersen_public_t* pub = NULL; + ring_pedersen_private_t* priv = NULL; + auto status = ring_pedersen_generate_key_pair(1024, &pub, &priv); + REQUIRE(status == RING_PEDERSEN_SUCCESS); + + SECTION("N is the product of two factors via phi_n consistency") + { + // phi_n should be (p-1)*(q-1) for some p, q where N = p*q + // We verify: N > phi_n (since N = p*q > (p-1)*(q-1)) + // and phi_n > 0 + REQUIRE(!BN_is_zero(priv->phi_n)); + REQUIRE(BN_cmp(pub->n, priv->phi_n) > 0); + } + + SECTION("N has requested bit size") + { + int n_bits = BN_num_bits(pub->n); + // key_len=1024 means N is ~1024 bits (p and q are each ~512 bits) + REQUIRE(n_bits >= 1023); + REQUIRE(n_bits <= 1024); + } + + SECTION("s is not 1 mod N") + { + REQUIRE(!BN_is_one(pub->s)); + } + + SECTION("t is not 1 mod N") + { + REQUIRE(!BN_is_one(pub->t)); + } + + SECTION("s != t") + { + REQUIRE(BN_cmp(pub->s, pub->t) != 0); + } + + SECTION("lambda is nonzero") + { + REQUIRE(!BN_is_zero(priv->lambda)); + } + + SECTION("t^lambda = s mod N") + { + // The Ring-Pedersen relation: s = t^lambda mod N + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* computed_s = BN_new(); + BN_mod_exp(computed_s, pub->t, priv->lambda, pub->n, ctx); + REQUIRE(BN_cmp(computed_s, pub->s) == 0); + BN_free(computed_s); + BN_CTX_free(ctx); + } + + ring_pedersen_free_public(pub); + ring_pedersen_free_private(priv); +} + +TEST_CASE("ring_pedersen_homomorphic", "[correctness]") +{ + ring_pedersen_public_t* pub = NULL; + ring_pedersen_private_t* priv = NULL; + auto status = ring_pedersen_generate_key_pair(1024, &pub, &priv); + REQUIRE(status == RING_PEDERSEN_SUCCESS); + + SECTION("Commit(x1,r1) * Commit(x2,r2) = Commit(x1+x2, r1+r2) mod N") + { + BN_CTX* ctx = BN_CTX_new(); + + // Generate random x1, r1, x2, r2 + BIGNUM* x1 = BN_new(); + BIGNUM* r1 = BN_new(); + BIGNUM* x2 = BN_new(); + BIGNUM* r2 = BN_new(); + BN_rand(x1, 256, -1, 0); + BN_rand(r1, 256, -1, 0); + BN_rand(x2, 256, -1, 0); + BN_rand(r2, 256, -1, 0); + + // Commit(x1, r1) = s^x1 * t^r1 mod N + BIGNUM* c1 = BN_new(); + REQUIRE(ring_pedersen_create_commitment_internal(pub, x1, r1, c1, ctx) == RING_PEDERSEN_SUCCESS); + + // Commit(x2, r2) = s^x2 * t^r2 mod N + BIGNUM* c2 = BN_new(); + REQUIRE(ring_pedersen_create_commitment_internal(pub, x2, r2, c2, ctx) == RING_PEDERSEN_SUCCESS); + + // Product: c1 * c2 mod N + BIGNUM* product = BN_new(); + BN_mod_mul(product, c1, c2, pub->n, ctx); + + // Commit(x1+x2, r1+r2) + BIGNUM* x_sum = BN_new(); + BIGNUM* r_sum = BN_new(); + BN_add(x_sum, x1, x2); + BN_add(r_sum, r1, r2); + BIGNUM* c_sum = BN_new(); + REQUIRE(ring_pedersen_create_commitment_internal(pub, x_sum, r_sum, c_sum, ctx) == RING_PEDERSEN_SUCCESS); + + REQUIRE(BN_cmp(product, c_sum) == 0); + + BN_free(x1); + BN_free(r1); + BN_free(x2); + BN_free(r2); + BN_free(c1); + BN_free(c2); + BN_free(product); + BN_free(x_sum); + BN_free(r_sum); + BN_free(c_sum); + BN_CTX_free(ctx); + } + + ring_pedersen_free_public(pub); + ring_pedersen_free_private(priv); +} + +TEST_CASE("ring_pedersen_keys_no_common_factors", "[statistical]") +{ + // Generate multiple Ring-Pedersen keys and verify GCD(N_i, N_j) == 1 for all pairs. + const int NUM_KEYS = 50; + + SECTION("50 keys have pairwise coprime moduli") + { + BIGNUM* moduli[50]; + + for (int i = 0; i < NUM_KEYS; i++) + { + ring_pedersen_public_t* pub_i = NULL; + ring_pedersen_private_t* s = NULL; + REQUIRE(ring_pedersen_generate_key_pair(1024, &pub_i, &s) == RING_PEDERSEN_SUCCESS); + moduli[i] = BN_dup(pub_i->n); + REQUIRE(moduli[i] != NULL); + ring_pedersen_free_public(pub_i); + ring_pedersen_free_private(s); + } + + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* gcd = BN_new(); + + for (int i = 0; i < NUM_KEYS; i++) + { + for (int j = i + 1; j < NUM_KEYS; j++) + { + BN_gcd(gcd, moduli[i], moduli[j], ctx); + REQUIRE(BN_is_one(gcd)); + } + } + + BN_free(gcd); + BN_CTX_free(ctx); + for (int i = 0; i < NUM_KEYS; i++) + { + BN_free(moduli[i]); + } + } +} diff --git a/test/crypto/secp256k1_algebra/tests.cpp b/test/crypto/secp256k1_algebra/tests.cpp index c5a5985..7a076cf 100644 --- a/test/crypto/secp256k1_algebra/tests.cpp +++ b/test/crypto/secp256k1_algebra/tests.cpp @@ -487,10 +487,9 @@ TEST_CASE( "secp256k1_algebra_add_scalars", "zkp") { GFp_curve_algebra_ctx_free(ctx); } - TEST_CASE( "reduce" ) { elliptic_curve256_algebra_ctx_t* secp256k1 = elliptic_curve256_new_secp256k1_algebra(); - + SECTION("basic") { REQUIRE(secp256k1); elliptic_curve256_scalar_t a; @@ -505,7 +504,7 @@ TEST_CASE( "reduce" ) { BN_CTX* bn_ctx = BN_CTX_new(); BN_CTX_start(bn_ctx); BIGNUM* bn_a = BN_CTX_get(bn_ctx); - + for (size_t i = 0; i < 1024; i++) { REQUIRE(RAND_bytes(a, sizeof(a))); @@ -552,4 +551,948 @@ TEST_CASE( "point_projection" ) { REQUIRE(overflow == 1); REQUIRE(memcmp(x_val, two, sizeof(elliptic_curve256_scalar_t)) == 0); GFp_curve_algebra_ctx_free(secp256k1); +} + +TEST_CASE( "hash_on_curve" ) { + elliptic_curve256_point_t res; + elliptic_curve256_point_t res2; + uint8_t msg[] = "Some example message"; + + SECTION("secp256k1") { + elliptic_curve256_algebra_ctx_t* secp256k1 = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(secp256k1); + REQUIRE(secp256k1->hash_on_curve(secp256k1, &res, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256k1->add_points(secp256k1, &res2, &res, &res) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256k1->hash_on_curve(secp256k1, &res2, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + + REQUIRE(secp256k1->hash_on_curve(secp256k1, &res2, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256k1->hash_on_curve(secp256k1, &res, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + elliptic_curve256_algebra_ctx_free(secp256k1); + } + + SECTION("secp256r1") { + elliptic_curve256_algebra_ctx_t* secp256r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(secp256r1); + REQUIRE(secp256r1->hash_on_curve(secp256r1, &res, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256r1->add_points(secp256r1, &res2, &res, &res) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256r1->hash_on_curve(secp256r1, &res2, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + + REQUIRE(secp256r1->hash_on_curve(secp256r1, &res2, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256r1->hash_on_curve(secp256r1, &res, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + elliptic_curve256_algebra_ctx_free(secp256r1); + } + + SECTION("stark") { + elliptic_curve256_algebra_ctx_t* stark = elliptic_curve256_new_stark_algebra(); + REQUIRE(stark); + REQUIRE(stark->hash_on_curve(stark, &res, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(stark->add_points(stark, &res2, &res, &res) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(stark->hash_on_curve(stark, &res2, msg, sizeof(msg)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + + REQUIRE(stark->hash_on_curve(stark, &res2, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(stark->hash_on_curve(stark, &res, NULL, 0) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(res, res2, sizeof(elliptic_curve256_point_t)) == 0); + elliptic_curve256_algebra_ctx_free(stark); + } + +} + +TEST_CASE("hash_on_curve_test_vectors") { + /* 11 precomputed test vectors per curve. + * If any hash-to-curve implementation changes, these will fail — that is + * intentional. Update the vectors only after reviewing the change. */ + + struct test_input { + const uint8_t *data; + uint32_t len; + const char *label; + }; + + const uint8_t byte_00 = 0x00; + const uint8_t byte_01 = 0x01; + const uint8_t byte_ff = 0xff; + const uint8_t abc[] = "abc"; + const uint8_t test_msg[] = "test"; + const uint8_t example[] = "Some example message"; + uint8_t zeros32[32]; memset(zeros32, 0x00, 32); + uint8_t ffs32[32]; memset(ffs32, 0xff, 32); + uint8_t incr32[32]; for (int i = 0; i < 32; i++) incr32[i] = (uint8_t)i; + const uint8_t fox[] = "The quick brown fox jumps over the lazy dog"; + + const test_input inputs[] = { + { NULL, 0, "NULL_empty" }, + { &byte_00, 1, "byte_0x00" }, + { &byte_01, 1, "byte_0x01" }, + { &byte_ff, 1, "byte_0xFF" }, + { abc, sizeof(abc), "abc_with_null" }, + { test_msg, sizeof(test_msg), "test_with_null" }, + { example, sizeof(example), "example_msg" }, + { zeros32, 32, "32_zero_bytes" }, + { ffs32, 32, "32_0xFF_bytes" }, + { incr32, 32, "32_incr_bytes" }, + { fox, sizeof(fox), "fox_with_null" }, + }; + const int N = sizeof(inputs) / sizeof(inputs[0]); + + SECTION("secp256k1") { + static const elliptic_curve256_point_t expected[] = { + {0x03, 0xbd, 0x0f, 0x07, 0x23, 0xbf, 0xba, 0x27, 0xe7, 0xd8, 0x1e, + 0x75, 0x5c, 0xb5, 0x85, 0x6e, 0x1c, 0xa2, 0x96, 0x7e, 0xda, 0x12, + 0x90, 0x92, 0x37, 0xe0, 0xe2, 0x7d, 0xc5, 0xd2, 0x2a, 0x6f, 0x49}, + {0x02, 0x14, 0x29, 0xa2, 0xce, 0x8f, 0x50, 0x03, 0xa2, 0x5b, 0x12, + 0x51, 0x0f, 0xdf, 0x2a, 0x40, 0x25, 0x5e, 0x7b, 0x28, 0xb9, 0xcf, + 0xdd, 0xb0, 0x5d, 0x95, 0xd0, 0x6d, 0xc4, 0x33, 0x95, 0x06, 0xbf}, + {0x03, 0xcb, 0xe4, 0xde, 0x92, 0x50, 0x1a, 0x04, 0x38, 0x83, 0x44, + 0xd0, 0x07, 0x12, 0x7a, 0x7e, 0xab, 0xe1, 0x95, 0x39, 0x24, 0xd8, + 0x6f, 0x03, 0x9d, 0x27, 0x26, 0xe1, 0xcf, 0x16, 0xf5, 0xef, 0x5d}, + {0x02, 0x4d, 0xd1, 0x5a, 0x49, 0x91, 0x44, 0x0f, 0x30, 0x76, 0x6d, + 0xbe, 0x08, 0x4e, 0x0a, 0xbb, 0x61, 0x58, 0x38, 0x77, 0xb0, 0x7c, + 0x44, 0xa3, 0x25, 0xb3, 0x3c, 0xb8, 0x6e, 0x5e, 0x7f, 0xa9, 0x32}, + {0x02, 0x3c, 0x2d, 0xd0, 0x34, 0xcf, 0x14, 0xf7, 0x8f, 0xa9, 0xbd, + 0x18, 0xc6, 0x58, 0x52, 0x2e, 0x7a, 0xc1, 0x65, 0xd4, 0x0c, 0xfa, + 0x50, 0x60, 0xae, 0x64, 0x7b, 0x60, 0x0c, 0x65, 0xbd, 0xa8, 0x17}, + {0x02, 0x4d, 0x4f, 0xbb, 0x70, 0x45, 0xc5, 0x63, 0x2d, 0xa2, 0x3c, + 0xae, 0xe7, 0x30, 0xa9, 0x28, 0x4f, 0xa2, 0xf9, 0x72, 0xaa, 0x5a, + 0xd1, 0xc6, 0x61, 0x01, 0xa4, 0xcb, 0x7c, 0xae, 0x6f, 0xc9, 0xa1}, + {0x02, 0x19, 0x2e, 0x28, 0xdf, 0x0d, 0xb5, 0xee, 0xec, 0xea, 0x7e, + 0x5b, 0x21, 0xf9, 0x55, 0xf6, 0x38, 0x85, 0x23, 0x87, 0xf6, 0x5a, + 0x4d, 0xaf, 0x7f, 0x87, 0xe7, 0xb5, 0x3d, 0x75, 0xb6, 0x6e, 0x38}, + {0x02, 0x43, 0xcf, 0xdf, 0x48, 0x6e, 0x6e, 0x50, 0x02, 0x17, 0xee, + 0x01, 0x45, 0x8e, 0x72, 0xac, 0xdd, 0x41, 0xfb, 0x7e, 0x2e, 0xc1, + 0x2d, 0x8a, 0xf6, 0xee, 0x51, 0xb5, 0x2c, 0x10, 0x9e, 0x1c, 0x7d}, + {0x03, 0x08, 0x7a, 0xbe, 0x83, 0x9a, 0x59, 0xdb, 0x64, 0x9f, 0xa4, + 0xaa, 0x86, 0x7c, 0xb6, 0x69, 0x7f, 0x97, 0x7e, 0x67, 0x76, 0x4d, + 0x4a, 0x00, 0x2c, 0xba, 0xce, 0x0f, 0x98, 0x2b, 0x0b, 0xd5, 0x9e}, + {0x03, 0x4b, 0x91, 0xe5, 0x8a, 0xd1, 0x50, 0x65, 0xee, 0x65, 0x81, + 0x35, 0x24, 0x98, 0x14, 0xf8, 0x0e, 0xf3, 0x1d, 0x8d, 0x36, 0xb3, + 0xb8, 0x90, 0x6e, 0xbf, 0x41, 0xc7, 0x0b, 0xa8, 0x28, 0x90, 0x78}, + {0x02, 0xee, 0x70, 0x67, 0x41, 0x2e, 0x28, 0x30, 0x1a, 0x2a, 0x6c, + 0x15, 0x66, 0x33, 0x92, 0x61, 0xa4, 0xf1, 0x3f, 0x55, 0x4f, 0x8f, + 0xa2, 0x07, 0x14, 0xc5, 0x87, 0x78, 0xf5, 0x29, 0x2f, 0xc5, 0xfe}, + }; + + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(ctx); + elliptic_curve256_point_t result; + for (int i = 0; i < N; i++) { + INFO("secp256k1 vector " << i << " (" << inputs[i].label << ")"); + REQUIRE(ctx->hash_on_curve(ctx, &result, inputs[i].data, inputs[i].len) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, expected[i], sizeof(elliptic_curve256_point_t)) == 0); + } + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("secp256r1") { + static const elliptic_curve256_point_t expected[] = { + {0x03, 0xe0, 0x8f, 0xbc, 0x74, 0x13, 0x4f, 0x74, 0xb5, 0xce, 0x16, + 0xc2, 0x44, 0x06, 0xa6, 0x36, 0x9f, 0x87, 0x44, 0xad, 0xff, 0xe8, + 0xf1, 0xb2, 0x1e, 0x1e, 0x4e, 0xaf, 0x2d, 0x33, 0xb7, 0x28, 0x14}, + {0x03, 0x8f, 0x03, 0xb3, 0x9e, 0x1d, 0x26, 0x7d, 0xd4, 0xd2, 0x07, + 0x74, 0x72, 0xee, 0x2d, 0xe9, 0x34, 0xc9, 0xbf, 0x1a, 0xb6, 0x25, + 0xb7, 0xee, 0x78, 0x6c, 0x26, 0xb1, 0xdb, 0x2b, 0xc8, 0xb9, 0xb5}, + {0x03, 0xdc, 0xfb, 0x2d, 0x45, 0xc0, 0x71, 0x58, 0xa1, 0xff, 0x68, + 0x26, 0xcc, 0x84, 0xd6, 0x08, 0x9f, 0x85, 0x6b, 0x79, 0xa9, 0x23, + 0x40, 0x60, 0xc3, 0x66, 0xf7, 0x64, 0x18, 0x34, 0x7b, 0x0f, 0x8e}, + {0x02, 0xc2, 0xfc, 0x30, 0x3f, 0x8f, 0xf4, 0x6b, 0x01, 0xf7, 0x41, + 0xd0, 0xbd, 0x16, 0x61, 0x34, 0x09, 0xd1, 0xc0, 0xdc, 0x19, 0xc1, + 0x78, 0x27, 0x1d, 0x76, 0xb7, 0x30, 0x06, 0xa5, 0x0e, 0xf9, 0x49}, + {0x02, 0x22, 0x7c, 0x47, 0x93, 0x68, 0xed, 0xa7, 0x10, 0x60, 0x53, + 0xd1, 0x38, 0x8f, 0x06, 0xd8, 0x14, 0xe8, 0x23, 0xb6, 0xa0, 0xd9, + 0x25, 0x98, 0x63, 0xdc, 0x14, 0x97, 0x80, 0x8e, 0xa5, 0x04, 0x58}, + {0x02, 0x41, 0x77, 0x62, 0x3e, 0xbc, 0x8e, 0x22, 0x13, 0x2a, 0x95, + 0x78, 0xb4, 0x0a, 0x92, 0xba, 0x32, 0xef, 0x5b, 0x20, 0x77, 0x59, + 0x2d, 0xe8, 0x11, 0xb1, 0xd7, 0x2b, 0x40, 0xcb, 0x65, 0x6e, 0x90}, + {0x03, 0xdf, 0x5a, 0xa7, 0x28, 0x32, 0xe5, 0x6b, 0x0b, 0x7e, 0x13, + 0x24, 0x15, 0xc9, 0x61, 0x37, 0x26, 0xb2, 0xbd, 0x63, 0x5e, 0x50, + 0x32, 0xa1, 0x17, 0x77, 0xef, 0x58, 0x30, 0x6a, 0x78, 0xa3, 0x59}, + {0x03, 0x8d, 0x43, 0xa2, 0x4e, 0xad, 0xf4, 0xd2, 0x84, 0x85, 0xc5, + 0x73, 0xf2, 0xe5, 0xc0, 0xcd, 0x92, 0xb2, 0x14, 0x04, 0x9f, 0x13, + 0x52, 0xac, 0x1f, 0xd6, 0xce, 0x09, 0x8c, 0xd6, 0x8f, 0x9f, 0xee}, + {0x03, 0xd3, 0xd4, 0x94, 0xfa, 0x32, 0x29, 0xe1, 0xc4, 0x06, 0x48, + 0x2a, 0x37, 0x5c, 0x0f, 0xf9, 0x86, 0x88, 0xb3, 0xb6, 0x7f, 0x9b, + 0x0f, 0x99, 0xca, 0xbe, 0xef, 0xa4, 0x76, 0xf4, 0xba, 0x9a, 0x46}, + {0x03, 0x59, 0x00, 0xcc, 0xcd, 0xb4, 0x5b, 0x28, 0x05, 0x53, 0xe8, + 0x2c, 0x19, 0xfe, 0x97, 0x08, 0x2c, 0x5c, 0x3e, 0xb0, 0xc7, 0xa6, + 0x01, 0x74, 0xda, 0x58, 0xac, 0x1a, 0xee, 0x5e, 0x8a, 0x98, 0x41}, + {0x02, 0xaf, 0xf7, 0xd7, 0x76, 0x2e, 0xc5, 0x38, 0xd9, 0xc8, 0x78, + 0xa9, 0x95, 0xe1, 0x85, 0xda, 0x8a, 0x92, 0xe7, 0xa6, 0xd6, 0xcf, + 0xc5, 0x3e, 0x7c, 0xef, 0x70, 0xe7, 0x29, 0x3d, 0x81, 0xbf, 0xca}, + }; + + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(ctx); + elliptic_curve256_point_t result; + for (int i = 0; i < N; i++) { + INFO("secp256r1 vector " << i << " (" << inputs[i].label << ")"); + REQUIRE(ctx->hash_on_curve(ctx, &result, inputs[i].data, inputs[i].len) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, expected[i], sizeof(elliptic_curve256_point_t)) == 0); + } + elliptic_curve256_algebra_ctx_free(ctx); + } + + SECTION("stark") { + static const elliptic_curve256_point_t expected[] = { + {0x03, 0x01, 0xa6, 0xff, 0x5a, 0x5a, 0xfe, 0x6d, 0x00, 0x07, 0xb5, + 0x0a, 0xad, 0x43, 0x7f, 0x80, 0x69, 0x98, 0x18, 0x77, 0xb2, 0xdf, + 0x89, 0x3e, 0xa4, 0xfc, 0x1a, 0x31, 0xb4, 0xbe, 0xd9, 0x1b, 0xba}, + {0x02, 0x06, 0xd0, 0xde, 0x30, 0x93, 0x44, 0x30, 0x49, 0x18, 0x7c, + 0xdd, 0x74, 0x49, 0xcb, 0xec, 0xf6, 0xc9, 0x60, 0xe9, 0xc7, 0xcb, + 0xbd, 0xa0, 0xd2, 0xa9, 0xd0, 0x75, 0x14, 0x24, 0x74, 0x18, 0x18}, + {0x03, 0x07, 0xb0, 0x13, 0xac, 0xe3, 0x1f, 0x1d, 0xee, 0x0e, 0x6f, + 0xe2, 0xac, 0xcf, 0x37, 0x46, 0xc6, 0xbf, 0x3c, 0xc1, 0x70, 0x45, + 0x92, 0xb9, 0x0c, 0x12, 0x49, 0xac, 0x70, 0x1e, 0x6a, 0x84, 0xdf}, + {0x03, 0x06, 0x4c, 0xb9, 0xe1, 0x92, 0xb4, 0xca, 0x89, 0x8c, 0xbe, + 0xd9, 0x95, 0x64, 0x53, 0x9e, 0xe2, 0x8c, 0x68, 0x92, 0xcf, 0x8d, + 0x39, 0xd2, 0x07, 0x86, 0xec, 0xe0, 0xbd, 0xb5, 0x64, 0x18, 0x0b}, + {0x02, 0x07, 0xc6, 0xad, 0x8a, 0xd0, 0xd2, 0x94, 0xa1, 0x69, 0xa9, + 0xf8, 0x05, 0x74, 0xbd, 0x79, 0x5a, 0xb7, 0x52, 0x1e, 0x70, 0x76, + 0x82, 0xba, 0x8f, 0xea, 0x7e, 0x9e, 0x44, 0xc3, 0x1b, 0x1a, 0x2a}, + {0x02, 0x02, 0x6b, 0x77, 0x76, 0xc5, 0x06, 0x71, 0xba, 0xe2, 0x1b, + 0xe5, 0x6e, 0xa0, 0xa8, 0xfa, 0x98, 0xa3, 0xd8, 0xd3, 0x0c, 0xf9, + 0x7c, 0x66, 0x65, 0x6d, 0xa9, 0xd8, 0xdd, 0xa6, 0x67, 0x06, 0x19}, + {0x02, 0x07, 0xfc, 0x93, 0x46, 0xaf, 0x07, 0x50, 0x11, 0x07, 0x3c, + 0x8d, 0x16, 0xcd, 0xca, 0xda, 0x63, 0x91, 0x86, 0xb0, 0x47, 0x0d, + 0x6b, 0x60, 0xd4, 0xcc, 0x22, 0xeb, 0xf3, 0xcb, 0x8f, 0x6f, 0xe0}, + {0x03, 0x03, 0xef, 0x04, 0x67, 0xd0, 0xc0, 0x01, 0x93, 0xe1, 0xa1, + 0x04, 0x4c, 0x94, 0x85, 0x77, 0x2f, 0xec, 0x32, 0x91, 0xf1, 0xed, + 0xfb, 0xe0, 0x8a, 0x16, 0x95, 0xff, 0xd8, 0x28, 0xe7, 0x8a, 0xcb}, + {0x02, 0x00, 0x79, 0xe7, 0xec, 0xf8, 0x0f, 0xbb, 0x6e, 0x13, 0x36, + 0x0c, 0xa8, 0x42, 0x84, 0x3f, 0xab, 0xd6, 0xf8, 0x42, 0xd0, 0x0b, + 0xf6, 0x8a, 0xc2, 0x79, 0x98, 0xe4, 0xe5, 0x09, 0xc8, 0x5e, 0x31}, + {0x03, 0x04, 0x29, 0xaf, 0xe7, 0x57, 0x6e, 0x30, 0x79, 0xac, 0x22, + 0x81, 0x33, 0x81, 0x5a, 0xc7, 0x86, 0x43, 0x8b, 0xfe, 0x8e, 0x40, + 0xf3, 0xe9, 0x4c, 0x0d, 0x59, 0x11, 0x19, 0x77, 0x69, 0x6a, 0x65}, + {0x02, 0x05, 0xc6, 0xa2, 0x17, 0x18, 0x8b, 0x54, 0xac, 0xa5, 0xfd, + 0x4a, 0xe9, 0x5d, 0x7c, 0x94, 0xcb, 0xb0, 0xf4, 0x39, 0x9c, 0x0e, + 0xaf, 0x7a, 0x0f, 0x56, 0x49, 0x95, 0x55, 0xfe, 0x8a, 0x3f, 0xbc}, + }; + + elliptic_curve256_algebra_ctx_t *ctx = elliptic_curve256_new_stark_algebra(); + REQUIRE(ctx); + elliptic_curve256_point_t result; + for (int i = 0; i < N; i++) { + INFO("stark vector " << i << " (" << inputs[i].label << ")"); + REQUIRE(ctx->hash_on_curve(ctx, &result, inputs[i].data, inputs[i].len) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, expected[i], sizeof(elliptic_curve256_point_t)) == 0); + } + elliptic_curve256_algebra_ctx_free(ctx); + } +} + +TEST_CASE("validate_non_infinity_point (GFp curves)", "secp256k1") { + SECTION("secp256k1: rejects infinity encoding (0x00 + garbage)") { + elliptic_curve256_algebra_ctx_t* secp256k1 = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(secp256k1); + REQUIRE(secp256k1->validate_non_infinity_point); + + elliptic_curve256_point_t inf_garbage; + memset(inf_garbage, 0xA5, sizeof(inf_garbage)); + inf_garbage[0] = 0x00; + REQUIRE(secp256k1->validate_non_infinity_point(secp256k1, &inf_garbage) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_point_t p; + elliptic_curve256_scalar_t one = {0}; + one[31] = 1; + REQUIRE(secp256k1->generator_mul(secp256k1, &p, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t sum; + REQUIRE(secp256k1->add_points(secp256k1, &sum, &inf_garbage, &p) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(sum, p, sizeof(elliptic_curve256_point_t)) == 0); + + elliptic_curve256_algebra_ctx_free(secp256k1); + } + + SECTION("secp256k1: rejects invalid encodings (bad prefix / out-of-range X)") { + elliptic_curve256_algebra_ctx_t* secp256k1 = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(secp256k1); + REQUIRE(secp256k1->validate_non_infinity_point); + + elliptic_curve256_point_t bad_prefix; + memset(bad_prefix, 0x11, sizeof(bad_prefix)); + bad_prefix[0] = 0x04; // uncompressed prefix but we only provide 33 bytes -> must be invalid + REQUIRE(secp256k1->validate_non_infinity_point(secp256k1, &bad_prefix) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_point_t x_too_large; + memset(x_too_large, 0xFF, sizeof(x_too_large)); + x_too_large[0] = 0x02; // "compressed, even y" with X >= p -> invalid + REQUIRE(secp256k1->validate_non_infinity_point(secp256k1, &x_too_large) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_algebra_ctx_free(secp256k1); + } + + SECTION("secp256r1: rejects infinity encoding (0x00 + garbage)") { + elliptic_curve256_algebra_ctx_t* secp256r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(secp256r1); + REQUIRE(secp256r1->validate_non_infinity_point); + + elliptic_curve256_point_t inf_garbage; + memset(inf_garbage, 0x5A, sizeof(inf_garbage)); + inf_garbage[0] = 0x00; + REQUIRE(secp256r1->validate_non_infinity_point(secp256r1, &inf_garbage) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_algebra_ctx_free(secp256r1); + } + + SECTION("secp256r1: rejects invalid encodings (bad prefix / out-of-range X)") { + elliptic_curve256_algebra_ctx_t* secp256r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(secp256r1); + REQUIRE(secp256r1->validate_non_infinity_point); + + elliptic_curve256_point_t bad_prefix; + memset(bad_prefix, 0x22, sizeof(bad_prefix)); + bad_prefix[0] = 0x04; + REQUIRE(secp256r1->validate_non_infinity_point(secp256r1, &bad_prefix) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_point_t x_too_large; + memset(x_too_large, 0xFF, sizeof(x_too_large)); + x_too_large[0] = 0x03; + REQUIRE(secp256r1->validate_non_infinity_point(secp256r1, &x_too_large) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_algebra_ctx_free(secp256r1); + } + + SECTION("stark: rejects infinity encoding (0x00 + garbage)") { + elliptic_curve256_algebra_ctx_t* stark = elliptic_curve256_new_stark_algebra(); + REQUIRE(stark); + REQUIRE(stark->validate_non_infinity_point); + + elliptic_curve256_point_t inf_garbage; + memset(inf_garbage, 0xFF, sizeof(inf_garbage)); + inf_garbage[0] = 0x00; + REQUIRE(stark->validate_non_infinity_point(stark, &inf_garbage) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_algebra_ctx_free(stark); + } + + SECTION("stark: rejects invalid encodings (bad prefix / out-of-range X)") { + elliptic_curve256_algebra_ctx_t* stark = elliptic_curve256_new_stark_algebra(); + REQUIRE(stark); + REQUIRE(stark->validate_non_infinity_point); + + elliptic_curve256_point_t bad_prefix; + memset(bad_prefix, 0x33, sizeof(bad_prefix)); + bad_prefix[0] = 0x04; + REQUIRE(stark->validate_non_infinity_point(stark, &bad_prefix) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_point_t x_too_large; + memset(x_too_large, 0xFF, sizeof(x_too_large)); + x_too_large[0] = 0x02; + REQUIRE(stark->validate_non_infinity_point(stark, &x_too_large) == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT); + + elliptic_curve256_algebra_ctx_free(stark); + } +} + +TEST_CASE("secp256k1_attacks", "[attack][secp256k1]") { + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + SECTION("generator_mul with zero scalar") { + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->generator_mul(algebra, &result, &zero); + // G^0 should either fail or return the infinity point + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("generator_mul with order scalar") { + elliptic_curve256_scalar_t order_scalar; + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + memcpy(order_scalar, order_bytes, sizeof(elliptic_curve256_scalar_t)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->generator_mul(algebra, &result, &order_scalar); + // G^n = infinity, should either fail or return infinity + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("generator_mul with order-1") { + // Compute order - 1 + elliptic_curve256_scalar_t order_minus_1; + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + memcpy(order_minus_1, order_bytes, sizeof(elliptic_curve256_scalar_t)); + // Subtract 1 from big-endian value + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; --i) { + if (order_minus_1[i] > 0) { + order_minus_1[i]--; + break; + } + order_minus_1[i] = 0xFF; + } + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->generator_mul(algebra, &result, &order_minus_1); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + // The result should NOT be infinity (it is the negation of G) + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) != 0); + // Verify: result + G = infinity (i.e. result is -G) + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t gen_point; + REQUIRE(algebra->generator_mul(algebra, &gen_point, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_point_t sum; + REQUIRE(algebra->add_points(algebra, &sum, &result, &gen_point) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(sum, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } + + SECTION("generator_mul with order+1") { + // Compute order + 1 + elliptic_curve256_scalar_t order_plus_1; + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + memcpy(order_plus_1, order_bytes, sizeof(elliptic_curve256_scalar_t)); + // Add 1 to big-endian value + int carry = 1; + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0 && carry; --i) { + int tmp = order_plus_1[i] + carry; + order_plus_1[i] = (uint8_t)(tmp & 0xFF); + carry = tmp >> 8; + } + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->generator_mul(algebra, &result, &order_plus_1); + // Should wrap modulo order, giving G^1 + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t g1; + REQUIRE(algebra->generator_mul(algebra, &g1, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, g1, sizeof(elliptic_curve256_point_t)) == 0); + } else { + // Some implementations may reject scalars >= order + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("generator_mul with max scalar 0xFF...") { + elliptic_curve256_scalar_t max_scalar; + memset(max_scalar, 0xFF, sizeof(max_scalar)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->generator_mul(algebra, &result, &max_scalar); + // 0xFF...FF is much larger than the order; should either reduce mod order or fail + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + // The result should be a valid non-infinity point (0xFF..FF mod n != 0) + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) != 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("point_mul with infinity point") { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + elliptic_curve256_scalar_t scalar = {0}; + scalar[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 5; + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->point_mul(algebra, &result, inf, &scalar); + // infinity * s should be infinity or error + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT || + status == ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER || + status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("point_mul with zero scalar") { + // Generate a valid point first + elliptic_curve256_scalar_t three = {0}; + three[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 3; + elliptic_curve256_point_t point; + REQUIRE(algebra->generator_mul(algebra, &point, &three) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->point_mul(algebra, &result, &point, &zero); + // P * 0 should be infinity or fail + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("add_points with infinity") { + // P + infinity = P + elliptic_curve256_scalar_t seven = {0}; + seven[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 7; + elliptic_curve256_point_t point; + REQUIRE(algebra->generator_mul(algebra, &point, &seven) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = algebra->add_points(algebra, &result, &point, inf); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, point, sizeof(elliptic_curve256_point_t)) == 0); + + // Also test infinity + P = P + status = algebra->add_points(algebra, &result, inf, &point); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(memcmp(result, point, sizeof(elliptic_curve256_point_t)) == 0); + } + + SECTION("add_points with P and -P") { + // P + (-P) = infinity + // -P is G^(order-1), P is G^1 + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t gen_point; + REQUIRE(algebra->generator_mul(algebra, &gen_point, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_scalar_t order_minus_1; + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + memcpy(order_minus_1, order_bytes, sizeof(elliptic_curve256_scalar_t)); + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; --i) { + if (order_minus_1[i] > 0) { + order_minus_1[i]--; + break; + } + order_minus_1[i] = 0xFF; + } + elliptic_curve256_point_t neg_gen; + REQUIRE(algebra->generator_mul(algebra, &neg_gen, &order_minus_1) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t sum; + REQUIRE(algebra->add_points(algebra, &sum, &gen_point, &neg_gen) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + REQUIRE(inf); + REQUIRE(memcmp(sum, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } + + SECTION("add_scalars overflow - beyond order") { + // Choose a = order - 1, b = 2 => a + b = order + 1 => should reduce to 1 mod order + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + elliptic_curve256_scalar_t a; + memcpy(a, order_bytes, sizeof(elliptic_curve256_scalar_t)); + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; --i) { + if (a[i] > 0) { + a[i]--; + break; + } + a[i] = 0xFF; + } + elliptic_curve256_scalar_t b = {0}; + b[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 2; + + elliptic_curve256_scalar_t result; + elliptic_curve_algebra_status status = algebra->add_scalars(algebra, &result, a, sizeof(a), b, sizeof(b)); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + // (order - 1) + 2 = order + 1 = 1 mod order + elliptic_curve256_scalar_t expected_one = {0}; + expected_one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + REQUIRE(memcmp(result, expected_one, sizeof(elliptic_curve256_scalar_t)) == 0); + } + + SECTION("mul_scalars with zero") { + elliptic_curve256_scalar_t a = {0}; + a[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 42; + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + + elliptic_curve256_scalar_t result; + elliptic_curve_algebra_status status = algebra->mul_scalars(algebra, &result, a, sizeof(a), zero, sizeof(zero)); + REQUIRE(status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t expected_zero; + memset(expected_zero, 0, sizeof(expected_zero)); + REQUIRE(memcmp(result, expected_zero, sizeof(elliptic_curve256_scalar_t)) == 0); + } + + SECTION("inverse of zero") { + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_scalar_t result; + elliptic_curve_algebra_status status = algebra->inverse(algebra, &result, &zero); + // Inverse of zero is undefined; must fail + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("inverse of order") { + // order mod order = 0, so inverse should fail + elliptic_curve256_scalar_t order_scalar; + const uint8_t* order_bytes = algebra->order(algebra); + REQUIRE(order_bytes); + memcpy(order_scalar, order_bytes, sizeof(elliptic_curve256_scalar_t)); + elliptic_curve256_scalar_t result; + elliptic_curve_algebra_status status = algebra->inverse(algebra, &result, &order_scalar); + // order = 0 mod order, so inverse is undefined; must fail + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +TEST_CASE("multi_curve_attacks", "[attack][multi_curve]") { + + SECTION("secp256r1 generator_mul zero") { + elliptic_curve256_algebra_ctx_t* r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(r1); + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = r1->generator_mul(r1, &result, &zero); + // G^0 should be infinity or fail + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = r1->infinity_point(r1); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + elliptic_curve256_algebra_ctx_free(r1); + } + + SECTION("secp256r1 add_points self-inverse") { + elliptic_curve256_algebra_ctx_t* r1 = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(r1); + // Compute G and -G (= G^(order-1)) + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t gen_point; + REQUIRE(r1->generator_mul(r1, &gen_point, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_scalar_t order_minus_1; + const uint8_t* order_bytes = r1->order(r1); + REQUIRE(order_bytes); + memcpy(order_minus_1, order_bytes, sizeof(elliptic_curve256_scalar_t)); + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; --i) { + if (order_minus_1[i] > 0) { + order_minus_1[i]--; + break; + } + order_minus_1[i] = 0xFF; + } + elliptic_curve256_point_t neg_gen; + REQUIRE(r1->generator_mul(r1, &neg_gen, &order_minus_1) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t sum; + REQUIRE(r1->add_points(r1, &sum, &gen_point, &neg_gen) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + const elliptic_curve256_point_t* inf = r1->infinity_point(r1); + REQUIRE(inf); + REQUIRE(memcmp(sum, *inf, sizeof(elliptic_curve256_point_t)) == 0); + elliptic_curve256_algebra_ctx_free(r1); + } + + SECTION("stark generator_mul zero") { + elliptic_curve256_algebra_ctx_t* stark = elliptic_curve256_new_stark_algebra(); + REQUIRE(stark); + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = stark->generator_mul(stark, &result, &zero); + // G^0 should be infinity or fail + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + const elliptic_curve256_point_t* inf = stark->infinity_point(stark); + REQUIRE(inf); + REQUIRE(memcmp(result, *inf, sizeof(elliptic_curve256_point_t)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_SCALAR || status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + elliptic_curve256_algebra_ctx_free(stark); + } + + SECTION("stark point on wrong curve") { + // Generate a point on secp256k1, then try to use it with STARK's point_mul + elliptic_curve256_algebra_ctx_t* k1 = elliptic_curve256_new_secp256k1_algebra(); + elliptic_curve256_algebra_ctx_t* stark = elliptic_curve256_new_stark_algebra(); + REQUIRE(k1); + REQUIRE(stark); + + elliptic_curve256_scalar_t three = {0}; + three[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 3; + elliptic_curve256_point_t k1_point; + REQUIRE(k1->generator_mul(k1, &k1_point, &three) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Use the secp256k1 point in a STARK point_mul -- should fail or produce wrong result + elliptic_curve256_scalar_t two = {0}; + two[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 2; + elliptic_curve256_point_t result; + elliptic_curve_algebra_status status = stark->point_mul(stark, &result, &k1_point, &two); + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + // If it somehow succeeded, the result must NOT match what secp256k1 would produce + elliptic_curve256_point_t k1_result; + REQUIRE(k1->point_mul(k1, &k1_result, &k1_point, &two) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + // Points from different curves should differ (different generators and group structure) + REQUIRE(memcmp(result, k1_result, sizeof(elliptic_curve256_point_t)) != 0); + } else { + // Expected: the library rejects a point not on the STARK curve + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT || + status == ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER || + status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + + elliptic_curve256_algebra_ctx_free(k1); + elliptic_curve256_algebra_ctx_free(stark); + } +} + +// ============================================================================ +// GFp Signature Verification Attack Tests +// Tests for GFp_curve_algebra_verify_signature and get_point_projection +// used in BAM signing flow. +// ============================================================================ + +TEST_CASE("GFp_verify_signature_attacks", "[attack][signature]") { + GFp_curve_algebra_ctx_t* ctx = secp256k1_algebra_ctx_new(); + REQUIRE(ctx); + + // Create a valid signature for tampering tests + // Generate keypair: private_key, public_key = G^private_key + elliptic_curve256_scalar_t private_key = {0}; + private_key[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 42; + elliptic_curve256_point_t public_key; + REQUIRE(GFp_curve_algebra_generator_mul_data(ctx, private_key, sizeof(private_key), &public_key) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Create a message hash + elliptic_curve256_scalar_t message = {0}; + message[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 0xAB; + message[ELLIPTIC_CURVE_FIELD_SIZE - 2] = 0xCD; + + // Create a "signature" with known r and s values + // Use k=7 as ephemeral nonce: R = G^7, r = R.x mod n + elliptic_curve256_scalar_t k = {0}; + k[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 7; + elliptic_curve256_point_t R_point; + REQUIRE(GFp_curve_algebra_generator_mul_data(ctx, k, sizeof(k), &R_point) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t sig_r; + REQUIRE(GFp_curve_algebra_get_point_projection(ctx, &sig_r, &R_point, NULL) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + SECTION("verify_signature with r = 0") { + elliptic_curve256_scalar_t zero_r; + memset(zero_r, 0, sizeof(zero_r)); + elliptic_curve256_scalar_t dummy_s = {0}; + dummy_s[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &public_key, &message, &zero_r, &dummy_s); + // r = 0 is invalid for ECDSA — must reject + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("verify_signature with s = 0") { + elliptic_curve256_scalar_t zero_s; + memset(zero_s, 0, sizeof(zero_s)); + + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &public_key, &message, &sig_r, &zero_s); + // s = 0 is invalid for ECDSA — must reject + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("verify_signature with r = group order") { + // secp256k1 group order n (big-endian) + elliptic_curve256_scalar_t order_r = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, + 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41 + }; + elliptic_curve256_scalar_t dummy_s = {0}; + dummy_s[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &public_key, &message, &order_r, &dummy_s); + // r = n means r = 0 mod n — must reject + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("verify_signature with wrong public key") { + // Generate a different public key + elliptic_curve256_scalar_t wrong_priv = {0}; + wrong_priv[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 99; + elliptic_curve256_point_t wrong_pubkey; + REQUIRE(GFp_curve_algebra_generator_mul_data(ctx, wrong_priv, sizeof(wrong_priv), &wrong_pubkey) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Use valid-looking r and s with wrong key — must fail verification + elliptic_curve256_scalar_t dummy_s = {0}; + dummy_s[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &wrong_pubkey, &message, &sig_r, &dummy_s); + // The signature was not produced with this key — verification should fail + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("verify_signature with infinity public key") { + elliptic_curve256_point_t inf_key; + memset(inf_key, 0, sizeof(inf_key)); // canonical infinity for GFp curves + elliptic_curve256_scalar_t dummy_s = {0}; + dummy_s[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &inf_key, &message, &sig_r, &dummy_s); + // Infinity is not a valid public key — must reject + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + SECTION("get_point_projection with infinity point") { + elliptic_curve256_point_t inf; + memset(inf, 0, sizeof(inf)); + elliptic_curve256_scalar_t x_val; + uint8_t overflow = 0; + + elliptic_curve_algebra_status status = GFp_curve_algebra_get_point_projection(ctx, &x_val, &inf, &overflow); + // Projecting infinity should either fail or return zero + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + elliptic_curve256_scalar_t zero; + memset(zero, 0, sizeof(zero)); + REQUIRE(memcmp(x_val, zero, sizeof(x_val)) == 0); + } else { + REQUIRE((status == ELLIPTIC_CURVE_ALGEBRA_INVALID_POINT || + status == ELLIPTIC_CURVE_ALGEBRA_INVALID_PARAMETER || + status == ELLIPTIC_CURVE_ALGEBRA_UNKNOWN_ERROR)); + } + } + + SECTION("verify_signature with both r and s equal to max (0xFF...)") { + elliptic_curve256_scalar_t max_val; + memset(max_val, 0xFF, sizeof(max_val)); + + elliptic_curve_algebra_status status = GFp_curve_algebra_verify_signature(ctx, &public_key, &message, &max_val, &max_val); + // Both r and s > order — must reject + REQUIRE(status != ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + } + + GFp_curve_algebra_ctx_free(ctx); +} + +TEST_CASE("ec_scalar_point_consistency", "[correctness]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + SECTION("a*G + b*G = (a+b)*G for 10 random pairs") + { + for (int i = 0; i < 10; i++) + { + elliptic_curve256_scalar_t a, b, a_plus_b; + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compute a*G and b*G separately + elliptic_curve256_point_t aG, bG; + REQUIRE(algebra->generator_mul(algebra, &aG, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &bG, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compute a*G + b*G via point addition + elliptic_curve256_point_t sum_points; + REQUIRE(algebra->add_points(algebra, &sum_points, &aG, &bG) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compute (a+b)*G via scalar addition then generator_mul + REQUIRE(algebra->add_scalars(algebra, &a_plus_b, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_point_t sum_scalar; + REQUIRE(algebra->generator_mul(algebra, &sum_scalar, &a_plus_b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(memcmp(sum_points, sum_scalar, sizeof(elliptic_curve256_point_t)) == 0); + } + } + + SECTION("a*(b*G) = (a*b)*G for 10 random pairs") + { + for (int i = 0; i < 10; i++) + { + elliptic_curve256_scalar_t a, b, ab; + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compute b*G, then a*(b*G) via point_mul + elliptic_curve256_point_t bG; + REQUIRE(algebra->generator_mul(algebra, &bG, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_point_t a_times_bG; + REQUIRE(algebra->point_mul(algebra, &a_times_bG, &bG, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compute (a*b)*G via scalar multiplication then generator_mul + REQUIRE(algebra->mul_scalars(algebra, &ab, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_point_t ab_times_G; + REQUIRE(algebra->generator_mul(algebra, &ab_times_G, &ab) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(memcmp(a_times_bG, ab_times_G, sizeof(elliptic_curve256_point_t)) == 0); + } + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +TEST_CASE("ec_known_test_vectors", "[correctness]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + SECTION("generator point matches secp256k1 specification") + { + // secp256k1 generator Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 + const uint8_t expected_gx[] = { + 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, + 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07, + 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, + 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98 + }; + + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t gen; + REQUIRE(algebra->generator_mul(algebra, &gen, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Compressed point: prefix byte (02 or 03) + 32-byte x-coordinate + REQUIRE(memcmp(&gen[1], expected_gx, 32) == 0); + } + + SECTION("2*G matches known value") + { + // 2*G x-coordinate: 0xC6047F9441ED7D6D3045406E95C07CD85C778E4B8CEF3CA7ABAC09B95C709EE5 + const uint8_t expected_2gx[] = { + 0xC6, 0x04, 0x7F, 0x94, 0x41, 0xED, 0x7D, 0x6D, + 0x30, 0x45, 0x40, 0x6E, 0x95, 0xC0, 0x7C, 0xD8, + 0x5C, 0x77, 0x8E, 0x4B, 0x8C, 0xEF, 0x3C, 0xA7, + 0xAB, 0xAC, 0x09, 0xB9, 0x5C, 0x70, 0x9E, 0xE5 + }; + + elliptic_curve256_scalar_t two = {0}; + two[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 2; + elliptic_curve256_point_t result; + REQUIRE(algebra->generator_mul(algebra, &result, &two) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(memcmp(&result[1], expected_2gx, 32) == 0); + } + + SECTION("G + G = 2*G via point addition") + { + elliptic_curve256_scalar_t one = {0}; + one[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t gen; + REQUIRE(algebra->generator_mul(algebra, &gen, &one) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t sum; + REQUIRE(algebra->add_points(algebra, &sum, &gen, &gen) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_scalar_t two = {0}; + two[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 2; + elliptic_curve256_point_t double_g; + REQUIRE(algebra->generator_mul(algebra, &double_g, &two) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(memcmp(sum, double_g, sizeof(elliptic_curve256_point_t)) == 0); + } + + elliptic_curve256_algebra_ctx_free(algebra); } \ No newline at end of file diff --git a/test/crypto/shamir_secret_sharing/tests.cpp b/test/crypto/shamir_secret_sharing/tests.cpp index 9bd37a4..d47d3a1 100644 --- a/test/crypto/shamir_secret_sharing/tests.cpp +++ b/test/crypto/shamir_secret_sharing/tests.cpp @@ -66,7 +66,7 @@ TEST_CASE( "basic", "secret_sharing") { REQUIRE_FALSE(memcmp(secret, secret2, sizeof(secret) - 1) == 0); } - SECTION("more then needed") { + SECTION("more than needed") { const unsigned char secret[33] = "01234567890123456789012345678912"; unsigned char secret2[33] = {0}; verifiable_secret_sharing_t *shamir; @@ -85,7 +85,7 @@ TEST_CASE( "basic", "secret_sharing") { printf("%s\n", secret2); } - SECTION("more then needed with invalid share") { + SECTION("more than needed with invalid share") { const unsigned char secret[33] = "01234567890123456789012345678912"; unsigned char secret2[33] = {0}; verifiable_secret_sharing_t *shamir; diff --git a/test/crypto/zero_knowledge_proof/attack_helpers.h b/test/crypto/zero_knowledge_proof/attack_helpers.h new file mode 100644 index 0000000..d49588a --- /dev/null +++ b/test/crypto/zero_knowledge_proof/attack_helpers.h @@ -0,0 +1,204 @@ +#ifndef __ZKP_ATTACK_HELPERS_H__ +#define __ZKP_ATTACK_HELPERS_H__ + +#include "crypto/elliptic_curve_algebra/elliptic_curve256_algebra.h" +#include +#include +#include +#include + +namespace attack_helpers { + +// ============================================================================ +// Identity / Special Element Helpers +// ============================================================================ + +// Fill a scalar with all zeros +inline void zero_scalar(elliptic_curve256_scalar_t* s) { + memset(*s, 0, sizeof(elliptic_curve256_scalar_t)); +} + +// Fill a scalar with value 1 (big-endian) +inline void one_scalar(elliptic_curve256_scalar_t* s) { + memset(*s, 0, sizeof(elliptic_curve256_scalar_t)); + (*s)[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; +} + +// Fill a scalar with the curve order (from algebra context) +inline void order_scalar(const elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_scalar_t* s) { + const uint8_t* order = algebra->order(algebra); + memcpy(*s, order, sizeof(elliptic_curve256_scalar_t)); +} + +// Fill a scalar with order - 1 +inline void order_minus_one_scalar(const elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_scalar_t* s) { + order_scalar(algebra, s); + // Subtract 1 from big-endian number + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; i--) { + if ((*s)[i] > 0) { + (*s)[i]--; + break; + } + (*s)[i] = 0xFF; + } +} + +// Get the infinity point for a curve +inline void infinity_point(const elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_point_t* p) { + const elliptic_curve256_point_t* inf = algebra->infinity_point(algebra); + memcpy(*p, *inf, sizeof(elliptic_curve256_point_t)); +} + +// Fill a point with all zeros +inline void zero_point(elliptic_curve256_point_t* p) { + memset(*p, 0, sizeof(elliptic_curve256_point_t)); +} + +// Get the generator point G (generator_mul with scalar 1) +inline void generator_point(elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_point_t* p) { + elliptic_curve256_scalar_t one; + one_scalar(&one); + algebra->generator_mul(algebra, p, &one); +} + +// Negate a compressed point (flip the y-coordinate parity byte) +// For secp256k1/r1: first byte is 0x02 or 0x03, flip to other +inline void negate_point(elliptic_curve256_point_t* p) { + if ((*p)[0] == 0x02) { + (*p)[0] = 0x03; + } else if ((*p)[0] == 0x03) { + (*p)[0] = 0x02; + } + // For ed25519, the sign bit is bit 7 of the last byte + // This is a simplified negation that may not produce valid ed25519 negation +} + +// ============================================================================ +// Bit Manipulation Helpers +// ============================================================================ + +// Flip a single bit at the given byte and bit position +inline void flip_bit(uint8_t* data, size_t byte_pos, uint8_t bit_pos) { + data[byte_pos] ^= (1 << bit_pos); +} + +// Flip a random bit in a buffer +inline void flip_random_bit(uint8_t* data, size_t len) { + uint32_t byte_pos; + RAND_bytes((uint8_t*)&byte_pos, sizeof(byte_pos)); + byte_pos %= len; + uint8_t bit_pos; + RAND_bytes(&bit_pos, 1); + bit_pos %= 8; + flip_bit(data, byte_pos, bit_pos); +} + +// Corrupt N random bytes in a buffer +inline void corrupt_random_bytes(uint8_t* data, size_t len, size_t num_bytes) { + for (size_t i = 0; i < num_bytes; i++) { + uint32_t pos; + RAND_bytes((uint8_t*)&pos, sizeof(pos)); + pos %= len; + uint8_t val; + RAND_bytes(&val, 1); + data[pos] = val; + } +} + +// Fill buffer with all 0xFF +inline void fill_ones(uint8_t* data, size_t len) { + memset(data, 0xFF, len); +} + +// Fill buffer with all 0x00 +inline void fill_zeros(uint8_t* data, size_t len) { + memset(data, 0x00, len); +} + +// Create a truncated copy (remove last N bytes) +inline std::vector truncate(const uint8_t* data, size_t len, size_t remove_bytes) { + if (remove_bytes >= len) return {}; + return std::vector(data, data + len - remove_bytes); +} + +// Create an extended copy (append random bytes) +inline std::vector extend(const uint8_t* data, size_t len, size_t extra_bytes) { + std::vector result(data, data + len); + result.resize(len + extra_bytes); + RAND_bytes(result.data() + len, (int)extra_bytes); + return result; +} + +// ============================================================================ +// Scalar overflow helpers +// ============================================================================ + +// Set scalar to order + 1 +inline void order_plus_one_scalar(const elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_scalar_t* s) { + order_scalar(algebra, s); + // Add 1 to big-endian number + for (int i = ELLIPTIC_CURVE_FIELD_SIZE - 1; i >= 0; i--) { + if ((*s)[i] < 0xFF) { + (*s)[i]++; + break; + } + (*s)[i] = 0x00; + } +} + +// Fill with max value (all 0xFF) +inline void max_scalar(elliptic_curve256_scalar_t* s) { + memset(*s, 0xFF, sizeof(elliptic_curve256_scalar_t)); +} + +// ============================================================================ +// Proof manipulation helpers +// ============================================================================ + +// Swap two byte ranges of equal size +inline void swap_fields(uint8_t* field_a, uint8_t* field_b, size_t len) { + std::vector tmp(field_a, field_a + len); + memcpy(field_a, field_b, len); + memcpy(field_b, tmp.data(), len); +} + +// Generate a valid random point on a curve (for cross-curve testing) +inline void random_point(elliptic_curve256_algebra_ctx_t* algebra, + elliptic_curve256_point_t* p) { + elliptic_curve256_scalar_t s; + algebra->rand(algebra, &s); + algebra->generator_mul(algebra, p, &s); +} + +// ============================================================================ +// Alternative infinity encodings +// Used to test that implementations reject non-canonical infinity representations +// ============================================================================ + +// Type 1: 0x00 prefix with non-zero trailing bytes +inline void alt_infinity_nonzero_trailing(elliptic_curve256_point_t* p) { + memset(*p, 0, sizeof(elliptic_curve256_point_t)); + (*p)[0] = 0x00; + (*p)[ELLIPTIC_CURVE_COMPRESSED_POINT_LEN - 1] = 0x01; +} + +// Type 2: 0x04 uncompressed format with all zeros +inline void alt_infinity_uncompressed_zeros(elliptic_curve256_point_t* p) { + memset(*p, 0, sizeof(elliptic_curve256_point_t)); + (*p)[0] = 0x04; +} + +// Type 3: Valid-looking prefix (0x02) but zero x-coordinate +inline void alt_infinity_valid_prefix_zero_x(elliptic_curve256_point_t* p) { + memset(*p, 0, sizeof(elliptic_curve256_point_t)); + (*p)[0] = 0x02; +} + +} // namespace attack_helpers + +#endif // __ZKP_ATTACK_HELPERS_H__ diff --git a/test/crypto/zero_knowledge_proof/tests.cpp b/test/crypto/zero_knowledge_proof/tests.cpp index ce24409..dc88ee6 100644 --- a/test/crypto/zero_knowledge_proof/tests.cpp +++ b/test/crypto/zero_knowledge_proof/tests.cpp @@ -5,16 +5,24 @@ #include "crypto/paillier_commitment/paillier_commitment.h" #include "../../../src/common/crypto/paillier/paillier_internal.h" #include "crypto/GFp_curve_algebra/GFp_curve_algebra.h" +#include "crypto/commitments/damgard_fujisaki.h" +#include "crypto/paillier_commitment/paillier_commitment.h" +#include "../../../src/common/crypto/paillier/paillier_internal.h" + +#include "attack_helpers.h" #include #include #include +#include #include #include +using namespace attack_helpers; + TEST_CASE("schnorr", "[default]") { GFp_curve_algebra_ctx_t* ctx = secp256k1_algebra_ctx_new(); @@ -305,7 +313,7 @@ TEST_CASE("schnorr", "[default]") secp256k1_algebra->release(secp256k1_algebra); } -TEST_CASE("ring_pedersen", "verify") +TEST_CASE("ring_pedersen", "[default]") { ring_pedersen_private_t* priv; ring_pedersen_public_t* pub; @@ -524,99 +532,123 @@ TEST_CASE("exp_range_proof", "[default]") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &proof) == ZKP_SUCCESS); - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, use_extended_seed, &proof) == ZKP_SUCCESS); + + // IMPORTANT: verify must use the same use_extended_seed that was used in generate. + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 1, use_extended_seed) == ZKP_SUCCESS); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 0, use_extended_seed) == ZKP_SUCCESS); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("multiple proofs") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t proof[2]; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - paillier_ciphertext_t* ciphertext = NULL; - REQUIRE(paillier_encrypt_to_ciphertext(paillier_pub, x, sizeof(elliptic_curve256_scalar_t), &ciphertext) == PAILLIER_SUCCESS); - paillier_get_ciphertext(ciphertext, NULL, 0, &proof[0].ciphertext_len); - proof[0].ciphertext = proof[1].ciphertext = new uint8_t[proof[0].ciphertext_len]; - REQUIRE(paillier_get_ciphertext(ciphertext, proof[0].ciphertext, proof[0].ciphertext_len, &proof[1].ciphertext_len) == PAILLIER_SUCCESS); - REQUIRE(proof[0].ciphertext_len == proof[1].ciphertext_len); - - range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, NULL, 0, &proof[0].proof_len); - proof[0].serialized_proof = new uint8_t[proof[0].proof_len]; - proof[1].serialized_proof = new uint8_t[proof[0].proof_len]; - REQUIRE(range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, proof[0].serialized_proof, proof[0].proof_len, &proof[1].proof_len) == ZKP_SUCCESS); - REQUIRE(proof[0].proof_len == proof[1].proof_len); - REQUIRE(range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, proof[1].serialized_proof, proof[0].proof_len, &proof[1].proof_len) == ZKP_SUCCESS); - REQUIRE(memcmp(proof[0].serialized_proof, proof[1].serialized_proof, proof[0].proof_len) != 0); - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &proof[0]) == ZKP_SUCCESS); - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &proof[1]) == ZKP_SUCCESS); - elliptic_curve256_point_t Xs[2]; - memcpy(Xs[0], X, sizeof(elliptic_curve256_point_t)); - memcpy(Xs[1], X, sizeof(elliptic_curve256_point_t)); - REQUIRE(range_proof_exponent_zkpok_batch_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, 2, Xs, proof) == ZKP_SUCCESS); - paillier_free_ciphertext(ciphertext); - delete[] proof[0].ciphertext; - delete[] proof[0].serialized_proof; - delete[] proof[1].serialized_proof; + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t proof[2]; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + paillier_ciphertext_t* ciphertext = NULL; + REQUIRE(paillier_encrypt_to_ciphertext(paillier_pub, x, sizeof(elliptic_curve256_scalar_t), &ciphertext) == PAILLIER_SUCCESS); + paillier_get_ciphertext(ciphertext, NULL, 0, &proof[0].ciphertext_len); + proof[0].ciphertext = proof[1].ciphertext = new uint8_t[proof[0].ciphertext_len]; + REQUIRE(paillier_get_ciphertext(ciphertext, proof[0].ciphertext, proof[0].ciphertext_len, &proof[1].ciphertext_len) == PAILLIER_SUCCESS); + REQUIRE(proof[0].ciphertext_len == proof[1].ciphertext_len); + + range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, use_extended_seed, NULL, 0, &proof[0].proof_len); + proof[0].serialized_proof = new uint8_t[proof[0].proof_len]; + proof[1].serialized_proof = new uint8_t[proof[0].proof_len]; + REQUIRE(range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, use_extended_seed, proof[0].serialized_proof, proof[0].proof_len, &proof[1].proof_len) == ZKP_SUCCESS); + REQUIRE(proof[0].proof_len == proof[1].proof_len); + REQUIRE(range_proof_paillier_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, ciphertext, use_extended_seed, proof[1].serialized_proof, proof[0].proof_len, &proof[1].proof_len) == ZKP_SUCCESS); + REQUIRE(memcmp(proof[0].serialized_proof, proof[1].serialized_proof, proof[0].proof_len) != 0); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &proof[0], 1, use_extended_seed) == ZKP_SUCCESS); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &proof[1], 1, use_extended_seed) == ZKP_SUCCESS); + paillier_free_ciphertext(ciphertext); + delete[] proof[0].ciphertext; + delete[] proof[0].serialized_proof; + delete[] proof[1].serialized_proof; + } } SECTION("invalid aad") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &proof) == ZKP_SUCCESS); - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, proof) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, use_extended_seed, &proof) == ZKP_SUCCESS); + + // IMPORTANT: verify must use the same use_extended_seed that was used in generate. + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("invalid proof") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &proof) == ZKP_SUCCESS); - proof->ciphertext[123]++; - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof) == ZKP_VERIFICATION_FAILED); - proof->ciphertext[123]--; - proof->serialized_proof[55]++; - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, use_extended_seed, &proof) == ZKP_SUCCESS); + + proof->ciphertext[123]++; + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + proof->ciphertext[123]--; + + proof->serialized_proof[55]++; + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("ed25519") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; auto ed25519 = elliptic_curve256_new_ed25519_algebra(); - REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->generator_mul(ed25519, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &proof) == ZKP_SUCCESS); - REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->generator_mul(ed25519, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate(ring_pedersen_pub, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, use_extended_seed, &proof) == ZKP_SUCCESS); + REQUIRE(range_proof_exponent_zkpok_verify(ring_pedersen_priv, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, proof, 1, use_extended_seed) == ZKP_SUCCESS); + range_proof_free_paillier_with_range_proof(proof); + } ed25519->release(ed25519); } @@ -642,148 +674,175 @@ TEST_CASE("exp_range_proof_small_group", "[default]") SECTION("valid") { - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_generate(damgard_fujisaki_pub, - paillier_priv, - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - x, - sizeof(x), - &proof) == ZKP_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + x, + sizeof(x), + use_extended_seed, + &proof) == ZKP_SUCCESS); + + // IMPORTANT: verify must use the same use_extended_seed that was used in generate. + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_SUCCESS); + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("invalid aad") { - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_generate(damgard_fujisaki_pub, - paillier_priv, - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - x, - sizeof(x), - &proof) == ZKP_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - algebra, - (const unsigned char*)"gello world", - sizeof("gello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + x, + sizeof(x), + use_extended_seed, + &proof) == ZKP_SUCCESS); + + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + algebra, + (const unsigned char*)"gello world", + sizeof("gello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_VERIFICATION_FAILED); + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("invalid proof") { - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_generate(damgard_fujisaki_pub, - paillier_priv, - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - x, - sizeof(x), - &proof) == ZKP_SUCCESS); - proof->ciphertext[123]++; - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_VERIFICATION_FAILED); - proof->ciphertext[123]--; - proof->serialized_proof[55]++; - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - algebra, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + x, + sizeof(x), + use_extended_seed, + &proof) == ZKP_SUCCESS); + proof->ciphertext[123]++; + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_VERIFICATION_FAILED); + proof->ciphertext[123]--; + proof->serialized_proof[55]++; + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_VERIFICATION_FAILED); + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("secp256r1") { - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; auto secp256r1 = elliptic_curve256_new_secp256r1_algebra(); - REQUIRE(secp256r1->rand(secp256r1, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(secp256r1->generator_mul(secp256r1, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_generate(damgard_fujisaki_pub, - paillier_priv, - secp256r1, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - x, - sizeof(x), - &proof) == ZKP_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - secp256r1, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(secp256r1->rand(secp256r1, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(secp256r1->generator_mul(secp256r1, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, + secp256r1, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + x, + sizeof(x), + use_extended_seed, + &proof) == ZKP_SUCCESS); + + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + secp256r1, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_SUCCESS); + range_proof_free_paillier_with_range_proof(proof); + } secp256r1->release(secp256r1); } SECTION("ed25519") { - elliptic_curve256_scalar_t x; - elliptic_curve256_point_t X; - paillier_with_range_proof_t *proof; auto ed25519 = elliptic_curve256_new_ed25519_algebra(); - REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->generator_mul(ed25519, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_generate(damgard_fujisaki_pub, - paillier_priv, - ed25519, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - x, - sizeof(x), - &proof) == ZKP_SUCCESS); - - REQUIRE(range_proof_paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, - paillier_commitment_private_cast_to_public(paillier_priv), - ed25519, - (const unsigned char*)"hello world", - sizeof("hello world") - 1, - &X, - reinterpret_cast(proof)) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t *proof; + REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->generator_mul(ed25519, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, + ed25519, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + x, + sizeof(x), + use_extended_seed, + &proof) == ZKP_SUCCESS); + + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), + ed25519, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_SUCCESS); + range_proof_free_paillier_with_range_proof(proof); + } ed25519->release(ed25519); } @@ -807,104 +866,176 @@ TEST_CASE("rddh", "[default]") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x, a, b; - elliptic_curve256_point_t X, A, B; - paillier_with_range_proof_t *proof; - - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + paillier_with_range_proof_t *proof = NULL; + + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, + paillier_pub, + algebra, + (const unsigned char*)"test aad", + sizeof("test aad") - 1, + &x, + &a, + &b, + use_extended_seed, + &proof) == ZKP_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &a, &b, &proof) == ZKP_SUCCESS); - elliptic_curve256_scalar_t tmp; - REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + elliptic_curve256_scalar_t tmp; + REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // IMPORTANT: verify must use the same use_extended_seed that was used in generate. + // strict_ciphertext_length = 1 (strict) + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"test aad", sizeof("test aad") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_SUCCESS); + + // strict_ciphertext_length = 0 (non-strict) + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"test aad", sizeof("test aad") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_SUCCESS); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("invalid aad") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x, a, b; - elliptic_curve256_point_t X, A, B; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &a, &b, &proof) == ZKP_SUCCESS); - elliptic_curve256_scalar_t tmp; - REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + paillier_with_range_proof_t *proof = NULL; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, + paillier_pub, + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &x, + &a, + &b, + use_extended_seed, + &proof) == ZKP_SUCCESS); + + elliptic_curve256_scalar_t tmp; + REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"gello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("invalid proof") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x, a, b; - elliptic_curve256_point_t X, A, B; - paillier_with_range_proof_t *proof; - REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &a, &b, &proof) == ZKP_SUCCESS); - elliptic_curve256_scalar_t tmp; - REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - proof->ciphertext[123]++; - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - proof->ciphertext[123]--; - proof->serialized_proof[55]++; - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - A[12]++; - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - A[12]--; - B[11]++; - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - B[11]--; - X[10]++; - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - tmp[31]++; - REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_VERIFICATION_FAILED); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + paillier_with_range_proof_t *proof = NULL; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, + paillier_pub, + algebra, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &x, + &a, + &b, + use_extended_seed, + &proof) == ZKP_SUCCESS); + + elliptic_curve256_scalar_t tmp; + REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + proof->ciphertext[123]++; + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + proof->ciphertext[123]--; + proof->serialized_proof[55]++; + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + A[12]++; + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + A[12]--; + B[11]++; + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + B[11]--; + X[10]++; + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + tmp[31]++; + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 0, use_extended_seed) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, algebra, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } } SECTION("ed25519") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); - elliptic_curve256_scalar_t x, a, b; - elliptic_curve256_point_t X, A, B; - paillier_with_range_proof_t *proof; auto ed25519 = elliptic_curve256_new_ed25519_algebra(); - REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->rand(ed25519, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->generator_mul(ed25519, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->rand(ed25519, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->generator_mul(ed25519, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - - REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &x, &a, &b, &proof) == ZKP_SUCCESS); - elliptic_curve256_scalar_t tmp; - REQUIRE(ed25519->mul_scalars(ed25519, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->add_scalars(ed25519, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(ed25519->generator_mul(ed25519, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); - REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof) == ZKP_SUCCESS); - range_proof_free_paillier_with_range_proof(proof); + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + paillier_with_range_proof_t *proof = NULL; + REQUIRE(ed25519->rand(ed25519, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->rand(ed25519, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->generator_mul(ed25519, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->rand(ed25519, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->generator_mul(ed25519, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate(ring_pedersen_pub, + paillier_pub, + ed25519, + (const unsigned char*)"hello world", + sizeof("hello world") - 1, + &x, + &a, + &b, + use_extended_seed, + &proof) == ZKP_SUCCESS); + + elliptic_curve256_scalar_t tmp; + REQUIRE(ed25519->mul_scalars(ed25519, &tmp, a, sizeof(elliptic_curve256_scalar_t), b, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->add_scalars(ed25519, &tmp, tmp, sizeof(elliptic_curve256_scalar_t), x, sizeof(elliptic_curve256_scalar_t)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed25519->generator_mul(ed25519, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(range_proof_diffie_hellman_zkpok_verify(ring_pedersen_priv, paillier_pub, ed25519, (const unsigned char*)"hello world", sizeof("hello world") - 1, &X, &A, &B, proof, 1, use_extended_seed) == ZKP_SUCCESS); + + range_proof_free_paillier_with_range_proof(proof); + } ed25519->release(ed25519); } @@ -1054,20 +1185,33 @@ TEST_CASE("paillier_large_factors", "[default]") paillier_private_key_t* paillier_priv = NULL; long res = paillier_generate_key_pair(2048, &paillier_pub, &paillier_priv); - SECTION("valid") + SECTION("valid-extended-hash") { REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); uint32_t len = 0; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); uint8_t* proof = new uint8_t[len]; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len, &len) == ZKP_SUCCESS); - REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len) == ZKP_SUCCESS); delete[] proof; } - SECTION("valid large keys") + SECTION("valid-reduced-hash") + { + REQUIRE(status == RING_PEDERSEN_SUCCESS); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t len = 0; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + uint8_t* proof = new uint8_t[len]; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len) == ZKP_SUCCESS); + delete[] proof; + } + + SECTION("valid large keys-extended") { ring_pedersen_public_t* large_ring_pedersen_pub; ring_pedersen_private_t* large_ring_pedersen_priv; @@ -1079,10 +1223,10 @@ TEST_CASE("paillier_large_factors", "[default]") REQUIRE(res == PAILLIER_SUCCESS); uint32_t len = 0; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); uint8_t* proof = new uint8_t[len]; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len, &len) == ZKP_SUCCESS); - REQUIRE(range_proof_paillier_large_factors_zkp_verify(large_paillier_pub, large_ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(large_paillier_pub, large_ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len) == ZKP_SUCCESS); delete[] proof; paillier_free_private_key(large_paillier_priv); @@ -1091,20 +1235,57 @@ TEST_CASE("paillier_large_factors", "[default]") ring_pedersen_free_public(large_ring_pedersen_pub); } - SECTION("invalid aad") + SECTION("valid large keys reduced") { + ring_pedersen_public_t* large_ring_pedersen_pub; + ring_pedersen_private_t* large_ring_pedersen_priv; + auto status = ring_pedersen_generate_key_pair(2048, &large_ring_pedersen_pub, &large_ring_pedersen_priv); + paillier_public_key_t* large_paillier_pub = NULL; + paillier_private_key_t* large_paillier_priv = NULL; + long res = paillier_generate_key_pair(3072, &large_paillier_pub, &large_paillier_priv); REQUIRE(status == RING_PEDERSEN_SUCCESS); REQUIRE(res == PAILLIER_SUCCESS); uint32_t len = 0; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); uint8_t* proof = new uint8_t[len]; - REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len, &len) == ZKP_SUCCESS); - REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"gello world", sizeof("hello world") - 1, proof, len) == ZKP_VERIFICATION_FAILED); + REQUIRE(range_proof_paillier_large_factors_zkp_generate(large_paillier_priv, large_ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(large_paillier_pub, large_ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len) == ZKP_SUCCESS); delete[] proof; + + paillier_free_private_key(large_paillier_priv); + paillier_free_public_key(large_paillier_pub); + ring_pedersen_free_private(large_ring_pedersen_priv); + ring_pedersen_free_public(large_ring_pedersen_pub); } - SECTION("invalid proof") + SECTION("invalid aad extended") + { + REQUIRE(status == RING_PEDERSEN_SUCCESS); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t len = 0; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + uint8_t* proof = new uint8_t[len]; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"gello world", sizeof("hello world") - 1, 1, proof, len) == ZKP_VERIFICATION_FAILED); + delete[] proof; + } + + SECTION("invalid aad reduced") + { + REQUIRE(status == RING_PEDERSEN_SUCCESS); + REQUIRE(res == PAILLIER_SUCCESS); + + uint32_t len = 0; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + uint8_t* proof = new uint8_t[len]; + REQUIRE(range_proof_paillier_large_factors_zkp_generate(paillier_priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len, &len) == ZKP_SUCCESS); + REQUIRE(range_proof_paillier_large_factors_zkp_verify(paillier_pub, ring_pedersen_priv, (const unsigned char*)"gello world", sizeof("hello world") - 1, 0, proof, len) == ZKP_VERIFICATION_FAILED); + delete[] proof; + } + + SECTION("invalid proof - extended") { REQUIRE(status == RING_PEDERSEN_SUCCESS); uint32_t len = 0; @@ -1118,10 +1299,34 @@ TEST_CASE("paillier_large_factors", "[default]") REQUIRE(BN_generate_prime_ex(priv.q, 2048 - 256, 0, NULL, NULL, NULL)); REQUIRE(BN_mul(priv.pub.n, priv.p, priv.q, ctx)); - REQUIRE(range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + REQUIRE(range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); uint8_t* proof = new uint8_t[len]; - range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len, &len); - REQUIRE(range_proof_paillier_large_factors_zkp_verify((paillier_public_key_t*)&priv.pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, proof, len) == ZKP_VERIFICATION_FAILED); + range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len, &len); + REQUIRE(range_proof_paillier_large_factors_zkp_verify((paillier_public_key_t*)&priv.pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 1, proof, len) == ZKP_VERIFICATION_FAILED); + delete[] proof; + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + + + SECTION("invalid proof - reduced") + { + REQUIRE(status == RING_PEDERSEN_SUCCESS); + uint32_t len = 0; + BN_CTX* ctx = BN_CTX_new(); + BN_CTX_start(ctx); + paillier_private_key priv = {}; + priv.p = BN_CTX_get(ctx); + priv.q = BN_CTX_get(ctx); + priv.pub.n = BN_CTX_get(ctx); + REQUIRE(BN_generate_prime_ex(priv.p, 256, 0, NULL, NULL, NULL)); + REQUIRE(BN_generate_prime_ex(priv.q, 2048 - 256, 0, NULL, NULL, NULL)); + REQUIRE(BN_mul(priv.pub.n, priv.p, priv.q, ctx)); + + REQUIRE(range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, NULL, 0, &len) == ZKP_INSUFFICIENT_BUFFER); + uint8_t* proof = new uint8_t[len]; + range_proof_paillier_large_factors_zkp_generate((paillier_private_key_t*)&priv, ring_pedersen_pub, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len, &len); + REQUIRE(range_proof_paillier_large_factors_zkp_verify((paillier_public_key_t*)&priv.pub, ring_pedersen_priv, (const unsigned char*)"hello world", sizeof("hello world") - 1, 0, proof, len) == ZKP_VERIFICATION_FAILED); delete[] proof; BN_CTX_end(ctx); BN_CTX_free(ctx); @@ -1138,7 +1343,7 @@ TEST_CASE("paillier_large_factors", "[default]") // To avoid any malicious intent, we took the smallest safe prime of // PAILLIER_LARGE_FACTOR_QUADRATIC_MAX_BITSIZE_FOR_HARCODED_D bitsize // this is 2^3460 + 1169115 - first prime to have 3460 digits. -// found by iterrating over i where p = 2^3460 + 2*i + 1 +// found by iterating over i where p = 2^3460 + 2*i + 1 static const uint8_t hardcoded_d[] = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -1180,7 +1385,7 @@ static const uint8_t hardcoded_d[] = 0xdb }; -TEST_CASE("pailler_large_factors_quadratic", "[default][large_factors_quadratic]") +TEST_CASE("paillier_large_factors_quadratic", "[default][large_factors_quadratic]") { paillier_public_key* pub; paillier_private_key* priv; @@ -1289,7 +1494,7 @@ TEST_CASE("pailler_large_factors_quadratic", "[default][large_factors_quadratic] paillier_free_public_key(pub); } -TEST_CASE("pailler_large_factors_quadratic-slow", "[.][slow]") +TEST_CASE("paillier_large_factors_quadratic-slow", "[.][slow]") { //very slow test - disable by default SECTION("valid bigger size") @@ -1439,4 +1644,1158 @@ TEST_CASE("damgard_fujisaki", "[default]") damgard_fujisaki_free_public(pub); damgard_fujisaki_free_private(priv); +} + +// ============================================================================ +// Schnorr ZKP Attack Tests +// ============================================================================ +TEST_CASE("schnorr_attacks", "[attacks][schnorr]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + + // Generate a valid proof for reuse in attack tests + elliptic_curve256_scalar_t secret; + elliptic_curve256_point_t pub_key; + schnorr_zkp_t valid_proof; + uint8_t prover_id[] = "test_prover"; + + REQUIRE(algebra->rand(algebra, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_key, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(schnorr_zkp_generate(algebra, prover_id, sizeof(prover_id) - 1, &secret, &pub_key, &valid_proof) == ZKP_SUCCESS); + + SECTION("zero secret") { + elliptic_curve256_scalar_t zero; + zero_scalar(&zero); + elliptic_curve256_point_t zero_pub; + // G^0 = infinity, generator_mul may fail or return infinity + auto status = algebra->generator_mul(algebra, &zero_pub, &zero); + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + schnorr_zkp_t proof; + auto zkp_status = schnorr_zkp_generate(algebra, prover_id, sizeof(prover_id) - 1, &zero, &zero_pub, &proof); + // Either generate fails (good) or verify should still work for zero + if (zkp_status == ZKP_SUCCESS) { + auto verify_status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &zero_pub, &proof); + // If zero is a valid scalar, proof should verify + REQUIRE(verify_status == ZKP_SUCCESS); + } + } + } + + SECTION("order scalar") { + elliptic_curve256_scalar_t order_s; + order_scalar(algebra, &order_s); + // n mod n = 0, so G^n = infinity + elliptic_curve256_point_t order_pub; + auto status = algebra->generator_mul(algebra, &order_pub, &order_s); + if (status == ELLIPTIC_CURVE_ALGEBRA_SUCCESS) { + schnorr_zkp_t proof; + auto zkp_status = schnorr_zkp_generate(algebra, prover_id, sizeof(prover_id) - 1, &order_s, &order_pub, &proof); + // Should either fail or produce a valid proof for the identity + REQUIRE(zkp_status == ZKP_SUCCESS); + } + } + + SECTION("infinity public key") { + elliptic_curve256_point_t inf; + infinity_point(algebra, &inf); + schnorr_zkp_t proof; + auto status = schnorr_zkp_generate(algebra, prover_id, sizeof(prover_id) - 1, &secret, &inf, &proof); + // Should fail: proof for wrong public key + if (status == ZKP_SUCCESS) { + auto verify_status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &inf, &proof); + REQUIRE(verify_status != ZKP_SUCCESS); + } + } + + SECTION("generator as public key") { + elliptic_curve256_point_t G; + generator_point(algebra, &G); + // G is the public key for secret=1, but we're using a different secret + schnorr_zkp_t proof; + auto status = schnorr_zkp_generate(algebra, prover_id, sizeof(prover_id) - 1, &secret, &G, &proof); + if (status == ZKP_SUCCESS) { + auto verify_status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &G, &proof); + REQUIRE(verify_status == ZKP_VERIFICATION_FAILED); + } + } + + SECTION("negated public key") { + elliptic_curve256_point_t neg_pub; + memcpy(neg_pub, pub_key, sizeof(elliptic_curve256_point_t)); + negate_point(&neg_pub); + // Verify with negated public key should fail + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &neg_pub, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("single bit flip in R") { + schnorr_zkp_t tampered = valid_proof; + flip_bit(tampered.R, 16, 3); // Flip bit 3 of byte 16 in R + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + // Non-deterministic: bit flip in compressed point R may produce an invalid encoding + // (INVALID_PARAMETER) or a valid but wrong point (VERIFICATION_FAILED), depending + // on the random key generated for this run. + REQUIRE((status == ZKP_VERIFICATION_FAILED || status == ZKP_INVALID_PARAMETER)); + } + + SECTION("single bit flip in s") { + schnorr_zkp_t tampered = valid_proof; + flip_bit(tampered.s, 16, 3); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-zeros R") { + schnorr_zkp_t tampered = valid_proof; + fill_zeros(tampered.R, sizeof(elliptic_curve256_point_t)); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-ones R") { + schnorr_zkp_t tampered = valid_proof; + fill_ones(tampered.R, sizeof(elliptic_curve256_point_t)); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_INVALID_PARAMETER); + } + + SECTION("all-zeros s") { + schnorr_zkp_t tampered = valid_proof; + fill_zeros(tampered.s, sizeof(elliptic_curve256_scalar_t)); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-ones s") { + schnorr_zkp_t tampered = valid_proof; + fill_ones(tampered.s, sizeof(elliptic_curve256_scalar_t)); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("swapped R and s") { + schnorr_zkp_t tampered = valid_proof; + // R is 33 bytes, s is 32 bytes - swap first 32 bytes + swap_fields(tampered.R, tampered.s, sizeof(elliptic_curve256_scalar_t)); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_INVALID_PARAMETER); + } + + SECTION("scalar overflow - order value in s") { + schnorr_zkp_t tampered = valid_proof; + order_scalar(algebra, &tampered.s); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("scalar overflow - max value in s") { + schnorr_zkp_t tampered = valid_proof; + max_scalar(&tampered.s); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("proof replay - valid proof for different public key") { + elliptic_curve256_scalar_t secret2; + elliptic_curve256_point_t pub2; + REQUIRE(algebra->rand(algebra, &secret2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub2, &secret2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + // Use valid_proof (for pub_key) against pub2 + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub2, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("proof replay - valid proof with different prover_id") { + uint8_t other_id[] = "other_prover"; + auto status = schnorr_zkp_verify(algebra, other_id, sizeof(other_id) - 1, &pub_key, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("alternative infinity encodings in R") { + schnorr_zkp_t tampered = valid_proof; + + // Alt infinity type 1: 0x00 prefix with non-zero trailing + alt_infinity_nonzero_trailing(&tampered.R); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + + // Alt infinity type 2: 0x04 uncompressed zeros + tampered = valid_proof; + alt_infinity_uncompressed_zeros(&tampered.R); + status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_INVALID_PARAMETER); + + // Alt infinity type 3: valid prefix, zero x + tampered = valid_proof; + alt_infinity_valid_prefix_zero_x(&tampered.R); + status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &pub_key, &tampered); + REQUIRE(status == ZKP_INVALID_PARAMETER); + } + + SECTION("alternative infinity encodings in public key") { + elliptic_curve256_point_t bad_pub; + + alt_infinity_nonzero_trailing(&bad_pub); + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &bad_pub, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + + alt_infinity_valid_prefix_zero_x(&bad_pub); + status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &bad_pub, &valid_proof); + REQUIRE(status == ZKP_INVALID_PARAMETER); + } + + SECTION("empty prover_id") { + auto status = schnorr_zkp_verify(algebra, prover_id, 0, &pub_key, &valid_proof); + // With zero-length ID, should either fail or produce different result + REQUIRE(status == ZKP_INVALID_PARAMETER); + } + + SECTION("multi-curve: secp256r1 proof verified on secp256k1") { + elliptic_curve256_algebra_ctx_t* r1_algebra = elliptic_curve256_new_secp256r1_algebra(); + REQUIRE(r1_algebra != nullptr); + + // Generate proof on secp256r1 + elliptic_curve256_scalar_t r1_secret; + elliptic_curve256_point_t r1_pub; + schnorr_zkp_t r1_proof; + REQUIRE(r1_algebra->rand(r1_algebra, &r1_secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(r1_algebra->generator_mul(r1_algebra, &r1_pub, &r1_secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(schnorr_zkp_generate(r1_algebra, prover_id, sizeof(prover_id) - 1, &r1_secret, &r1_pub, &r1_proof) == ZKP_SUCCESS); + + // Verify on secp256k1 - should fail + auto status = schnorr_zkp_verify(algebra, prover_id, sizeof(prover_id) - 1, &r1_pub, &r1_proof); + // Non-deterministic: secp256r1 point may or may not be valid on secp256k1. + // If valid point on wrong curve -> VERIFICATION_FAILED; if invalid encoding -> INVALID_PARAMETER. + REQUIRE((status == ZKP_VERIFICATION_FAILED || status == ZKP_INVALID_PARAMETER)); + + elliptic_curve256_algebra_ctx_free(r1_algebra); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// Ring Pedersen Attack Tests +// ============================================================================ +TEST_CASE("ring_pedersen_attacks", "[attacks][ring_pedersen]") +{ + ring_pedersen_public_t* pub; + ring_pedersen_private_t* priv; + auto status = ring_pedersen_generate_key_pair(1024, &pub, &priv); + REQUIRE(status == RING_PEDERSEN_SUCCESS); + + // Generate a valid ZKP + uint32_t proof_len = 0; + auto res = ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"Test AAD", 8, NULL, 0, &proof_len); + REQUIRE(res == ZKP_INSUFFICIENT_BUFFER); + REQUIRE(proof_len > 0); + std::vector valid_proof(proof_len); + res = ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"Test AAD", 8, valid_proof.data(), proof_len, NULL); + REQUIRE(res == ZKP_SUCCESS); + + SECTION("single bit flip in proof") { + for (size_t byte_pos = 0; byte_pos < proof_len; byte_pos += proof_len / 10) { + std::vector tampered = valid_proof; + flip_bit(tampered.data(), byte_pos, 0); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + } + + SECTION("truncated proof") { + for (size_t remove = 1; remove <= 32; remove *= 2) { + if (remove >= proof_len) continue; + auto tampered = truncate(valid_proof.data(), proof_len, remove); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), (uint32_t)tampered.size()); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + } + + SECTION("extended proof") { + auto tampered = extend(valid_proof.data(), proof_len, 32); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), (uint32_t)tampered.size()); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + + SECTION("all-zeros proof") { + std::vector tampered(proof_len, 0); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-ones proof") { + std::vector tampered(proof_len, 0xFF); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + + SECTION("random bytes as proof") { + std::vector tampered(proof_len); + RAND_bytes(tampered.data(), (int)proof_len); + auto verify_res = ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"Test AAD", 8, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + + SECTION("commitment with tampered x") { + // Create a valid commitment first + uint8_t x[32], r[32]; + RAND_bytes(x, sizeof(x)); + RAND_bytes(r, sizeof(r)); + + uint32_t commitment_len = 0; + ring_pedersen_create_commitment(pub, x, sizeof(x), r, sizeof(r), NULL, 0, &commitment_len); + REQUIRE(commitment_len > 0); + std::vector commitment(commitment_len); + REQUIRE(ring_pedersen_create_commitment(pub, x, sizeof(x), r, sizeof(r), + commitment.data(), commitment_len, &commitment_len) == RING_PEDERSEN_SUCCESS); + + // Tamper with x + x[0] ^= 1; + REQUIRE(ring_pedersen_verify_commitment(priv, x, sizeof(x), r, sizeof(r), + commitment.data(), commitment_len) == RING_PEDERSEN_INVALID_COMMITMENT); + } + + SECTION("commitment with tampered r") { + uint8_t x[32], r[32]; + RAND_bytes(x, sizeof(x)); + RAND_bytes(r, sizeof(r)); + + uint32_t commitment_len = 0; + ring_pedersen_create_commitment(pub, x, sizeof(x), r, sizeof(r), NULL, 0, &commitment_len); + std::vector commitment(commitment_len); + REQUIRE(ring_pedersen_create_commitment(pub, x, sizeof(x), r, sizeof(r), + commitment.data(), commitment_len, &commitment_len) == RING_PEDERSEN_SUCCESS); + + r[0] ^= 1; + REQUIRE(ring_pedersen_verify_commitment(priv, x, sizeof(x), r, sizeof(r), + commitment.data(), commitment_len) == RING_PEDERSEN_INVALID_COMMITMENT); + } + + SECTION("zero-length x in commitment") { + uint8_t r[32]; + RAND_bytes(r, sizeof(r)); + uint32_t commitment_len = 0; + auto cstatus = ring_pedersen_create_commitment(pub, NULL, 0, r, sizeof(r), NULL, 0, &commitment_len); + REQUIRE(cstatus == RING_PEDERSEN_INVALID_PARAMETER); + } + + SECTION("deserialization of corrupted key") { + // Serialize the public key + uint32_t key_len = 0; + ring_pedersen_public_serialize(pub, NULL, 0, &key_len); + REQUIRE(key_len > 0); + std::vector key_data(key_len); + ring_pedersen_public_serialize(pub, key_data.data(), key_len, &key_len); + + // Corrupt and attempt to deserialize + key_data[key_len / 2] ^= 0xFF; + auto* bad_pub = ring_pedersen_public_deserialize(key_data.data(), key_len); + // Either returns NULL or returns an invalid key + if (bad_pub) { + // If it deserializes, proofs should still fail with the corrupted key + auto verify_res = ring_pedersen_parameters_zkp_verify(bad_pub, (const uint8_t*)"Test AAD", 8, + valid_proof.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + ring_pedersen_free_public(bad_pub); + } + } + + SECTION("cross-key verification") { + // Generate a second key pair and try to verify key A's proof with key B + ring_pedersen_public_t* pub2; + ring_pedersen_private_t* priv2; + REQUIRE(ring_pedersen_generate_key_pair(1024, &pub2, &priv2) == RING_PEDERSEN_SUCCESS); + + auto verify_res = ring_pedersen_parameters_zkp_verify(pub2, (const uint8_t*)"Test AAD", 8, + valid_proof.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + + ring_pedersen_free_public(pub2); + ring_pedersen_free_private(priv2); + } + + ring_pedersen_free_public(pub); + ring_pedersen_free_private(priv); +} + +// ============================================================================ +// DDH (Diffie-Hellman Log) Attack Tests +// ============================================================================ +TEST_CASE("ddh_attacks", "[attacks][ddh]") +{ + elliptic_curve256_algebra_ctx_t* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra != nullptr); + + // Setup valid DDH proof: A = g^a, B = g^b, X = g^secret, C = g^(ab + secret) + elliptic_curve256_scalar_t secret, a, b; + REQUIRE(algebra->rand(algebra, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t base_point; + generator_point(algebra, &base_point); + + diffie_hellman_log_public_data_t pub_data; + REQUIRE(algebra->generator_mul(algebra, &pub_data.A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.X, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // C = g^(a*b + secret) + elliptic_curve256_scalar_t ab; + REQUIRE(algebra->mul_scalars(algebra, &ab, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t ab_plus_secret; + REQUIRE(algebra->add_scalars(algebra, &ab_plus_secret, ab, sizeof(ab), secret, sizeof(secret)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.C, &ab_plus_secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_zkp_t valid_proof; + REQUIRE(diffie_hellman_log_zkp_generate(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &secret, &a, &b, &pub_data, &valid_proof) == ZKP_SUCCESS); + REQUIRE(diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data, &valid_proof) == ZKP_SUCCESS); + + SECTION("infinity point as A") { + diffie_hellman_log_public_data_t bad_data = pub_data; + infinity_point(algebra, &bad_data.A); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &bad_data, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("infinity point as base_point") { + elliptic_curve256_point_t inf; + infinity_point(algebra, &inf); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &inf, + &pub_data, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-zeros in proof D") { + diffie_hellman_log_zkp_t tampered = valid_proof; + fill_zeros(tampered.D, sizeof(elliptic_curve256_point_t)); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("all-ones in proof w") { + diffie_hellman_log_zkp_t tampered = valid_proof; + fill_ones(tampered.w, sizeof(elliptic_curve256_scalar_t)); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("swapped w and z") { + diffie_hellman_log_zkp_t tampered = valid_proof; + swap_fields(tampered.w, tampered.z, sizeof(elliptic_curve256_scalar_t)); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("random byte corruption") { + for (int trial = 0; trial < 5; trial++) { + diffie_hellman_log_zkp_t tampered = valid_proof; + corrupt_random_bytes((uint8_t*)&tampered, sizeof(diffie_hellman_log_zkp_t), 1); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data, &tampered); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + } + + SECTION("negated A point") { + diffie_hellman_log_public_data_t bad_data = pub_data; + negate_point(&bad_data.A); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &bad_data, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("swapped A and B") { + diffie_hellman_log_public_data_t bad_data = pub_data; + swap_fields(bad_data.A, bad_data.B, sizeof(elliptic_curve256_point_t)); + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &bad_data, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + SECTION("cross-curve: ed25519 proof verified on secp256k1") { + elliptic_curve256_algebra_ctx_t* ed_algebra = elliptic_curve256_new_ed25519_algebra(); + REQUIRE(ed_algebra != nullptr); + + // Generate valid DDH proof on ed25519 + elliptic_curve256_scalar_t ed_secret, ed_a, ed_b; + REQUIRE(ed_algebra->rand(ed_algebra, &ed_secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->rand(ed_algebra, &ed_a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->rand(ed_algebra, &ed_b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_point_t ed_base; + elliptic_curve256_scalar_t one_s; + one_scalar(&one_s); + REQUIRE(ed_algebra->generator_mul(ed_algebra, &ed_base, &one_s) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_public_data_t ed_pub; + REQUIRE(ed_algebra->generator_mul(ed_algebra, &ed_pub.A, &ed_a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->generator_mul(ed_algebra, &ed_pub.B, &ed_b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->generator_mul(ed_algebra, &ed_pub.X, &ed_secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t ed_ab, ed_abs; + REQUIRE(ed_algebra->mul_scalars(ed_algebra, &ed_ab, ed_a, sizeof(ed_a), ed_b, sizeof(ed_b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->add_scalars(ed_algebra, &ed_abs, ed_ab, sizeof(ed_ab), ed_secret, sizeof(ed_secret)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(ed_algebra->generator_mul(ed_algebra, &ed_pub.C, &ed_abs) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_zkp_t ed_proof; + REQUIRE(diffie_hellman_log_zkp_generate(ed_algebra, (const uint8_t*)"DDH AAD", 7, &ed_base, + &ed_secret, &ed_a, &ed_b, &ed_pub, &ed_proof) == ZKP_SUCCESS); + + // Verify ed25519 proof on secp256k1 - should fail + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &ed_pub, &ed_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + + elliptic_curve256_algebra_ctx_free(ed_algebra); + } + + SECTION("cross-key: proof from key set A verified with key set B") { + // Generate a completely independent set of keys and public data + elliptic_curve256_scalar_t secret2, a2, b2; + REQUIRE(algebra->rand(algebra, &secret2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_public_data_t pub_data2; + REQUIRE(algebra->generator_mul(algebra, &pub_data2.A, &a2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data2.B, &b2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data2.X, &secret2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t ab2, abs2; + REQUIRE(algebra->mul_scalars(algebra, &ab2, a2, sizeof(a2), b2, sizeof(b2)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &abs2, ab2, sizeof(ab2), secret2, sizeof(secret2)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data2.C, &abs2) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + // Verify proof from key A with public data from key B - should fail + auto status = diffie_hellman_log_zkp_verify(algebra, (const uint8_t*)"DDH AAD", 7, &base_point, + &pub_data2, &valid_proof); + REQUIRE(status == ZKP_VERIFICATION_FAILED); + } + + elliptic_curve256_algebra_ctx_free(algebra); +} + +// ============================================================================ +// Damgard-Fujisaki Attack Tests +// ============================================================================ +TEST_CASE("damgard_fujisaki_attacks", "[attacks][damgard_fujisaki]") +{ + damgard_fujisaki_private_t* priv; + damgard_fujisaki_public_t* pub; + auto key_status = damgard_fujisaki_generate_key_pair(1024, 2, &pub, &priv); + REQUIRE(key_status == RING_PEDERSEN_SUCCESS); + + // Generate a valid proof + uint32_t proof_len = 0; + auto res = damgard_fujisaki_parameters_zkp_generate(priv, (const unsigned char*)"Test AAD", 8, 1, NULL, 0, &proof_len); + REQUIRE(res == ZKP_INSUFFICIENT_BUFFER); + std::vector valid_proof(proof_len); + res = damgard_fujisaki_parameters_zkp_generate(priv, (const unsigned char*)"Test AAD", 8, 1, valid_proof.data(), proof_len, &proof_len); + REQUIRE(res == ZKP_SUCCESS); + + SECTION("truncated proof - remove 1 byte") { + auto tampered = truncate(valid_proof.data(), proof_len, 1); + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 1, tampered.data(), (uint32_t)tampered.size()); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + + SECTION("truncated proof - remove 16 bytes") { + auto tampered = truncate(valid_proof.data(), proof_len, 16); + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 1, tampered.data(), (uint32_t)tampered.size()); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + + SECTION("extended proof") { + auto tampered = extend(valid_proof.data(), proof_len, 32); + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 1, tampered.data(), (uint32_t)tampered.size()); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + + SECTION("all-zeros proof") { + std::vector tampered(proof_len, 0); + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 1, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + + SECTION("random garbage as proof") { + std::vector tampered(proof_len); + RAND_bytes(tampered.data(), (int)proof_len); + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 1, tampered.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + } + + SECTION("mismatched challenge_bitlength") { + // Proof generated with challenge_bitlength=1, verify with challenge_bitlength=25 + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub, (const unsigned char*)"Test AAD", 8, 25, valid_proof.data(), proof_len); + REQUIRE(verify_res == ZKP_INVALID_PARAMETER); + } + + SECTION("cross-key verification") { + // Generate a second key pair and try to verify with it + damgard_fujisaki_private_t* priv2; + damgard_fujisaki_public_t* pub2; + REQUIRE(damgard_fujisaki_generate_key_pair(1024, 2, &pub2, &priv2) == RING_PEDERSEN_SUCCESS); + + auto verify_res = damgard_fujisaki_parameters_zkp_verify(pub2, (const unsigned char*)"Test AAD", 8, 1, valid_proof.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + + damgard_fujisaki_free_public(pub2); + damgard_fujisaki_free_private(priv2); + } + + SECTION("deserialization with corrupted data") { + uint32_t key_len = 0; + damgard_fujisaki_public_serialize(pub, NULL, 0, &key_len); + std::vector key_data(key_len); + damgard_fujisaki_public_serialize(pub, key_data.data(), key_len, &key_len); + + // Corrupt middle bytes + key_data[key_len / 2] ^= 0xFF; + key_data[key_len / 2 + 1] ^= 0xAA; + + auto* bad_pub = damgard_fujisaki_public_deserialize(key_data.data(), key_len); + if (bad_pub) { + auto verify_res = damgard_fujisaki_parameters_zkp_verify(bad_pub, (const unsigned char*)"Test AAD", 8, 1, valid_proof.data(), proof_len); + REQUIRE(verify_res == ZKP_VERIFICATION_FAILED); + damgard_fujisaki_free_public(bad_pub); + } + } + + // damgard_fujisaki_public_deserialize returns NULL gracefully on invalid input. + SECTION("deserialization with truncated data") { + uint32_t key_len = 0; + damgard_fujisaki_public_serialize(pub, NULL, 0, &key_len); + std::vector key_data(key_len); + damgard_fujisaki_public_serialize(pub, key_data.data(), key_len, &key_len); + auto* bad_pub = damgard_fujisaki_public_deserialize(key_data.data(), key_len / 2); + REQUIRE(bad_pub == nullptr); + } + SECTION("deserialization with zero-length data") { + auto* bad_pub = damgard_fujisaki_public_deserialize(NULL, 0); + REQUIRE(bad_pub == nullptr); + } + + damgard_fujisaki_free_public(pub); + damgard_fujisaki_free_private(priv); +} + +// ============================================================================ +// Proof Randomization Tests +// Two proofs of the same statement must differ - verifies randomization. +// ============================================================================ +TEST_CASE("zkp_proof_randomization", "[correctness]") +{ + SECTION("schnorr: two proofs of same statement differ") + { + auto* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + auto* gfp_ctx = secp256k1_algebra_ctx_new(); + REQUIRE(gfp_ctx); + + elliptic_curve256_scalar_t secret; + elliptic_curve256_point_t pub_point; + REQUIRE(GFp_curve_algebra_rand(gfp_ctx, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(GFp_curve_algebra_generator_mul_data(gfp_ctx, (uint8_t*)secret, sizeof(secret), &pub_point) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + schnorr_zkp_t proof1, proof2; + REQUIRE(schnorr_zkp_generate(algebra, secret, sizeof(secret), &secret, &pub_point, &proof1) == ZKP_SUCCESS); + REQUIRE(schnorr_zkp_generate(algebra, secret, sizeof(secret), &secret, &pub_point, &proof2) == ZKP_SUCCESS); + + // Both proofs must verify + REQUIRE(schnorr_zkp_verify(algebra, secret, sizeof(secret), &pub_point, &proof1) == ZKP_SUCCESS); + REQUIRE(schnorr_zkp_verify(algebra, secret, sizeof(secret), &pub_point, &proof2) == ZKP_SUCCESS); + + // But they must differ (randomized commitment) + REQUIRE(memcmp(&proof1, &proof2, sizeof(schnorr_zkp_t)) != 0); + + GFp_curve_algebra_ctx_free(gfp_ctx); + elliptic_curve256_algebra_ctx_free(algebra); + } + + SECTION("ring_pedersen: two proofs of same key differ") + { + ring_pedersen_public_t* pub; + ring_pedersen_private_t* priv; + REQUIRE(ring_pedersen_generate_key_pair(1024, &pub, &priv) == RING_PEDERSEN_SUCCESS); + + // Generate two proofs for the same key + uint32_t proof_len = 0; + ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"AAD", 3, NULL, 0, &proof_len); + REQUIRE(proof_len > 0); + + std::vector p1(proof_len), p2(proof_len); + REQUIRE(ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"AAD", 3, p1.data(), proof_len, NULL) == ZKP_SUCCESS); + REQUIRE(ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"AAD", 3, p2.data(), proof_len, NULL) == ZKP_SUCCESS); + + // Both must verify + REQUIRE(ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"AAD", 3, p1.data(), proof_len) == ZKP_SUCCESS); + REQUIRE(ring_pedersen_parameters_zkp_verify(pub, (const uint8_t*)"AAD", 3, p2.data(), proof_len) == ZKP_SUCCESS); + + // But they must differ + REQUIRE(memcmp(p1.data(), p2.data(), proof_len) != 0); + + ring_pedersen_free_public(pub); + ring_pedersen_free_private(priv); + } + + SECTION("damgard_fujisaki: two proofs of same key differ") + { + damgard_fujisaki_private_t* df_priv; + damgard_fujisaki_public_t* df_pub; + REQUIRE(damgard_fujisaki_generate_key_pair(1024, 2, &df_pub, &df_priv) == RING_PEDERSEN_SUCCESS); + + uint32_t proof_len = 0; + damgard_fujisaki_parameters_zkp_generate(df_priv, (const unsigned char*)"AAD", 3, 1, NULL, 0, &proof_len); + REQUIRE(proof_len > 0); + + std::vector p1(proof_len), p2(proof_len); + REQUIRE(damgard_fujisaki_parameters_zkp_generate(df_priv, (const unsigned char*)"AAD", 3, 1, p1.data(), proof_len, &proof_len) == ZKP_SUCCESS); + REQUIRE(damgard_fujisaki_parameters_zkp_generate(df_priv, (const unsigned char*)"AAD", 3, 1, p2.data(), proof_len, &proof_len) == ZKP_SUCCESS); + + // Both must verify + REQUIRE(damgard_fujisaki_parameters_zkp_verify(df_pub, (const unsigned char*)"AAD", 3, 1, p1.data(), proof_len) == ZKP_SUCCESS); + REQUIRE(damgard_fujisaki_parameters_zkp_verify(df_pub, (const unsigned char*)"AAD", 3, 1, p2.data(), proof_len) == ZKP_SUCCESS); + + // But they must differ + REQUIRE(memcmp(p1.data(), p2.data(), proof_len) != 0); + + damgard_fujisaki_free_public(df_pub); + damgard_fujisaki_free_private(df_priv); + } +} + +// ============================================================================ +// ZKP Element Size Validation +// Verify proof byte lengths are consistent and within expected bounds. +// ============================================================================ +TEST_CASE("zkp_element_size_validation", "[correctness]") +{ + SECTION("ring_pedersen proof size is consistent across multiple generations") + { + ring_pedersen_public_t* pub; + ring_pedersen_private_t* priv; + REQUIRE(ring_pedersen_generate_key_pair(1024, &pub, &priv) == RING_PEDERSEN_SUCCESS); + + uint32_t proof_len1 = 0, proof_len2 = 0; + ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"AAD", 3, NULL, 0, &proof_len1); + REQUIRE(proof_len1 > 0); + + std::vector p1(proof_len1); + uint32_t real_len1 = 0; + REQUIRE(ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"AAD", 3, p1.data(), proof_len1, &real_len1) == ZKP_SUCCESS); + + // Generate a second proof and check size consistency + ring_pedersen_parameters_zkp_generate(priv, (const uint8_t*)"BBB", 3, NULL, 0, &proof_len2); + REQUIRE(proof_len2 == proof_len1); + + ring_pedersen_free_public(pub); + ring_pedersen_free_private(priv); + } + + SECTION("schnorr proof has non-zero fields") + { + auto* algebra = elliptic_curve256_new_secp256k1_algebra(); + auto* gfp_ctx = secp256k1_algebra_ctx_new(); + REQUIRE(algebra); + REQUIRE(gfp_ctx); + + elliptic_curve256_scalar_t secret; + elliptic_curve256_point_t pub_point; + REQUIRE(GFp_curve_algebra_rand(gfp_ctx, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(GFp_curve_algebra_generator_mul_data(gfp_ctx, (uint8_t*)secret, sizeof(secret), &pub_point) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + schnorr_zkp_t proof; + REQUIRE(schnorr_zkp_generate(algebra, secret, sizeof(secret), &secret, &pub_point, &proof) == ZKP_SUCCESS); + + // R (commitment point) must not be all-zeros + uint8_t zeros[sizeof(elliptic_curve256_point_t)] = {0}; + REQUIRE(memcmp(proof.R, zeros, sizeof(elliptic_curve256_point_t)) != 0); + + // s (response scalar) must not be all-zeros + uint8_t scalar_zeros[sizeof(elliptic_curve256_scalar_t)] = {0}; + REQUIRE(memcmp(proof.s, scalar_zeros, sizeof(elliptic_curve256_scalar_t)) != 0); + + GFp_curve_algebra_ctx_free(gfp_ctx); + elliptic_curve256_algebra_ctx_free(algebra); + } + + SECTION("ddh proof has non-zero fields") + { + auto* algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + elliptic_curve256_scalar_t secret, a, b; + REQUIRE(algebra->rand(algebra, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + elliptic_curve256_scalar_t one_s = {0}; + one_s[ELLIPTIC_CURVE_FIELD_SIZE - 1] = 1; + elliptic_curve256_point_t base_point; + REQUIRE(algebra->generator_mul(algebra, &base_point, &one_s) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_public_data_t pub_data; + REQUIRE(algebra->generator_mul(algebra, &pub_data.A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.X, &secret) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + elliptic_curve256_scalar_t ab, abs_val; + REQUIRE(algebra->mul_scalars(algebra, &ab, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &abs_val, ab, sizeof(ab), secret, sizeof(secret)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &pub_data.C, &abs_val) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + diffie_hellman_log_zkp_t proof; + REQUIRE(diffie_hellman_log_zkp_generate(algebra, (const uint8_t*)"DDH", 3, &base_point, + &secret, &a, &b, &pub_data, &proof) == ZKP_SUCCESS); + + // All proof fields must be non-zero + uint8_t point_zeros[sizeof(elliptic_curve256_point_t)] = {0}; + uint8_t scalar_zeros[sizeof(elliptic_curve256_scalar_t)] = {0}; + REQUIRE(memcmp(proof.D, point_zeros, sizeof(elliptic_curve256_point_t)) != 0); + REQUIRE(memcmp(proof.Y, point_zeros, sizeof(elliptic_curve256_point_t)) != 0); + REQUIRE(memcmp(proof.V, point_zeros, sizeof(elliptic_curve256_point_t)) != 0); + REQUIRE(memcmp(proof.w, scalar_zeros, sizeof(elliptic_curve256_scalar_t)) != 0); + REQUIRE(memcmp(proof.z, scalar_zeros, sizeof(elliptic_curve256_scalar_t)) != 0); + + elliptic_curve256_algebra_ctx_free(algebra); + } +} + +// Verify that coprimality check failures on S and T fields return +// ZKP_VERIFICATION_FAILED (not ZKP_UNKNOWN_ERROR). Before the fix, the +// is_coprime_fast checks for S and T did not set status before goto cleanup. +TEST_CASE("exp_range_proof_coprime_status", "[attacks][range_proof]") +{ + ring_pedersen_public_t* ring_pedersen_pub; + ring_pedersen_private_t* ring_pedersen_priv; + auto rp_status = ring_pedersen_generate_key_pair(1024, &ring_pedersen_pub, &ring_pedersen_priv); + REQUIRE(rp_status == RING_PEDERSEN_SUCCESS); + + paillier_public_key_t* paillier_pub = NULL; + paillier_private_key_t* paillier_priv = NULL; + long pail_res = paillier_generate_key_pair(2048, &paillier_pub, &paillier_priv); + REQUIRE(pail_res == PAILLIER_SUCCESS); + + auto algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + const uint8_t* aad = (const uint8_t*)"test_coprime_status"; + const uint32_t aad_len = 19; + + SECTION("zeroed S must return ZKP_VERIFICATION_FAILED in verify") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + paillier_with_range_proof_t* valid_proof; + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate( + ring_pedersen_pub, paillier_pub, algebra, + aad, aad_len, &x, use_extended_seed, &valid_proof) == ZKP_SUCCESS); + + REQUIRE(range_proof_exponent_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, valid_proof, 1, use_extended_seed) == ZKP_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)valid_proof->serialized_proof; + + std::vector tampered(valid_proof->serialized_proof, + valid_proof->serialized_proof + valid_proof->proof_len); + memset(tampered.data() + 8, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = valid_proof->ciphertext; + bad_proof.ciphertext_len = valid_proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = valid_proof->proof_len; + + auto result = range_proof_exponent_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, &bad_proof, 1, use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(valid_proof); + } + } + + SECTION("zeroed T must return ZKP_VERIFICATION_FAILED in verify") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + paillier_with_range_proof_t* valid_proof; + REQUIRE(range_proof_paillier_encrypt_with_exponent_zkpok_generate( + ring_pedersen_pub, paillier_pub, algebra, + aad, aad_len, &x, use_extended_seed, &valid_proof) == ZKP_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)valid_proof->serialized_proof; + uint32_t pail_n_size = *(uint32_t*)(valid_proof->serialized_proof + 4); + uint32_t t_offset = 8 + rp_n_size + 2 * pail_n_size + sizeof(elliptic_curve256_point_t); + + std::vector tampered(valid_proof->serialized_proof, + valid_proof->serialized_proof + valid_proof->proof_len); + memset(tampered.data() + t_offset, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = valid_proof->ciphertext; + bad_proof.ciphertext_len = valid_proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = valid_proof->proof_len; + + auto result = range_proof_exponent_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, &bad_proof, 1, use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(valid_proof); + } + } + + ring_pedersen_free_public(ring_pedersen_pub); + ring_pedersen_free_private(ring_pedersen_priv); + paillier_free_public_key(paillier_pub); + paillier_free_private_key(paillier_priv); + algebra->release(algebra); +} + +// Tests that zeroed S/T in the paillier commitment exponent proof returns +// ZKP_VERIFICATION_FAILED (not ZKP_UNKNOWN_ERROR). +TEST_CASE("paillier_commitment_coprime_status", "[attacks][range_proof]") +{ + damgard_fujisaki_public* damgard_fujisaki_pub; + damgard_fujisaki_private* damgard_fujisaki_priv; + REQUIRE(damgard_fujisaki_generate_key_pair(1024, 2, &damgard_fujisaki_pub, &damgard_fujisaki_priv) == RING_PEDERSEN_SUCCESS); + + paillier_commitment_private_key_t* paillier_priv = NULL; + REQUIRE(paillier_commitment_generate_private_key(2048, &paillier_priv) == PAILLIER_SUCCESS); + + auto algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + const uint8_t* aad = (const uint8_t*)"test_pc_coprime"; + const uint32_t aad_len = 15; + + SECTION("zeroed S must return ZKP_VERIFICATION_FAILED") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t* proof = NULL; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, algebra, aad, aad_len, x, sizeof(x), + use_extended_seed, &proof) == ZKP_SUCCESS); + + REQUIRE(paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), algebra, + aad, aad_len, &X, + reinterpret_cast(proof), + use_extended_seed) == ZKP_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)proof->serialized_proof; + + std::vector tampered(proof->serialized_proof, + proof->serialized_proof + proof->proof_len); + memset(tampered.data() + 8, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = proof->ciphertext; + bad_proof.ciphertext_len = proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = proof->proof_len; + + auto result = paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), algebra, + aad, aad_len, &X, + reinterpret_cast(&bad_proof), + use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } + } + + SECTION("zeroed T must return ZKP_VERIFICATION_FAILED") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x; + elliptic_curve256_point_t X; + paillier_with_range_proof_t* proof = NULL; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(paillier_commitment_encrypt_with_exponent_zkpok_generate(damgard_fujisaki_pub, + paillier_priv, algebra, aad, aad_len, x, sizeof(x), + use_extended_seed, &proof) == ZKP_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)proof->serialized_proof; + uint32_t pail_n_size = *(uint32_t*)(proof->serialized_proof + 4); + uint32_t t_offset = 8 + rp_n_size + 2 * pail_n_size + + sizeof(elliptic_curve256_point_t); + + std::vector tampered(proof->serialized_proof, + proof->serialized_proof + proof->proof_len); + memset(tampered.data() + t_offset, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = proof->ciphertext; + bad_proof.ciphertext_len = proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = proof->proof_len; + + auto result = paillier_commitment_exponent_zkpok_verify(damgard_fujisaki_priv, + paillier_commitment_private_cast_to_public(paillier_priv), algebra, + aad, aad_len, &X, + reinterpret_cast(&bad_proof), + use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(proof); + } + } + + damgard_fujisaki_free_public(damgard_fujisaki_pub); + damgard_fujisaki_free_private(damgard_fujisaki_priv); + paillier_commitment_free_private_key(paillier_priv); + algebra->release(algebra); +} + +// Tests that zeroed S/T in the diffie-hellman range proof returns +// ZKP_VERIFICATION_FAILED (not ZKP_UNKNOWN_ERROR). +TEST_CASE("dh_range_proof_coprime_status", "[attacks][range_proof]") +{ + ring_pedersen_public_t* ring_pedersen_pub; + ring_pedersen_private_t* ring_pedersen_priv; + REQUIRE(ring_pedersen_generate_key_pair(1024, &ring_pedersen_pub, &ring_pedersen_priv) == RING_PEDERSEN_SUCCESS); + + paillier_public_key_t* paillier_pub = NULL; + paillier_private_key_t* paillier_priv = NULL; + REQUIRE(paillier_generate_key_pair(2048, &paillier_pub, &paillier_priv) == PAILLIER_SUCCESS); + + auto algebra = elliptic_curve256_new_secp256k1_algebra(); + REQUIRE(algebra); + + const uint8_t* aad = (const uint8_t*)"test_dh_coprime"; + const uint32_t aad_len = 15; + + SECTION("zeroed S must return ZKP_VERIFICATION_FAILED") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + paillier_with_range_proof_t* valid_proof = NULL; + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate( + ring_pedersen_pub, paillier_pub, algebra, + aad, aad_len, &x, &a, &b, use_extended_seed, &valid_proof) == ZKP_SUCCESS); + + elliptic_curve256_scalar_t tmp; + REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(tmp), x, sizeof(x)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + REQUIRE(range_proof_diffie_hellman_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, &A, &B, valid_proof, 0, use_extended_seed) == ZKP_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)valid_proof->serialized_proof; + + std::vector tampered(valid_proof->serialized_proof, + valid_proof->serialized_proof + valid_proof->proof_len); + memset(tampered.data() + 8, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = valid_proof->ciphertext; + bad_proof.ciphertext_len = valid_proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = valid_proof->proof_len; + + auto result = range_proof_diffie_hellman_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, &A, &B, &bad_proof, 0, use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(valid_proof); + } + } + + SECTION("zeroed T must return ZKP_VERIFICATION_FAILED") + { + for (const uint8_t use_extended_seed : { (uint8_t)0, (uint8_t)1 }) + { + elliptic_curve256_scalar_t x, a, b; + elliptic_curve256_point_t X, A, B; + REQUIRE(algebra->rand(algebra, &x) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &A, &a) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->rand(algebra, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &B, &b) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + paillier_with_range_proof_t* valid_proof = NULL; + REQUIRE(range_proof_paillier_encrypt_with_diffie_hellman_zkpok_generate( + ring_pedersen_pub, paillier_pub, algebra, + aad, aad_len, &x, &a, &b, use_extended_seed, &valid_proof) == ZKP_SUCCESS); + + elliptic_curve256_scalar_t tmp; + REQUIRE(algebra->mul_scalars(algebra, &tmp, a, sizeof(a), b, sizeof(b)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->add_scalars(algebra, &tmp, tmp, sizeof(tmp), x, sizeof(x)) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + REQUIRE(algebra->generator_mul(algebra, &X, &tmp) == ELLIPTIC_CURVE_ALGEBRA_SUCCESS); + + uint32_t rp_n_size = *(uint32_t*)valid_proof->serialized_proof; + uint32_t pail_n_size = *(uint32_t*)(valid_proof->serialized_proof + 4); + uint32_t t_offset = 8 + rp_n_size + 2 * pail_n_size + + sizeof(elliptic_curve256_point_t); + + std::vector tampered(valid_proof->serialized_proof, + valid_proof->serialized_proof + valid_proof->proof_len); + memset(tampered.data() + t_offset, 0, rp_n_size); + + paillier_with_range_proof_t bad_proof; + bad_proof.ciphertext = valid_proof->ciphertext; + bad_proof.ciphertext_len = valid_proof->ciphertext_len; + bad_proof.serialized_proof = tampered.data(); + bad_proof.proof_len = valid_proof->proof_len; + + auto result = range_proof_diffie_hellman_zkpok_verify( + ring_pedersen_priv, paillier_pub, algebra, + aad, aad_len, &X, &A, &B, &bad_proof, 0, use_extended_seed); + + REQUIRE(result == ZKP_VERIFICATION_FAILED); + + range_proof_free_paillier_with_range_proof(valid_proof); + } + } + + ring_pedersen_free_public(ring_pedersen_pub); + ring_pedersen_free_private(ring_pedersen_priv); + paillier_free_public_key(paillier_pub); + paillier_free_private_key(paillier_priv); + algebra->release(algebra); } \ No newline at end of file