diff --git a/curve.py b/curve.py index f9330cc..ddda6bf 100644 --- a/curve.py +++ b/curve.py @@ -35,7 +35,7 @@ def ec_mul(pt, coeff): # Elliptic curve linear combination. A truly optimized implementation # would replace this with a fast lin-comb algo, see https://ethresear.ch/t/7238 -def ec_lincomb(pairs): +def ec_lincomb(pairs: list[tuple[G1Point, Scalar]]): return lincomb( [pt for (pt, _) in pairs], [int(n) % b.curve_order for (_, n) in pairs], diff --git a/poly.py b/poly.py index d6b172d..2bfebe7 100644 --- a/poly.py +++ b/poly.py @@ -30,12 +30,16 @@ def __add__(self, other): self.basis, ) else: + assert self.basis == Basis.LAGRANGE assert isinstance(other, Scalar) return Polynomial( [x + other for x in self.values], self.basis, ) + def __radd__(self, other): + return self + other + def __sub__(self, other): if isinstance(other, Polynomial): assert len(self.values) == len(other.values) @@ -69,6 +73,9 @@ def __mul__(self, other): self.basis, ) + def __rmul__(self, other): + return self * other + def __truediv__(self, other): if isinstance(other, Polynomial): assert self.basis == Basis.LAGRANGE @@ -134,6 +141,14 @@ def _fft(vals, modulus, roots_of_unity): def ifft(self): return self.fft(True) + def pad(self, length): + if self.basis == Basis.LAGRANGE: + p = self.ifft() + else: + p = Polynomial(self.values, Basis.MONOMIAL) + p.values += [Scalar(0)] * (length - len(self.values)) + return p.fft() + # Converts a list of evaluations at [1, w, w**2... w**(n-1)] to # a list of evaluations at # [offset, offset * q, offset * q**2 ... offset * q**(4n-1)] where q = w**(1/4) @@ -144,7 +159,7 @@ def to_coset_extended_lagrange(self, offset): assert self.basis == Basis.LAGRANGE group_order = len(self.values) x_powers = self.ifft().values - x_powers = [(offset**i * x) for i, x in enumerate(x_powers)] + [Scalar(0)] * ( + x_powers = [(offset ** i * x) for i, x in enumerate(x_powers)] + [Scalar(0)] * ( group_order * 3 ) return Polynomial(x_powers, Basis.MONOMIAL).fft() @@ -159,8 +174,8 @@ def coset_extended_lagrange_to_coeffs(self, offset): shifted_coeffs = self.ifft().values inv_offset = 1 / offset return Polynomial( - [v * inv_offset**i for (i, v) in enumerate(shifted_coeffs)], - Basis.LAGRANGE, + [v * inv_offset ** i for (i, v) in enumerate(shifted_coeffs)], + Basis.MONOMIAL, ) # Given a polynomial expressed as a list of evaluations at roots of unity, @@ -174,9 +189,9 @@ def barycentric_eval(self, x: Scalar): (Scalar(x) ** order - 1) / order * sum( - [ - value * root / (x - root) - for value, root in zip(self.values, roots_of_unity) - ] - ) + [ + value * root / (x - root) + for value, root in zip(self.values, roots_of_unity) + ] + ) ) diff --git a/prover.py b/prover.py index 1354cdd..0cf9a5d 100644 --- a/prover.py +++ b/prover.py @@ -52,6 +52,9 @@ def prove(self, witness: dict[Optional[str], int]) -> Proof: # Initialise Fiat-Shamir transcript transcript = Transcript(b"plonk") + print(witness) + print([x.wires for x in self.program.constraints]) + # Collect fixed and public information # FIXME: Hash pk and PI into transcript public_vars = self.program.get_public_assignments() @@ -94,15 +97,20 @@ def round_1( if None not in witness: witness[None] = 0 - # Compute wire assignments for A, B, C, corresponding: - # - A_values: witness[program.wires()[i].L] - # - B_values: witness[program.wires()[i].R] - # - C_values: witness[program.wires()[i].O] - - # Construct A, B, C Lagrange interpolation polynomials for - # A_values, B_values, C_values + padding = [Scalar(0)] * (group_order - len(program.constraints)) - # Compute a_1, b_1, c_1 commitments to A, B, C polynomials + self.A = Polynomial( + [Scalar(witness.get(constraint.wires.L, 0)) for constraint in program.constraints] + padding, + basis=Basis.LAGRANGE, + ) + self.B = Polynomial( + [Scalar(witness.get(constraint.wires.R, 0)) for constraint in program.constraints] + padding, + basis=Basis.LAGRANGE, + ) + self.C = Polynomial( + [Scalar(witness.get(constraint.wires.O, 0)) for constraint in program.constraints] + padding, + basis=Basis.LAGRANGE, + ) # Sanity check that witness fulfils gate constraints assert ( @@ -116,90 +124,103 @@ def round_1( ) # Return a_1, b_1, c_1 + a_1 = self.setup.commit(self.A) + b_1 = self.setup.commit(self.B) + c_1 = self.setup.commit(self.C) return Message1(a_1, b_1, c_1) def round_2(self) -> Message2: group_order = self.group_order setup = self.setup - # Using A, B, C, values, and pk.S1, pk.S2, pk.S3, compute - # Z_values for permutation grand product polynomial Z - # - # Note the convenience function: - # self.rlc(val1, val2) = val_1 + self.beta * val_2 + gamma - # Check that the last term Z_n = 1 - assert Z_values.pop() == 1 + Z_values = [Scalar(1)] + roots_of_unity = Scalar.roots_of_unity(group_order) - # Sanity-check that Z was computed correctly for i in range(group_order): - assert ( + q1 = ( self.rlc(self.A.values[i], roots_of_unity[i]) * self.rlc(self.B.values[i], 2 * roots_of_unity[i]) * self.rlc(self.C.values[i], 3 * roots_of_unity[i]) - ) * Z_values[i] - ( + ) + q2 = ( self.rlc(self.A.values[i], self.pk.S1.values[i]) * self.rlc(self.B.values[i], self.pk.S2.values[i]) * self.rlc(self.C.values[i], self.pk.S3.values[i]) - ) * Z_values[ - (i + 1) % group_order - ] == 0 + ) + Z_values.append(q1 * Z_values[i] / q2) + assert Z_values.pop() == 1 - # Construct Z, Lagrange interpolation polynomial for Z_values - # Cpmpute z_1 commitment to Z polynomial + print(roots_of_unity) + + # Sanity-check that Z was computed correctly + for i in range(group_order): + assert ( + self.rlc(self.A.values[i], roots_of_unity[i]) + * self.rlc(self.B.values[i], 2 * roots_of_unity[i]) + * self.rlc(self.C.values[i], 3 * roots_of_unity[i]) + ) * Z_values[i] - ( + self.rlc(self.A.values[i], self.pk.S1.values[i]) + * self.rlc(self.B.values[i], self.pk.S2.values[i]) + * self.rlc(self.C.values[i], self.pk.S3.values[i]) + ) * Z_values[ + (i + 1) % group_order + ] == 0 # Return z_1 + self.Z = Polynomial(Z_values, basis=Basis.LAGRANGE) + z_1 = self.setup.commit(self.Z) return Message2(z_1) def round_3(self) -> Message3: group_order = self.group_order setup = self.setup - - # Compute the quotient polynomial - - # List of roots of unity at 4x fineness, i.e. the powers of µ - # where µ^(4n) = 1 - - # Using self.fft_expand, move A, B, C into coset extended Lagrange basis - - # Expand public inputs polynomial PI into coset extended Lagrange - - # Expand selector polynomials pk.QL, pk.QR, pk.QM, pk.QO, pk.QC - # into the coset extended Lagrange basis - - # Expand permutation grand product polynomial Z into coset extended - # Lagrange basis - - # Expand shifted Z(ω) into coset extended Lagrange basis - - # Expand permutation polynomials pk.S1, pk.S2, pk.S3 into coset - # extended Lagrange basis - - # Compute Z_H = X^N - 1, also in evaluation form in the coset - - # Compute L0, the Lagrange basis polynomial that evaluates to 1 at x = 1 = ω^0 - # and 0 at other roots of unity + fft_cofactor = self.fft_cofactor + roots_of_unity = Scalar.roots_of_unity(group_order) + alpha = self.alpha # Expand L0 into the coset extended Lagrange basis L0_big = self.fft_expand( - Polynomial([Scalar(1)] + [Scalar(0)] * (group_order - 1), Basis.LAGRANGE) + Polynomial([Scalar(1), Scalar(0)] + [Scalar(0)] * (group_order - 2), Basis.LAGRANGE) ) - - # Compute the quotient polynomial (called T(x) in the paper) - # It is only possible to construct this polynomial if the following - # equations are true at all roots of unity {1, w ... w^(n-1)}: - # 1. All gates are correct: - # A * QL + B * QR + A * B * QM + C * QO + PI + QC = 0 - # - # 2. The permutation accumulator is valid: - # Z(wx) = Z(x) * (rlc of A, X, 1) * (rlc of B, 2X, 1) * - # (rlc of C, 3X, 1) / (rlc of A, S1, 1) / - # (rlc of B, S2, 1) / (rlc of C, S3, 1) - # rlc = random linear combination: term_1 + beta * term2 + gamma * term3 - # - # 3. The permutation accumulator equals 1 at the start point - # (Z - 1) * L0 = 0 - # L0 = Lagrange polynomial, equal at all roots of unity except 1 + Xpoly = Polynomial(Scalar.roots_of_unity(group_order), basis=Basis.LAGRANGE) + + assert self.Z.basis == Basis.LAGRANGE + Z_xomega = self.Z.ifft() + assert Z_xomega.basis == Basis.MONOMIAL + for i in range(len(Z_xomega.values)): + Z_xomega.values[i] *= roots_of_unity[i] + Z_xomega = Z_xomega.fft() + assert Z_xomega.basis == Basis.LAGRANGE + + PI = self.fft_expand(self.PI) + A = self.fft_expand(self.A) + B = self.fft_expand(self.B) + C = self.fft_expand(self.C) + Z = self.fft_expand(self.Z) + + Z_xomega = self.fft_expand(Z_xomega) + Xpoly = self.fft_expand(Xpoly) + + S1 = self.fft_expand(self.pk.S1) + S2 = self.fft_expand(self.pk.S2) + S3 = self.fft_expand(self.pk.S3) + QO = self.fft_expand(self.pk.QO) + QM = self.fft_expand(self.pk.QM) + QL = self.fft_expand(self.pk.QL) + QR = self.fft_expand(self.pk.QR) + QC = self.fft_expand(self.pk.QC) + + x_powers = [Scalar(-1)] + [Scalar(0)] * (group_order - 1) + [Scalar(1)] + [Scalar(0)] * (3 * group_order - 1) + x_powers = [(fft_cofactor ** i * x) for i, x in enumerate(x_powers)] + Zh = Polynomial(x_powers, basis=Basis.MONOMIAL).fft() + print(Zh.values) + + q1 = (A * B * QM + A * QL + B * QR + C * QO + PI + QC) / Zh + q2 = (self.rlc(A, Xpoly)) * (self.rlc(B, Xpoly * Scalar(2))) * (self.rlc(C, Xpoly * Scalar(3))) * Z * alpha / Zh + q3 = (self.rlc(A, S1)) * (self.rlc(B, S2)) * (self.rlc(C, S3) * Z_xomega) * alpha / Zh + q4 = (Z - Scalar(1)) * L0_big * alpha * alpha / Zh + QUOT_big = q1 + q2 - q3 + q4 # Sanity check: QUOT has degree < 3n assert ( @@ -208,92 +229,111 @@ def round_3(self) -> Message3: ) print("Generated the quotient polynomial") - # Split up T into T1, T2 and T3 (needed because T has degree 3n, so is - # too big for the trusted setup) - + quot = self.expanded_evals_to_coeffs(QUOT_big) + self.T1 = Polynomial(quot.values[:group_order], basis=Basis.MONOMIAL).fft() + self.T2 = Polynomial(quot.values[group_order:group_order * 2], basis=Basis.MONOMIAL).fft() + self.T3 = Polynomial(quot.values[group_order * 2:group_order * 3], basis=Basis.MONOMIAL).fft() # Sanity check that we've computed T1, T2, T3 correctly assert ( - T1.barycentric_eval(fft_cofactor) - + T2.barycentric_eval(fft_cofactor) * fft_cofactor**group_order - + T3.barycentric_eval(fft_cofactor) * fft_cofactor ** (group_order * 2) - ) == QUOT_big.values[0] + self.T1.barycentric_eval(fft_cofactor) + + self.T2.barycentric_eval(fft_cofactor) * fft_cofactor ** group_order + + self.T3.barycentric_eval(fft_cofactor) * fft_cofactor ** (group_order * 2) + ) == QUOT_big.values[0] print("Generated T1, T2, T3 polynomials") - - # Compute commitments t_lo_1, t_mid_1, t_hi_1 to T1, T2, T3 polynomials - + print(self.T1.values) + print(self.T2.values) + print(self.T3.values) + t_lo_1 = setup.commit(self.T1) + t_mid_1 = setup.commit(self.T2) + t_hi_1 = setup.commit(self.T3) + print(t_lo_1, t_mid_1, t_hi_1) # Return t_lo_1, t_mid_1, t_hi_1 return Message3(t_lo_1, t_mid_1, t_hi_1) def round_4(self) -> Message4: - # Compute evaluations to be used in constructing the linearization polynomial. - - # Compute a_eval = A(zeta) - # Compute b_eval = B(zeta) - # Compute c_eval = C(zeta) - # Compute s1_eval = pk.S1(zeta) - # Compute s2_eval = pk.S2(zeta) - # Compute z_shifted_eval = Z(zeta * ω) - + assert self.A.basis == Basis.LAGRANGE + assert self.B.basis == Basis.LAGRANGE + assert self.C.basis == Basis.LAGRANGE + assert self.pk.S1.basis == Basis.LAGRANGE + assert self.pk.S2.basis == Basis.LAGRANGE + assert self.Z.basis == Basis.LAGRANGE + + self.a_eval = self.A.barycentric_eval(self.zeta) + self.b_eval = self.B.barycentric_eval(self.zeta) + self.c_eval = self.C.barycentric_eval(self.zeta) + self.s1_eval = self.pk.S1.barycentric_eval(self.zeta) + self.s2_eval = self.pk.S2.barycentric_eval(self.zeta) + + root_of_unity = Scalar.root_of_unity(self.group_order) + self.z_shifted_eval = self.Z.barycentric_eval(self.zeta * root_of_unity) # Return a_eval, b_eval, c_eval, s1_eval, s2_eval, z_shifted_eval - return Message4(a_eval, b_eval, c_eval, s1_eval, s2_eval, z_shifted_eval) + return Message4(self.a_eval, self.b_eval, self.c_eval, self.s1_eval, self.s2_eval, self.z_shifted_eval) def round_5(self) -> Message5: - # Evaluate the Lagrange basis polynomial L0 at zeta - # Evaluate the vanishing polynomial Z_H(X) = X^n at zeta - - # Move T1, T2, T3 into the coset extended Lagrange basis - # Move pk.QL, pk.QR, pk.QM, pk.QO, pk.QC into the coset extended Lagrange basis - # Move Z into the coset extended Lagrange basis - # Move pk.S3 into the coset extended Lagrange basis - - # Compute the "linearization polynomial" R. This is a clever way to avoid - # needing to provide evaluations of _all_ the polynomials that we are - # checking an equation betweeen: instead, we can "skip" the first - # multiplicand in each term. The idea is that we construct a - # polynomial which is constructed to equal 0 at Z only if the equations - # that we are checking are correct, and which the verifier can reconstruct - # the KZG commitment to, and we provide proofs to verify that it actually - # equals 0 at Z - # - # In order for the verifier to be able to reconstruct the commitment to R, - # it has to be "linear" in the proof items, hence why we can only use each - # proof item once; any further multiplicands in each term need to be - # replaced with their evaluations at Z, which do still need to be provided - - # Commit to R + challenge = self.v + group_order = self.group_order + + L0 = Polynomial([Scalar(1), Scalar(0)] + [Scalar(0)] * (group_order - 2), Basis.LAGRANGE) + + pi_eval = self.PI.barycentric_eval(self.zeta) + + gate_term = self.pk.QM * (self.a_eval * self.b_eval) + \ + self.pk.QL * self.a_eval + \ + self.pk.QR * self.b_eval + \ + self.pk.QO * self.c_eval + \ + pi_eval + \ + self.pk.QC + accum_term = ( + self.Z * self.rlc(self.a_eval, self.zeta) * + self.rlc(self.b_eval, 2 * self.zeta) * + self.rlc(self.c_eval, 3 * self.zeta) + - self.rlc(self.c_eval, self.pk.S3) * + self.rlc(self.a_eval, self.s1_eval) * + self.rlc(self.b_eval, self.s2_eval) * + self.z_shifted_eval + ) * self.alpha + permutation_first_row = (self.Z - Scalar(1)) * L0.barycentric_eval(self.zeta) * (self.alpha ** 2) + sub_term = ( + self.T1 + + self.T2 * (self.zeta ** group_order) + + self.T3 * (self.zeta ** (2 * group_order)) + ) * (self.zeta ** group_order - 1) + + R = gate_term + accum_term + permutation_first_row - sub_term # Sanity-check R - assert R.barycentric_eval(zeta) == 0 + assert R.barycentric_eval(self.zeta) == 0 print("Generated linearization polynomial R") - # Generate proof that W(z) = 0 and that the provided evaluations of - # A, B, C, S1, S2 are correct - - # Move A, B, C into the coset extended Lagrange basis - # Move pk.S1, pk.S2 into the coset extended Lagrange basis + W_z_num = ( + self.fft_expand(R) + + self.fft_expand(self.A - self.a_eval) * self.v + + self.fft_expand(self.B - self.b_eval) * self.v ** 2 + + self.fft_expand(self.C - self.c_eval) * self.v ** 3 + + self.fft_expand(self.pk.S1 - self.s1_eval) * self.v ** 4 + + self.fft_expand(self.pk.S2 - self.s2_eval) * self.v ** 5 + ) - # In the COSET EXTENDED LAGRANGE BASIS, - # Construct W_Z = ( - # R - # + v * (A - a_eval) - # + v**2 * (B - b_eval) - # + v**3 * (C - c_eval) - # + v**4 * (S1 - s1_eval) - # + v**5 * (S2 - s2_eval) - # ) / (X - zeta) + ID = self.fft_expand( + Polynomial( + [Scalar(int(i == 1)) for i in range(group_order)], + Basis.MONOMIAL + ).fft() + ) + W_z = self.expanded_evals_to_coeffs(W_z_num / (ID - self.zeta)) + W_z_coeffs = W_z.values + W_z = W_z.fft() # Check that degree of W_z is not greater than n assert W_z_coeffs[group_order:] == [0] * (group_order * 3) - # Compute W_z_1 commitment to W_z + W_zw_num = self.fft_expand(self.Z - self.z_shifted_eval) - # Generate proof that the provided evaluation of Z(z*w) is correct. This - # awkwardly different term is needed because the permutation accumulator - # polynomial Z is the one place where we have to check between adjacent - # coordinates, and not just within one coordinate. - # In other words: Compute W_zw = (Z - z_shifted_eval) / (X - zeta * ω) + W_zw = self.expanded_evals_to_coeffs(W_zw_num / (ID - self.zeta * Scalar.root_of_unity(group_order))) + W_zw_coeffs = W_zw.values + W_zw = W_zw.fft() # Check that degree of W_z is not greater than n assert W_zw_coeffs[group_order:] == [0] * (group_order * 3) @@ -302,6 +342,9 @@ def round_5(self) -> Message5: print("Generated final quotient witness polynomials") + W_z_1 = self.setup.commit(W_z) + W_zw_1 = self.setup.commit(W_zw) + # Return W_z_1, W_zw_1 return Message5(W_z_1, W_zw_1) @@ -312,4 +355,4 @@ def expanded_evals_to_coeffs(self, x: Polynomial): return x.coset_extended_lagrange_to_coeffs(self.fft_cofactor) def rlc(self, term_1, term_2): - return term_1 + term_2 * self.beta + self.gamma + return term_2 * self.beta + self.gamma + term_1 diff --git a/setup.py b/setup.py index bb2b5ef..fd18c06 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from utils import * import py_ecc.bn128 as b -from curve import ec_lincomb, G1Point, G2Point +from curve import ec_lincomb, G1Point, G2Point, ec_mul from compiler.program import CommonPreprocessedInput from verifier import VerificationKey from dataclasses import dataclass @@ -63,15 +63,26 @@ def from_file(cls, filename): return cls(powers_of_x, X2) # Encodes the KZG commitment that evaluates to the given values in the group - def commit(self, values: Polynomial) -> G1Point: - assert values.basis == Basis.LAGRANGE + def commit(self, p: Polynomial) -> G1Point: + assert p.basis == Basis.LAGRANGE + p = p.ifft() + assert p.basis == Basis.MONOMIAL - # Run inverse FFT to convert values from Lagrange basis to monomial basis - # Optional: Check values size does not exceed maximum power setup can handle - # Compute linear combination of setup with values - return NotImplemented + return ec_lincomb(list(zip(self.powers_of_x[:len(p.values)], p.values))) # Generate the verification key for this program with the given setup def verification_key(self, pk: CommonPreprocessedInput) -> VerificationKey: # Create the appropriate VerificationKey object - return NotImplemented + return VerificationKey( + pk.group_order, + self.commit(pk.QM), + self.commit(pk.QL), + self.commit(pk.QR), + self.commit(pk.QO), + self.commit(pk.QC), + self.commit(pk.S1), + self.commit(pk.S2), + self.commit(pk.S3), + self.X2, + Scalar.root_of_unity(pk.group_order), + ) \ No newline at end of file diff --git a/verifier.py b/verifier.py index ddc5bd9..46c7c8c 100644 --- a/verifier.py +++ b/verifier.py @@ -35,60 +35,116 @@ class VerificationKey: # More optimized version that tries hard to minimize pairings and # elliptic curve multiplications, but at the cost of being harder # to understand and mixing together a lot of the computations to - # efficiently batch them + # efficiently batch them. def verify_proof(self, group_order: int, pf, public=[]) -> bool: - # 4. Compute challenges - - # 5. Compute zero polynomial evaluation Z_H(ζ) = ζ^n - 1 - - # 6. Compute Lagrange polynomial evaluation L_0(ζ) - - # 7. Compute public input polynomial evaluation PI(ζ). - - # Compute the constant term of R. This is not literally the degree-0 - # term of the R polynomial; rather, it's the portion of R that can - # be computed directly, without resorting to elliptic cutve commitments - - # Compute D = (R - r0) + u * Z, and E and F - - # Run one pairing check to verify the last two checks. - # What's going on here is a clever re-arrangement of terms to check - # the same equations that are being checked in the basic version, - # but in a way that minimizes the number of EC muls and even - # compressed the two pairings into one. The 2 pairings -> 1 pairing - # trick is basically to replace checking - # - # Y1 = A * (X - a) and Y2 = B * (X - b) - # - # with - # - # Y1 + A * a = A * X - # Y2 + B * b = B * X - # - # so at this point we can take a random linear combination of the two - # checks, and verify it with only one pairing. - return False - # Basic, easier-to-understand version of what's going on + # Basic, easier-to-understand version of what's going on. + # Feel free to use multiple pairings. def verify_proof_unoptimized(self, group_order: int, pf, public=[]) -> bool: - # 4. Compute challenges - - # 5. Compute zero polynomial evaluation Z_H(ζ) = ζ^n - 1 - - # 6. Compute Lagrange polynomial evaluation L_0(ζ) - - # 7. Compute public input polynomial evaluation PI(ζ). - - # Recover the commitment to the linearization polynomial R, - # exactly the same as what was created by the prover - - # Verify that R(z) = 0 and the prover-provided evaluations - # A(z), B(z), C(z), S1(z), S2(z) are all correct - - # Verify that the provided value of Z(zeta*w) is correct - - return False + # 4: compute challenges + beta, gamma, alpha, zeta, v, u = self.compute_challenges(pf) + proof = pf.flatten() + self.beta, self.gamma, self.alpha, self.zeta = beta, gamma, alpha, zeta + # 5: compute Zh(zeta) + Z_H = zeta ** group_order - 1 + # 6: compute L1(zeta) + L0 = Z_H / group_order / (zeta - 1) + # 7: compute PI(zeta) + PI = Polynomial( + [Scalar(-v) for v in public] + + [Scalar(0) for _ in range(self.group_order - len(public))], + Basis.LAGRANGE, + ) + # 8: compute r(x) + # r0 = PI - L1 * alpha * alpha \ + # - alpha * (pf.msg_4.a_eval + beta * pf.msg_4.s1_eval + gamma) \ + # * (pf.msg_4.b_eval + beta * pf.msg_4.s2_eval + gamma) \ + # * (pf.msg_4.c_eval + gamma) * pf.msg_4.z_shifted_eval + + T1 = pf.msg_3.t_lo_1 + T2 = pf.msg_3.t_mid_1 + T3 = pf.msg_3.t_hi_1 + + Z = pf.msg_2.z_1 + S3 = self.S3 + n = self.group_order + + a_eval = pf.msg_4.a_eval + b_eval = pf.msg_4.b_eval + c_eval = pf.msg_4.c_eval + s1_eval = pf.msg_4.s1_eval + s2_eval = pf.msg_4.s2_eval + + PIz = PI.barycentric_eval(self.zeta) + + gate_term = ec_lincomb( + [ + (self.Qm, a_eval*b_eval), + (self.Ql, a_eval), + (self.Qr, b_eval), + (self.Qo, c_eval), + (b.G1, PIz), + (self.Qc, 1), + ]) + + z_shifted_eval = pf.msg_4.z_shifted_eval + perm_term = ec_lincomb( + [ + (Z, self.rlc(a_eval, zeta) * self.rlc(b_eval, zeta*2) * self.rlc(c_eval, zeta*3)), + (S3, beta * self.rlc(a_eval, s1_eval) * self.rlc(b_eval, s2_eval) * z_shifted_eval * -1), + (b.G1, (gamma + c_eval) * self.rlc(a_eval, s1_eval) * self.rlc(b_eval, s2_eval) * z_shifted_eval * -1) + ]) + z_check_term = ec_lincomb( + [ + (Z, L0), + (b.G1, L0 * -1) + ]) + + final_term = ec_lincomb( + [ + (T1, Z_H), + (T2, (zeta**n) * Z_H), + (T3, (zeta**(2*n)) * Z_H) + ]) + + alpha = self.alpha + R_pt = ec_lincomb([ + (gate_term, 1), + (perm_term, alpha), + (z_check_term, alpha**2), + (final_term, -1) + ]) + + print("verifier R_pt", R_pt) + + quarter_roots = Scalar.roots_of_unity(n * 4) + W_z_pt = ec_lincomb( + [ + (R_pt, 1), + (pf.msg_1.a_1, v), + (b.G1, -a_eval * v), + (pf.msg_1.b_1, v**2), + (b.G1, -b_eval * v**2), + (pf.msg_1.c_1, v**3), + (b.G1, -c_eval * v**3), + (self.S1, v**4), + (b.G1, -s1_eval * v**4), + (self.S2, v**5), + (b.G1, -s2_eval * v**5), + ]) + W_z_pt_check = b.pairing(b.add(self.X_2, ec_mul(b.G2, -zeta)), pf.msg_5.W_z_1) + assert b.pairing(b.G2, W_z_pt) == W_z_pt_check + + W_wz_pt = ec_lincomb( + [ + (Z, 1), + (b.G1, -z_shifted_eval) + ]) + omega = Scalar.root_of_unity(n) + W_wz_pt_check = b.pairing(b.add(self.X_2, ec_mul(b.G2, -zeta * omega)), pf.msg_5.W_zw_1) + assert b.pairing(b.G2, W_wz_pt) == W_wz_pt_check + return True # Compute challenges (should be same as those computed by prover) def compute_challenges( @@ -102,3 +158,6 @@ def compute_challenges( u = transcript.round_5(proof.msg_5) return beta, gamma, alpha, zeta, v, u + + def rlc(self, term_1, term_2): + return term_2 * self.beta + self.gamma + term_1