Single-shot Ligero prover — the prover half of the matrix-commitment PCS.
commit lays one multilinear f out as a rows x cols matrix X̃
(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 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.
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)
|