diff --git a/.github/actions/compile-witness-generator/action.yml b/.github/actions/compile-witness-generator/action.yml index b7781e8..16a5866 100644 --- a/.github/actions/compile-witness-generator/action.yml +++ b/.github/actions/compile-witness-generator/action.yml @@ -83,6 +83,7 @@ runs: cp "${SOURCES_ROOT}/circom_adapter.hpp" "${CIRCUIT_CPP_PATH}/circom_adapter.hpp" cp "${SOURCES_ROOT}/circom_fwd.hpp" "${CIRCUIT_CPP_PATH}/circom_fwd.hpp" cp "${SOURCES_ROOT}/types.hpp" "${CIRCUIT_CPP_PATH}/types.hpp" + cp "${SOURCES_ROOT}/assert.h" "${CIRCUIT_CPP_PATH}/assert.h" # TODO: Instead of replace, make a fork that generates the appropriate Makefile - name: Replace ${{ inputs.circuit-name-display }}'s Makefile diff --git a/justfile b/justfile index a81c249..d6efac5 100644 --- a/justfile +++ b/justfile @@ -35,7 +35,7 @@ poq: check-circom # 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}/' blend/poq_cpp/main.cpp cp -r {{src}}/poq blend/poq_cpp/poq - cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp blend/poq_cpp/ + cp {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h blend/poq_cpp/ cp {{ci_makefile}} blend/poq_cpp/Makefile cp blend/test_ffi.cpp blend/poq_cpp/test_ffi.cpp make -C blend/poq_cpp PROJECT=poq linux-lib @@ -51,7 +51,7 @@ pol: check-circom # 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 {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h 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 @@ -67,7 +67,7 @@ poc: check-circom # 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 {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h 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 @@ -83,7 +83,7 @@ signature: check-circom # 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 {{src}}/circom_adapter.cpp {{src}}/circom_adapter.hpp {{src}}/circom_fwd.hpp {{src}}/types.hpp {{src}}/assert.h 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 diff --git a/src/assert.h b/src/assert.h new file mode 100644 index 0000000..2f2a120 --- /dev/null +++ b/src/assert.h @@ -0,0 +1,25 @@ +// Assert-to-throw shim for circom-generated circuit code. +// +// Problem +// ------- +// Circom generates C++ code that calls the standard assert() macro to enforce +// circuit constraints (e.g. `assert(Fr_isTrue(&expaux[0]))`). When compiled +// into a standalone binary, a failing assert aborts the subprocess and the +// caller receives a non-zero exit code; an error. When compiled into a static +// library and linked into the caller's process, the same abort kills the entire +// process. A library must never call abort() on its caller. +// +// Mechanism +// --------- +// This file is copied into each circuit's build directory as assert.h. The +// Makefile already passes -I. so the compiler finds this file before the +// system assert.h when the generated code does `#include `. +// #include_next pulls in the real system header (so all +// declarations are present), then we redefine the assert macro to throw a +// std::runtime_error instead of calling abort(). #pragma once prevents a +// second include from re-running and undoing the redefinition. +#pragma once +#include_next +#undef assert +#include +#define assert(cond) ((cond) ? void(0) : throw std::runtime_error("Failed assert: " #cond)) diff --git a/src/poc/ffi.cpp b/src/poc/ffi.cpp index 511a1f3..d5a25d4 100644 --- a/src/poc/ffi.cpp +++ b/src/poc/ffi.cpp @@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { Circom_Circuit* circuit = loadCircuit(circuit_bytes); Circom_CalcWit* ctx = new Circom_CalcWit(circuit); - loadJson(ctx, input->inputs_json); + try { + loadJson(ctx, input->inputs_json); + } catch (...) { + delete ctx; + delete circuit; + throw; + } 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; diff --git a/src/pol/ffi.cpp b/src/pol/ffi.cpp index e4404ba..4a9cac2 100644 --- a/src/pol/ffi.cpp +++ b/src/pol/ffi.cpp @@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { Circom_Circuit* circuit = loadCircuit(circuit_bytes); Circom_CalcWit* ctx = new Circom_CalcWit(circuit); - loadJson(ctx, input->inputs_json); + try { + loadJson(ctx, input->inputs_json); + } catch (...) { + delete ctx; + delete circuit; + throw; + } 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; diff --git a/src/poq/ffi.cpp b/src/poq/ffi.cpp index eb5ebc9..7715c89 100644 --- a/src/poq/ffi.cpp +++ b/src/poq/ffi.cpp @@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { Circom_Circuit* circuit = loadCircuit(circuit_bytes); Circom_CalcWit* ctx = new Circom_CalcWit(circuit); - loadJson(ctx, input->inputs_json); + try { + loadJson(ctx, input->inputs_json); + } catch (...) { + delete ctx; + delete circuit; + throw; + } 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; diff --git a/src/signature/ffi.cpp b/src/signature/ffi.cpp index fdd05f6..94a7990 100644 --- a/src/signature/ffi.cpp +++ b/src/signature/ffi.cpp @@ -92,7 +92,13 @@ static Status generate_witness_impl(const WitnessInput* input, Bytes* output) { Circom_Circuit* circuit = loadCircuit(circuit_bytes); Circom_CalcWit* ctx = new Circom_CalcWit(circuit); - loadJson(ctx, input->inputs_json); + try { + loadJson(ctx, input->inputs_json); + } catch (...) { + delete ctx; + delete circuit; + throw; + } 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;