Skip to content

zorch.pcs.kzg.verifier

KZG verifier: one multi-pairing check per opening.

The check e(C − [f(z)]₁, [1]₂) = e(π, [τ − z]₂) is rearranged so every scalar combination lands on the G1 side (a lax.msm) and the G2 side is just the two fixed verifier-key points — no G2 scalar-mul:

e(C − f(z)·G1 + z·π, [1]₂) · e(−π, [τ]₂) == 1

This forces a device split: lax.msm is a GPU-only kernel while lax.pairing_check legalizes to CPU only. frx exposes no device_put, so the G1 combinations are computed on the GPU, their coordinates pulled to the host, and the points rebuilt on the CPU for the pairing — the split any pairing-based verifier on this stack must make. The verifier is O(1), so the round-trip is irrelevant. The rebuild is domain-faithful (rawfrom_raw of the same dtype), so Montgomery-form keys verify correctly.

KzgVerifier dataclass

Bases: VerifierStage[OpeningClaim[KzgCommitment], TrivialClaim, OpeningProof[KzgProof]]

Source code in zorch/pcs/kzg/verifier.py
 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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@dataclass(frozen=True)
class KzgVerifier(
    VerifierStage[
        OpeningClaim[KzgCommitment],
        TrivialClaim,
        OpeningProof[KzgProof],
    ]
):
    vk: KzgVerifierKey

    def verify(
        self,
        claim: OpeningClaim[KzgCommitment],
        reduction_proof: OpeningProof[KzgProof],
        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: KzgCommitment,
        points: Sequence[Array],
        values: Array,
        proof: KzgProof,
        transcript: Transcript,
    ) -> tuple[Array, Transcript]:
        """Check each opening's pairing equation; return `(all_ok, transcript)`."""
        k = commitment.shape[0]
        if not len(points) == values.shape[0] == proof.shape[0] == k:
            raise ValueError(
                f"batch mismatch: commitment={k}, points={len(points)}, "
                f"values={values.shape[0]}, proof={proof.shape[0]}"
            )
        one = fnp.array(1, dtype=values.dtype)
        cpu = frx.devices("cpu")[0]
        gen_g2 = _point_to_host(self.vk.gen_g2, cpu)
        tau_g2 = _point_to_host(self.vk.tau_g2, cpu)
        oks = []
        for c, z, fz, pi in zip(commitment, points, values, proof):
            # G1 side (GPU msm): linear combos keep every scalar off G2.
            g1_combo = lax.msm(
                fnp.stack([one, -fz, z]), fnp.stack([c, self.vk.gen_g1, pi])
            )  # C − f(z)·G1 + z·π
            neg_pi = lax.msm(fnp.stack([-one]), fnp.stack([pi]))  # −π
            with frx.default_device(cpu):
                g1 = fnp.stack(
                    [_point_to_host(g1_combo, cpu), _point_to_host(neg_pi, cpu)]
                )
                g2 = fnp.stack([gen_g2, tau_g2])
                oks.append(lax.pairing_check(g1, g2))
        return fnp.all(fnp.stack(oks)), transcript

verify

verify(
    claim: OpeningClaim[KzgCommitment],
    reduction_proof: OpeningProof[KzgProof],
    transcript: Transcript,
) -> VerifyResult[TrivialClaim]

Check the claimed evaluations against the commitment.

Source code in zorch/pcs/kzg/verifier.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def verify(
    self,
    claim: OpeningClaim[KzgCommitment],
    reduction_proof: OpeningProof[KzgProof],
    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)