Skip to content

zorch.pcs.ligero.prover

Single-shot Ligero prover — the prover half of the matrix-commitment PCS.

commit lays one multilinear f out as a rows x cols matrix (rows = code.message_len, cols = len(f) / rows), low-degree-extends each column (LinearCode.encode; the native-NTT Reed-Solomon today) and Merkle-commits the codeword rows — structurally basefold's matrix commit, but the columns are the low-bit slices of one polynomial rather than a batch of separate ones.

open at z = (z_row, z_col) sends w = X̃ · r_col (r_col = eq(z_col)) in the clear and opens a few codeword rows. Unlike basefold there is no fold-to-end: the verifier checks proximity <X[s], r_col> = encode(w)[s] on the opened rows and value <r_row, w> = y directly. Ligero needs only LinearCode.encode, not the FoldableCode fold seam.

LigeroProverData dataclass

Retained witness from LigeroProver.commit: the commitment root, the message-domain matrix [rows, cols] (open dots it with r_col), the committed base-field leaves (what the Merkle commit/open hashes), the Merkle digest layers, plus cols. A pytree so commit/open ride a @jit zone.

Source code in zorch/pcs/ligero/prover.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@partial(
    frx.tree_util.register_dataclass,
    data_fields=["root", "matrix", "leaves", "digest_layers"],
    meta_fields=["cols"],
)
@dataclass(frozen=True)
class LigeroProverData:
    """Retained witness from `LigeroProver.commit`: the commitment root, the
    message-domain matrix `X̃` `[rows, cols]` (open dots it with `r_col`), the
    committed base-field leaves (what the Merkle commit/open hashes), the Merkle
    digest layers, plus `cols`. A pytree so `commit`/`open` ride a `@jit` zone."""

    root: Array
    matrix: Array  # [rows, cols] message-domain X̃
    leaves: Array  # [block_len, cols*limbs] base-field Merkle leaves
    digest_layers: list[Array]
    cols: int

LigeroProver dataclass

Bases: ProverStage[OpeningClaim[LigeroCommitment], OpeningWitness[LigeroProverData], TrivialClaim, OpeningProof[LigeroProof]]

Single-shot Ligero PCS prover. code fixes the matrix row count (= message_len); tree commits the codeword rows.

Source code in zorch/pcs/ligero/prover.py
 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@dataclass(frozen=True)
class LigeroProver(
    ProverStage[
        OpeningClaim[LigeroCommitment],
        OpeningWitness[LigeroProverData],
        TrivialClaim,
        OpeningProof[LigeroProof],
    ]
):
    """Single-shot Ligero PCS prover. `code` fixes the matrix row
    count (`= message_len`); `tree` commits the codeword rows."""

    code: LinearCode
    tree: MerkleTree
    num_queries: int = 4  # query repetitions; placeholder, not soundness-calibrated

    def commit(
        self, polys: Sequence[Array]
    ) -> tuple[LigeroCommitment, LigeroProverData]:
        """Commit one multilinear laid out as a `rows x cols` matrix. The seam
        takes a `Sequence[Array]`; single-shot Ligero commits exactly one poly
        (batching several is a follow-up)."""
        if len(polys) != 1:
            raise ValueError(
                f"single-shot Ligero commits exactly one polynomial, got {len(polys)}"
            )
        f = polys[0]
        rows = self.code.message_len
        if f.ndim != 1 or f.shape[0] % rows != 0 or not is_power_of_two(f.shape[0]):
            raise ValueError(
                f"polynomial length {f.shape[0]} must be a power of two and a "
                f"multiple of rows={rows} (= code.message_len)"
            )
        cols = f.shape[0] // rows
        return _commit_body(self.code, self.tree, f, rows, cols)

    def prove(
        self,
        claim: OpeningClaim[LigeroCommitment],
        witness: OpeningWitness[LigeroProverData],
        transcript: Transcript,
    ) -> ProveResult[TrivialClaim, OpeningProof[LigeroProof]]:
        """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: LigeroProverData,
        points: Sequence[Array],
        transcript: Transcript,
    ) -> tuple[Array, LigeroProof, Transcript]:
        """Open the committed matrix at the shared point `z`. Returns
        `(value, proof, transcript)` with `value` the scalar `f(z)`."""
        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 (1 << (num_vars - k_row)) != prover_data.cols:
            raise ValueError(
                f"point dimension {num_vars} doesn't match matrix cols "
                f"{prover_data.cols} (expected 2^(num_vars - {k_row}))"
            )
        return _open_body(self, prover_data, z, transcript)

commit

commit(
    polys: Sequence[Array],
) -> tuple[LigeroCommitment, LigeroProverData]

Commit one multilinear laid out as a rows x cols matrix. The seam takes a Sequence[Array]; single-shot Ligero commits exactly one poly (batching several is a follow-up).

Source code in zorch/pcs/ligero/prover.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def commit(
    self, polys: Sequence[Array]
) -> tuple[LigeroCommitment, LigeroProverData]:
    """Commit one multilinear laid out as a `rows x cols` matrix. The seam
    takes a `Sequence[Array]`; single-shot Ligero commits exactly one poly
    (batching several is a follow-up)."""
    if len(polys) != 1:
        raise ValueError(
            f"single-shot Ligero commits exactly one polynomial, got {len(polys)}"
        )
    f = polys[0]
    rows = self.code.message_len
    if f.ndim != 1 or f.shape[0] % rows != 0 or not is_power_of_two(f.shape[0]):
        raise ValueError(
            f"polynomial length {f.shape[0]} must be a power of two and a "
            f"multiple of rows={rows} (= code.message_len)"
        )
    cols = f.shape[0] // rows
    return _commit_body(self.code, self.tree, f, rows, cols)

prove

prove(
    claim: OpeningClaim[LigeroCommitment],
    witness: OpeningWitness[LigeroProverData],
    transcript: Transcript,
) -> ProveResult[TrivialClaim, OpeningProof[LigeroProof]]

Open the committed polynomials at the claim's points.

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

Source code in zorch/pcs/ligero/prover.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def prove(
    self,
    claim: OpeningClaim[LigeroCommitment],
    witness: OpeningWitness[LigeroProverData],
    transcript: Transcript,
) -> ProveResult[TrivialClaim, OpeningProof[LigeroProof]]:
    """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)