Skip to content

zorch.pcs.kzg.prover

KZG prover: commit and open, both backed by lax.msm.

commit is C = Σ aᵢ·[τⁱ]₁ = msm(coeffs, powers_g1); open at z is the same MSM over the quotient q(x) = (f(x) − f(z))/(x − z), with f(z) falling out of the same synthetic division as the remainder. Both run entirely on the GPU: the field arithmetic is fusion-ready normal form (the quotient recurrence) and the MSM lowers to stablehlo.msm, a dedicated GPU kernel — so neither hits the LLVM-NVPTX codegen cliff that fusing raw EC arithmetic would. Polynomials are taken in the coefficient basis (KZG's commitment is over powers of τ); an evaluation-form input must be interpolated to coefficients first.

KzgProverData dataclass

Retained witness from KzgProver.commit: the coefficient vectors, kept to build the opening quotients. The tuple holds references to the (immutable) input arrays — no polynomial data is copied.

Source code in zorch/pcs/kzg/prover.py
48
49
50
51
52
53
54
@dataclass(frozen=True)
class KzgProverData:
    """Retained witness from `KzgProver.commit`: the coefficient vectors, kept to
    build the opening quotients. The tuple holds references to the (immutable)
    input arrays — no polynomial data is copied."""

    coeffs: tuple[Array, ...]

KzgProver dataclass

Bases: ProverStage[OpeningClaim[KzgCommitment], OpeningWitness[KzgProverData], TrivialClaim, OpeningProof[KzgProof]]

Source code in zorch/pcs/kzg/prover.py
 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
105
106
107
108
@dataclass(frozen=True)
class KzgProver(
    ProverStage[
        OpeningClaim[KzgCommitment],
        OpeningWitness[KzgProverData],
        TrivialClaim,
        OpeningProof[KzgProof],
    ]
):
    pk: KzgProvingKey

    def commit(self, polys: Sequence[Array]) -> tuple[KzgCommitment, KzgProverData]:
        """Commit a batch of coefficient-basis polynomials. Returns the stacked G1
        commitments and the coeffs as prover data (kept to build quotients)."""
        commitments = [lax.msm(c, self.pk.powers_g1[: c.shape[0]]) for c in polys]
        return fnp.stack(commitments), KzgProverData(tuple(polys))

    def prove(
        self,
        claim: OpeningClaim[KzgCommitment],
        witness: OpeningWitness[KzgProverData],
        transcript: Transcript,
    ) -> ProveResult[TrivialClaim, OpeningProof[KzgProof]]:
        """Open the committed polynomials at the claim's points.

        Terminal: an opening closes its claim rather than reducing it."""
        values, proof, transcript = self._open(
            witness.prover_data, claim.points, transcript
        )
        return ProveResult(TrivialClaim(), OpeningProof(values, proof), transcript)

    def _open(
        self,
        prover_data: KzgProverData,
        points: Sequence[Array],
        transcript: Transcript,
    ) -> tuple[Array, KzgProof, Transcript]:
        """Open poly `j` at `points[j]`. Returns `(values, proofs, transcript)`.
        KZG runs no fold rounds, so the transcript passes through unchanged for a
        single (poly, point) each; batching many openings into one proof (a γ
        sampled from the transcript) is a later extension."""
        if len(prover_data.coeffs) != len(points):
            raise ValueError(
                f"batch mismatch: {len(prover_data.coeffs)} polys vs "
                f"{len(points)} points"
            )
        values, proofs = [], []
        for coeffs, z in zip(prover_data.coeffs, points):
            q, fz = _quotient_and_eval(coeffs, z)
            proofs.append(lax.msm(q, self.pk.powers_g1[: q.shape[0]]))
            values.append(fz)
        return fnp.stack(values), fnp.stack(proofs), transcript

commit

commit(
    polys: Sequence[Array],
) -> tuple[KzgCommitment, KzgProverData]

Commit a batch of coefficient-basis polynomials. Returns the stacked G1 commitments and the coeffs as prover data (kept to build quotients).

Source code in zorch/pcs/kzg/prover.py
68
69
70
71
72
def commit(self, polys: Sequence[Array]) -> tuple[KzgCommitment, KzgProverData]:
    """Commit a batch of coefficient-basis polynomials. Returns the stacked G1
    commitments and the coeffs as prover data (kept to build quotients)."""
    commitments = [lax.msm(c, self.pk.powers_g1[: c.shape[0]]) for c in polys]
    return fnp.stack(commitments), KzgProverData(tuple(polys))

prove

prove(
    claim: OpeningClaim[KzgCommitment],
    witness: OpeningWitness[KzgProverData],
    transcript: Transcript,
) -> ProveResult[TrivialClaim, OpeningProof[KzgProof]]

Open the committed polynomials at the claim's points.

Terminal: an opening closes its claim rather than reducing it.

Source code in zorch/pcs/kzg/prover.py
74
75
76
77
78
79
80
81
82
83
84
85
86
def prove(
    self,
    claim: OpeningClaim[KzgCommitment],
    witness: OpeningWitness[KzgProverData],
    transcript: Transcript,
) -> ProveResult[TrivialClaim, OpeningProof[KzgProof]]:
    """Open the committed polynomials at the claim's points.

    Terminal: an opening closes its claim rather than reducing it."""
    values, proof, transcript = self._open(
        witness.prover_data, claim.points, transcript
    )
    return ProveResult(TrivialClaim(), OpeningProof(values, proof), transcript)