Tracing the Enum Labyrinth: A Cross-Layer Investigation of Proof Type Mappings in the Filecoin Proving Stack

In a sprawling, multi-language proving stack like Filecoin's, where Go orchestrates proof requests, Rust executes the heavy cryptographic lifting, and C serves as the FFI bridge between them, few bugs are more insidious than a silent enum mismatch. A single misaligned numeric value between layers can cause a proof to be generated with the wrong parameters, validated against the wrong circuit, or—worst of all—fail verification at the consensus layer with no clear indication of why. This article chronicles a methodical investigation into precisely such a concern: whether the RegisteredSealProof and RegisteredPoStProof enum definitions across Go, C, and Rust were consistent, and whether a suspected mismatch could explain an intermittent proving failure in the CuZK GPU proving engine.

The Spark: A User's Hint About Mismatched Mappings

The investigation began with a user report that CuZK-generated SNARKs were intermittently failing ffi.VerifySeal. The user offered a critical hint: "SPT - rust has different mappings most likely, follow filecoin-ffi/etc there are multiple enum mappings." The term "SPT" likely referred to "Seal Proof Type" or "Stacked Proof Type," and the suggestion was clear—somewhere in the chain of enum definitions linking Go's abi.RegisteredSealProof to Rust's filecoin_proofs_api::RegisteredSealProof, a numeric value might be diverging [11].

The assistant's first move was to map the entire terrain. The proving stack has three distinct layers where proof type enums are defined: Go's go-state-types library (the authoritative source of numeric values), the filecoin-ffi Rust FFI layer (which produces a C-compatible #[repr(i32)] enum), and the filecoin-proofs-api crate (which CuZK imports directly). Each layer could theoretically assign different numeric values to the same variant, and the CuZK gRPC service layer adds a fourth translation point where numeric proof type values arrive over the wire and must be converted back into Rust enums [15].

Systematic Enum Tracing Across Three Layers

The assistant launched a coordinated search across the codebase, beginning with broad file and content searches to locate every enum definition. The first round of exploration used parallel find and grep commands to search for RegisteredSealProof across all Rust files in the workspace [1]. The grep returned exactly one match—pub enum RegisteredSealProof at line 46 of /tmp/czk/extern/filecoin-ffi/rust/src/proofs/types.rs [3]—while the file-name search for registered_seal_proof* returned nothing, indicating the enum was embedded in a larger file rather than isolated in its own module.

This single match was significant: if multiple conflicting definitions existed, they would all have appeared in the grep results. The assistant had discovered that both the filecoin-ffi layer and the CuZK prover depend on the filecoin-proofs-api crate, but at potentially different versions [1]. A critical pivot came when the assistant searched for Cargo.lock files across the workspace [1], revealing three independent Rust projects—supraseal-c2, cuzk, and filecoin-ffi/rust—each with its own dependency resolution. The lock files confirmed that filecoin-ffi used both version 18.1.0 and 19.0.0 of filecoin-proofs-api (in different dependency contexts), while cuzk used only 19.0.0 [9]. This version divergence raised the specter of an enum mismatch, but further analysis showed the enum definitions were identical across versions.

The Go-side enum was found in the go-state-types module at /home/theuser/go/pkg/mod/github.com/filecoin-project/go-state-types@v0.14.0-dev/abi/sector.go, where RegisteredSealProof is defined as a Go type with explicit integer values starting at 0 for StackedDrg2KiBV1 and incrementing through V1_1 variants (5-9), SyntheticPoRep variants (10-14), and—in the v0.16.0 version used by filecoin-ffi—NiPoRep variants (15-19). These numeric values are the canonical reference point; every other layer must agree with them.

The FFI-facing Rust enum was found in /tmp/czk/extern/filecoin-ffi/rust/src/proofs/types.rs. This #[repr(i32)] enum has no explicit discriminants—Rust auto-numbers from 0 in declaration order. The assistant read the full definition and confirmed that the variant order exactly matches Go's numeric assignments: StackedDrg2KiBV1 at 0, StackedDrg8MiBV1 at 1, and so on through StackedDrg64GiBV1_2_Feat_NonInteractivePoRep at 19. The C header file filcrypto.h (auto-generated by safer_ffi) confirmed this mapping was exposed correctly to the CGo bridge.

The third layer—filecoin_proofs_api::RegisteredSealProof—was not checked out locally (it is a registry dependency at version 19.0.0), but the From implementation in the filecoin-ffi types.rs file revealed a 1:1 name-for-name mapping between the FFI enum and the api::RegisteredSealProof enum. Since both enums declare variants in the same order and the FFI enum starts at 0, their numeric values are identical.

Conclusion for RegisteredSealProof: all three layers agree perfectly. No mismatch exists.

The Go Enum Source Dead End

Locating the Go-side enum values proved unexpectedly difficult. The assistant attempted to find the go-state-types source code locally, reasoning that the Go module cache might contain a checkout of the dependency [2]. A find command searching for *go-state-types* directories under /tmp/czk returned nothing—the dependency was not vendored. A fallback grep of the go.mod file confirmed the exact version: github.com/filecoin-project/go-state-types v0.16.0 [2].

This dead end forced the investigation to pivot. Rather than reading the Go source directly, the assistant turned to the auto-generated C header file filcrypto.h, which contains #define constants for all enum values. This file, produced by the safer_ffi Rust crate, is the authoritative bridge between Go and Rust—it defines the exact integer values that cross the FFI boundary. By reading the C header, the assistant could verify the numeric values without needing the Go source at all [15].

The assistant also examined the Go-side mapping function toFilRegisteredSealProof in /tmp/czk/extern/filecoin-ffi/proofs.go, which converts Go's abi.RegisteredSealProof values to cgo.RegisteredSealProof constants [12]. This function maps by name rather than by numeric value, meaning it would silently produce incorrect results if the numeric values diverged. The fact that it works correctly in practice is further evidence that the enum values are consistent.

The PoSt Proof Twist: A Deliberate Renaming

The investigation then turned to RegisteredPoStProof, where the user's concern about "multiple enum mappings" proved more warranted. The Go abi.RegisteredPoStProof defines WinningPoSt variants at values 0-4 and WindowPoSt variants at 5-9, with Window V1_1 variants at 10-14. The FFI Rust enum mirrors this exactly, using the same V1_1 naming. However, the filecoin-proofs-api crate uses V1_2 for the WindowPoSt variants—a deliberate renaming to reflect a grindability fix in the proof construction.

The filecoin-ffi From implementation handles this gracefully, with explicit comments documenting the translation: "rust-filecoin-proofs-api WindowPoSt uses api_version V1_2 to fix the grindability issue, which we map here as V1_1 for Lotus/actors compat reasons." The CuZK prover, in its registered_post_proof_from_u64 function at /tmp/czk/extern/cuzk/cuzk-core/src/prover.rs, correctly maps Go's numeric values 10-14 directly to filecoin-proofs-api's V1_2 variants, with a comment noting "FFI V1_1 → proofs-api V1_2 (grindability fix)."

This was a critical finding: the naming discrepancy exists, but it is intentional and correctly handled at every translation point. The CuZK code is not buggy in this regard.

The gRPC Wire Protocol: Verifying the Full Path

To complete the picture, the assistant traced how proof type values travel from Go to CuZK. The protobuf definition in /tmp/czk/extern/cuzk/cuzk-proto/proto/cuzk/v1/proving.proto defines a registered_proof field that carries the numeric proof type as a uint64 [23]. The Go side sends this value from its abi.RegisteredSealProof enum, and the CuZK server receives it in service.rs at line 41: registered_proof: req.registered_proof. For PoRep C2 proofs, the proof type is not transmitted numerically at all—it is embedded in the SealCommitPhase1Output JSON structure, which is deserialized via serde directly into a filecoin_proofs_api::RegisteredSealProof value, bypassing any numeric translation entirely [23].

The assistant confirmed that the SealCommitPhase1Output struct in the CuZK Rust code matches the Go Commit1OutRaw struct field-for-field, and that the non-CuZK FFI path works correctly with Go-re-serialized JSON. This proved the semantic validity of the round-trip and narrowed the root cause of the intermittent failure to a subtle byte-level discrepancy in the JSON payload itself—likely in how public inputs are derived from the serialized data.

The fr32 Seed Masking Investigation

With the enum mappings confirmed correct, the assistant turned to another lead: the user's hint about fr32 seed masking. The powsrv service (which generates PoRep challenges) was discovered to not apply the standard seed[31] &= 0x3f truncation that the Filecoin specification requires. This could theoretically produce invalid Fr elements and cause intermittent proving failures.

The assistant traced the Rust challenge derivation code and found that the seed bytes are used as input to SHA256, not directly as an Fr element. The SHA256 output is then reduced into the field. Since the seed is not directly interpreted as a field element, the missing mask does not cause the intermittent failure. However, the assistant noted that this remains a correctness issue in powsrv that should be fixed independently.

Extending the Test Harness and Adding Diagnostic Logging

To finally capture the elusive byte-level discrepancy, the assistant extended the existing 2KiB roundtrip test to cover the full CuZK wrapper and FFI C2 verification path. This ensures that any future test run will exercise the exact same code path that produces the intermittent failure. Additionally, comprehensive diagnostic logging was added to computePoRep in task_prove.go to capture exact byte streams and verification inputs at the moment of failure. When the intermittent failure next occurs, these logs will provide the precise data needed to compare the Go-serialized and CuZK-serialized JSON payloads byte-by-byte and identify the root cause.

Themes and Methodology

This chunk of the investigation exemplifies several important debugging principles. First, systematic elimination: before chasing exotic explanations (seed masking, enum mismatches), verify the foundational assumptions. The assistant traced every enum definition across every layer, confirmed their consistency, and documented the one intentional discrepancy (V1_1 vs V1_2) with its correct handling. Second, instrumentation over speculation: rather than guessing at the byte-level difference, the assistant built the tools to capture it. The extended roundtrip test and diagnostic logging transform an intermittent, hard-to-reproduce failure into a captured, analyzable event. Third, cross-language awareness: in a stack spanning Go, C, Rust, and protobuf, a bug can hide at any boundary. The assistant methodically traced each boundary—Go→CGo→C→Rust FFI→filecoin-proofs-api→CuZK—and verified correctness at every step.

Conclusion

The enum mapping investigation resolved the user's primary concern: the RegisteredSealProof and RegisteredPoStProof enums are consistent across all layers of the Filecoin proving stack. The one naming discrepancy (WindowPoSt V1_1 vs V1_2) is intentional, well-documented, and correctly handled by both the filecoin-ffi translation layer and the CuZK prover. The intermittent CuZK proving failure is not caused by an enum mismatch. With the enum hypothesis eliminated and the seed masking hypothesis ruled out, the investigation is now positioned to capture the exact byte-level discrepancy through enhanced instrumentation—a classic example of narrowing the search space through methodical, cross-layer analysis.## References

[1] "The Cargo.lock Pivot: How a Simple File Search Unlocked the Enum Mapping Investigation" — describes the strategic pivot to dependency lock files when direct source search failed.

[2] "The Dead End: Tracing Go Enum Sources in a Cross-Language Proof System" — documents the failed attempt to locate go-state-types source code locally.

[3] "Navigating the Enum Labyrinth: A Targeted Search for RegisteredSealProof Definitions" — analyzes the parallel find/grep search strategy that located the enum definition.

[9] "Version Divergence in the Filecoin Proving Stack: Uncovering a Dual-Dependency Bug Through Cargo.lock Analysis" — examines the version mismatch between filecoin-proofs-api 18.1.0 and 19.0.0.

[11] "Tracing the Enum Mapping Labyrinth: A Deep Dive into RegisteredSealProof Across Go, Rust, and FFI Layers" — the initial reconnaissance message that launched the investigation.

[12] "Tracing the Enum Bridge: How Message 12 Maps Go's RegisteredSealProof to the FFI Layer" — analysis of the toFilRegisteredSealProof Go mapping function.

[15] "Tracing the Enum Boundary: How Proof Type Values Cross the Go-Rust FFI Divide" — examines the C header file filcrypto.h as the authoritative FFI bridge.

[23] "Tracing the gRPC Wire Protocol: How a Protobuf Read Completed the Enum Mapping Investigation" — traces proof type values through the protobuf gRPC layer into CuZK.