Tracing the Supraseal Path: A Single File Read in a Chain Randomness Investigation

Introduction

In the middle of a deep forensic investigation into how Filecoin's chain randomness flows from the beacon through to SNARK proof verification, the assistant performs what appears at first glance to be a routine file read. Message [msg 27] is deceptively simple: the assistant reads lines 1411–1418 of /tmp/czk/lib/paths/local.go to examine the supraPoRepVanillaProof function. But this single read operation is a carefully motivated step in a complex chain of reasoning, one that reveals how the assistant is systematically testing a hypothesis about a subtle but critical discrepancy in randomness handling across different proving paths.

The Broader Investigation

To understand why this message matters, we must step back to the investigation's origin. In message [msg 0], the user posed a precise question: does the Filecoin chain apply [31] &= 0x3f (known as "fr32 masking") to randomness returned by StateGetRandomnessFromBeacon with DomainSeparationTag_InteractiveSealChallengeSeed? And does the on-chain actors code apply such masking during seal verification? The stakes are high: if the chain returns raw randomness but the on-chain verification expects masked randomness (or vice versa), proofs that pass local testing could fail on-chain, causing sector fault penalties and lost revenue.

The assistant has been methodically tracing every layer of the randomness pipeline. Messages [msg 1] through [msg 4] establish the search space, finding key locations where DrawRandomnessFromBase, InteractiveSealChallengeSeed, and 0x3f masking appear in the Curio codebase. Messages [msg 5] through [msg 14] trace the chain side, following StateGetRandomnessFromBeacon through the Lotus state manager to the DrawRandomnessFromBase implementation, which uses blake2b-256 hashing with no masking. Messages [msg 15] through [msg 24] examine the FFI bridge, the builtin-actors verification code, and the Lotus sealing pipeline to see how each layer handles the seed.

By message [msg 26], the assistant has established a critical finding: the chain returns raw blake2b-256 output with no fr32 masking. The builtin-actors verification path also does not apply masking. However, the Curio codebase has multiple locations where rand[31] &= 0x3f is applied — in the WinningPoSt task, the WindowPoSt computation, the test CLI, and the web API. This creates a potential mismatch: if some paths mask and others don't, proofs could be inconsistent.

Why Read the Supraseal Path?

Message [msg 27] is motivated by a specific question the assistant articulates at the end of message [msg 26]: "Now let me look at how the supraseal path handles the seed." The assistant has already noted that the C1Check path (in sdr_funcs.go at line 320) generates a fully random seed with rand.Read(sd[:]) and applies no fr32 masking. This is significant because the C1Check path is a self-consistency check that runs during sealing — if it uses unmasked randomness while the actual on-chain path uses masked randomness, the check could pass locally but the real proof could fail on-chain.

The supraseal path represents an alternative proving pipeline. Curio supports multiple proving backends: the standard filecoin-ffi path, the cuzk (GPU-accelerated) path, and the supraseal path (which uses a different Rust library for SNARK proving). Each path might handle the seed differently. The assistant needs to verify that the supraseal path does not introduce yet another inconsistency.

The Message Content

The message is a read tool call that retrieves the function signature and first few lines of supraPoRepVanillaProof:

[assistant] [read] /tmp/czk/lib/paths/local.go
<path>/tmp/czk/lib/paths/local.go</path>
<type>file</type>
<content>1411: func (st *Local) supraPoRepVanillaProof(src storiface.SectorPaths, sr storiface.SectorRef, _, unsealed cid.Cid, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness) ([]byte, error) {
1412: 	batchMetaPath := filepath.Join(src.Cache, BatchMetaFile)
1413: 	bmdata, err := os.ReadFile(batchMetaPath)
1414: 	if err != nil {
1415: 		return nil, xerrors.Errorf("read batch meta file: %w", err)
1416: 	}
1417: 
1418: 	var bm...

The function signature reveals several important details. The seed parameter has type abi.InteractiveSealRandomness, which is a [32]byte alias — a raw 32-byte array with no semantic constraints. The function also takes ticket as abi.SealRandomness (also [32]byte). The parameter list includes src storiface.SectorPaths for sector storage paths, sr storiface.SectorRef for the sector reference, an unnamed parameter (the sealed CID, discarded with _), and the unsealed CID.

The function immediately reads a "batch meta file" from the cache directory. This is the BatchMetaFile constant, which stores metadata about the batch sealing operation that produced this sector. The supraseal path is designed for batch sealing — multiple sectors are sealed together in a single operation, and the batch metadata contains information needed to reconstruct the proof context.

Assumptions and Reasoning

The assistant makes several assumptions in choosing to read this function. First, it assumes that the supraseal path is sufficiently different from the standard FFI path that it might handle randomness differently. This is a reasonable assumption given that supraseal uses a different Rust library with its own proof generation logic. Second, the assistant assumes that examining the Go-level function signature and initial implementation will reveal whether masking is applied at this layer. The function receives seed as a raw byte array — if masking were to be applied, it would likely happen either before this call (in the caller) or inside the Rust layer.

The assistant also assumes that the batch meta file is relevant to randomness handling. The file read at line 1413 suggests that the supraseal path reconstructs proof context from batch metadata rather than from individual sector parameters. This is a structural difference from the standard path, which passes seed and ticket directly to the Rust SealCommitPhase1 function.

Knowledge Required

To understand this message, the reader needs substantial background in Filecoin's proof architecture. They must understand that SealCommitPhase1 (C1) produces a "vanilla proof" that is then compressed by SealCommitPhase2 (C2) into a SNARK proof. They must understand fr32 masking — the operation [31] &amp;= 0x3f that clears the two high bits of the last byte to ensure the 32-byte value represents a valid field element in BLS12-381's scalar field. They must understand that abi.SealRandomness and abi.InteractiveSealRandomness are both [32]byte aliases with no semantic difference at the type level — the distinction is purely contextual (ticket vs. seed).

The reader also needs to understand the Curio architecture: storiface.SectorPaths contains the file paths for a sector's cache, sealed, and unsealed files; storiface.SectorRef contains the sector ID and proof type; and the Local struct is the implementation of the sector storage interface that handles local disk operations.

Knowledge Created

This read operation produces several pieces of knowledge. First, it confirms that the supraseal path receives the seed as a raw [32]byte through the Go type system — no masking is applied at the Go layer of this function. Second, it reveals that the supraseal path reads a batch meta file, indicating a fundamentally different approach to proof generation compared to the standard per-sector path. Third, the function signature shows that the sealed CID is discarded (the _ parameter), which is unusual — in the standard path, the sealed CID is a critical input to C1. This suggests that the supraseal path may derive the sealed CID from the batch metadata or from the unsealed CID plus replica ID.

The read also sets up the next investigative step. The assistant will need to continue reading the function body (the truncated var bm... at line 1418) to see how the seed is actually used — whether it's passed directly to the Rust layer, or whether it undergoes any transformation. The subsequent messages ([msg 28] through [msg 44]) show the assistant pivoting to examine the Rust layer directly, finding the critical bytes_into_fr vs bytes_into_fr_repr_safe distinction in the fr32 library.

Significance

Message [msg 27] exemplifies the investigative methodology at work in this session. The assistant is not reading files randomly — each read is a targeted probe into a specific layer of the randomness pipeline. The supraseal path read fills in a gap in the investigation: having examined the chain side, the Lotus pipeline, the builtin-actors verification, the standard FFI path, and the C1Check path, the assistant now needs to ensure the supraseal path is also accounted for.

The ultimate finding of this investigation — that the chain returns unmasked randomness, that bytes_into_fr in the Rust layer will reject values with high bits set, and that as_safe_commitment applies masking only to commitments (not to seeds) — has direct operational implications. If any code path passes an unmasked seed to the Rust layer without the caller having applied [31] &amp;= 0x3f, the proof generation could fail with a "BadFrBytes" error, or worse, produce an invalid proof that passes local checks but fails on-chain verification. The Curio codebase's scattered 0x3f masking operations (found in at least 9 locations across the codebase) suggest an ad-hoc approach to this problem, where each developer independently discovered the need for masking in their specific context.

This single file read, modest as it appears, is a necessary thread in a much larger tapestry — one that ultimately protects Filecoin storage providers from costly proof failures.