zorch.pcs.ipa.prover¶
IPA prover: commit and open by log-n basis folding.
commit is the Pedersen MSM P = ⟨a, G⟩ = msm(coeffs, basis). open proves
p(x) = ⟨a, b⟩ for b = (1, x, …, x^{n-1}) by the Bulletproofs/Halo fold. The
opening first squeezes a seed challenge ξ₀ binding (P, x, v) and scales the
inner-product generator to h' = U·ξ₀ (arkworks ipa_pc's h_prime); each of
the k = log₂ n rounds then sends two cross-term group elements
L_j = ⟨a_hi, G_lo⟩ + ⟨a_hi, b_lo⟩·h'
R_j = ⟨a_lo, G_hi⟩ + ⟨a_lo, b_hi⟩·h'
absorbs them into the Fiat-Shamir transcript, samples a challenge u_j, and folds
all three vectors in half
a ← a_lo + a_hi·u_j⁻¹
b ← b_lo + b_hi·u_j
G ← G_lo + G_hi·u_j
The L/R labeling (L pairs a_hi with G_lo) and the no-inverse fold (the low
half carried unscaled, the high half scaled by u_j) are arkworks ipa_pc's
convention — the same the check polynomial h(X) = ∏(1 + u_j·X^…) and the
decider's final-key MSM are written against (see math.py).
Folding continues until each vector collapses to a single element. Each cross
term is one lax.msm (the h' term folded in as one extra (scalar, point) pair),
so the only raw EC arithmetic is the basis fold G_lo + G_hi·u — vectorized
scalar-mul and point-add, with the result converted back to affine each round to
keep the point representation (and thus the next round's lax.msm input) stable.
The fold is a lax.scan over the round count, not a Python unroll: the unroll's
static-slice fold fuses cleanly but recompiles per size (compile grows linearly in
k = log₂ n), while the scan compiles in O(1). The scan's fixed-shape carry keeps
a/b/G at full size n and reads the collapsing half with a masked dynamic_slice,
byte-identical to the shrinking fold (0·P = identity) — trading the unroll's
static-slice fusion for the dynamic_slice/scatter fusion boundaries the scan
needs (a compile-time-for-fusion trade, not a claim of one fused kernel). Warm
runtime is unchanged at tested sizes (FS-permute-bound); the valid_count msm
operand removes the resulting k·n mask padding (see _open_one).
Scope: one base-field polynomial per opening, power-of-two length. open is
transparent (no blinding); the hiding/zk _open_one_zk below blinds the witness
and opens an s-randomized commitment. A demonstration of the seam, not a
hardened prover.
IpaProverData
dataclass
¶
Retained witness from IpaProver.commit: the coefficient vectors (to drive
the fold in open) and the commitments (the opening's Fiat-Shamir binds them
as part of the statement). Holds references to the (immutable) inputs — no
polynomial data is copied.
Source code in zorch/pcs/ipa/prover.py
81 82 83 84 85 86 87 88 89 | |
IpaProver
dataclass
¶
Bases: ProverStage[OpeningClaim[IpaCommitment], OpeningWitness[IpaProverData], TrivialClaim, OpeningProof[list[IpaProof]]]
Source code in zorch/pcs/ipa/prover.py
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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
commit ¶
commit(
polys: Sequence[Array],
) -> tuple[IpaCommitment, IpaProverData]
Pedersen-commit a batch of coefficient vectors: P_j = ⟨a_j, G⟩.
Returns the stacked G1 commitments and the prover data (coeffs plus the
commitments, which the opening's Fiat-Shamir binds).
Source code in zorch/pcs/ipa/prover.py
103 104 105 106 107 108 109 110 | |
commit_zk ¶
commit_zk(
polys: Sequence[Array], randomnesses: Sequence[Array]
) -> tuple[IpaCommitment, IpaProverData]
Hiding Pedersen commit: P_j = ⟨a_j, G⟩ + r_j·s. The blinding r_j·s
makes the commitment hiding; _open_one_zk opens such a commitment to a
zero-knowledge proof, removing the blinding inside the fold (the
− combined_rand·s term, which is why the commitment must carry r_j·s to
begin with). Requires the key's blinding generator key.s.
Source code in zorch/pcs/ipa/prover.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
prove ¶
prove(
claim: OpeningClaim[IpaCommitment],
witness: OpeningWitness[IpaProverData],
transcript: Transcript,
) -> ProveResult[
TrivialClaim, OpeningProof[list[IpaProof]]
]
Open the committed polynomials at the claim's points.
Terminal: an opening closes its claim rather than reducing it.
Source code in zorch/pcs/ipa/prover.py
136 137 138 139 140 141 142 143 144 145 146 147 148 | |