Skip to content

zorch.pcs.fri.verifier

FRI verifier: rebuild the quotient from f, then check fold consistency.

For each query the verifier reconstructs the quotient g at the conjugate pair from the committed f values — g(x) = (f(x) − v)/(x − z) — and binds that pair to the committed layer-0 leaf, so a false claim v ≠ f(z) yields a non-low-degree g that fails both the per-layer fold check and the final-layer constant check. It never trusts a prover-sent layer-0 oracle. All arithmetic (NTT domain, field divide, Merkle rebuild) lowers on CPU and GPU.

FriVerifier dataclass

Bases: VerifierStage[OpeningClaim[FriCommitment], TrivialClaim, OpeningProof[list[FriProof]]]

Source code in zorch/pcs/fri/verifier.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@dataclass(frozen=True)
class FriVerifier(
    VerifierStage[
        OpeningClaim[FriCommitment],
        TrivialClaim,
        OpeningProof[list[FriProof]],
    ]
):
    params: FriParams

    def verify(
        self,
        claim: OpeningClaim[FriCommitment],
        reduction_proof: OpeningProof[list[FriProof]],
        transcript: Transcript,
    ) -> VerifyResult[TrivialClaim]:
        """Check the claimed evaluations against the commitment."""
        ok, transcript = self._verify_opening(
            claim.commitment,
            claim.points,
            reduction_proof.values,
            reduction_proof.proof,
            transcript,
        )
        return VerifyResult(TrivialClaim(), transcript, ok)

    def _verify_opening(
        self,
        commitment: FriCommitment,
        points: Sequence[Array],
        values: Array,
        proof: Sequence[FriProof],
        transcript: Transcript,
    ) -> tuple[Array, Transcript]:
        k = commitment.shape[0]
        if not len(points) == values.shape[0] == len(proof) == k:
            raise ValueError(
                f"batch mismatch: commitment={k}, points={len(points)}, "
                f"values={values.shape[0]}, proof={len(proof)}"
            )
        # Fail loud on a structurally short proof: the replay scan iterates over
        # whatever fri_roots it is handed, so a missing layer would silently skip
        # a round's checks rather than error. Eager, ahead of the jit zone.
        rounds = self.params.num_rounds
        for pf in proof:
            if len(pf.fri_roots) != rounds or len(pf.query_openings) != rounds:
                raise ValueError(
                    f"malformed proof: expected {rounds} fold layers, got "
                    f"{len(pf.fri_roots)} roots / {len(pf.query_openings)} openings"
                )
        t = transcript
        oks = []
        for f_root, z, v, pf in zip(commitment, points, values, proof):
            t, ok = _verify_one(self.params, f_root, z, v, pf, t)
            oks.append(ok)
        return fnp.all(fnp.stack(oks)), t

verify

verify(
    claim: OpeningClaim[FriCommitment],
    reduction_proof: OpeningProof[list[FriProof]],
    transcript: Transcript,
) -> VerifyResult[TrivialClaim]

Check the claimed evaluations against the commitment.

Source code in zorch/pcs/fri/verifier.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def verify(
    self,
    claim: OpeningClaim[FriCommitment],
    reduction_proof: OpeningProof[list[FriProof]],
    transcript: Transcript,
) -> VerifyResult[TrivialClaim]:
    """Check the claimed evaluations against the commitment."""
    ok, transcript = self._verify_opening(
        claim.commitment,
        claim.points,
        reduction_proof.values,
        reduction_proof.proof,
        transcript,
    )
    return VerifyResult(TrivialClaim(), transcript, ok)