The Checkpoint That Validated a Fork: Bellperson's Minimal API Exposure for Pipelined SNARK Proving

In any software engineering project, there is a moment of quiet triumph that speaks louder than any feature announcement: the moment when tests pass. Message <msg id=425> captures exactly such a moment in the development of the cuzk pipelined SNARK proving daemon for Filecoin. The assistant reports that "All 8 tests pass. The bellperson fork compiles cleanly and the entire cuzk workspace works with it," then immediately transitions into a git status command to prepare a commit checkpoint. This short message, barely a dozen lines of visible content, is the culmination of a meticulously planned and executed fork of the bellperson cryptographic library — a fork that represents the critical architectural bridge between Phase 1 and Phase 2 of the cuzk project.

The Context: Why a Fork Was Necessary

To understand the significance of this message, one must understand the architectural challenge that the cuzk project was designed to solve. Filecoin storage providers must periodically generate Groth16 zero-knowledge proofs to demonstrate they are still storing the data they committed to store. These proofs — Proofs-of-Replication (PoRep), WinningPoSt, WindowPoSt, and SnapDeals — are computationally expensive, requiring both CPU-intensive circuit synthesis and GPU-intensive multiexponentiation and number-theoretic transform operations. The existing system performed these two phases sequentially and monolithically: synthesize the entire circuit, then prove it on the GPU, with no opportunity for pipelining or parallelization.

The cuzk project's Phase 2 aimed to split this monolithic process into two independently schedulable phases: a CPU-based synthesis phase that produces a ProvingAssignment (the intermediate constraint system state), and a GPU-based proving phase that consumes that assignment to generate the final proof. This split would enable a pipelined architecture where the CPU could be synthesizing the next proof while the GPU was proving the current one, dramatically improving throughput.

However, the bellperson library — the Rust crate that implements the Groth16 proving system for Filecoin — did not expose this split as a public API. Internally, bellperson's supraseal module already contained a function called synthesize_circuits_batch() that performed the CPU synthesis, and the GPU-phase code lived in create_proof_batch_priority_inner(). But both were private, hidden behind module visibility modifiers. The only public entry point was create_proof_batch_priority(), which called both phases sequentially.

The cuzk team faced a choice: rewrite significant portions of bellperson to create a new public API, or fork bellperson with minimal changes to expose what already existed. The latter approach — the "minimal fork" philosophy — was chosen, and messages <msg id=408> through <msg id=424> document the execution of that fork. Message <msg id=425> is the validation point where the fork is proven correct.

What the Message Actually Says

The message begins with a declarative statement of success: "All 8 tests pass. The bellperson fork compiles cleanly and the entire cuzk workspace works with it." This is followed by a transition: "Now let me commit this checkpoint:" and then a git status command that shows the current state of the working directory.

The git status output reveals that only two tracked files have been modified: extern/cuzk/Cargo.lock and extern/cuzk/Cargo.toml. These are the workspace configuration files that were patched to redirect the bellperson dependency from the upstream crates.io version to the local fork at extern/bellperson/. The fact that only these two files changed — and not, say, dozens of source files across the workspace — is a testament to the minimalism of the approach. The fork itself lives in extern/bellperson/, which appears as an untracked directory in the git status output.

The untracked files list also reveals some artifacts of the development process: .claude/ (Claude AI session data), AGENTS.md and CLAUDE.md (project documentation), RACE_CONDITION_ANALYSIS.md (a prior investigation), and _bellpepper_core_src and _bell... (truncated) — likely backup copies of source code that were consulted during the analysis. These are not part of the commit but reflect the investigative work that preceded the fork.

The Reasoning Behind the Minimal Fork Strategy

The decision to create a minimal fork rather than a more invasive rewrite was driven by several considerations that are visible in the preceding messages. In <msg id=400>, the assistant analyzed the bellperson API surface and identified exactly three changes needed:

  1. Make ProvingAssignment<Scalar> and its fields pub
  2. Make synthesize_circuits_batch() pub
  3. Add a new pub fn prove_from_assignments() that extracts the GPU-phase code from create_proof_batch_priority_inner These changes amount to approximately 130 lines of modifications — a remarkably small diff for a change that unlocks an entirely new architectural capability. The philosophy was explicitly stated: "minimal-fork philosophy (only exposing existing internal APIs rather than rewriting)." This approach carries several assumptions. First, it assumes that the internal implementation of synthesize_circuits_batch() and the GPU-phase code is correct and well-tested. By exposing existing code rather than rewriting it, the fork inherits the correctness guarantees of the upstream library. Second, it assumes that the internal data structures (ProvingAssignment, its fields) are stable enough to expose publicly — that their layout and semantics won't change in ways that would break external consumers. Third, it assumes that the upstream maintainers would accept these changes if proposed as a pull request, or alternatively, that the fork can be maintained independently. There is a subtle risk here: by making ProvingAssignment's fields public, the fork creates a coupling to the internal representation that might not have been intended for public consumption. If upstream bellperson changes these fields in a future version, the fork would need to be updated. However, given that the fork is localized to a single project (cuzk) with a specific, well-understood use case, this risk is acceptable.

The Validation Process

The message reports that "all 8 tests pass." These are the existing bellperson tests, which include unit tests for the Groth16 proving system, constraint system correctness, and edge cases. By passing these tests, the fork demonstrates that it has not introduced regressions — the public API changes do not break any existing functionality.

But the validation goes further: "the entire cuzk workspace works with it." This means that not only does bellperson itself compile and pass tests, but all six crates in the cuzk workspace (cuzk-proto, cuzk-core, cuzk-server, cuzk-daemon, cuzk-bench, and the future cuzk-ffi) compile successfully against the forked bellperson. This is a critical validation step because the workspace patch ([patch.crates-io] in Cargo.toml) redirects the bellperson dependency for all workspace members. If any crate had a dependency on a bellperson API that was inadvertently changed or removed by the fork, the compilation would fail.

The compilation check also validates that the version compatibility is correct. Earlier, in <msg id=421>, the assistant encountered a warning that the patch was not being used because the version number didn't match: the fork was initially versioned 0.26.0-cuzk.1, but the workspace dependency required 0.26.0 (exact match). In semver, a pre-release version like 0.26.0-cuzk.1 does not satisfy a requirement for 0.26.0. The assistant fixed this in <msg id=422> by changing the fork's version to exactly 0.26.0, and then ran cargo update -p bellperson in <msg id=423> to update the lock file. The successful compilation in <msg id=424> confirmed the fix.

The Thinking Process Behind the Commit Decision

The message's transition — from validation to checkpoint — reveals a deliberate engineering discipline. The assistant does not immediately proceed to implement the Phase 2 pipelined prover. Instead, it creates a clean commit point that captures the bellperson fork in its validated state. This is important for several reasons:

First, it creates a reversible checkpoint. If the subsequent Phase 2 implementation work encounters problems, the team can always return to this known-good state. Second, it separates concerns: the bellperson fork is a distinct piece of work with its own rationale and validation, and it deserves its own commit with a clear message. Third, it enables parallel work: other developers could potentially start using the forked bellperson APIs while the Phase 2 pipeline is being built.

The git status command serves as the pre-commit review. By showing the current state of the working directory, the assistant is effectively performing a sanity check: "Are these the only files I expect to change? Are there any stray modifications that shouldn't be in this commit?" The answer is yes — only Cargo.lock and Cargo.toml were modified, and the untracked files are either development artifacts or unrelated to the fork.

Input Knowledge Required

To fully understand this message, one needs knowledge of several domains:

Groth16 proving systems: The message assumes familiarity with the two-phase structure of Groth16 proof generation — constraint system synthesis (CPU work) followed by prover computation (GPU work). Without this context, the significance of splitting these phases is lost.

Rust's module system and visibility: The concept of pub, pub(super), and pub(crate) visibility modifiers is central to understanding what the fork changes. The assistant's earlier analysis in <msg id=400> identified that synthesize_circuits_batch was pub(super) (visible only within the parent module) and ProvingAssignment fields were private.

Cargo's patch mechanism: The [patch.crates-io] section in Cargo.toml is a Rust-specific feature that allows overriding a dependency from crates.io with a local version. The version compatibility issue (pre-release vs. exact match) required specific knowledge of semver semantics.

Filecoin proof types: The message is situated in a project that deals with PoRep, WinningPoSt, WindowPoSt, and SnapDeas proofs. While not directly relevant to the fork, this context explains why the pipelining is valuable — these proofs are generated continuously by storage providers.

Output Knowledge Created

This message creates several forms of knowledge:

Validation knowledge: The definitive statement that "all 8 tests pass" and "the entire cuzk workspace works with it" is a piece of engineering knowledge that the team can rely on. It marks a transition from "the fork might work" to "the fork does work."

Checkpoint knowledge: The git status output documents the exact state of the repository at this milestone. Future developers can look at this commit to understand what was changed and why.

Architectural knowledge: The successful fork validates the architectural decision to use a minimal-fork approach rather than a more invasive rewrite. This is a reusable pattern for similar situations where internal APIs need to be exposed.

The Broader Significance

Message <msg id=425> is, on its surface, a mundane status update: tests pass, compilation succeeds, time to commit. But in the context of the cuzk project's trajectory, it represents a critical inflection point. Before this message, Phase 2 was a design concept documented in cuzk-phase2-design.md (written in <msg id=403> and refined in <msg id=405> and <msg id=406>). After this message, Phase 2 has a concrete, validated foundation — a forked bellperson that exposes the APIs needed to build the pipelined prover.

The message also embodies a philosophy of software engineering that values minimalism, validation, and clean checkpoints. The fork is not a rewrite; it is a surgical exposure of existing capabilities. The validation is not assumed; it is demonstrated through tests and compilation. The checkpoint is not an afterthought; it is a deliberate act of engineering discipline.

In the broader narrative of the cuzk project, this message is the moment when the foundation for the pipelined prover was laid and verified. The subsequent implementation work — building the per-partition pipeline, implementing the SRS manager, and wiring the synthesis/proving split into cuzk-core — would all depend on the correctness of this fork. By taking the time to validate thoroughly and commit cleanly, the assistant ensured that this foundation was solid.