zorch.poly.eq¶
Equality polynomial eq(w, x) hypercube expansion.
eq(w, x) = Π_i (1 - x_i - w_i + 2·x_i·w_i); Σ_{w∈{0,1}^n} eq(w,x) = 1.
eq_factor ¶
eq_factor(t: Array, z: Array) -> Array
One coordinate's eq factor eq(t, z) = t·z + (1-t)(1-z), elementwise over
any broadcastable shapes. Symmetric in t/z.
The building block eval_eq multiplies across coordinates, and the
per-round factor a sumcheck binds one variable at a time: a prover that
folds variable z_k at challenge r multiplies its running bound-eq mass by
eq_factor(r, z_k) (the jagged provers' pad_adj/eq_adj
accumulation step).
Source code in zorch/poly/eq.py
16 17 18 19 20 21 22 23 24 25 26 | |
eq_root ¶
eq_root(z: Array) -> Array
The root (in t) of the eq factor: eq_factor(eq_root(z), z) == 0 at
b = (1-z)/(1-2z), elementwise over any shape.
Both sides derive b from z alone, so a round polynomial whose
summand carries the current variable's eq factor has a known zero there --
the free interpolation point of the Gruen round-poly compression
(https://eprint.iacr.org/2024/108, zorch.sumcheck.gruen). Undefined at
z = 1/2 (the factor is constant) and colliding with the t = 1 node at
z = 0; a transcript-sampled z avoids both w.h.p.
Source code in zorch/poly/eq.py
29 30 31 32 33 34 35 36 37 38 39 40 | |
eval_eq ¶
eval_eq(w: Array, x: Array) -> Array
eq(w, x) = Π_i (1 - w_i - x_i + 2·w_i·x_i) for two equal-length points, in O(len) time and O(1) memory.
The closed form of the equality polynomial evaluated at a pair of points --
equals (expand_eq_to_hypercube(x, 1) · expand_eq_to_hypercube(w, 1)).sum()
but without materializing either 2^len vector, so a verifier evaluating eq at
a bound point stays succinct. Symmetric in w/x and order-agnostic (a
product over coordinates), so MSB/LSB indexing does not matter.
Source code in zorch/poly/eq.py
43 44 45 46 47 48 49 50 51 52 | |
expand_hypercube_step ¶
expand_hypercube_step(
state: Array, coord: Array, *, msb: bool = False
) -> Array
(2ᵏ,) -> (2ᵏ⁺¹,): add a new variable's (1-coord)/coord split — LSB (default) interleaves the shares, msb=True concatenates [low, high].
Source code in zorch/poly/eq.py
55 56 57 58 59 60 61 62 | |
contract_hypercube_step ¶
contract_hypercube_step(state: Array) -> Array
(2^{k+1},) -> (2^k,): Σ-marginalize the LSB variable by summing adjacent
pairs, out[j] = state[2j] + state[2j+1] (over the last axis).
The mass-preserving dual of expand_hypercube_step: expand splits each
entry into (1-coord)/coord shares, so contracting recovers the
pre-expansion table exactly -- Σ_b eq((w, b), x) = eq(w, x[:-1]). A
fixed-shape round loop that binds variables LSB-first keeps its eq table
current with one of these per round instead of re-expanding. The last axis
must be even.
Source code in zorch/poly/eq.py
65 66 67 68 69 70 71 72 73 74 75 | |
expand_eq_to_hypercube ¶
expand_eq_to_hypercube(
x: Array, scalar: Array, *, msb: bool = False
) -> Array
scalar·eq(w, x) for all w in {0,1}^n. msb=False interleaves each new share
(default, w[0] the MSB); msb=True concatenates [low, high], placing
x[j] at bit j.
NOTE: explicit indexing instead of for coord in x — iterating a JAX array
of an extension-field dtype dispatches lax.sign, a XLA gotcha.
Source code in zorch/poly/eq.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 | |
expand_eq_family ¶
expand_eq_family(
cs: Array,
*,
msb: bool = ...,
suffix: bool = ...,
keep: None = ...
) -> list[Array]
expand_eq_family(
cs: Array,
*,
msb: bool = ...,
suffix: bool = ...,
keep: Callable[[int], bool]
) -> list[Array | None]
expand_eq_family(
cs: Array,
*,
msb: bool = False,
suffix: bool = False,
keep: Callable[[int], bool] | None = None
) -> list[Array] | list[Array | None]
The nested eq tables [eq(s₁), …, eq(sₙ)] over every prefix s_k = cs[:k]
(suffix=True: every suffix s_k = cs[n-k:]), entry k-1 of shape (2ᵏ,) —
the prefix family appends each coordinate walking forwards, the suffix
family prepends walking backwards, and msb is the placement of each added
coordinate as in expand_hypercube_step. The slice's first coordinate
lands at the MSB for the (prefix, msb=False) and (suffix, msb=True)
combinations, at the LSB for the other two.
Past _OUTER_SPLIT_MIN each large member is emitted as ONE outer product
of a shared half instead of a retained doubling chain (see
_OUTER_SPLIT_MIN for why): every member factors over the family's
first-added half — for the suffix family, eq(cs[i:]) = eq(cs[i:k]) ⊗
eq(cs[k:]) for every i < k — so both halves recurse on half-size families
and each large table is one write-only GF multiply per element over two
small inputs. GF multiplication is exact, so every member stays byte-equal
to its chain.
keep: optional predicate on the member index — the entries the caller
will read. Kept entries are always present and exact; un-kept entries
come back as None, and past the outer-product split their emissions are
skipped entirely — an un-read half recurses no further. The elision must
live here in the emitter: families are built eagerly in round
constructors (zorch.sumcheck.eq.eq_poly), where every emission
materializes, and even under jit an explicit skip keeps the contract
instead of leaning on compiler DCE. Below the split the chain layers are
each other's building blocks, so nothing can be skipped — un-kept
entries are only masked.
Source code in zorch/poly/eq.py
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 | |
expand_monomial_step ¶
expand_monomial_step(state: Array, coord: Array) -> Array
(2^k,) -> (2^{k+1},): add a new variable as the LSB, monomial basis.
result[2j] = state[j], result[2j+1] = state[j]·coord — the ⊗(1, coord)
factor, where expand_hypercube_step is ⊗(1-coord, coord).
Source code in zorch/poly/eq.py
217 218 219 220 221 | |
expand_monomial_to_hypercube ¶
expand_monomial_to_hypercube(
x: Array, scalar: Array
) -> Array
scalar·Π_{i: w_i=1} x_i for all w in {0,1}^n — the monomial
(coefficient-basis) dual of expand_eq_to_hypercube, same (2^n,) shape and
MSB-first indexing (w[0] binds x[0]). <coeffs, expand_monomial(x)> is the
monomial-basis evaluation at x, as <evals, expand_eq(x)> is the eval-basis
one.
Source code in zorch/poly/eq.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | |