The Pre-Compiled Constraint Evaluator: How Deterministic Circuit Structure Unlocks 10x Faster Groth16 Proving
Introduction
In the high-stakes world of Filecoin proof generation, every second and every gigabyte counts. The SUPRASEAL_C2 pipeline—the component responsible for generating Groth16 proofs for Filecoin's Proof-of-Replication (PoRep)—had already been subjected to extensive optimization analysis across four prior proposals. These proposals tackled peak memory reduction through sequential partition synthesis, persistent prover daemons to eliminate SRS loading overhead, cross-sector batching for throughput improvement, and a deep catalog of compute-level micro-optimizations across GPU kernels, CPU synthesis, and memory transfers. Each proposal chipped away at the ~360-second per-proof baseline, but none addressed what would turn out to be the single largest optimization opportunity hiding in plain sight.
Message 51 in this conversation represents a turning point. It is the moment when the investigation pivots from optimizing how computation is performed to questioning whether the computation needs to be performed at all. The message—a sprawling, multi-threaded analysis of constraint-shape-aware optimizations—delivers a devastating insight: the R1CS constraint matrices A, B, and C are identical for every 32 GiB PoRep proof. Only the witness vector changes. Yet the current pipeline re-runs the entire circuit synthesis from scratch for every single proof, rebuilding ~130 million LinearCombination objects, evaluating closures, and constructing sparse matrices that are, in fact, already known.
This article examines message 51 in depth: its reasoning, its assumptions, its discoveries, and its implications. We will explore how the assistant arrived at the Pre-Compiled Constraint Evaluator (PCE) concept, why the SHA-256 dominance of the circuit matters, what mathematical transpositions become possible when proofs are destined for SnarkPack aggregation, and why the message's "Thread 5: What Doesn't Work" section is as valuable as its positive findings. We will also examine the decision-making visible in the message's structure—how the assistant chose to present findings, what questions it asked of the user, and how it framed the implementation tradeoffs.
The Context: Four Proposals Deep
To understand why message 51 is so significant, we must first understand what came before it. The conversation had already produced four optimization proposals for the SUPRASEAL_C2 pipeline:
Proposal 1 (Sequential Partition Synthesis with GPU Pipelining) addressed the ~200 GiB peak memory problem by streaming partitions sequentially rather than materializing all ten partition circuits in memory simultaneously. This reduced peak host memory from ~200 GiB to ~52 GiB while improving GPU utilization from ~30-50% to ~70-90%.
Proposal 2 (Persistent Prover Daemon with SRS Residency) eliminated the per-proof SRS loading overhead—~47 GiB of allocation and ~60 seconds of deserialization—by keeping the Structured Reference String resident in GPU memory across proof generations.
Proposal 3 (Cross-Sector Proof Batching) multiplied throughput by batching multiple sectors' partition circuits into a single GPU proving invocation, amortizing fixed costs and improving GPU SM utilization for a 2-3x throughput improvement.
Proposal 4 (Compute-Level Optimizations) cataloged 18 specific micro-optimizations across GPU kernels, CPU synthesis, and memory transfers, targeting a 25-45% reduction in per-proof wall-clock time.
These four proposals represented a comprehensive optimization campaign. They touched every layer of the stack: Go orchestration, Rust FFI, C++ GPU kernels, and CUDA memory management. They addressed memory, latency, throughput, and utilization. They were, by any measure, thorough.
But they all accepted one fundamental premise: that the circuit synthesis phase—the synthesize() method that constructs the R1CS constraint system from the circuit definition—must run for every proof. The question that triggered message 51, posed by the user in message 45, challenged this premise directly:
"Any optimizations possible for known constraint shapes, e.g. lots of sha256 constraints? or even more is the knowledge of constraints structure usable in ways that with some pre-computation (compile or runtime) would allow for better performance/parallelism? Mathematical transpositions for batching possible? Or Mathematical transposition when output is SnarkPack-ed?"
This question is deceptively simple. It asks whether the shape of the constraints—not just their values, but their structure, their patterns, their mathematical form—can be exploited. It asks whether pre-computation can transform the proving problem into something fundamentally cheaper. And it asks whether the aggregation layer (SnarkPack) changes the optimization landscape.
Message 51 is the assistant's answer to this question. It is not a simple answer.
The Central Discovery: Synthesis Is Waste
The message's most important finding is stated with characteristic understatement: "This is the single highest-impact finding. The R1CS constraint matrices A, B, C are identical for every 32 GiB PoRep proof. Only the witness vector w changes."
This is the kind of insight that seems obvious in retrospect but requires deep system knowledge to identify. To understand why it matters, we need to understand what happens during Groth16 proof generation.
The Groth16 proving protocol works as follows. Given a circuit defining a constraint system, the prover:
- Synthesizes the circuit to produce the R1CS matrices A, B, C and the witness vector w
- Computes the QAP polynomials from the R1CS matrices
- Evaluates the polynomials at a secret point τ (the toxic waste) to produce a, b, c
- Computes the quotient polynomial H(τ) = (a·b - c) / δ(τ)
- Performs multi-scalar multiplications (MSMs) and number-theoretic transforms (NTTs) to produce the proof elements A, B, C Step 1—synthesis—is where the circuit's
synthesize()method is called. In bellperson (the Rust library used by Filecoin), this method walks the circuit graph, callingenforce()for each constraint andalloc()for each witness variable. Eachenforce()call constructsLinearCombinationobjects representing the constraint's contribution to A, B, and C. These are then evaluated against the witness to produce the sparse matrix rows. The critical insight is that for the Filecoin PoRep circuit, the constraint structure is completely deterministic. The circuit is defined by the sector size (32 GiB) and the proving parameters (10 partitions, specific hash functions). Every 32 GiB proof uses the same SHA-256 constraints, the same Poseidon constraints, the same wiring between variables. Only the witness values—the actual data being proven—change between proofs. Yet the current pipeline re-executes the entire synthesis for every proof. It rebuilds everyLinearCombination, re-evaluates everyenforce()closure, re-constructs every sparse matrix row. For a circuit with ~130 million constraints across 10 partitions, this is an enormous amount of wasted computation. The message's proposed solution—the Pre-Compiled Constraint Evaluator (PCE)—is elegant in its simplicity. Run synthesis once with a recordingConstraintSystemthat captures the sparse matrices A, B, C in Compressed Sparse Row (CSR) format. Serialize these matrices (~500 MiB compressed for all 10 partitions). For all subsequent proofs, load the pre-compiled matrices and computea = A*w,b = B*w,c = C*wvia sparse matrix-vector multiplication. This eliminates: LinearCombination construction,enforce()closures,eval_with_trackers(), density tracking—everything that currently takes 1-3 minutes per partition on CPU. The remaining work is witness computation (thealloc()closures that compute SHA-256 and Poseidon intermediates), which must still run because the witness values depend on the input data. The message estimates a 50-70% reduction in synthesis time. To put this in context: if synthesis currently accounts for ~90-180 seconds of the ~360-second total proof time, a 50-70% reduction saves 45-126 seconds per proof. This is larger than any single optimization in Proposals 1-4.
The Catch: Separating Witness Generation from Constraint Evaluation
The message does not gloss over the complexity. It immediately identifies a critical catch: "The alloc() closures compute witness values as side effects during synthesis. The enforce() closures reference these computed values. So we can't fully eliminate synthesis without also separating witness generation from constraint evaluation."
This is a crucial systems insight. In bellperson's architecture, alloc() and enforce() are interleaved. The circuit defines variables (via alloc()) and then constrains them (via enforce()). The enforce() closures reference variables that were allocated earlier. If we skip enforce() entirely, we must still run alloc() to compute the witness values.
The message proposes a two-phase approach:
- Witness generation: Run a stripped-down synthesis that only executes
alloc()(andalloc_input()) closures, skippingenforce()entirely. Bellperson already supports this viais_witness_generator()mode. - Constraint evaluation: Load pre-compiled CSR matrices, evaluate
a = A*w,b = B*w,c = C*wvia sparse MatVec. This is not trivial to implement. It requires forking bellperson to add a "recording"ConstraintSystemand a "replaying" MatVec evaluator. It requires careful handling of the variable indices to ensure the witness vector is correctly ordered. It requires serialization formats for the CSR matrices. But it is architecturally clean and well-understood. The message's treatment of this catch demonstrates a mature engineering sensibility. Rather than presenting the PCE as a magical silver bullet, it acknowledges the complexity, describes the solution architecture, and estimates the remaining work. This is not a research paper; it is an engineering proposal with a clear implementation path.
Thread 2: Exploiting SHA-256 Constraint Shape
The message's second thread examines whether the internal structure of the SHA-256 constraints—which constitute 88% of all constraints—can be exploited for additional gains.
The key observations are:
- Most SHA-256 constraints have 1-3 term LinearCombinations (very sparse rows)
- ~70% of coefficients are ±1 (no field multiply needed)
- ~20% of coefficients are powers of 2 (field addition suffices)
- 99% of witness variables are boolean (0 or 1) These properties enable a cascade of optimizations within the MatVec itself. For coefficient = 1, the field multiply can be skipped entirely—just copy the witness value. For powers of 2, use field addition (a ~5 cycle operation) instead of field multiplication (~50 cycles). For boolean witnesses, the multiply
coeff * w[i]becomes either 0 (skip) or justcoeff(copy). The message proposes SIMD-vectorized sparse MatVec that batches rows of the same length and processes them with AVX-512 gather/scatter. For 1-term rows: just a field multiply + accumulate (or skip the multiply for ±1 coefficients). For 2-term rows: 2 multiplies + accumulate. Combined with the PCE, the message estimates synthesis time could be reduced from ~90-180s to ~15-30s per partition. This is a 6-12x improvement in the synthesis phase alone. What is particularly impressive about this analysis is that it goes beyond the obvious "the matrices are sparse" observation. It quantifies the sparsity patterns (row-length histograms), the coefficient distributions (70% ±1), and the witness distributions (99% boolean). These numbers come from actual circuit analysis—the assistant had previously explored the circuit synthesis structure in detail (see the exploration tasks referenced in messages 46-48).
Thread 3: Pre-Computed Split MSM Topology
The third thread extends the boolean witness exploitation to the multi-scalar multiplication (MSM) phase of proof generation. The existing split-MSM already classifies scalars into zero/one/significant to optimize the MSM computation. But the message proposes going further by pre-computing the split plan.
Since the indices of boolean variables are deterministic (they're defined by the circuit structure, not the witness values), the "split plan"—which MSM bases always get batch-added versus Pippenger'd—can be pre-computed. Only ~1% of scalars need Pippenger at full cost. The SRS points can be pre-sorted so that boolean-indexed points are contiguous, enabling a single batch_addition pass without indirection.
This eliminates the runtime classification scan that currently determines, for each scalar, whether it is zero, one, or significant. In a circuit with ~130 million constraints, this scan is not free.
Thread 4: SnarkPack Transpositions
The fourth thread is the most mathematically sophisticated. It examines how the optimization landscape changes when the final output is a SnarkPack aggregate proof rather than individual Groth16 proofs.
SnarkPack (BCLMS21) is an aggregation scheme that takes N Groth16 proofs and produces a single aggregate proof via the GIPA/TIPP/MIPP protocol. The aggregation computation is dominated by inner pairing products and MSMs in G1 and G2.
The message identifies five transpositions:
Transposition A: Deferred C2 — The observation that SnarkPack aggregation only needs the proof triplets (A, B, C) ∈ G1 × G2 × G1, not the intermediate NTT results or the H polynomial. This doesn't directly eliminate computation from C2, but it reframes what needs to be preserved.
Transposition B: Batch-C2-then-aggregate pipeline — The insight that SnarkPack aggregation is CPU-dominated (pairings + MSMs) while C2 is GPU-dominated (NTT + MSM). This enables a pipeline where the GPU computes C2 for sector N+1 while the CPU aggregates sectors 1..N, with zero GPU contention.
Transposition C: Amortized random blinding — The observation that SnarkPack adds its own randomization, potentially allowing deterministic/structured blinding for individual proofs. The message correctly flags this as "theoretically interesting but practically risky" due to zero-knowledge requirements.
Transposition D: SnarkPack-aware MSM batching — If multiple C2 proofs are computed on the same GPU, the proof components are already in GPU memory. The SnarkPack MSM over proof.C components could be done on-GPU before downloading, saving a CPU MSM.
Transposition E: "Lazy evaluation" for the proofshare marketplace — The architectural vision of a two-tier system where fast GPUs compute C2 and cheap CPU machines aggregate via SnarkPack.
These transpositions are not fully fleshed out in the message—they are identified as promising directions rather than fully specified proposals. But they demonstrate a deep understanding of both the proving protocol and the aggregation protocol, and they open up a design space that the earlier proposals had not considered.
Thread 5: What Doesn't Work
One of the most valuable sections of the message is "Thread 5: What Doesn't Work (Important to Document)." This lists five approaches that were investigated and ruled out:
- Streaming NTT during synthesis: Not feasible because NTT Step 1 needs the full domain
- Pre-computing INTT of A/B/C matrix columns: Turns sparse into dense—net loss
- Partial QAP evaluation: The QAP division H(τ) depends on the full a,b,c—can't be incrementally computed
- Tensor cores for field arithmetic: BLS12-381 Fr doesn't map to INT8/FP16 tensor ops
- Groth16 "proof recycling": Each proof requires fresh randomness; can't reuse intermediate results across proofs with different witnesses This section is valuable for two reasons. First, it saves future investigators from exploring dead ends. Second, it demonstrates intellectual rigor—the assistant did not simply find positive results and stop; it actively searched for negative results and documented them. The inclusion of this section is a hallmark of good engineering analysis. It is easy to propose optimizations that sound plausible but don't work. It is harder to test those ideas, find them infeasible, and document why. The message's willingness to do this increases confidence in its positive findings.
The Thinking Process: How the Message Was Constructed
The message's structure reveals the assistant's thinking process. It is organized as five threads, each representing a line of investigation. The threads are ordered by impact: Thread 1 (PCE) is the biggest win, Threads 2-3 are refinements, Thread 4 is a new architectural direction, and Thread 5 is negative results.
This ordering is itself a decision. The assistant could have presented the findings chronologically (how they were discovered) or by complexity (simplest first). Instead, it chose to lead with impact. This is a rhetorical choice that signals to the reader: "Here is the most important thing you need to know."
The message also includes a proposed document structure for c2-optimization-proposal-5.md, organized into five parts (A-E) that map roughly to the five threads. This structure is not merely descriptive; it is prescriptive. It tells the user how the assistant thinks the findings should be organized into a deliverable document.
The final section of the message—the user questions about document scope—is particularly interesting. The assistant asks two questions:
- Whether to go deep on PCE implementation details or split constraint-shape and SnarkPack into separate proposals
- Whether to include a total impact assessment across all five proposals These questions reveal that the assistant is thinking about the deliverable as much as the analysis. It has produced findings; now it needs to package them into a useful document. The questions are about scope, depth, and presentation—the classic concerns of a technical writer organizing complex material.
Assumptions and Their Validity
The message makes several assumptions that deserve scrutiny:
Assumption 1: The circuit structure is truly deterministic across all proofs. This is well-supported by the code analysis. The PoRep circuit is defined by the sector size and proving parameters, which are fixed for a given deployment. However, if the circuit definition changes (e.g., a new version of the proving system with different hash functions), the pre-compiled matrices would need to be regenerated. This is a maintenance cost, not a correctness issue.
Assumption 2: The CSR matrices can be serialized and loaded efficiently. The message estimates ~500 MiB compressed for all 10 partitions. This is plausible but depends on the sparsity of the matrices. If the matrices are denser than expected, the serialization size could be larger. The message does not provide precise sparsity measurements.
Assumption 3: Bellperson's is_witness_generator() mode works as described. This is a feature of the existing bellperson library. The message assumes it can be used to run a stripped-down synthesis that only executes alloc() closures. This may require verification against the actual bellperson codebase.
Assumption 4: The SnarkPack aggregation layer will be used. The message's Thread 4 analysis is contingent on the output being a SnarkPack aggregate. If the deployment uses individual proofs (not aggregated), the SnarkPack transpositions are irrelevant. The message acknowledges this implicitly by framing them as conditional optimizations.
Assumption 5: The 99% boolean witness statistic is accurate. This comes from the circuit analysis performed in earlier exploration tasks. It is likely correct for the PoRep circuit, but it depends on the specific circuit definition and may change with different proving parameters.
These assumptions are reasonable and well-supported by the available evidence. The message does not overclaim—it presents findings with appropriate caveats and acknowledges uncertainties.
Input Knowledge Required
To fully understand message 51, the reader needs knowledge of:
- Groth16 proving protocol: The structure of the proof (A, B, C elements), the role of the QAP, the use of MSMs and NTTs
- R1CS constraint systems: How circuits are represented as sparse matrices A, B, C with witness vector w
- Bellperson/bellman architecture: The
synthesize()method,enforce()andalloc()closures,LinearCombinationobjects - Filecoin PoRep: The 32 GiB sector size, 10 partitions, SHA-256 and Poseidon hash functions
- SnarkPack aggregation: The GIPA/TIPP/MIPP protocol, inner pairing products, MSM aggregation
- CSR sparse matrix format: Compressed Sparse Row representation for efficient MatVec
- CUDA GPU architecture: SM utilization, memory transfers, kernel launch overhead
- BLS12-381 curve: Field arithmetic characteristics, Montgomery form, cycle counts for multiply vs. add This is a demanding knowledge requirement. The message operates at the intersection of cryptography, systems programming, GPU computing, and protocol design. It is not accessible to a general audience—it is written for specialists who understand the full stack.
Output Knowledge Created
The message creates several forms of output knowledge:
- The PCE concept: A concrete architectural proposal for eliminating redundant circuit synthesis
- The two-phase witness generation approach: A practical method for separating witness computation from constraint evaluation
- Coefficient and witness specialization techniques: Specific optimizations for the MatVec phase based on measured distributions
- Pre-computed split MSM topology: A method for eliminating runtime classification in MSM computation
- Five SnarkPack transpositions: New architectural directions for co-designing C2 proving with aggregation
- Negative results: Five approaches that were investigated and ruled out, saving future work
- Implementation roadmap: A proposed document structure and questions about scope that guide the next steps This knowledge is immediately actionable. The PCE concept alone could drive a multi-week implementation effort with clear expected returns. The SnarkPack transpositions open up longer-term architectural possibilities.
The Broader Significance
Message 51 represents a shift in how the optimization problem is framed. The earlier proposals (1-4) accepted the existing pipeline architecture and optimized within it. They made synthesis faster, memory more efficient, and GPU utilization higher. But they did not question whether synthesis needed to happen at all.
Message 51 asks a more fundamental question: "What computation is truly necessary, and what is redundant?" This is the kind of question that leads to step-function improvements rather than incremental gains.
The PCE concept is not a micro-optimization. It is a re-architecting of the proving pipeline that eliminates an entire phase of computation. It is enabled by a property of the Filecoin PoRep circuit—deterministic constraint structure—that was always there but had not been exploited.
This is a common pattern in systems optimization. The biggest wins often come not from making existing code faster, but from identifying and eliminating unnecessary work. Message 51 demonstrates this pattern beautifully.
Conclusion
Message 51 is a landmark in this optimization campaign. It identifies the single largest optimization opportunity across the entire C2 proof generation stack—the elimination of redundant circuit synthesis via the Pre-Compiled Constraint Evaluator. It supports this finding with detailed analysis of constraint structure, coefficient distributions, and witness patterns. It extends the analysis to SnarkPack aggregation, opening up new architectural directions. And it documents what doesn't work, saving future investigators from dead ends.
The message's structure—five threads ordered by impact, a proposed document outline, and explicit questions about scope—reveals a methodical thinking process. The assistant is not just presenting findings; it is organizing them into a coherent narrative that guides the user toward the most important insights.
For anyone working on Groth16 proof generation, whether for Filecoin or other applications, the core insight of message 51 is broadly applicable: if your circuit structure is deterministic, you are wasting time by re-synthesizing it for every proof. The PCE concept can be adapted to any proving system where the constraint matrices are fixed across multiple proofs with different witnesses.
The message also serves as a model for technical analysis. It combines deep protocol knowledge, systems thinking, and practical engineering judgment. It quantifies its claims, acknowledges complexity, and documents negative results. It is the kind of analysis that does not just answer a question—it changes how the question itself is framed.