Add thin wrapper to expose witness generation via FFI.

This commit is contained in:
Alejandro Cabeza Romero 2026-04-09 17:43:58 +02:00
parent d64e9f0b03
commit d0bb48b0e0
No known key found for this signature in database
GPG Key ID: DA3D14AE478030FD
5 changed files with 125 additions and 6 deletions

View File

@ -69,6 +69,16 @@ runs:
CIRCUIT_FILENAME: ${{ steps.parse-circuit-path.outputs.CIRCUIT_FILENAME }}
run: circom --c --r1cs --no_asm --O2 "${CIRCUIT_FILENAME}"
- name: Copy ${{ inputs.circuit-name-display }} FFI Sources
shell: bash
env:
SOURCES_ROOT: ${{ github.workspace }}/src
CIRCUIT_CPP_PATH: ${{ steps.parse-circuit-path.outputs.CIRCUIT_CPP_PATH }}
run: |
cp "${SOURCES_ROOT}/ffi.cpp" "${CIRCUIT_CPP_PATH}/ffi.cpp"
cp "${SOURCES_ROOT}/ffi.hpp" "${CIRCUIT_CPP_PATH}/ffi.hpp"
cp "${SOURCES_ROOT}/types.hpp" "${CIRCUIT_CPP_PATH}/types.hpp"
# TODO: Instead of replace, make a fork that generates the appropriate Makefile
- name: Replace ${{ inputs.circuit-name-display }}'s Makefile
shell: bash
@ -114,6 +124,8 @@ runs:
mv "${CIRCUIT_CPP_PATH}/calcwit.hpp" "${CIRCUIT_CPP_PATH}/include/"
mv "${CIRCUIT_CPP_PATH}/circom.hpp" "${CIRCUIT_CPP_PATH}/include/"
mv "${CIRCUIT_CPP_PATH}/fr.hpp" "${CIRCUIT_CPP_PATH}/include/"
mv "${CIRCUIT_CPP_PATH}/ffi.hpp" "${CIRCUIT_CPP_PATH}/include/"
mv "${CIRCUIT_CPP_PATH}/types.hpp" "${CIRCUIT_CPP_PATH}/include/"
- name: Upload ${{ inputs.circuit-name-display }}
uses: actions/upload-artifact@de65e23aa2b7e23d713bb51fbfcb6d502f8667d8

View File

@ -20,11 +20,13 @@ else
# On non-macOS targets we assume objcopy is available on PATH.
OBJCOPY := objcopy
endif
SRCS := main.cpp calcwit.cpp fr.cpp $(PROJECT).cpp
OBJS := $(SRCS:.cpp=.o)
LIB_SRCS := calcwit.cpp fr.cpp $(PROJECT).cpp
COMMON_SRCS := main.cpp calcwit.cpp fr.cpp $(PROJECT).cpp
COMMON_OBJS := $(COMMON_SRCS:.cpp=.o)
LIB_ONLY_SRCS := ffi.cpp
LIB_ONLY_OBJS := $(LIB_ONLY_SRCS:.cpp=.o)
LIB_SRCS := $(COMMON_SRCS) $(LIB_ONLY_SRCS)
LIB_OBJS := $(LIB_SRCS:.cpp=.o)
DEPS_HPP := circom.hpp calcwit.hpp fr.hpp
DEPS_HPP := circom.hpp calcwit.hpp fr.hpp types.hpp ffi.hpp
BIN := $(PROJECT)
ifeq ($(OS),windows)
LIB_EXT := .lib
@ -61,7 +63,7 @@ windows-lib: CXXFLAGS=$(CXXFLAGS_COMMON) -fPIC -I/include -Duint="unsigned int"
windows-lib: $(LIB)
# ---- Rules ----
$(BIN): $(OBJS)
$(BIN): $(COMMON_OBJS)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(LIB): $(LIB_OBJS)
@ -72,4 +74,4 @@ $(LIB): $(LIB_OBJS)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(LIB_OBJS) $(BIN) $(LIB)
rm -f $(COMMON_OBJS) $(LIB_ONLY_OBJS) $(BIN) $(LIB)

39
src/ffi.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "ffi.hpp"
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "calcwit.hpp"
#include "circom.hpp"
#include "fr.hpp"
using json = nlohmann::json;
// ---- Forward declarations from circom-generated main.cpp ----
bool check_valid_number(std::string& s, uint base);
void json2FrElements(json val, std::vector<FrElement>& vval);
json::value_t check_type(std::string prefix, json in);
void qualify_input(std::string prefix, json& in, json& in1);
void qualify_input_list(std::string prefix, json& in, json& in1);
// ---- File-based entry point (wraps circom-generated main) ----
extern "C" int generate_witness_from_files(const char* dat, const char* inputs, const char* output) {
char* argv[] = {
(char*)dat,
(char*)inputs,
(char*)output,
nullptr
};
return main(3, argv);
}
// ---- Memory-based entry point ----
extern "C" int generate_witness(const WitnessInput input, Bytes* output) {
// TODO
return 0;
}

40
src/ffi.hpp Normal file
View File

@ -0,0 +1,40 @@
#ifndef FFI_HPP
#define FFI_HPP
#include "types.hpp"
/// Inputs for witness generation.
struct WitnessInput {
/// Contents of the circuit's .dat file.
const ConstBytes dat;
/// Null-terminated JSON string of circuit inputs.
const char* inputs_json;
};
#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 where the output witness file will be written.
int 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`: On success, this will be populated with the generated witness bytes.
/// The caller is responsible for freeing this buffer.
int generate_witness(const WitnessInput input, Bytes* output);
#ifdef __cplusplus
}
#endif
#endif // FFI_HPP

26
src/types.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef TYPES_HPP
#define TYPES_HPP
#include <stddef.h>
#include <stdint.h>
/// A pointer to a contiguous sequence of elements with a known length.
///
/// # Parameters
///
/// - `T`: The element type. Use `const T` for immutable data.
template<typename T>
struct Slice {
/// Pointer to the first element.
T* data;
/// Number of elements.
size_t size;
};
/// Mutable byte buffer.
using Bytes = Slice<uint8_t>;
/// Immutable byte buffer.
using ConstBytes = Slice<const uint8_t>;
#endif // TYPES_HPP