Skip to content

zorch.logup_gkr.verifier

Dense LogUp-GKR verifier -- the dual of the prover chain.

A GkrLayerRound replays one layer's per-variable sumcheck (the agnostic zorch.verify driver over zorch.sumcheck.verifier.SumcheckRound), checks the LogUp oracle at the bound point via the shared logup_combine, then reduces the claim across the child selector. The whole GKR verifier is verify_rounds([GkrLayerRound() for _ in layer_proofs]), threading the same (num_eval, den_eval, eval_point) carry the prover does and ANDing every layer's check. The eq factor of the oracle is evaluated with the O(n) eval_eq, so verification stays succinct (no 2**n weight vector).

It stops at the reduced point-claim. The final claim == leaf_mle(point) check needs a PCS opening of the input trace and is the consumer's, keeping this block PCS-agnostic; the roundtrip tests close it directly against the dense leaf MLE.

GkrLayerRound

Bases: VerifierRound

Verify one GKR layer; the chain of these is the GKR verifier.

Source code in zorch/logup_gkr/verifier.py
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
class GkrLayerRound(VerifierRound):
    """Verify one GKR layer; the chain of these is the GKR verifier."""

    def __init__(self, challenges: ChallengePolicy) -> None:
        self.challenges = challenges

    def __call__(
        self, claim: LayerClaim, transcript: Transcript, layer_proof: LayerProof
    ) -> tuple[LayerClaim, Transcript, Array]:
        num_eval, den_eval, eval_point = claim
        n0, n1 = layer_proof.numerator_0, layer_proof.numerator_1
        d0, d1 = layer_proof.denominator_0, layer_proof.denominator_1
        transcript, lam = self.challenges.sample(transcript)
        claim = lam * num_eval + den_eval
        point, final_claim, transcript, ok_sc = verify(
            SumcheckVerifierRound(_DEGREE, self.challenges),
            claim,
            layer_proof.round_polys,
            transcript,
        )
        # The carry's eval_point must have one coordinate per sumcheck round, or
        # `eval_eq` reads the wrong eq weight (a degenerate length-1 carry would
        # broadcast silently against the bound point) -- reject, never broadcast.
        if eval_point.shape[0] != point.shape[0]:
            raise ValueError(
                f"eq point mismatch: claim has {eval_point.shape[0]} coords, "
                f"layer ran {point.shape[0]} rounds"
            )
        # LogUp oracle: the reduced claim equals the combine at the bound point.
        # eq is MSB-first on both sides, so the points align with no flip.
        eq_eval = eval_eq(eval_point, point)
        combined = logup_combine(lam, eq_eval, n0, d1, n1, d0)
        ok = ok_sc & (combined == final_claim)

        transcript, r = self.challenges.observe_and_sample(
            transcript, fnp.stack([n0, n1, d0, d1])
        )
        num_eval, den_eval, eval_point = fold_carry(n0, n1, d0, d1, point, r)
        return (num_eval, den_eval, eval_point), transcript, ok