Skip to content

zorch.pcs.ligero.verifier

Single-shot Ligero verifier — the verifier half of the matrix PCS.

verify replays the prover's Fiat-Shamir order (bind root, value, sent w, then sample the query positions) and runs Ligero's two checks on the opened codeword rows:

  • proximity <X[s], r_col> == encode(w)[s] for every sampled row s, and
  • value <r_row, w> == y.

Proximity holds because encode is linear: encode(w) = encode(X̃ · r_col) = X · r_col, so encode(w)[s] = <X[s], r_col> for an honest w; a forged w disagrees with the committed codeword on ≥ distance positions and a random s catches it. Value then reads f(z) = r_row^T X̃ r_col = <r_row, w>. The verifier holds only the public params (code for the block geometry + the proximity right-hand side encode, tree for the Merkle config) — never the prover's matrix. Ligero needs only LinearCode.encode, not the fold seam.

LigeroVerifier dataclass

Bases: VerifierStage[OpeningClaim[LigeroCommitment], TrivialClaim, OpeningProof[LigeroProof]]

Single-shot Ligero PCS verifier.

Source code in zorch/pcs/ligero/verifier.py
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
86
87
88
89
90
91
92
93
94
95
96
97
@dataclass(frozen=True)
class LigeroVerifier(
    VerifierStage[
        OpeningClaim[LigeroCommitment],
        TrivialClaim,
        OpeningProof[LigeroProof],
    ]
):
    """Single-shot Ligero PCS verifier."""

    code: LinearCode
    tree: MerkleTree
    # Must match the prover's; placeholder count, not soundness-calibrated.
    num_queries: int = 4

    def verify(
        self,
        claim: OpeningClaim[LigeroCommitment],
        reduction_proof: OpeningProof[LigeroProof],
        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: LigeroCommitment,
        points: Sequence[Array],
        value: Array,
        proof: LigeroProof,
        transcript: Transcript,
    ) -> tuple[Array, Transcript]:
        """Return `(ok, transcript)` where `ok` is a scalar boolean array."""
        if len(points) != 1:
            raise ValueError(f"Ligero opens at one point, got {len(points)}")
        z = points[0]
        num_vars = z.shape[0]
        k_row = log2_strict_usize(self.code.message_len)
        if num_vars < k_row:
            raise ValueError(
                f"point dimension {num_vars} is fewer than the row variables "
                f"{k_row} (= log2 message_len)"
            )
        if proof.w.shape[0] != self.code.message_len:
            raise ValueError(
                f"sent vector w has length {proof.w.shape[0]}, expected "
                f"rows={self.code.message_len} (= code.message_len)"
            )
        return _verify_body(self, commitment, z, value, proof, transcript)

verify

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

Check the claimed evaluations against the commitment.

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