From 7f5c4c2b41e3b43b8b7b9b08c4677823e5662f45 Mon Sep 17 00:00:00 2001
From: "yitang.lh" <liheact@gmail.com>
Date: Wed, 26 Apr 2023 00:47:04 +0800
Subject: [PATCH 1/3] feat: finish Basic test

---
 setup.py | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index bb2b5ef..132089d 100644
--- a/setup.py
+++ b/setup.py
@@ -61,6 +61,16 @@ def from_file(cls, filename):
         # assert b.pairing(b.G2, powers_of_x[1]) == b.pairing(X2, b.G1)
         # print("X^1 points checked consistent")
         return cls(powers_of_x, X2)
+    
+    # Encodes the KZG commitment to the given polynomial coeffs
+    def coeffs_to_point(self, coeffs):
+        if len(coeffs) > len(self.powers_of_x):
+            raise Exception("Not enough powers in setup")
+        return ec_lincomb([(s, x) for s, x in zip(self.powers_of_x, coeffs)])
+
+    # Encodes the KZG commitment that evaluates to the given values in the group
+    def evaluations_to_point(self, evals):
+        return self.coeffs_to_point(evals.ifft().values)
 
     # Encodes the KZG commitment that evaluates to the given values in the group
     def commit(self, values: Polynomial) -> G1Point:
@@ -69,9 +79,24 @@ def commit(self, values: Polynomial) -> G1Point:
         # 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 self.evaluations_to_point(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
+        
+        vk = VerificationKey(
+            Qm=self.evaluations_to_point(pk.QM),
+            Ql=self.evaluations_to_point(pk.QL),
+            Qr=self.evaluations_to_point(pk.QR),
+            Qo=self.evaluations_to_point(pk.QO),
+            Qc=self.evaluations_to_point(pk.QC),
+            S1=self.evaluations_to_point(pk.S1),
+            S2=self.evaluations_to_point(pk.S2),
+            S3=self.evaluations_to_point(pk.S3),
+            X_2=self.X2,
+            group_order=pk.group_order,
+            w=Scalar(Scalar.root_of_unity(pk.group_order))
+        )
+        return vk

From e9597b062ff25bc37c0d19378d1c6f55ee9f8389 Mon Sep 17 00:00:00 2001
From: "yitang.lh" <liheact@gmail.com>
Date: Mon, 15 May 2023 00:52:01 +0800
Subject: [PATCH 2/3] feat: finish prover

---
 prover.py | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 167 insertions(+), 8 deletions(-)

diff --git a/prover.py b/prover.py
index 927fc66..4c7649e 100644
--- a/prover.py
+++ b/prover.py
@@ -6,7 +6,6 @@
 from transcript import Transcript, Message1, Message2, Message3, Message4, Message5
 from poly import Polynomial, Basis
 
-
 @dataclass
 class Proof:
     msg_1: Message1
@@ -99,10 +98,27 @@ def round_1(
         # - B_values: witness[program.wires()[i].R]
         # - C_values: witness[program.wires()[i].O]
 
+        A_values = [Scalar(0) for _ in range(group_order)]
+        B_values = [Scalar(0) for _ in range(group_order)]
+        C_values = [Scalar(0) for _ in range(group_order)]
+
+        for i in range(len(program.wires())):
+            A_values[i] =  Scalar(witness[program.wires()[i].L])
+            B_values[i] =  Scalar(witness[program.wires()[i].R])
+            C_values[i] =  Scalar(witness[program.wires()[i].O])
+
         # Construct A, B, C Lagrange interpolation polynomials for
         # A_values, B_values, C_values
+        self.A = Polynomial(A_values, Basis.LAGRANGE)
+        self.B = Polynomial(B_values, Basis.LAGRANGE)
+        self.C = Polynomial(C_values, Basis.LAGRANGE)
 
         # Compute a_1, b_1, c_1 commitments to A, B, C polynomials
+        a_1 = self.setup.evaluations_to_point(self.A)
+        b_1 = self.setup.evaluations_to_point(self.B)
+        c_1 = self.setup.evaluations_to_point(self.C)
+
+        # Compute wire assignments
 
         # Sanity check that witness fulfils gate constraints
         assert (
@@ -129,6 +145,20 @@ def round_2(self) -> Message2:
         #       self.rlc(val1, val2) = val_1 + self.beta * val_2 + gamma
 
         # Check that the last term Z_n = 1
+        Z_values = [Scalar(1)]
+        roots_of_unity = Scalar.roots_of_unity(group_order)
+        print(roots_of_unity)
+        for i in range(group_order):
+            Z_values.append(
+                Z_values[-1] *
+                (self.A.values[i] + self.beta * roots_of_unity[i] + self.gamma) *
+                (self.B.values[i] + self.beta * 2 * roots_of_unity[i] + self.gamma) *
+                (self.C.values[i] + self.beta * 3 * roots_of_unity[i] + self.gamma) /
+                (self.A.values[i] + self.beta * self.pk.S1.values[i] + self.gamma) /
+                (self.B.values[i] + self.beta * self.pk.S2.values[i] + self.gamma) /
+                (self.C.values[i] + self.beta * self.pk.S3.values[i] + self.gamma)
+            )
+            
         assert Z_values.pop() == 1
 
         # Sanity-check that Z was computed correctly
@@ -146,8 +176,9 @@ def round_2(self) -> Message2:
             ] == 0
 
         # Construct Z, Lagrange interpolation polynomial for Z_values
+        self.Z = Polynomial(Z_values, Basis.LAGRANGE)
         # Cpmpute z_1 commitment to Z polynomial
-
+        z_1 = self.setup.evaluations_to_point(self.Z)
         # Return z_1
         return Message2(z_1)
 
@@ -159,29 +190,49 @@ def round_3(self) -> Message3:
 
         # List of roots of unity at 4x fineness, i.e. the powers of µ
         # where µ^(4n) = 1
+        self.quarter_roots = Scalar.roots_of_unity(group_order * 4)
 
         # Using self.fft_expand, move A, B, C into coset extended Lagrange basis
+        self.A_big = self.fft_expand(self.A)
+        self.B_big = self.fft_expand(self.B)
+        self.C_big = self.fft_expand(self.C)
+        # Z_H = X^N - 1, also in evaluation form in the coset
+        self.ZH_big = [
+            ((Scalar(r) * self.fft_cofactor) ** group_order - 1)
+            for r in self.quarter_roots
+        ]
 
         # Expand public inputs polynomial PI into coset extended Lagrange
+        self.PI_big = self.fft_expand(self.PI)
 
         # Expand selector polynomials pk.QL, pk.QR, pk.QM, pk.QO, pk.QC
         # into the coset extended Lagrange basis
 
+        self.QL_big, self.QR_big, self.QM_big, self.QO_big, self.QC_big  = \
+        (self.fft_expand(x) for x in (self.pk.QL, self.pk.QR,self.pk.QM, self.pk.QO, self.pk.QC))
+
         # Expand permutation grand product polynomial Z into coset extended
         # Lagrange basis
+        self.Z_big = self.fft_expand(self.Z)
 
         # Expand shifted Z(ω) into coset extended Lagrange basis
+        Z_shifted_big = Polynomial(self.Z_big.values[4:] + self.Z_big.values[:4], Basis.LAGRANGE)
 
+    
         # Expand permutation polynomials pk.S1, pk.S2, pk.S3 into coset
         # extended Lagrange basis
 
+        self.S1_big = self.fft_expand(self.pk.S1)
+        self.S2_big = self.fft_expand(self.pk.S2)
+        self.S3_big = self.fft_expand(self.pk.S3)
+
         # 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
 
         # Expand L0 into the coset extended Lagrange basis
-        L0_big = self.fft_expand(
+        self.L1_big = self.fft_expand(
             Polynomial([Scalar(1)] + [Scalar(0)] * (group_order - 1), Basis.LAGRANGE)
         )
 
@@ -201,6 +252,27 @@ def round_3(self) -> Message3:
         #    (Z - 1) * L0 = 0
         #    L0 = Lagrange polynomial, equal at all roots of unity except 1
 
+        QUOT_big = Polynomial([((
+            self.A_big.values[i] * self.QL_big.values[i] +
+            self.B_big.values[i] * self.QR_big.values[i] +
+            self.A_big.values[i] * self.B_big.values[i] * self.QM_big.values[i] +
+            self.C_big.values[i] * self.QO_big.values[i] + 
+            self.PI_big.values[i] +
+            self.QC_big.values[i]
+        ) + (
+            (self.A_big.values[i] + self.beta * self.fft_cofactor * self.quarter_roots[i] + self.gamma) *
+            (self.B_big.values[i] + self.beta * 2 * self.fft_cofactor * self.quarter_roots[i] + self.gamma) *
+            (self.C_big.values[i] + self.beta * 3 * self.fft_cofactor * self.quarter_roots[i] + self.gamma)
+        ) * self.alpha * self.Z_big.values[i] - (
+            (self.A_big.values[i] + self.beta * self.S1_big.values[i] + self.gamma) *
+            (self.B_big.values[i] + self.beta * self.S2_big.values[i] + self.gamma) *
+            (self.C_big.values[i] + self.beta * self.S3_big.values[i] + self.gamma)
+        ) * self.alpha * Z_shifted_big.values[i] + (
+            (self.Z_big.values[i] - 1) * self.L1_big.values[i] * self.alpha**2
+        )) / self.ZH_big[i] for i in range(group_order * 4)], Basis.LAGRANGE)
+
+        all_coeffs = self.expanded_evals_to_coeffs(QUOT_big)
+
         # Sanity check: QUOT has degree < 3n
         assert (
             self.expanded_evals_to_coeffs(QUOT_big).values[-group_order:]
@@ -211,16 +283,23 @@ def round_3(self) -> Message3:
         # Split up T into T1, T2 and T3 (needed because T has degree 3n - 4, so is
         # too big for the trusted setup)
 
+        self.T1 = Polynomial(all_coeffs.values[:group_order], Basis.MONOMIAL).fft()
+        self.T2 = Polynomial((all_coeffs.values[group_order: group_order * 2]), Basis.MONOMIAL).fft()
+        self.T3 = Polynomial((all_coeffs.values[group_order * 2: group_order * 3]), 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)
+            self.T1.barycentric_eval(self.fft_cofactor)
+            + self.T2.barycentric_eval(self.fft_cofactor) * self.fft_cofactor**group_order
+            + self.T3.barycentric_eval(self.fft_cofactor) * self.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
+        t_lo_1 = self.setup.evaluations_to_point(self.T1)
+        t_mid_1 = self.setup.evaluations_to_point(self.T2)
+        t_hi_1 = self.setup.evaluations_to_point(self.T3)
 
         # Return t_lo_1, t_mid_1, t_hi_1
         return Message3(t_lo_1, t_mid_1, t_hi_1)
@@ -235,17 +314,44 @@ def round_4(self) -> Message4:
         # Compute s2_eval = pk.S2(zeta)
         # Compute z_shifted_eval = Z(zeta * ω)
 
+        roots_of_unity = Scalar.roots_of_unity(self.group_order)
+
+        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)
+        self.z_shifted_eval = self.Z.barycentric_eval(self.zeta * roots_of_unity[1])
+
         # 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:
+
+        group_order = self.group_order
         # Evaluate the Lagrange basis polynomial L0 at zeta
         # Evaluate the vanishing polynomial Z_H(X) = X^n - 1 at zeta
+        self.L1_eval = Polynomial([Scalar(1)] + [Scalar(0)] * (group_order - 1), Basis.LAGRANGE).barycentric_eval(self.zeta)
+        self.ZH_eval = self.zeta ** group_order - 1
+        self.PI_eval = self.PI.barycentric_eval(self.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
+        self.T1_big = self.fft_expand(self.T1)
+        self.T2_big = self.fft_expand(self.T2)
+        self.T3_big = self.fft_expand(self.T3)
+
+        self.QL_big, self.QR_big, self.QM_big, self.QO_big, self.QC_big  = (
+            self.fft_expand(x) for x in (self.pk.QL, self.pk.QR,self.pk.QM, self.pk.QO, self.pk.QC)
+            )
+
+        self.Z_big = self.fft_expand(self.Z)
+        self.S3_big = self.fft_expand(self.pk.S3)
+
+
+
 
         # Compute the "linearization polynomial" R. This is a clever way to avoid
         # needing to provide evaluations of _all_ the polynomials that we are
@@ -262,15 +368,55 @@ def round_5(self) -> Message5:
         # replaced with their evaluations at Z, which do still need to be provided
 
         # Commit to R
+        R_big = Polynomial([(
+            self.a_eval * self.QL_big.values[i] +
+            self.b_eval * self.QR_big.values[i] +
+            self.a_eval * self.b_eval * self.QM_big.values[i] +
+            self.c_eval * self.QO_big.values[i] +
+            self.PI_eval +
+            self.QC_big.values[i]
+        ) + (
+            (self.a_eval + self.beta * self.zeta + self.gamma) *
+            (self.b_eval + self.beta * 2 * self.zeta + self.gamma) *
+            (self.c_eval + self.beta * 3 * self.zeta + self.gamma)
+        ) * self.alpha * self.Z_big.values[i] - (
+            (self.a_eval + self.beta * self.s1_eval + self.gamma) * 
+            (self.b_eval + self.beta * self.s2_eval + self.gamma) *
+            (self.c_eval + self.beta * self.S3_big.values[i] + self.gamma)
+        ) * self.alpha * self.z_shifted_eval + (
+            (self.Z_big.values[i] - 1) * self.L1_eval
+        ) * self.alpha**2 - (
+            self.T1_big.values[i] +
+            self.zeta ** group_order * self.T2_big.values[i] +
+            self.zeta ** (group_order * 2) * self.T3_big.values[i]
+        ) * self.ZH_eval for i in range(4 * group_order)], Basis.LAGRANGE)
+
+        R_coeffs = self.expanded_evals_to_coeffs(R_big)
+        R = Polynomial(R_coeffs.values[:group_order], Basis.MONOMIAL).fft()
 
         # 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
 
+
+        # Generate proof that W(z) = 0 and that the provided evaluations of
+        # A, B, C, S1, S2 are correct
+
+        W_z_big = Polynomial([(
+            R_big.values[i] +
+            self.v * (self.A_big.values[i] - self.a_eval) +
+            self.v**2 * (self.B_big.values[i] - self.b_eval) +
+            self.v**3 * (self.C_big.values[i] - self.c_eval) +
+            self.v**4 * (self.S1_big.values[i] - self.s1_eval) +
+            self.v**5 * (self.S2_big.values[i] - self.s2_eval)
+        ) / (self.fft_cofactor * self.quarter_roots[i] - self.zeta) for i in range(group_order * 4)], Basis.LAGRANGE)
+
+        W_z_coeffs = self.expanded_evals_to_coeffs(W_z_big).values
+
         # Move A, B, C into the coset extended Lagrange basis
         # Move pk.S1, pk.S2 into the coset extended Lagrange basis
 
@@ -288,6 +434,8 @@ def round_5(self) -> Message5:
         assert W_z_coeffs[group_order:] == [0] * (group_order * 3)
 
         # Compute W_z_1 commitment to W_z
+        W_z = Polynomial(W_z_coeffs[:group_order], Basis.MONOMIAL).fft()
+        W_z_1 = self.setup.evaluations_to_point(W_z)
 
         # Generate proof that the provided evaluation of Z(z*w) is correct. This
         # awkwardly different term is needed because the permutation accumulator
@@ -296,9 +444,20 @@ def round_5(self) -> Message5:
         # In other words: Compute W_zw = (Z - z_shifted_eval) / (X - zeta * ω)
 
         # Check that degree of W_z is not greater than n
+        roots_of_unity = Scalar.roots_of_unity(self.group_order)
+
+        W_zw_big = Polynomial([
+            (self.Z_big.values[i] - self.z_shifted_eval) /
+            (self.fft_cofactor * self.quarter_roots[i] - self.zeta * roots_of_unity[1])
+        for i in range(group_order * 4)], Basis.LAGRANGE)
+
+        W_zw_coeffs = self.expanded_evals_to_coeffs(W_zw_big).values
+        
         assert W_zw_coeffs[group_order:] == [0] * (group_order * 3)
 
         # Compute W_z_1 commitment to W_z
+        W_zw = Polynomial(W_zw_coeffs[:group_order], Basis.MONOMIAL).fft()
+        W_zw_1 = self.setup.evaluations_to_point(W_zw)
 
         print("Generated final quotient witness polynomials")
 

From 88558ccc471d96b002b33a649b86edcefb0dfa4e Mon Sep 17 00:00:00 2001
From: "yitang.lh" <liheact@gmail.com>
Date: Mon, 22 May 2023 16:21:02 +0800
Subject: [PATCH 3/3] feat: finish verifier

---
 prover.py   |   4 ++
 verifier.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 154 insertions(+), 9 deletions(-)

diff --git a/prover.py b/prover.py
index 4c7649e..ee7204d 100644
--- a/prover.py
+++ b/prover.py
@@ -368,6 +368,10 @@ def round_5(self) -> Message5:
         # replaced with their evaluations at Z, which do still need to be provided
 
         # Commit to R
+        self.QL_big, self.QR_big, self.QM_big, self.QO_big, self.QC_big  = (
+            self.fft_expand(x) for x in (self.pk.QL, self.pk.QR,self.pk.QM, self.pk.QO, self.pk.QC)
+            )
+            
         R_big = Polynomial([(
             self.a_eval * self.QL_big.values[i] +
             self.b_eval * self.QR_big.values[i] +
diff --git a/verifier.py b/verifier.py
index 4945e2f..894d939 100644
--- a/verifier.py
+++ b/verifier.py
@@ -4,6 +4,7 @@
 from curve import *
 from transcript import Transcript
 from poly import Polynomial, Basis
+from transcript import Transcript
 
 
 @dataclass
@@ -38,19 +39,76 @@ class VerificationKey:
     # to understand and mixing together a lot of the computations to
     # efficiently batch them
     def verify_proof(self, group_order: int, pf, public=[]) -> bool:
+
+        print("verify_proof")
+
         # 4. Compute challenges
+        self.beta, self.gamma, self.alpha, self.zeta, self.v, self.u = self.compute_challenges(pf)
 
         # 5. Compute zero polynomial evaluation Z_H(ζ) = ζ^n - 1
-
+        self.ZH_eval = self.zeta ** group_order - 1
+        
         # 6. Compute Lagrange polynomial evaluation L_0(ζ)
-
+        self.L1_eval = Polynomial([Scalar(1)] + [Scalar(0)] * (group_order - 1), Basis.LAGRANGE).barycentric_eval(self.zeta)
+        
         # 7. Compute public input polynomial evaluation PI(ζ).
+        self.PI_eval = Polynomial([Scalar(-x) for x in public] +
+        [Scalar(0) for _ in range(group_order - len(public))], Basis.LAGRANGE).barycentric_eval(self.zeta)
+
+
+        flat_proof = pf.flatten()
 
         # 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
+        r0 = (
+            self.PI_eval - self.L1_eval * self.alpha ** 2 - (
+                self.alpha *
+                (flat_proof["a_eval"] + self.beta * flat_proof["s1_eval"] + self.gamma) *
+                (flat_proof["b_eval"] + self.beta * flat_proof["s2_eval"] + self.gamma) *
+                (flat_proof["c_eval"] + self.gamma) *
+                flat_proof["z_shifted_eval"]
+            )
+        )
+
 
         # Compute D = (R - r0) + u * Z, and E and F
+        D_pt = ec_lincomb([
+            (self.Qm, flat_proof["a_eval"] * flat_proof["b_eval"]),
+            (self.Ql, flat_proof["a_eval"]),
+            (self.Qr, flat_proof["b_eval"]), 
+            (self.Qo, flat_proof["c_eval"]), 
+            (self.Qc, 1),
+            (flat_proof["z_1"], (
+                (flat_proof["a_eval"] + self.beta * self.zeta + self.gamma) *
+                (flat_proof["b_eval"] + self.beta * 2 * self.zeta + self.gamma) *
+                (flat_proof["c_eval"] + self.beta * 3 * self.zeta + self.gamma) * self.alpha +
+                self.L1_eval * self.alpha ** 2 +
+                self.u
+            )),
+            (self.S3, (
+                -(flat_proof["a_eval"] + self.beta * flat_proof["s1_eval"] + self.gamma) * 
+                (flat_proof["b_eval"] + self.beta * flat_proof["s2_eval"] + self.gamma) * 
+                self.alpha * self.beta * flat_proof["z_shifted_eval"]
+            )),
+            (flat_proof["t_lo_1"], -self.ZH_eval),
+            (flat_proof["t_mid_1"], -self.ZH_eval * self.zeta**group_order),
+            (flat_proof["t_hi_1"], -self.ZH_eval * self.zeta**(group_order*2)),
+        ])
+    
+        F_pt = ec_lincomb([
+            (D_pt, 1),
+            (flat_proof["a_1"], self.v),
+            (flat_proof["b_1"], self.v**2),
+            (flat_proof["c_1"], self.v**3),
+            (self.S1, self.v**4),
+            (self.S2, self.v**5),
+        ])
+
+        E_pt = ec_mul(b.G1, (
+            -r0 + self.v * flat_proof["a_eval"] + self.v**2 * flat_proof["b_eval"] + self.v**3 * flat_proof["c_eval"] +
+            self.v**4 * flat_proof["s1_eval"] + self.v**5 * flat_proof["s2_eval"] + self.u * flat_proof["z_shifted_eval"]
+        ))
 
         # Run one pairing check to verify the last two checks.
         # What's going on here is a clever re-arrangement of terms to check
@@ -68,28 +126,111 @@ def verify_proof(self, group_order: int, pf, public=[]) -> bool:
         #
         # so at this point we can take a random linear combination of the two
         # checks, and verify it with only one pairing.
-
-        return False
+        
+        assert b.pairing(self.X_2, ec_lincomb([
+            (flat_proof["W_z_1"], 1),
+            (flat_proof["W_zw_1"], self.u)
+        ])) == b.pairing(b.G2, ec_lincomb([
+            (flat_proof["W_z_1"], self.zeta),
+            (flat_proof["W_zw_1"], self.u * self.zeta * self.w),
+            (F_pt, 1),
+            (E_pt, -1)
+        ]))
+    
+        print("verify_proof done combined check")
+        
+        return True
 
     # Basic, easier-to-understand version of what's going on
-    def verify_proof_unoptimized(self, group_order: int, pf, public=[]) -> bool:
+    def verify_proof_unoptimized(self, group_order: int, pf , public=[]) -> bool:
+        
         # 4. Compute challenges
+        self.beta, self.gamma, self.alpha, self.zeta, self.v, self.u = self.compute_challenges(pf)
 
         # 5. Compute zero polynomial evaluation Z_H(ζ) = ζ^n - 1
-
+        self.ZH_eval = self.zeta ** group_order - 1
+        
         # 6. Compute Lagrange polynomial evaluation L_0(ζ)
-
+        self.L1_eval = Polynomial([Scalar(1)] + [Scalar(0)] * (group_order - 1), Basis.LAGRANGE).barycentric_eval(self.zeta)
+        
         # 7. Compute public input polynomial evaluation PI(ζ).
+        self.PI_eval = Polynomial([Scalar(-x) for x in public] +
+        [Scalar(0) for _ in range(group_order - len(public))], Basis.LAGRANGE).barycentric_eval(self.zeta)
+
+        flat_proof = pf.flatten()
 
         # Recover the commitment to the linearization polynomial R,
         # exactly the same as what was created by the prover
+        R = ec_lincomb([
+            (self.Qm, flat_proof["a_eval"] * flat_proof["b_eval"]),
+            (self.Ql, flat_proof["a_eval"]),
+            (self.Qr, flat_proof["b_eval"]), 
+            (self.Qo, flat_proof["c_eval"]), 
+            (b.G1, self.PI_eval),
+            (self.Qc, 1),
+            (flat_proof["z_1"], (
+                (flat_proof["a_eval"] + self.beta * self.zeta + self.gamma) *
+                (flat_proof["b_eval"] + self.beta * 2 * self.zeta + self.gamma) *
+                (flat_proof["c_eval"] + self.beta * 3 * self.zeta + self.gamma) *
+                self.alpha
+            )),
+            (self.S3, (
+                -(flat_proof["a_eval"] + self.beta * flat_proof["s1_eval"] + self.gamma) * 
+                (flat_proof["b_eval"] + self.beta * flat_proof["s2_eval"] + self.gamma) *
+                self.beta *
+                self.alpha * flat_proof["z_shifted_eval"]
+            )),
+            (b.G1, (
+                -(flat_proof["a_eval"] + self.beta * flat_proof["s1_eval"] + self.gamma) * 
+                (flat_proof["b_eval"] + self.beta * flat_proof["s2_eval"] + self.gamma) *
+                (flat_proof["c_eval"] + self.gamma) *
+                self.alpha * flat_proof["z_shifted_eval"]
+            )),
+            (flat_proof["z_1"], self.L1_eval * self.alpha ** 2),
+            (b.G1, -self.L1_eval * self.alpha ** 2),
+            (flat_proof["t_lo_1"], -self.ZH_eval),
+            (flat_proof["t_mid_1"], -self.ZH_eval * self.zeta**group_order),
+            (flat_proof["t_hi_1"], -self.ZH_eval * self.zeta**(group_order*2)),
+        ])
+        print('verify_proof_unoptimized verifier R_pt', R)
 
         # Verify that R(z) = 0 and the prover-provided evaluations
         # A(z), B(z), C(z), S1(z), S2(z) are all correct
+        assert b.pairing(
+            b.G2,
+            ec_lincomb([
+                (R, 1),
+                (flat_proof["a_1"], self.v),
+                (b.G1, -self.v * flat_proof["a_eval"]),
+                (flat_proof["b_1"], self.v**2),
+                (b.G1, -self.v**2 * flat_proof["b_eval"]),
+                (flat_proof["c_1"], self.v**3),
+                (b.G1, -self.v**3 * flat_proof["c_eval"]),
+                (self.S1, self.v**4),
+                (b.G1, -self.v**4 * flat_proof["s1_eval"]),
+                (self.S2, self.v**5),
+                (b.G1, -self.v**5 * flat_proof["s2_eval"]),
+            ])
+        ) == b.pairing(
+            b.add(self.X_2, ec_mul(b.G2, -self.zeta)),
+            flat_proof["W_z_1"]
+        )
+        print("verify_proof_unoptimized done check 1")
 
         # Verify that the provided value of Z(zeta*w) is correct
-
-        return False
+        assert b.pairing(
+            b.G2,
+            ec_lincomb([
+                (flat_proof["z_1"], 1),
+                (b.G1, -flat_proof["z_shifted_eval"])
+            ])
+        ) == b.pairing(
+            b.add(self.X_2, ec_mul(b.G2, -self.zeta * self.w)),
+            flat_proof["W_zw_1"]
+        )
+        print("verify_proof_unoptimized done check 2")
+
+        return True
 
     # Compute challenges (should be same as those computed by prover)
     def compute_challenges(