Tracing the PoRep PSProve Bug: The ToABI Mapping Investigation
Introduction
In the course of a deep-dive debugging session targeting a production failure in a zero-knowledge proof system, the assistant issued a seemingly simple read command to examine lines 125–132 of a Go source file. This message, [msg 1607], is a single tool invocation within a much larger investigation into why PSProve (ProofShare) tasks fail for PoRep (Proof of Replication) challenges while succeeding for Snap challenges. The error message — "porep failed to validate" — had been traced back to the computeProof function in task_prove.go, and the assistant was now methodically working through the data flow to identify the root cause. This article examines that single message in depth, unpacking the reasoning, assumptions, and knowledge boundaries that surround it.
The Message Itself
The message contains a single read tool call targeting /tmp/czk/lib/proof/porep_vproof_types.go. The content returned shows lines 125 through 132 of that file:
125: return abi.RegisteredSealProof_StackedDrg64GiBV1_1_Feat_SyntheticPoRep, nil
126:
127: case "StackedDrg2KiBV1_2_Feat_NonInteractivePoRep":
128: return abi.RegisteredSealProof_StackedDrg2KiBV1_2_Feat_NiPoRep, nil
129: case "StackedDrg8MiBV1_2_Feat_NonInteractivePoRep":
130: return abi.RegisteredSealProof_StackedDrg8MiBV1_2_Feat_NiPoRep, nil
131: case "StackedDrg512MiBV1_2_Feat_NonInteractivePoRep":
132: ...
At first glance, this is just a fragment of a Go switch statement. But in the context of the investigation, it is a critical piece of evidence. The function being read is ToABI(), which converts a StringRegisteredProofType — a human-readable string representation of a proof type — into an abi.RegisteredSealProof enum value used by the Filecoin proof system. This conversion sits at the boundary between two different code paths: the normal proving pipeline, which passes ABI enum values directly through Go FFI to the Rust backend, and the PSProve pipeline, which serializes and deserializes proof data as JSON, using string-based proof type identifiers.
Why This Message Was Written: The Investigation Context
The message was written as part of a systematic debugging effort. The user had reported a production bug in message [msg 1601]: PSProve tasks were failing for PoRep challenges with the error "porep failed to validate", while Snap challenges processed through the same PSProve pipeline worked correctly. The user provided extensive context about how challenges are derived, noting that the challenge generation algorithm is identical between the PoW service and a real miner, and that the PSProve path had never been tested with PoRep before.
The assistant responded by launching a comprehensive investigation. In [msg 1602], it dispatched a task to trace the PSProve code paths. In [msg 1603], it read task_prove.go to locate the exact error path. In [msg 1604], it began examining porep_vproof_types.go to understand the Commit1OutRaw struct and how RegisteredProof is handled. In [msg 1605], it grepped for the ToABI function, and in [msg 1606], it read the beginning of that function. Message [msg 1607] continues this reading, picking up where the previous read left off.
The reasoning is clear: the assistant is following a hypothesis that the PSProve path differs from the normal CuZK path in how it handles proof type identifiers. If the PSProve path uses string-based proof types (from JSON deserialization) and converts them to ABI enums via ToABI(), while the normal path passes ABI enums directly, then any mismatch, omission, or incorrect mapping in ToABI() could cause the Rust CuZK backend to receive an unexpected or invalid proof type, triggering the validation failure.
Input Knowledge Required
To understand the significance of this message, several pieces of domain knowledge are necessary:
The PSProve Architecture: PSProve is a ProofShare task system where proving work is distributed across a network. Unlike the normal proving pipeline, which calls ffi.SealCommitPhase1() and ffi.SealCommitPhase2() directly through Go FFI, the PSProve path receives proof data as JSON from a market queue, unmarshals it into Go structs, and then re-marshals it for the CuZK proving engine. This round-trip through JSON serialization is a potential source of data corruption or structural differences.
The Proof Type System: Filecoin proofs use a RegisteredSealProof enum that distinguishes between different proof types: interactive PoRep (StackedDrg32GiBV1_1), synthetic PoRep (StackedDrg32GiBV1_1_Feat_SyntheticPoRep), non-interactive PoRep (StackedDrg32GiBV1_2_Feat_NonInteractivePoRep), and Snap proofs. Each has different challenge derivation algorithms and different parameters. The PSProve path represents these as strings (e.g., "StackedDrg32GiBV1_2_Feat_NonInteractivePoRep") in its JSON data, while the normal path uses the ABI enum directly.
The CuZK Pipeline: CuZK is a GPU-accelerated proving engine that replaces the standard C2 (Groth16 SNARK wrapping) step. It receives proof data via gRPC, where the proof type is communicated as a protobuf field. The Rust CuZK backend then deserializes the vanilla proof bytes and processes them. A critical finding from earlier investigation was that the Rust CuZK prover ignores the registered_proof field from the gRPC request for PoRep and instead uses the value embedded inside the C1 JSON output itself — meaning the proof type must be correctly encoded in the JSON blob.
The Go/Rust Boundary: The codebase spans Go (task orchestration, JSON handling) and Rust (proof computation, CuZK engine). Data crossing this boundary must be serialized and deserialized correctly. Custom MarshalJSON methods exist on some types but may lack corresponding UnmarshalJSON methods, creating asymmetry in the round-trip.
Output Knowledge Created
This message reveals several important details about the ToABI() function:
First, the function handles both interactive (V1_1) and non-interactive (V1_2) proof types. Line 125 shows a synthetic PoRep variant (StackedDrg64GiBV1_1_Feat_SyntheticPoRep), while lines 127–131 show non-interactive variants for smaller sector sizes (2KiB, 8MiB, 512MiB). The naming convention in the ABI enum uses an abbreviated form: _Feat_NiPoRep instead of _Feat_NonInteractivePoRep.
Second, the mapping is straightforward string-to-enum conversion with no additional logic. There is no validation, no fallback, and no error handling beyond the error return type. If a string doesn't match any case, the function presumably returns an error — but the code shown doesn't include the default case.
Third, the function appears to be comprehensive for the non-interactive variants, covering 2KiB, 8MiB, 512MiB, and presumably 32GiB and 64GiB (which would appear on subsequent lines). This suggests that the mapping itself is unlikely to be the source of the bug — unless the PSProve path is using a proof type string that doesn't have a corresponding case.
Assumptions and Potential Pitfalls
The assistant is operating under several assumptions in this investigation:
Assumption 1: The bug is caused by a difference in how proof types are handled between the PSProve and normal paths. This is a reasonable hypothesis given that Snap PSProve works (Snap uses different proof types) while PoRep PSProve fails. However, the root cause could also lie elsewhere — in the JSON serialization of the C1 output struct, in the Commit1OutRaw wrapper, or in the Rust CuZK deserialization logic.
Assumption 2: The ToABI() function is relevant to the PSProve path. The assistant is tracing the code to confirm that PSProve actually calls this function. If PSProve bypasses ToABI() and uses a different mechanism to determine the proof type, then this investigation path would be a dead end.
Assumption 3: The proof type string in the PSProve JSON data matches one of the cases in ToABI(). If the market sends a proof type string that doesn't match any case (e.g., a legacy format or a typo), the conversion would fail, but the error would likely be different from "porep failed to validate."
Potential Mistake: The assistant is reading the file incrementally, line by line. This is thorough but risks missing the forest for the trees. The full ToABI() function might have a subtle bug — such as mapping a string to the wrong ABI enum value — that would only be visible when viewing the complete mapping side by side. Reading in fragments could obscure such a pattern.
The Thinking Process Visible in the Investigation
The assistant's reasoning, visible across the sequence of messages, follows a classic debugging methodology:
- Reproduce and isolate: The error is specific to PSProve + PoRep. Snap PSProve works, normal CuZK PoRep works. This narrows the search space to the intersection of PSProve and PoRep.
- Trace the error path: Starting from the error message "porep failed to validate," the assistant traces back through
computeProof→computePoRep→ the CuZK client call. - Examine data structures: The assistant reads
Commit1OutRawand the wrapper struct to understand how proof data flows through the PSProve path. - Follow the proof type: The assistant identifies that proof types are represented differently in the two paths (string vs. ABI enum) and investigates the conversion function
ToABI(). - Read systematically: The assistant reads
ToABI()in two chunks ([msg 1606] and [msg 1607]), covering the beginning and middle of the function. This suggests the function is long enough to warrant multiple reads. This is methodical and disciplined. The assistant is not jumping to conclusions or making wild guesses — it is tracing the actual code, line by line, to understand exactly what happens when a PSProve PoRep request is processed.
The Bigger Picture
Message [msg 1607] is a small but essential step in a larger investigation that would ultimately uncover a subtle JSON serialization round-trip bug. As noted in the segment summary, the investigation would later identify that the Commit1OutRaw struct is unmarshaled from the market's JSON and then re-marshaled for CuZK, and that custom MarshalJSON methods on PoseidonDomain and Sha256Domain types lack corresponding UnmarshalJSON methods, causing the HasherDomain = any type alias to bypass the custom marshalers on the round-trip. This produces JSON that differs from what the Rust CuZK backend expects, causing the verification failure.
The ToABI() function, while not the root cause, was a critical piece of the puzzle. By examining it, the assistant could rule out a simple proof type mapping error and focus on the more subtle serialization issue. This is the hallmark of thorough debugging: systematically eliminating hypotheses until the true cause is found.
Conclusion
Message [msg 1607] appears unremarkable at first — a single read command returning a few lines of a Go switch statement. But in context, it represents a deliberate, reasoned step in a complex debugging investigation spanning multiple codebases, languages, and serialization boundaries. The assistant's methodical approach — tracing the error path, examining data structures, and systematically reading the conversion functions — demonstrates a disciplined debugging methodology. While this message alone does not solve the bug, it contributes essential knowledge: the ToABI() mapping exists, it covers the expected proof types, and the investigation must therefore look elsewhere for the root cause. In the broader narrative of the coding session, this message is a waypoint, not a destination — but understanding the waypoints is essential to understanding the journey.