The First Cut: How a Single Protobuf Edit Anchored Phase 1 of the cuzk Proving Engine
"Let me start implementing. First, the protobuf update to support multiple vanilla proofs."
These seventeen words, followed by an edit command applied to a single file, mark the precise moment when weeks of research, architectural planning, and codebase reconnaissance crystallized into concrete action. In message 292 of the cuzk proving engine development session, the assistant makes its first code change for Phase 1 of the project — an edit to the protobuf definition file proving.proto. On its surface, the message is almost absurdly brief: a declarative sentence, a file path, and a confirmation that the edit was applied. But beneath that terseness lies the culmination of an extraordinary investigative effort spanning seven prior messages, three separate research tasks, and dozens of source files read across Rust crates, Go FFI layers, and protobuf schemas. Understanding why this particular message matters requires unpacking the entire chain of reasoning that led to it.
The Weight of Preparation
To appreciate what this message accomplishes, one must first understand what preceded it. The assistant did not simply sit down and start editing protobufs. Messages 285 through 291 form a dense research pipeline that systematically mapped the entire Phase 1 landscape. Message 285 reviewed the project plan and impact assessment documents, establishing the high-level deliverables: wire up WinningPoSt, WindowPoSt, and SnapDeals provers; build a multi-GPU worker pool; implement priority scheduling with SRS affinity; and add per-GPU SRS tracking. Message 286 then dove into the existing codebase, reading every source file in the extern/cuzk/ workspace — all six crates, their Cargo.toml files, the engine, scheduler, prover, types, and service modules. This established a baseline understanding of what already existed and what needed to change.
Message 287 shifted focus outward, exploring the filecoin-proofs-api crate (version 19.0.0) to understand the exact Rust function signatures for the three new proof types. This was not a casual skim; the assistant searched through the registry source, examined type definitions, and documented the precise parameter types: ChallengeSeed, Commitment, ProverId, Ticket, and crucially, the RegisteredSealProof enum values that map to numeric constants. Message 288 then examined Curio's Go FFI layer, reading compute_do.go and local.go to understand how vanilla proofs are serialized for each proof type in production. This revealed a critical design constraint: PoSt proofs require multiple vanilla proofs per request (one per sector), unlike PoRep which uses a single vanilla proof. SnapDeals, meanwhile, needs three commitment CIDs.
Messages 289 and 290 read the current protobuf definition and the gRPC service implementation, confirming that the existing vanilla_proof field was a single bytes — insufficient for the multi-proof needs of PoSt and SnapDeals. Message 291 finally synthesized everything into a precise implementation plan with six sub-tasks, the first of which was "Phase 1.1: Update protobuf — add repeated bytes vanilla_proofs for PoSt/Snap multi-proof support."
The Decision Hidden in the Edit
The protobuf edit itself embodies several interconnected design decisions. First, the choice to use repeated bytes rather than an alternative encoding (such as a single bytes containing concatenated proofs with length prefixes, or a message wrapper) reflects a commitment to protobuf's native repeated-field semantics. This decision carries implications: it makes the API self-describing, allows protobuf libraries to handle parsing automatically, and aligns with gRPC best practices. It also means the Go client (Curio) can use protobuf's generated accessors to append proofs one at a time as it iterates over sectors.
Second, the decision to modify the existing vanilla_proof field rather than creating a separate field for multi-proof requests reveals an architectural assumption: that the protobuf schema should unify all proof types under a single request structure, with the proof_kind enum disambiguating which fields are relevant. This keeps the API surface small and avoids proliferation of RPC methods. The assistant could have defined separate RPCs like SubmitWinningPoSt and SubmitWindowPoSt, but chose instead to extend the existing SubmitProof — a decision that prioritizes simplicity and evolvability over type safety at the API boundary.
Third, the timing of the edit — making it the very first implementation action — reflects a deliberate bottom-up strategy. The protobuf definition is the outermost contract of the system, the boundary between the daemon and its clients. Changing it first ensures that all downstream code (types, prover, service, client) can be developed against the correct interface. It is a textbook example of interface-first design: define the contract, then implement against it.
Input Knowledge Required
Understanding this message requires substantial context that the assistant had assembled through its research. One must know that the existing protobuf schema defined vanilla_proof as a single bytes field, used by the PoRep C2 proof path. One must know that WinningPoSt and WindowPoSt each require one vanilla proof per challenged sector — potentially hundreds per request — making repeated bytes the natural choice. One must know that the filecoin-proofs-api functions generate_winning_post_with_vanilla and generate_single_window_post_with_vanilla accept vectors of vanilla proofs, not single ones. One must know that the RegisteredSealProof enum maps proof kinds to numeric values through a #[repr(i32)] C FFI enum, and that these values must be correctly translated across the Rust FFI boundary. One must know that the existing cuzk-core/src/types.rs defines a ProofRequest struct with a vanilla_proof: Vec<u8> field that will need corresponding changes. And one must know that the gRPC service in service.rs deserializes the protobuf request into this ProofRequest struct, meaning the change ripples through at least three layers.
None of this knowledge is stated in message 292 itself. The message is opaque to an outsider. Its meaning is carried entirely by the research that preceded it.
Output Knowledge Created
With this single edit, the assistant created a new interface contract. The protobuf file now declares that a proof request can carry zero or more vanilla proofs, not just one. This immediately enables the downstream changes that follow in subsequent messages: types.rs gains a vanilla_proofs: Vec<Vec<u8>> field; prover.rs is rewritten with real implementations that iterate over these proofs; the scheduler and engine are updated for multi-proof dispatching. The edit also implicitly commits the project to a unified RPC design — a decision that would be costly to reverse if later found inadequate.
More subtly, the edit creates knowledge about the system's evolution. It tells future readers that Phase 1 began with the API boundary, not the implementation internals. It establishes a pattern: understand the interface, then build inward. This is architectural knowledge encoded in the commit history itself.
Assumptions and Potential Pitfalls
The edit makes several assumptions worth examining. It assumes that repeated bytes is the correct encoding for all proof types — but what if some future proof type requires structured metadata per vanilla proof (e.g., sector index, timestamp)? A repeated message wrapper might have been more extensible. It assumes that the Go client (Curio) can easily adapt to sending repeated bytes — which is true for protobuf but requires changes to Curio's serialization logic. It assumes that the proof order in the repeated field corresponds to the order expected by the FFI functions — a silent coupling that could cause subtle bugs if mismatched.
The edit also assumes that the existing PoRep C2 path, which uses a single vanilla_proof, can coexist with the new vanilla_proofs field without ambiguity. In protobuf, an empty repeated bytes field is indistinguishable from absence, so a PoRep request would set vanilla_proofs to a single-element list while a PoSt request might set it to a hundred-element list. The proof_kind field disambiguates, but this creates an implicit invariant: every proof kind must agree on the interpretation of vanilla_proofs. A future developer adding a new proof kind must understand this invariant or risk introducing bugs.
The Thinking Process
The visible reasoning in the preceding messages reveals a methodical, almost forensic approach. The assistant does not guess at API signatures — it reads the actual source. It does not assume serialization formats — it traces Curio's Go code. It does not design in a vacuum — it reads every existing file in the workspace to understand the current state. The protobuf edit is the first output of this process, but the real work was the mapping of constraints: the FFI enum values, the multi-proof semantics of PoSt, the three-commitment structure of SnapDeals, the existing single-proof pattern of PoRep. Each constraint was discovered through direct code reading, not inference.
This message, then, is not merely an edit to a protobuf file. It is the moment when understanding became action, when research became engineering, when the map became the territory. It is the first cut — the one that makes all subsequent cuts possible.