From 7a10f70eca281d77340428a3c1f8298565bba9ce Mon Sep 17 00:00:00 2001 From: Alejandro Cabeza Romero Date: Wed, 22 Apr 2026 12:41:07 +0200 Subject: [PATCH] Add other circuits FFIs and smoke tests. --- .gitignore | 1 + blend/test_ffi.cpp | 4 +- justfile | 50 +++++++++++++++- mantle/test_poc.cpp | 64 ++++++++++++++++++++ mantle/test_pol.cpp | 64 ++++++++++++++++++++ mantle/test_signature.cpp | 64 ++++++++++++++++++++ src/poc/ffi.cpp | 119 ++++++++++++++++++++++++++++++++++++++ src/poc/ffi.hpp | 42 ++++++++++++++ src/pol/ffi.cpp | 119 ++++++++++++++++++++++++++++++++++++++ src/pol/ffi.hpp | 42 ++++++++++++++ src/signature/ffi.cpp | 119 ++++++++++++++++++++++++++++++++++++++ src/signature/ffi.hpp | 42 ++++++++++++++ 12 files changed, 727 insertions(+), 3 deletions(-) create mode 100644 mantle/test_poc.cpp create mode 100644 mantle/test_pol.cpp create mode 100644 mantle/test_signature.cpp create mode 100644 src/poc/ffi.cpp create mode 100644 src/poc/ffi.hpp create mode 100644 src/pol/ffi.cpp create mode 100644 src/pol/ffi.hpp create mode 100644 src/signature/ffi.cpp create mode 100644 src/signature/ffi.hpp diff --git a/.gitignore b/.gitignore index 5000caa..c1cffe9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ result *_cpp/ *.r1cs **/input.json +**/*-input.json diff --git a/blend/test_ffi.cpp b/blend/test_ffi.cpp index 16755a0..2a52a67 100644 --- a/blend/test_ffi.cpp +++ b/blend/test_ffi.cpp @@ -20,7 +20,7 @@ static uint8_t* read_file(const char* path, size_t* out_size) { int main() { Status status = poq_generate_witness_from_files( "poq", - "../input.json", + "../poq-input.json", "witness.wtns" ); @@ -33,7 +33,7 @@ int main() { size_t dat_size, json_size; uint8_t* dat_data = read_file("poq.dat", &dat_size); - uint8_t* json_data = read_file("../input.json", &json_size); + uint8_t* json_data = read_file("../poq-input.json", &json_size); WitnessInput input = { {dat_data, dat_size}, diff --git a/justfile b/justfile index 7c105f6..3b43224 100644 --- a/justfile +++ b/justfile @@ -30,5 +30,53 @@ test-poq: poq g++ -std=c++11 -O3 -I blend/poq_cpp blend/poq_cpp/test_ffi.cpp -L blend/poq_cpp -lwitness_poq -lgmp -o blend/poq_cpp/test_ffi cd blend/poq_cpp && ./test_ffi +# Build the PoL circuit and its C++ witness generator, equivalent to the CI build. +pol: + circom mantle/pol.circom --c --r1cs --no_asm --O2 --output mantle + # circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop + sed -i ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/pol_cpp/main.cpp + cp -r {{src}}/pol mantle/pol_cpp/pol + cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/pol_cpp/ + cp {{ci_makefile}} mantle/pol_cpp/Makefile + cp mantle/test_pol.cpp mantle/pol_cpp/test_pol.cpp + make -C mantle/pol_cpp PROJECT=pol linux-lib + +# Run a simple smoke test of the PoL witness generator. +test-pol: pol + g++ -std=c++11 -O3 -I mantle/pol_cpp mantle/pol_cpp/test_pol.cpp -L mantle/pol_cpp -lwitness_pol -lgmp -o mantle/pol_cpp/test_pol + cd mantle/pol_cpp && ./test_pol + +# Build the PoC circuit and its C++ witness generator, equivalent to the CI build. +poc: + circom mantle/poc.circom --c --r1cs --no_asm --O2 --output mantle + # circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop + sed -i ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/poc_cpp/main.cpp + cp -r {{src}}/poc mantle/poc_cpp/poc + cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/poc_cpp/ + cp {{ci_makefile}} mantle/poc_cpp/Makefile + cp mantle/test_poc.cpp mantle/poc_cpp/test_poc.cpp + make -C mantle/poc_cpp PROJECT=poc linux-lib + +# Run a simple smoke test of the PoC witness generator. +test-poc: poc + g++ -std=c++11 -O3 -I mantle/poc_cpp mantle/poc_cpp/test_poc.cpp -L mantle/poc_cpp -lwitness_poc -lgmp -o mantle/poc_cpp/test_poc + cd mantle/poc_cpp && ./test_poc + +# Build the signature circuit and its C++ witness generator, equivalent to the CI build. +signature: + circom mantle/signature.circom --c --r1cs --no_asm --O2 --output mantle + # circom-generated main() has no return on the success path; patch it before -O3 turns it into an infinite loop + sed -i ':a;N;$!ba;s/\n}\n\n*$/\n return 0;\n}/' mantle/signature_cpp/main.cpp + cp -r {{src}}/signature mantle/signature_cpp/signature + cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp mantle/signature_cpp/ + cp {{ci_makefile}} mantle/signature_cpp/Makefile + cp mantle/test_signature.cpp mantle/signature_cpp/test_signature.cpp + make -C mantle/signature_cpp PROJECT=signature linux-lib + +# Run a simple smoke test of the signature witness generator. +test-signature: signature + g++ -std=c++11 -O3 -I mantle/signature_cpp mantle/signature_cpp/test_signature.cpp -L mantle/signature_cpp -lwitness_signature -lgmp -o mantle/signature_cpp/test_signature + cd mantle/signature_cpp && ./test_signature + clean: - rm -rf blend/poq_cpp + rm -rf blend/poq_cpp mantle/pol_cpp mantle/poc_cpp mantle/signature_cpp diff --git a/mantle/test_poc.cpp b/mantle/test_poc.cpp new file mode 100644 index 0000000..d65253d --- /dev/null +++ b/mantle/test_poc.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include "poc/ffi.hpp" + +static uint8_t* read_file(const char* path, size_t* out_size) { + FILE* f = fopen(path, "rb"); + if (!f) return nullptr; + fseek(f, 0, SEEK_END); + *out_size = ftell(f); + rewind(f); + uint8_t* buf = (uint8_t*)malloc(*out_size + 1); + fread(buf, 1, *out_size, f); + buf[*out_size] = '\0'; + fclose(f); + return buf; +} + +int main() { + Status status = poc_generate_witness_from_files( + "poc", + "../poc-input.json", + "witness.wtns" + ); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + printf("generate_witness_from_files: OK\n"); + + size_t dat_size, json_size; + uint8_t* dat_data = read_file("poc.dat", &dat_size); + uint8_t* json_data = read_file("../poc-input.json", &json_size); + + WitnessInput input = { + {dat_data, dat_size}, + (const char*)json_data + }; + Bytes output = {nullptr, 0}; + + status = poc_generate_witness(&input, &output); + + free(dat_data); + free(json_data); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + size_t wtns_size; + uint8_t* wtns_data = read_file("witness.wtns", &wtns_size); + assert(wtns_data != nullptr); + assert(output.size == wtns_size); + assert(memcmp(output.data, wtns_data, output.size) == 0); + free(wtns_data); + + printf("generate_witness: OK (%zu bytes, matches witness.wtns)\n", output.size); + free_bytes(&output); + return 0; +} diff --git a/mantle/test_pol.cpp b/mantle/test_pol.cpp new file mode 100644 index 0000000..00fd5ee --- /dev/null +++ b/mantle/test_pol.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include "pol/ffi.hpp" + +static uint8_t* read_file(const char* path, size_t* out_size) { + FILE* f = fopen(path, "rb"); + if (!f) return nullptr; + fseek(f, 0, SEEK_END); + *out_size = ftell(f); + rewind(f); + uint8_t* buf = (uint8_t*)malloc(*out_size + 1); + fread(buf, 1, *out_size, f); + buf[*out_size] = '\0'; + fclose(f); + return buf; +} + +int main() { + Status status = pol_generate_witness_from_files( + "pol", + "../pol-input.json", + "witness.wtns" + ); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + printf("generate_witness_from_files: OK\n"); + + size_t dat_size, json_size; + uint8_t* dat_data = read_file("pol.dat", &dat_size); + uint8_t* json_data = read_file("../pol-input.json", &json_size); + + WitnessInput input = { + {dat_data, dat_size}, + (const char*)json_data + }; + Bytes output = {nullptr, 0}; + + status = pol_generate_witness(&input, &output); + + free(dat_data); + free(json_data); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + size_t wtns_size; + uint8_t* wtns_data = read_file("witness.wtns", &wtns_size); + assert(wtns_data != nullptr); + assert(output.size == wtns_size); + assert(memcmp(output.data, wtns_data, output.size) == 0); + free(wtns_data); + + printf("generate_witness: OK (%zu bytes, matches witness.wtns)\n", output.size); + free_bytes(&output); + return 0; +} diff --git a/mantle/test_signature.cpp b/mantle/test_signature.cpp new file mode 100644 index 0000000..3c34aea --- /dev/null +++ b/mantle/test_signature.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include "signature/ffi.hpp" + +static uint8_t* read_file(const char* path, size_t* out_size) { + FILE* f = fopen(path, "rb"); + if (!f) return nullptr; + fseek(f, 0, SEEK_END); + *out_size = ftell(f); + rewind(f); + uint8_t* buf = (uint8_t*)malloc(*out_size + 1); + fread(buf, 1, *out_size, f); + buf[*out_size] = '\0'; + fclose(f); + return buf; +} + +int main() { + Status status = signature_generate_witness_from_files( + "signature", + "../signature-input.json", + "witness.wtns" + ); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + printf("generate_witness_from_files: OK\n"); + + size_t dat_size, json_size; + uint8_t* dat_data = read_file("signature.dat", &dat_size); + uint8_t* json_data = read_file("../signature-input.json", &json_size); + + WitnessInput input = { + {dat_data, dat_size}, + (const char*)json_data + }; + Bytes output = {nullptr, 0}; + + status = signature_generate_witness(&input, &output); + + free(dat_data); + free(json_data); + + if (status_is_error(status)) { + fprintf(stderr, "Error [%d]: %s\n", status.code, status.message); + return 1; + } + + size_t wtns_size; + uint8_t* wtns_data = read_file("witness.wtns", &wtns_size); + assert(wtns_data != nullptr); + assert(output.size == wtns_size); + assert(memcmp(output.data, wtns_data, output.size) == 0); + free(wtns_data); + + printf("generate_witness: OK (%zu bytes, matches witness.wtns)\n", output.size); + free_bytes(&output); + return 0; +} diff --git a/src/poc/ffi.cpp b/src/poc/ffi.cpp new file mode 100644 index 0000000..511a1f3 --- /dev/null +++ b/src/poc/ffi.cpp @@ -0,0 +1,119 @@ +#include "poc/ffi.hpp" +#include "circom_fwd.hpp" +#include "circom_adapter.hpp" + +#include +#include + +#include "../types.hpp" + +template +static Status exceptions_into_status(T&& func) { + try { + return func(); + } catch (const std::bad_alloc&) { + return status_from_code(StatusCode_OutOfMemory); + } catch (const std::exception& e) { + return status_new(StatusCode_DynError, e.what()); + } catch (...) { + return status_new(StatusCode_DynError, "An unknown error occurred."); + } +} + +static Status validate_generate_witness_from_files_arguments(const char* dat, const char* inputs, const char* output) { + if (dat == nullptr) { + return status_new(StatusCode_InvalidInput, "dat is null."); + } + if (inputs == nullptr) { + return status_new(StatusCode_InvalidInput, "inputs is null."); + } + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + return status_ok(); +} + +static Status generate_witness_from_files_impl(const char* dat, const char* inputs, const char* output) { + char* argv[] = { + const_cast(dat), + const_cast(inputs), + const_cast(output), + nullptr + }; + + const int code = circom_main(3, argv); + if (code == 0) { + return status_ok(); + } + const std::string message = "Witness generation [circom main()] failed with code: " + std::to_string(code) + "."; + return status_new(StatusCode_DynError, message.c_str()); +} + +extern "C" Status poc_generate_witness_from_files(const char* dat, const char* inputs, const char* output) { + const Status status = validate_generate_witness_from_files_arguments(dat, inputs, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_from_files_impl(dat, inputs, output); + }); +} + +// ---- Memory-based entry point ---- + +static Status validate_witness_arguments(const WitnessInput* input, const Bytes* output) { + if (input == nullptr) { + return status_new(StatusCode_InvalidInput, "input is null."); + } + if (input->dat.data == nullptr) { + return status_new(StatusCode_InvalidInput, "input.dat.data is null."); + } + if (input->dat.size == 0) { + return status_new(StatusCode_InvalidInput, "input.dat.size is zero."); + } + if (input->inputs_json == nullptr) { + return status_new(StatusCode_InvalidInput, "input.inputs_json is null."); + } + + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + if (output->data != nullptr) { + return status_new(StatusCode_InvalidInput, "output.data is not null."); + } + + return status_ok(); +} + +static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { + const ConstBytes& circuit_bytes = input->dat; + + Circom_Circuit* circuit = loadCircuit(circuit_bytes); + Circom_CalcWit* ctx = new Circom_CalcWit(circuit); + + loadJson(ctx, input->inputs_json); + if (ctx->getRemaingInputsToBeSet()!=0) { + const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + "."; + delete ctx; + delete circuit; + return status_new(StatusCode_InvalidInput, message.c_str()); + } + + writeBinWitness(ctx, output); + delete ctx; + delete circuit; + + return status_ok(); +} + +extern "C" Status poc_generate_witness(const WitnessInput* input, Bytes* output) { + const Status status = validate_witness_arguments(input, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_impl(input, output); + }); +} diff --git a/src/poc/ffi.hpp b/src/poc/ffi.hpp new file mode 100644 index 0000000..eb60754 --- /dev/null +++ b/src/poc/ffi.hpp @@ -0,0 +1,42 @@ +#ifndef FFI_POC_HPP +#define FFI_POC_HPP + +#include "types.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Generates a witness by delegating to the circom-generated CLI entry point. +/// +/// # Parameters +/// +/// - `dat`: Path to the .dat file. Must be extensionless. +/// - `inputs`: Path to the inputs file for the circuit. Must be a JSON file. +/// - `output`: Path to the output file where the witness will be written. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and writes the witness to the specified output file. +/// On failure, returns a `Status` with an appropriate error code. +Status poc_generate_witness_from_files(const char* dat, const char* inputs, const char* output); + +/// Generates a witness from in-memory buffers. +/// +/// # Parameters +/// +/// - `input`: The `WitnessInput` struct containing the circuit information. +/// - `output`: Pointer to a `Bytes` struct that will be populated with the generated witness bytes. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and populates `output` with the generated witness bytes. The +/// caller is responsible for freeing the resources allocated into `output` by this function using `free_bytes`. +/// On failure, returns a `Status` with an appropriate error code, and `output` will not be modified. +Status poc_generate_witness(const WitnessInput* input, Bytes* output); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pol/ffi.cpp b/src/pol/ffi.cpp new file mode 100644 index 0000000..e4404ba --- /dev/null +++ b/src/pol/ffi.cpp @@ -0,0 +1,119 @@ +#include "pol/ffi.hpp" +#include "circom_fwd.hpp" +#include "circom_adapter.hpp" + +#include +#include + +#include "../types.hpp" + +template +static Status exceptions_into_status(T&& func) { + try { + return func(); + } catch (const std::bad_alloc&) { + return status_from_code(StatusCode_OutOfMemory); + } catch (const std::exception& e) { + return status_new(StatusCode_DynError, e.what()); + } catch (...) { + return status_new(StatusCode_DynError, "An unknown error occurred."); + } +} + +static Status validate_generate_witness_from_files_arguments(const char* dat, const char* inputs, const char* output) { + if (dat == nullptr) { + return status_new(StatusCode_InvalidInput, "dat is null."); + } + if (inputs == nullptr) { + return status_new(StatusCode_InvalidInput, "inputs is null."); + } + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + return status_ok(); +} + +static Status generate_witness_from_files_impl(const char* dat, const char* inputs, const char* output) { + char* argv[] = { + const_cast(dat), + const_cast(inputs), + const_cast(output), + nullptr + }; + + const int code = circom_main(3, argv); + if (code == 0) { + return status_ok(); + } + const std::string message = "Witness generation [circom main()] failed with code: " + std::to_string(code) + "."; + return status_new(StatusCode_DynError, message.c_str()); +} + +extern "C" Status pol_generate_witness_from_files(const char* dat, const char* inputs, const char* output) { + const Status status = validate_generate_witness_from_files_arguments(dat, inputs, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_from_files_impl(dat, inputs, output); + }); +} + +// ---- Memory-based entry point ---- + +static Status validate_witness_arguments(const WitnessInput* input, const Bytes* output) { + if (input == nullptr) { + return status_new(StatusCode_InvalidInput, "input is null."); + } + if (input->dat.data == nullptr) { + return status_new(StatusCode_InvalidInput, "input.dat.data is null."); + } + if (input->dat.size == 0) { + return status_new(StatusCode_InvalidInput, "input.dat.size is zero."); + } + if (input->inputs_json == nullptr) { + return status_new(StatusCode_InvalidInput, "input.inputs_json is null."); + } + + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + if (output->data != nullptr) { + return status_new(StatusCode_InvalidInput, "output.data is not null."); + } + + return status_ok(); +} + +static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { + const ConstBytes& circuit_bytes = input->dat; + + Circom_Circuit* circuit = loadCircuit(circuit_bytes); + Circom_CalcWit* ctx = new Circom_CalcWit(circuit); + + loadJson(ctx, input->inputs_json); + if (ctx->getRemaingInputsToBeSet()!=0) { + const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + "."; + delete ctx; + delete circuit; + return status_new(StatusCode_InvalidInput, message.c_str()); + } + + writeBinWitness(ctx, output); + delete ctx; + delete circuit; + + return status_ok(); +} + +extern "C" Status pol_generate_witness(const WitnessInput* input, Bytes* output) { + const Status status = validate_witness_arguments(input, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_impl(input, output); + }); +} diff --git a/src/pol/ffi.hpp b/src/pol/ffi.hpp new file mode 100644 index 0000000..f287d10 --- /dev/null +++ b/src/pol/ffi.hpp @@ -0,0 +1,42 @@ +#ifndef FFI_POL_HPP +#define FFI_POL_HPP + +#include "types.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Generates a witness by delegating to the circom-generated CLI entry point. +/// +/// # Parameters +/// +/// - `dat`: Path to the .dat file. Must be extensionless. +/// - `inputs`: Path to the inputs file for the circuit. Must be a JSON file. +/// - `output`: Path to the output file where the witness will be written. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and writes the witness to the specified output file. +/// On failure, returns a `Status` with an appropriate error code. +Status pol_generate_witness_from_files(const char* dat, const char* inputs, const char* output); + +/// Generates a witness from in-memory buffers. +/// +/// # Parameters +/// +/// - `input`: The `WitnessInput` struct containing the circuit information. +/// - `output`: Pointer to a `Bytes` struct that will be populated with the generated witness bytes. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and populates `output` with the generated witness bytes. The +/// caller is responsible for freeing the resources allocated into `output` by this function using `free_bytes`. +/// On failure, returns a `Status` with an appropriate error code, and `output` will not be modified. +Status pol_generate_witness(const WitnessInput* input, Bytes* output); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/signature/ffi.cpp b/src/signature/ffi.cpp new file mode 100644 index 0000000..fdd05f6 --- /dev/null +++ b/src/signature/ffi.cpp @@ -0,0 +1,119 @@ +#include "signature/ffi.hpp" +#include "circom_fwd.hpp" +#include "circom_adapter.hpp" + +#include +#include + +#include "../types.hpp" + +template +static Status exceptions_into_status(T&& func) { + try { + return func(); + } catch (const std::bad_alloc&) { + return status_from_code(StatusCode_OutOfMemory); + } catch (const std::exception& e) { + return status_new(StatusCode_DynError, e.what()); + } catch (...) { + return status_new(StatusCode_DynError, "An unknown error occurred."); + } +} + +static Status validate_generate_witness_from_files_arguments(const char* dat, const char* inputs, const char* output) { + if (dat == nullptr) { + return status_new(StatusCode_InvalidInput, "dat is null."); + } + if (inputs == nullptr) { + return status_new(StatusCode_InvalidInput, "inputs is null."); + } + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + return status_ok(); +} + +static Status generate_witness_from_files_impl(const char* dat, const char* inputs, const char* output) { + char* argv[] = { + const_cast(dat), + const_cast(inputs), + const_cast(output), + nullptr + }; + + const int code = circom_main(3, argv); + if (code == 0) { + return status_ok(); + } + const std::string message = "Witness generation [circom main()] failed with code: " + std::to_string(code) + "."; + return status_new(StatusCode_DynError, message.c_str()); +} + +extern "C" Status signature_generate_witness_from_files(const char* dat, const char* inputs, const char* output) { + const Status status = validate_generate_witness_from_files_arguments(dat, inputs, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_from_files_impl(dat, inputs, output); + }); +} + +// ---- Memory-based entry point ---- + +static Status validate_witness_arguments(const WitnessInput* input, const Bytes* output) { + if (input == nullptr) { + return status_new(StatusCode_InvalidInput, "input is null."); + } + if (input->dat.data == nullptr) { + return status_new(StatusCode_InvalidInput, "input.dat.data is null."); + } + if (input->dat.size == 0) { + return status_new(StatusCode_InvalidInput, "input.dat.size is zero."); + } + if (input->inputs_json == nullptr) { + return status_new(StatusCode_InvalidInput, "input.inputs_json is null."); + } + + if (output == nullptr) { + return status_new(StatusCode_InvalidInput, "output is null."); + } + if (output->data != nullptr) { + return status_new(StatusCode_InvalidInput, "output.data is not null."); + } + + return status_ok(); +} + +static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { + const ConstBytes& circuit_bytes = input->dat; + + Circom_Circuit* circuit = loadCircuit(circuit_bytes); + Circom_CalcWit* ctx = new Circom_CalcWit(circuit); + + loadJson(ctx, input->inputs_json); + if (ctx->getRemaingInputsToBeSet()!=0) { + const std::string message = "Not all inputs have been set. Only " + std::to_string(get_main_input_signal_no()-ctx->getRemaingInputsToBeSet()) + " out of " + std::to_string(get_main_input_signal_no()) + "."; + delete ctx; + delete circuit; + return status_new(StatusCode_InvalidInput, message.c_str()); + } + + writeBinWitness(ctx, output); + delete ctx; + delete circuit; + + return status_ok(); +} + +extern "C" Status signature_generate_witness(const WitnessInput* input, Bytes* output) { + const Status status = validate_witness_arguments(input, output); // NOLINT: if-init + if (status_is_error(status)) { + return status; + } + + return exceptions_into_status([&] { + return generate_witness_impl(input, output); + }); +} diff --git a/src/signature/ffi.hpp b/src/signature/ffi.hpp new file mode 100644 index 0000000..3a25dd6 --- /dev/null +++ b/src/signature/ffi.hpp @@ -0,0 +1,42 @@ +#ifndef FFI_SIGNATURE_HPP +#define FFI_SIGNATURE_HPP + +#include "types.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Generates a witness by delegating to the circom-generated CLI entry point. +/// +/// # Parameters +/// +/// - `dat`: Path to the .dat file. Must be extensionless. +/// - `inputs`: Path to the inputs file for the circuit. Must be a JSON file. +/// - `output`: Path to the output file where the witness will be written. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and writes the witness to the specified output file. +/// On failure, returns a `Status` with an appropriate error code. +Status signature_generate_witness_from_files(const char* dat, const char* inputs, const char* output); + +/// Generates a witness from in-memory buffers. +/// +/// # Parameters +/// +/// - `input`: The `WitnessInput` struct containing the circuit information. +/// - `output`: Pointer to a `Bytes` struct that will be populated with the generated witness bytes. +/// +/// # Returns +/// +/// On success, returns a `Status` with `StatusCode_Ok` and populates `output` with the generated witness bytes. The +/// caller is responsible for freeing the resources allocated into `output` by this function using `free_bytes`. +/// On failure, returns a `Status` with an appropriate error code, and `output` will not be modified. +Status signature_generate_witness(const WitnessInput* input, Bytes* output); + +#ifdef __cplusplus +} +#endif + +#endif