The Critical Enum Mapping: How One File Read Unlocked Phase 1 of the cuzk Proving Engine

Introduction

In the middle of a sprawling implementation effort to extend the cuzk proving engine from a single-proof-type daemon into a full-fledged multi-proof Filecoin proving system, there is a message that appears, at first glance, to be trivial: a file read. Message <msg id=314> is simply the assistant reading a Rust source file — /home/theuser/curio/extern/filecoin-ffi/rust/src/proofs/types.rs — to examine an impl From<api::RegisteredPoStProof> for RegisteredPoStProof block. But this single read operation sits at the crux of one of the most delicate architectural challenges in the entire Phase 1 implementation: correctly mapping numeric proof-type identifiers across three language boundaries (Go → C FFI → Rust API) so that the gRPC daemon can dispatch WinningPoSt, WindowPoSt, and SnapDeals proofs to the correct underlying proving functions. Without this mapping being precisely correct, the entire multi-proof engine would silently produce wrong proofs or crash with confusing type errors.

Context: The Phase 1 Implementation Challenge

To understand why this file read matters, one must understand the architecture of the cuzk proving engine and the problem it solves. The cuzk daemon is a gRPC server that accepts proof-generation requests from Curio (a Filecoin storage-mining orchestrator written in Go), dispatches them to GPU workers, and returns the resulting proofs. Phase 0 had implemented only the simplest case: PoRep C2 proofs, where a single registered_proof numeric value maps directly to a single seal_commit_phase2 call. Phase 1 aimed to add the three remaining Filecoin proof types: WinningPoSt, WindowPoSt, and SnapDeals (empty sector updates).

The challenge is that each proof type has a different calling convention. WinningPoSt takes a single vanilla proof and produces a single partition proof. WindowPoSt takes multiple vanilla proofs (one per sector being proven) and produces a single aggregate proof. SnapDeals takes three commitment CIDs (comm_r_old, comm_r_new, comm_d) and a vanilla proof, producing a serialized proof blob. The gRPC protobuf needed to carry all these fields, and the engine needed to dispatch to the correct Rust API function based on the registered_proof value.

But the registered_proof value itself is the crux of the difficulty. Curio's Go code uses an ABI enum (abi.RegisteredPoStProof_StackedDrgWinning32GiBV1, etc.) that gets serialized to an integer and passed through C FFI. The Rust side of the FFI has its own #[repr(i32)] enum (RegisteredPoStProof) with variants in a specific order. And the filecoin-proofs-api crate (the actual proving library) has yet another enum (api::RegisteredPoStProof) with different variant names and ordering. The mapping between these three representations is not straightforward — the variant names differ between the FFI enum and the API enum, and the numeric values assigned by #[repr(i32)] depend on the order variants are declared.

The Message Itself: What Was Read

Message <msg id=314> is the assistant reading the file types.rs from the filecoin-ffi Rust crate, specifically looking at lines 190–198 which contain:

impl From<api::RegisteredPoStProof> for RegisteredPoStProof {
    fn from(other: api::RegisteredPoStProof) -> Self {
        use api::RegisteredPoStProof::*;

        match other {
            StackedDrgWinning2KiBV1 => RegisteredPoStProof::StackedDrgWinning2KiBV1,
            StackedDrgWinning8MiBV1 => RegisteredPoStProof::StackedDrgWinning8MiBV1,
            ...

This From implementation converts between the filecoin-proofs-api enum (the "api" version) and the FFI's own RegisteredPoStProof enum (the C-compatible #[repr(i32)] version). The file read was triggered by a chain of grep searches in messages &lt;msg id=312&gt; and &lt;msg id=313&gt; that attempted to find the numeric values of the enum variants. The grep RegisteredPoStProof.*=.*\d in &lt;msg id=313&gt; found 15 matches showing lines like RegisteredPoStProof::StackedDrgWinning2KiBV1 =&gt; StackedDrgWinning2KiBV1, but these lines show mapping to constant names, not the actual numeric values. The assistant needed to see the full context — the enum declaration itself and its #[repr(i32)] attribute — to understand how the integers were assigned.

Input Knowledge Required

To make sense of this file read, the assistant needed to have already accumulated a substantial body of knowledge from the preceding research:

  1. The gRPC protobuf structure: The SubmitProofRequest message carries a registered_proof field as a uint32, which arrives at the Rust engine as a raw integer. This integer must be converted to the correct API enum variant.
  2. The Curio Go FFI layer: From messages &lt;msg id=288&gt; and &lt;msg id=289&gt;, the assistant had learned that Curio's Go code uses Go ABI constants like abi.RegisteredPoStProof_StackedDrgWinning32GiBV1 which map to C-side integer values. The Go constants were defined in cgo/const.go (read in &lt;msg id=311&gt;) but only as references to C macros — the actual numeric values were opaque.
  3. The filecoin-proofs-api function signatures: Messages &lt;msg id=287&gt; and &lt;msg id=296&gt; had exhaustively documented the API functions generate_winning_post_with_vanilla, generate_single_window_post_with_vanilla, and generate_empty_sector_update_proof_with_vanilla, each taking an api::RegisteredPoStProof or api::RegisteredUpdateProof enum value.
  4. The two-enum architecture: The assistant had discovered in &lt;msg id=309&gt;&lt;msg id=313&gt; that there are two Rust enums for proof types in the FFI crate — the C-compatible #[repr(i32)] RegisteredPoStProof (used for FFI dispatch) and the api::RegisteredPoStProof (from filecoin-proofs-api, used for actual proving calls). The mapping between them is handled by From implementations.
  5. The V1_1 vs V1_2 naming discrepancy: Earlier research had revealed that what Go calls "V1_1" (e.g., StackedDrgWinning32GiBV1_1) maps to what filecoin-proofs-api calls "V1_2" — a critical detail that could cause silent proof failures if mishandled.

The Reasoning Process: What the Assistant Was Thinking

The assistant's reasoning, visible across the chain of messages &lt;msg id=309&gt; through &lt;msg id=315&gt;, reveals a systematic investigative process. The question being answered was: "When Curio sends a registered_proof integer over gRPC, how do I convert that integer to the correct api::RegisteredPoStProof variant to pass to the proving function?"

The initial approach was to find the actual numeric values. Message &lt;msg id=309&gt; grepped for RegisteredPoStProof_StackedDrg.*= looking for constant definitions. Message &lt;msg id=310&gt; tried a bash grep in the cgo/const.go file. Message &lt;msg id=311&gt; read that file but found only C macro references, not actual numbers. Message &lt;msg id=312&gt; tried grepping the C header directly for REGISTERED_PO_ST_PROOF_* constants but found nothing.

This dead end forced a change in strategy. Instead of finding the numeric values directly, the assistant realized in &lt;msg id=313&gt; that the mapping could be understood by examining the From implementations. The grep RegisteredPoStProof.*=.*\d found lines showing the mapping from FFI enum variants to constant names — but these were output constants (like StackedDrgWinning2KiBV1), not the discriminant values. The assistant needed to see the enum definition itself to understand the #[repr(i32)] layout.

Message &lt;msg id=314&gt; is the pivot point: reading the full types.rs file to see both the #[repr(i32)] enum declaration and the From&lt;api::RegisteredPoStProof&gt; implementation. The key insight, confirmed in &lt;msg id=315&gt;, is that the FFI enum uses #[repr(i32)] with variants in declaration order, meaning StackedDrgWinning2KiBV1 = 0, StackedDrgWinning8MiBV1 = 1, etc. The From implementation then maps each api::RegisteredPoStProof variant to the corresponding FFI variant — but critically, the variant names differ between the two enums (the FFI enum uses names like StackedDrgWinning32GiBV1 while the API enum uses StackedDrgWinning32GiBV1_1 or _2 depending on version).

Output Knowledge Created

This single file read produced several critical pieces of knowledge:

  1. The mapping strategy is confirmed: The From&lt;api::RegisteredPoStProof&gt; for RegisteredPoStProof implementation exists and handles the conversion. This means the cuzk engine does not need to reimplement this mapping — it can either use the FFI's conversion or, since the cuzk engine calls filecoin-proofs-api directly (not through the FFI), it needs to understand the relationship between the gRPC integer and the API enum.
  2. The #[repr(i32)] layout is implicit: The FFI enum's variants are assigned integer values 0, 1, 2, ... based on declaration order. This means the gRPC integer from Curio corresponds to the FFI enum's discriminant, not the API enum's discriminant. To convert a gRPC integer to an api::RegisteredPoStProof, one must go through the FFI enum: convert integer → FFI variant → API variant.
  3. The naming discrepancy is real and must be handled: The FFI enum uses StackedDrgWinning32GiBV1 (without version suffix) while the API enum uses versioned names like StackedDrgWinning32GiBV1_1 and StackedDrgWinning32GiBV1_2. The From implementation in the FFI crate handles this mapping, but the cuzk engine calls the API directly and must use the correct API variant names.
  4. The approach for the prover implementation: Rather than trying to decode the integer at the gRPC layer and convert it through the FFI enum, the assistant could take a simpler approach: have the prover functions accept the api::RegisteredPoStProof directly, and let the gRPC service layer handle the conversion. Or, even simpler, since the cuzk engine controls both the client and server sides, the protobuf could carry a string-based proof type identifier instead of a raw integer.

Assumptions and Potential Pitfalls

The assistant made several assumptions during this investigation:

  1. That the #[repr(i32)] enum uses default discriminant values (0, 1, 2, ...). This is the standard behavior for #[repr(i32)] enums in Rust when no explicit discriminant values are assigned, and it was confirmed in &lt;msg id=315&gt; when the enum declaration was read. However, the assistant did not verify that the Go side assigns the same numeric values to its ABI constants. If Go's enum had a different ordering or used explicit values, the mapping would be broken.
  2. That the From implementation is correct and complete. The assistant assumed that the existing FFI code correctly maps every API variant to the corresponding FFI variant. If there were missing cases or incorrect mappings, the cuzk engine would silently produce wrong proofs.
  3. That the gRPC integer is the FFI enum discriminant, not the API enum discriminant. This assumption is reasonable given the architecture (Go → C FFI → Rust FFI), but it was not explicitly verified by tracing a concrete value through the entire chain.
  4. That the V1_1/V1_2 distinction is handled by the From implementation. The assistant noted in &lt;msg id=315&gt; that "V1_1 in Go/FFI maps to V1_2 in filecoin-proofs-api," but this insight came after message &lt;msg id=314&gt;, suggesting that the initial file read may not have fully resolved the naming question.

The Broader Significance

Message &lt;msg id=314&gt; exemplifies a pattern that recurs throughout the cuzk development effort: the need to trace data flow across multiple abstraction layers and language boundaries. The cuzk engine sits at the intersection of Go orchestration code, C FFI bindings, Rust API libraries, and CUDA GPU kernels. A single integer — the registered_proof field — traverses all these layers, and getting its semantics wrong at any layer would cause failures that are difficult to debug because they manifest as "wrong proof" rather than "crash."

The file read in &lt;msg id=314&gt; is not glamorous. It does not produce code, fix a bug, or add a feature. But it represents the kind of meticulous, trace-every-byte investigation that distributed systems engineering demands. The assistant could have guessed the enum mapping, written the prover implementations, and hoped for the best. Instead, it traced the actual code paths, read the actual source files, and confirmed the mapping before writing a single line of prover code. This discipline is what separates robust systems from fragile ones.

Conclusion

Message &lt;msg id=314&gt; — a simple file read — is a microcosm of the entire cuzk development philosophy: research first, implement second. The assistant spent dozens of messages exploring the codebase, reading API signatures, studying Go FFI patterns, and tracing enum mappings before writing the Phase 1 prover implementations. This particular file read resolved a critical ambiguity about how proof-type identifiers flow from Curio's Go layer through the C FFI into the Rust proving API. Without this knowledge, the Phase 1 implementation would have been built on guesswork, and the resulting proof engine would have been unreliable at best. The message stands as a testament to the value of deep, systematic investigation in complex distributed systems engineering.