Exemplo n.º 1
0
def babysnarkopt_prover(U, n_stmt, CRS, precomp, a):
    (m, n) = U.shape
    assert n == len(a)
    assert len(CRS) == (m + 1) + 2 + (n - n_stmt)

    taus = CRS[: m + 1]
    bUis = CRS[-(n - n_stmt) :]

    Uis, T = precomp

    # Target is the vanishing polynomial
    mpow2 = m
    assert mpow2 & mpow2 - 1 == 0, "mpow2 must be a power of 2"
    omega = omega_base ** (2 ** 32 // mpow2)
    omega2 = omega_base ** (2 ** 32 // (2 * mpow2))
    PolyEvalRep = polynomialsEvalRep(Fp, omega, mpow2)
    t = vanishing_poly(omega, mpow2)

    # 1. Find the polynomial p(X)

    # First compute v
    v = PolyEvalRep((), ())
    for (i, k), y in U.items():
        x = ROOTS[i]
        v += PolyEvalRep([x], [y]) * a[k]

    # Now we need to convert between representations to multiply and divide
    PolyEvalRep2 = polynomialsEvalRep(Fp, omega2, 2 * mpow2)
    roots2 = [omega2 ** i for i in range(2 * mpow2)]
    ONE = PolyEvalRep2(roots2, [Fp(1) for _ in roots2])

    vv = v.to_coeffs()
    v2 = PolyEvalRep2.from_coeffs(v.to_coeffs())
    p2 = v2 * v2 - ONE
    p = p2.to_coeffs()

    # Find the polynomial h by dividing p / t
    h = PolyEvalRep2.divideWithCoset(p, t)
    # assert p == h * t

    # 2. Compute the H term
    H = evaluate_in_exponent(taus, h)

    # 3. Compute the Vw terms, using precomputed Uis
    Vw = sum([Uis[k] * a[k] for k in range(n_stmt, n)], G * 0)
    # assert G * vw(tau) == Vw

    # 4. Compute the Bw terms
    Bw = sum([bUis[k - n_stmt] * a[k] for k in range(n_stmt, n)], G * 0)
    # assert G * (beta * vw(tau)) == Bw

    # V = G * vv(tau)
    # assert H.pair(T) * GT == V.pair(V)

    # print('H:', H)
    # print('Bw:', Bw)
    # print('Vw:', Vw)
    return H, Bw, Vw
Exemplo n.º 2
0
def setup_algo(gates_matrix, permutation, L, p_i):
    print("Starting Setup Phase...")
    (m, n) = gates_matrix.shape

    assert n & n - 1 == 0, "n must be a power of 2"
    omega = omega_base ** (2 ** 32 // n)
    ROOTS = [omega ** i for i in range(n)]

    PolyEvalRep = polynomialsEvalRep(Fp, omega, n)

    # Generate polynomials from columns of gates_matrix
    q_L = PolyEvalRep(ROOTS, [Fp(i) for i in gates_matrix[0]])
    q_R = PolyEvalRep(ROOTS, [Fp(i) for i in gates_matrix[1]])
    q_M = PolyEvalRep(ROOTS, [Fp(i) for i in gates_matrix[2]])
    q_O = PolyEvalRep(ROOTS, [Fp(i) for i in gates_matrix[3]])
    q_C = PolyEvalRep(ROOTS, [Fp(i) for i in gates_matrix[4]])
    Qs = [q_L, q_R, q_M, q_O, q_C]

    # The public input poly vanishes everywhere except for the position of the
    # public input gate where it evaluates to -(public_input)
    public_input = [Fp(0) for i in range(len(ROOTS))]
    for i in L:
        public_input[i] = Fp(-p_i)
    p_i_poly = PolyEvalRep(ROOTS, public_input)

    # We generate domains on which we can evaluate the witness polynomials
    k = random_fp()
    id_domain_a = ROOTS
    id_domain_b = [k * root for root in ROOTS]
    id_domain_c = [k**2 * root for root in ROOTS]
    id_domain = id_domain_a + id_domain_b + id_domain_c

    # We permute the positions of the domain generated above
    perm_domain = [id_domain[i - 1] for i in permutation]
    perm_domain_a = perm_domain[:n]
    perm_domain_b = perm_domain[n: 2*n]
    perm_domain_c = perm_domain[2*n:3*n]

    # Generate polynomials that return the permuted index when evaluated on the
    # domain
    S_sigma_1 = PolyEvalRep(ROOTS, perm_domain_a)
    S_sigma_2 = PolyEvalRep(ROOTS, perm_domain_b)
    S_sigma_3 = PolyEvalRep(ROOTS, perm_domain_c)
    Ss = [S_sigma_1, S_sigma_2, S_sigma_3]

    perm_precomp = [id_domain, perm_domain, k, Ss]

    # We perform the trusted setup
    tau = random_fp()
    CRS = [G * (tau ** i) for i in range(n + 3)]

    # We take some work off the shoulders of the verifier
    print("Starting Verifier Preprocessing...")
    q_exp = [evaluate_in_exponent(CRS, q.to_coeffs()) for q in Qs]
    s_exp = [evaluate_in_exponent(CRS, s.to_coeffs()) for s in Ss]
    x_exp = G2 * tau
    verifier_preprocessing = [q_exp, s_exp, x_exp]
    print("Setup Phase Finished!")

    return CRS, Qs, p_i_poly, perm_precomp, verifier_preprocessing
Exemplo n.º 3
0
def babysnarkopt_setup(U, n_stmt):
    (m, n) = U.shape
    assert n_stmt < n

    # Generate roots for each gate
    # TODO: Handle padding?
    global mpow2, omega
    mpow2 = m
    assert mpow2 & mpow2 - 1 == 0, "mpow2 must be a power of 2"
    omega = omega_base ** (2 ** 32 // mpow2)
    PolyEvalRep = polynomialsEvalRep(Fp, omega, mpow2)

    global ROOTS
    if len(ROOTS) != m:
        ROOTS.clear()
        ROOTS += [omega ** i for i in range(m)]

    # Generate polynomials u from columns of U
    Us = [PolyEvalRep((), ()) for _ in range(n)]
    for (i, k), y in U.items():
        x = ROOTS[i]
        Us[k] += PolyEvalRep([x], [y])

    # Trapdoors
    global tau, beta, gamma
    tau = random_fp()
    beta = random_fp()
    gamma = random_fp()

    # CRS elements
    CRS = (
        [G * (tau ** i) for i in range(m + 1)]
        + [G * gamma, G * (beta * gamma)]
        + [G * (beta * Ui(tau)) for Ui in Us[n_stmt:]]
    )

    # Precomputation
    # Note: This is not considered part of the trusted setup, since it
    # could be computed direcftly from the G * (tau **i) terms.

    # Compute the target poly term
    t = vanishing_poly(omega, m)
    T = G * t(tau)

    # Evaluate the Ui's corresponding to statement values
    Uis = [G * Ui(tau) for Ui in Us]
    precomp = Uis, T

    return CRS, precomp
Exemplo n.º 4
0
def _sparse_poly_demo():
    PolyEvalRep = polynomialsEvalRep(Fp, omega, mpow2)

    # Example polynomial that has roots at most of the powers of omega
    xs = [omega ** 1, omega ** 4, omega ** 5]
    ys = [Fp(3), Fp(5), Fp(1)]
    f_rep = PolyEvalRep(xs, ys)

    for i in [0, 2, 3, 6, 7]:
        assert f_rep(omega ** i) == Fp(0)
    for i, x in enumerate(xs):
        assert f_rep(x) == ys[i]

    # Convert to coeffs and back
    f = f_rep.to_coeffs()
    assert f_rep.to_coeffs() == PolyEvalRep.from_coeffs(f).to_coeffs()

    # Check f and f_rep are consistent
    tau = random_fp()
    assert f(tau) == f_rep(tau)
Exemplo n.º 5
0
def verifier_algo(proof_SNARK, n, p_i_poly, verifier_preprocessing, k):
    print("Starting Verification...")
    omega = omega_base**(2**32 // n)

    first_output, second_output, third_output, fifth_output, fourth_output = proof_SNARK
    a_eval_exp, b_eval_exp, c_eval_exp = first_output
    z_eval_exp = second_output
    t_lo_eval_exp, t_mid_eval_exp, t_hi_eval_exp = third_output
    a_zeta, b_zeta, c_zeta, S_1_zeta, S_2_zeta, accumulator_shift_zeta, t_zeta, r_zeta = fourth_output
    W_zeta_eval_exp, W_zeta_omega_eval_exp = fifth_output

    q_exp, s_exp, x_exp = verifier_preprocessing
    q_L_exp, q_R_exp, q_M_exp, q_O_exp, q_C_exp = q_exp
    s_1_exp, s_2_exp, s_3_exp = s_exp

    print("Check1: Elements in group?")
    assert type(a_eval_exp) is SS_BLS12_381
    assert type(b_eval_exp) is SS_BLS12_381
    assert type(c_eval_exp) is SS_BLS12_381
    assert type(z_eval_exp) is SS_BLS12_381
    assert type(t_lo_eval_exp) is SS_BLS12_381
    assert type(t_mid_eval_exp) is SS_BLS12_381
    assert type(t_hi_eval_exp) is SS_BLS12_381
    assert type(W_zeta_eval_exp) is SS_BLS12_381
    assert type(W_zeta_omega_eval_exp) is SS_BLS12_381

    print("Check2: Elements in field?")
    assert type(a_zeta) is Fp
    assert type(b_zeta) is Fp
    assert type(c_zeta) is Fp
    assert type(S_1_zeta) is Fp
    assert type(S_2_zeta) is Fp
    assert type(r_zeta) is Fp
    assert type(accumulator_shift_zeta) is Fp

    print("Check3: Public input in field?")
    assert type(p_i_poly) == polynomialsEvalRep(Fp, omega, n)
    print(type(p_i_poly))

    print("Step4: Recompute challenges from transcript")
    beta = random_fp_seeded(str(first_output) + "0")
    gamma = random_fp_seeded(str(first_output) + "1")
    alpha = random_fp_seeded(str(first_output) + str(second_output))
    zeta = random_fp_seeded(
        str(first_output) + str(second_output) + str(third_output))
    nu = random_fp_seeded(
        str(first_output) + str(second_output) + str(third_output) +
        str(fourth_output))
    u = random_fp_seeded(str(proof_SNARK))

    print("Step5: Evaluate vanishing polynomial at zeta")
    vanishing_poly_eval = zeta**n - Fp(1)

    print("Step6: Evaluate lagrange polynomial at zeta")
    L_1_zeta = (zeta**n - Fp(1)) / (n * (zeta - Fp(1)))

    print("Step7: Evaluate public input polynomial at zeta")
    p_i_poly_zeta = eval_poly(p_i_poly, [zeta])[0]

    print("Step8: Compute quotient polynomial evaluation")
    t_zeta = (r_zeta + p_i_poly_zeta - (a_zeta + beta * S_1_zeta + gamma) *
              (b_zeta + beta * S_2_zeta + gamma) *
              (c_zeta + gamma) * accumulator_shift_zeta * alpha -
              L_1_zeta * alpha**2) / vanishing_poly_eval

    print("Step9: Comupte first part of batched polynomial commitment")
    D_1_exp = (q_M_exp * a_zeta * b_zeta * nu + q_L_exp * a_zeta * nu +
               q_R_exp * b_zeta * nu + q_O_exp * c_zeta * nu + q_C_exp * nu)
    D_1_exp += (z_eval_exp * ((a_zeta + beta * zeta + gamma) *
                              (b_zeta + beta * k * zeta + gamma) *
                              (c_zeta + beta *
                               (k**2) * zeta + gamma) * alpha * nu + L_1_zeta *
                              (alpha**2) * nu + u))
    D_1_exp += (s_3_exp * (a_zeta + beta * S_1_zeta + gamma) *
                (b_zeta + beta * S_2_zeta + gamma) * alpha * beta *
                accumulator_shift_zeta * nu) * Fp(-1)

    print("Step10: Compute full batched polynomial commitment")
    F_1_exp = (t_lo_eval_exp + t_mid_eval_exp * zeta**(n + 2) +
               t_hi_eval_exp * zeta**(2 * (n + 2)) + D_1_exp +
               a_eval_exp * nu**2 + b_eval_exp * nu**3 + c_eval_exp * nu**4 +
               s_1_exp * nu**5 + s_2_exp * nu**6)

    print("Step 11: Compute group encoded batch evaluation")
    E_1_exp = G * (t_zeta + nu * r_zeta + nu**2 * a_zeta + nu**3 * b_zeta +
                   nu**4 * c_zeta + nu**5 * S_1_zeta + nu**6 * S_2_zeta +
                   u * accumulator_shift_zeta)

    print("Check12: Batch validate all evaluations via pairing")
    e11 = W_zeta_eval_exp + W_zeta_omega_eval_exp * u
    e21 = (W_zeta_eval_exp * zeta + W_zeta_omega_eval_exp * u * zeta * omega +
           F_1_exp + (E_1_exp * Fp(-1)))
    assert e11.pair(x_exp) == e21.pair(G2)
    print("Verification Successful!")
Exemplo n.º 6
0
def prover_algo(witness, CRS, Qs, p_i_poly, perm_precomp):
    print("Starting the Prover Algorithm")
    n = int(len(witness) / 3)
    assert n & n - 1 == 0, "n must be a power of 2"
    id_domain, perm_domain, k, Ss = perm_precomp

    # We need to convert between representations to multiply and divide more
    # efficiently. In round 3 we have to divide a polynomial of degree 4*n+6
    # Not sure if there is a big benefit in keeping the lower order
    # representations or if it makes sense to do everything in the highest
    # order 8*n right away...

    # polys represented with n points
    omega = omega_base ** (2 ** 32 // n)
    ROOTS = [omega ** i for i in range(n)]
    PolyEvalRep = polynomialsEvalRep(Fp, omega, n)
    witness = [Fp(i) for i in witness]
    witness_poly_a = PolyEvalRep(ROOTS, witness[:n])
    witness_poly_b = PolyEvalRep(ROOTS, witness[n:n*2])
    witness_poly_c = PolyEvalRep(ROOTS, witness[2*n:])
    vanishing_pol_coeff = vanishing_poly(omega, n)

    # polys represented with 2*n points
    omega2 = omega_base ** (2 ** 32 // (2 * n))
    PolyEvalRep2 = polynomialsEvalRep(Fp, omega2, 2 * n)
    vanishing_poly_ext = PolyEvalRep2.from_coeffs(vanishing_pol_coeff)
    witness_poly_a_ext = PolyEvalRep2.from_coeffs(witness_poly_a.to_coeffs())
    witness_poly_b_ext = PolyEvalRep2.from_coeffs(witness_poly_b.to_coeffs())
    witness_poly_c_ext = PolyEvalRep2.from_coeffs(witness_poly_c.to_coeffs())

    # polys represented with 8*n points
    omega3 = omega_base ** (2 ** 32 // (8 * n))
    PolyEvalRep3 = polynomialsEvalRep(Fp, omega3, 8 * n)
    roots3 = [omega3 ** i for i in range(8 * n)]
    S1, S2, S3 = Ss
    S1_ext3 = PolyEvalRep3.from_coeffs(S1.to_coeffs())
    S2_ext3 = PolyEvalRep3.from_coeffs(S2.to_coeffs())
    S3_ext3 = PolyEvalRep3.from_coeffs(S3.to_coeffs())
    p_i_poly_ext3 = PolyEvalRep3.from_coeffs(p_i_poly.to_coeffs())
    qs_ext3 = [PolyEvalRep3.from_coeffs(q.to_coeffs()) for q in Qs]
    q_L_ext3, q_R_ext3, q_M_ext3, q_O_ext3, q_C_ext3 = qs_ext3

    # Following the paper, we are using the Fiat Shamir Heuristic. We are
    # Simulating 5 rounds of communication with the verifier using a
    # random oracle for verifier answers
    print("Starting Round 1...")

    # Generate "random" blinding scalars
    rand_scalars = [random_fp_seeded("1234") for i in range(9)]

    # Generate polys with the random scalars as coefficients and convert to
    # evaluation representation. These are needed for zero knowledge to
    # obfuscate the witness.
    a_blind_poly_ext = Poly([rand_scalars[1], rand_scalars[0]])
    b_blind_poly_ext = Poly([rand_scalars[3], rand_scalars[2]])
    c_blind_poly_ext = Poly([rand_scalars[5], rand_scalars[4]])
    a_blind_poly_ext = PolyEvalRep2.from_coeffs(a_blind_poly_ext)
    b_blind_poly_ext = PolyEvalRep2.from_coeffs(b_blind_poly_ext)
    c_blind_poly_ext = PolyEvalRep2.from_coeffs(c_blind_poly_ext)

    # These polynomals have random evaluations at all points except ROOTS where
    # they evaluate to the witness
    a_poly_ext = a_blind_poly_ext * vanishing_poly_ext + witness_poly_a_ext
    b_poly_ext = b_blind_poly_ext * vanishing_poly_ext + witness_poly_b_ext
    c_poly_ext = c_blind_poly_ext * vanishing_poly_ext + witness_poly_c_ext

    # Evaluate the witness polynomials in the exponent using the CRS
    a_eval_exp = evaluate_in_exponent(CRS, a_poly_ext.to_coeffs())
    b_eval_exp = evaluate_in_exponent(CRS, b_poly_ext.to_coeffs())
    c_eval_exp = evaluate_in_exponent(CRS, c_poly_ext.to_coeffs())

    first_output = [a_eval_exp, b_eval_exp, c_eval_exp]
    print("Round 1 Finished with output: ", first_output)

    print("Starting Round 2...")
    # Compute permutation challenges from imaginary verifier
    beta = random_fp_seeded(str(first_output) + "0")
    gamma = random_fp_seeded(str(first_output) + "1")

    # Compute permutation polynomial. z_1 is the blinding summand needed for ZK
    z_1 = Poly([rand_scalars[8], rand_scalars[7], rand_scalars[6]])
    z_1 = PolyEvalRep2.from_coeffs(z_1)
    z_1 = z_1 * vanishing_poly_ext
    accumulator_poly_eval = [Fp(1)]
    accumulator_poly_eval += [accumulator_factor(n,
                                                 i,
                                                 witness, beta,
                                                 id_domain,
                                                 perm_domain,
                                                 gamma)
                              for i in range(n-1)]
    accumulator_poly = PolyEvalRep(ROOTS, accumulator_poly_eval)
    accumulator_poly = z_1 + PolyEvalRep2.from_coeffs(accumulator_poly.to_coeffs())

    second_output = evaluate_in_exponent(CRS, accumulator_poly.to_coeffs())
    print("Round 2 Finished with output: ", second_output)

    print("Starting Round 3...")
    alpha = random_fp_seeded(str(first_output) + str(second_output))

    accumulator_poly_ext3 = PolyEvalRep3.from_coeffs(accumulator_poly.to_coeffs())

    # The third summand of t has the accumulator poly evaluated at a shift
    accumulator_poly_shift_evaluations = eval_poly(accumulator_poly,
                                                   roots3,
                                                   ROOTS[1])
    accumulator_poly_ext3_shift = PolyEvalRep3(roots3,
                                               accumulator_poly_shift_evaluations)

    a_poly_ext3 = PolyEvalRep3.from_coeffs(a_poly_ext.to_coeffs())
    b_poly_ext3 = PolyEvalRep3.from_coeffs(b_poly_ext.to_coeffs())
    c_poly_ext3 = PolyEvalRep3.from_coeffs(c_poly_ext.to_coeffs())

    id_poly_1_ext3 = PolyEvalRep3.from_coeffs(Poly([gamma, beta]))
    id_poly_2_ext3 = PolyEvalRep3.from_coeffs(Poly([gamma, beta * k]))
    id_poly_3_ext3 = PolyEvalRep3.from_coeffs(Poly([gamma, beta * k**2]))

    gamma_poly = PolyEvalRep3.from_coeffs(Poly([gamma]))
    L_1 = PolyEvalRep(ROOTS, [Fp(1)] + [Fp(0) for i in range(len(ROOTS)-1)])

    # Compute quotient polynomial: we are dividing by the vanishing poly which
    # has zeros at n roots so we need to do division by swapping to a coset
    # first summand should have degree 3n+1, second and third should have
    # degree 4n + 5
    t = ((a_poly_ext3 * b_poly_ext3 * q_M_ext3) +
         (a_poly_ext3 * q_L_ext3) +
         (b_poly_ext3 * q_R_ext3) +
         (c_poly_ext3 * q_O_ext3) +
         q_C_ext3 + p_i_poly_ext3)

    t += ((a_poly_ext3 + id_poly_1_ext3) *
          (b_poly_ext3 + id_poly_2_ext3) *
          (c_poly_ext3 + id_poly_3_ext3) * accumulator_poly_ext3 * alpha)

    t -= ((a_poly_ext3 + S1_ext3 * beta + gamma_poly) *
          (b_poly_ext3 + S2_ext3 * beta + gamma_poly) *
          (c_poly_ext3 + S3_ext3 * beta + gamma_poly) *
          accumulator_poly_ext3_shift * alpha)

    t += ((accumulator_poly_ext3 - PolyEvalRep3.from_coeffs(Poly([Fp(1)]))) *
          PolyEvalRep3.from_coeffs(L_1.to_coeffs()) * alpha ** 2)

    t = PolyEvalRep3.divideWithCoset(t.to_coeffs(), vanishing_pol_coeff)

    t_coeff = t.coefficients

    # We split up the polynomial t in three polynomials so that:
    # t= t_lo + x^n*t_mid + t^2n*t_hi
    # I found that n has actually to be (n+2) to accomodate the CRS because
    # t can be of degree 4n+5
    t_lo = Poly(t_coeff[:n+2])
    t_mid = Poly(t_coeff[n+2:2*(n+2)])
    t_hi = Poly(t_coeff[2*(n+2):])

    t_lo_eval_exp = evaluate_in_exponent(CRS, t_lo)
    t_mid_eval_exp = evaluate_in_exponent(CRS, t_mid)
    t_hi_eval_exp = evaluate_in_exponent(CRS, t_hi)

    third_output = [t_lo_eval_exp, t_mid_eval_exp, t_hi_eval_exp]
    print("Round 3 Finished with output: ", third_output)

    print("Starting Round 4...")
    # Compute the evaluation challenge
    zeta = random_fp_seeded(str(first_output) +
                            str(second_output) +
                            str(third_output))

    # Compute the opening evaluations
    a_zeta = eval_poly(a_poly_ext, [zeta])[0]
    b_zeta = eval_poly(b_poly_ext, [zeta])[0]
    c_zeta = eval_poly(c_poly_ext, [zeta])[0]
    S_1_zeta = eval_poly(S1, [zeta])[0]
    S_2_zeta = eval_poly(S2, [zeta])[0]
    t_zeta = eval_poly(PolyEvalRep3.from_coeffs(t), [zeta])[0]
    accumulator_shift_zeta = eval_poly(accumulator_poly_ext3,
                                       [zeta * ROOTS[1]])[0]

    # Compute linerisation polynomial
    r = (q_M_ext3 * a_zeta * b_zeta +
         q_L_ext3 * a_zeta +
         q_R_ext3 * b_zeta +
         q_O_ext3 * c_zeta +
         q_C_ext3)
    r += (accumulator_poly_ext3 *
          (a_zeta + beta * zeta + gamma) *
          (b_zeta + beta * k * zeta + gamma) *
          (c_zeta + beta * (k ** 2) * zeta + gamma) * alpha)
    r -= (S3_ext3 *
          (a_zeta + beta * S_1_zeta + gamma) *
          (b_zeta + beta * S_2_zeta + gamma) *
          alpha * beta * accumulator_shift_zeta)
    r += accumulator_poly_ext3 * eval_poly(L_1, [zeta])[0] * alpha ** 2

    # Evaluate r at zeta
    r_zeta = eval_poly(r, [zeta])[0]

    fourth_output = [a_zeta, b_zeta, c_zeta, S_1_zeta, S_2_zeta,
                     accumulator_shift_zeta, t_zeta, r_zeta]
    print("Round 4 Finished with output: ", fourth_output)

    print("Starting Round 5...")
    # Compute opening challeng
    nu = random_fp_seeded(str(first_output) +
                          str(second_output) +
                          str(third_output) +
                          str(fourth_output))

    # Compute opening proof polynomial
    W_zeta = (PolyEvalRep3.from_coeffs(t_lo) +
              PolyEvalRep3.from_coeffs(t_mid) * zeta ** (n+2) +
              PolyEvalRep3.from_coeffs(t_hi) * zeta ** (2*(n+2)) -
              PolyEvalRep3.from_coeffs(Poly([t_zeta])) +
              (r - PolyEvalRep3.from_coeffs(Poly([r_zeta]))) * nu +
              (a_poly_ext3 - PolyEvalRep3.from_coeffs(Poly([a_zeta]))) * nu ** 2 +
              (b_poly_ext3 - PolyEvalRep3.from_coeffs(Poly([b_zeta]))) * nu ** 3 +
              (c_poly_ext3 - PolyEvalRep3.from_coeffs(Poly([c_zeta]))) * nu ** 4 +
              (S1_ext3 - PolyEvalRep3.from_coeffs(Poly([S_1_zeta]))) * nu ** 5 +
              (S2_ext3 - PolyEvalRep3.from_coeffs(Poly([S_2_zeta]))) * nu ** 6)
    W_zeta = W_zeta / PolyEvalRep3.from_coeffs(Poly([-zeta, Fp(1)]))

    # Compute the opening proof polynomial
    W_zeta_omega = accumulator_poly_ext3 - PolyEvalRep3.from_coeffs(Poly([accumulator_shift_zeta]))
    W_zeta_omega = W_zeta_omega / PolyEvalRep3.from_coeffs(Poly([-zeta*ROOTS[1], Fp(1)]))

    W_zeta_eval_exp = evaluate_in_exponent(CRS, W_zeta.to_coeffs())
    W_zeta_omega_eval_exp = evaluate_in_exponent(CRS, W_zeta_omega.to_coeffs())

    fifth_output = [W_zeta_eval_exp, W_zeta_omega_eval_exp]
    proof_SNARK = [first_output, second_output, third_output, fifth_output, fourth_output]
    print("Round 5 Finished with output: ", fifth_output)

    u = random_fp_seeded(str(proof_SNARK))
    return proof_SNARK, u