diff --git a/.gitignore b/.gitignore index 24199bfe8..6d887f0ab 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ libsnark/zk_proof_systems/zksnark/ram_zksnark/tests/test_ram_zksnark build *~ +TAGS +.dir-locals.el diff --git a/CMakeLists.txt b/CMakeLists.txt index ee32d891e..d1e97d7f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ set( "BN128" CACHE STRING - "Default curve: one of ALT_BN128, BN128, EDWARDS, MNT4, MNT6" + "Default curve: one of ALT_BN128, BN128, EDWARDS, MNT4, MNT6, BW6_761, BLS12_377, BLS12_381" ) option( diff --git a/depends/libff b/depends/libff index 5f9a4bdd9..9f01c327c 160000 --- a/depends/libff +++ b/depends/libff @@ -1 +1 @@ -Subproject commit 5f9a4bdd9bea61036b57cfe972354fc97c497457 +Subproject commit 9f01c327cf928d9bb25d2586d7b4b8bc9726bc4c diff --git a/libsnark/CMakeLists.txt b/libsnark/CMakeLists.txt index 1d3e0e29a..8b9742400 100644 --- a/libsnark/CMakeLists.txt +++ b/libsnark/CMakeLists.txt @@ -205,6 +205,7 @@ if ("${IS_LIBSNARK_PARENT}") libsnark_test(test_r1cs_ppzksnark_verifier_gadget gadgetlib1/tests/test_r1cs_ppzksnark_verifier_gadget.cpp) libsnark_test(test_r1cs_gg_ppzksnark_verifier_gadget gadgetlib1/tests/test_r1cs_gg_ppzksnark_verifier_gadget.cpp) libsnark_test(test_kzg10_verifier_gadget gadgetlib1/tests/test_kzg10_verifier_gadget.cpp) + libsnark_test(test_plonk zk_proof_systems/plonk/tests/example.cpp zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.cpp zk_proof_systems/plonk/tests/test_plonk.cpp) # TODO (howardwu): Resolve runtime on targets: # libsnark_test(zk_proof_systems_uscs_ppzksnark_test zk_proof_systems/ppzksnark/uscs_ppzksnark/tests/test_uscs_ppzksnark.cpp) diff --git a/libsnark/common/data_structures/polynomial.hpp b/libsnark/common/data_structures/polynomial.hpp index 2143b36b4..d1984fc87 100644 --- a/libsnark/common/data_structures/polynomial.hpp +++ b/libsnark/common/data_structures/polynomial.hpp @@ -9,6 +9,8 @@ #ifndef __LIBSNARK_COMMON_DATA_STRUCTURES_POLYNOMIAL_HPP__ #define __LIBSNARK_COMMON_DATA_STRUCTURES_POLYNOMIAL_HPP__ +#include + namespace libsnark { diff --git a/libsnark/transcript_hasher/transcript_hasher.hpp b/libsnark/transcript_hasher/transcript_hasher.hpp new file mode 100644 index 000000000..1409c73cf --- /dev/null +++ b/libsnark/transcript_hasher/transcript_hasher.hpp @@ -0,0 +1,32 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_TRANSCRIPT_HASHER_TRANSCRIPT_HASHER_HPP_ +#define LIBSNARK_TRANSCRIPT_HASHER_TRANSCRIPT_HASHER_HPP_ + +// // interface for a common transcript_hasher class used to implement +// // functionality for hashing the communication transcript in ZK proof +// // systems under ./zk_proof_systems +// template class transcript_hasher +// { +// public: +// transcript_hasher(); +// +// // add an Fr element to the transcript buffer for hashing +// void add_element(const libff::Fr &element); +// // add the coordinates of a G1 curve point to the transcript buffer for +// // hashing +// void add_element(const libff::G1 &element); +// // add the coordinates of a G2 curve point to the transcript buffer for +// // hashing +// void add_element(const libff::G2 &element); +// // return the hash value of the communication transcript +// libff::Fr get_hash(); +// }; + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/prover.hpp b/libsnark/zk_proof_systems/plonk/prover.hpp new file mode 100644 index 000000000..0db33c1e4 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/prover.hpp @@ -0,0 +1,444 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_HPP_ + +#include "libsnark/zk_proof_systems/plonk/srs.hpp" + +/// Declaration of Prover interfaces for ppzkSNARK proof system Plonk.This +/// includes: +/// +/// - class for proof +/// - class for prover +/// +/// Reference: +/// - \[GWC19]: +/// Title: "Plonk: Permutations over lagrange-bases for oecumenical +/// noninteractive arguments of knowledge", Ariel Gabizon, Zachary +/// J. Williamson, and Oana Ciobotaru, Cryptology ePrint Archive, +/// Report 2019/953, 2019, + +namespace libsnark +{ + +/// A proof for the Plonk GG-ppzkSNARK. +template class plonk_proof +{ +public: + using Field = libff::Fr; + + /// Plonk proof Pi + /// + /// Pi ([a]_1, [b]_1, [c]_1, [z]_1, + /// [t_lo]_1, [t_mi]_1, [t_hi]_1, + /// \bar{a}, \bar{b}, \bar{c}, + /// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w}, + /// [W_zeta]_1, [W_{zeta omega_roots}]_1 + /// r_zeta (*)) + + /// [a]_1, [b]_1, [c]_1 + std::vector> W_polys_blinded_at_secret_g1; + + /// [z]_1, + libff::G1 z_poly_at_secret_g1; + + /// [t_lo]_1, [t_mi]_1, [t_hi]_1 + std::vector> t_poly_at_secret_g1; + + /// \bar{a}, \bar{b}, \bar{c}, + Field a_zeta; + Field b_zeta; + Field c_zeta; + + /// \bar{S_sigma1}, \bar{S_sigma2}, + Field S_0_zeta; + Field S_1_zeta; + + /// \bar{z_w} + Field z_poly_xomega_zeta; + + /// [W_zeta]_1, [W_{zeta omega_roots}]_1 + libff::G1 W_zeta_at_secret; + libff::G1 W_zeta_omega_at_secret; + + /// r_zeta (*) + Field r_zeta; + + plonk_proof( + std::vector> &W_polys_blinded_at_secret_g1, + libff::G1 &z_poly_at_secret_g1, + std::vector> &t_poly_at_secret_g1, + Field &a_zeta, + Field &b_zeta, + Field &c_zeta, + Field &S_0_zeta, + Field &S_1_zeta, + Field &z_poly_xomega_zeta, + libff::G1 &W_zeta_at_secret, + libff::G1 &W_zeta_omega_at_secret, + Field &r_zeta); +}; + +/// Prover round 0 output +template struct round_zero_out_t { + + /// - zh_poly: vanishing polynomial Zh (from round 0) + std::vector> zh_poly; + + /// - null_poly: 0 polynomial (from round 0) + polynomial> null_poly; + + /// - neg_one_poly: -1 polynomial (from round 0) + polynomial> neg_one_poly; + + round_zero_out_t( + const std::vector> &&zh_poly, + const polynomial> &&null_poly, + const polynomial> &&neg_one_poly); +}; + +/// Prover round 1 output +template struct round_one_out_t { + + /// - W_polys: witness polynomials (Lagrange interpolation of the + /// witness values) + std::vector>> W_polys; + + /// - W_polys_blinded: blinded witness polynomials + std::vector>> W_polys_blinded; + + /// - W_polys_blinded_at_secret_g1: the blinded witness polynomials + /// evaluated at the secret input denoted [a]_1, [b]_1, [c]_1 in + /// [GWC19] + std::vector> W_polys_blinded_at_secret_g1; + + round_one_out_t( + const std::vector>> &&W_polys, + const std::vector>> &&W_polys_blinded, + const std::vector> &&W_polys_blinded_at_secret_g1); +}; + +/// Prover round 2 output +template struct round_two_out_t { + + /// - z_poly: blinded accumulator poly z(x) + polynomial> z_poly; + + /// - z_poly_at_secret_g1: blinded accumulator poly z(x) evaluated at + /// secret + libff::G1 z_poly_at_secret_g1; + + round_two_out_t( + polynomial> &&z_poly, + libff::G1 &&z_poly_at_secret_g1); +}; + +/// Prover round 3 output +template struct round_three_out_t { + + /// - z_poly_xomega: the polynomial z(x*w) i.e. z(x) shifted by w + std::vector> z_poly_xomega; + + /// - t_poly: t(x) divided in three parts t(x) = t_lo(x) + t_mid(x) x^n + /// + t_hi(x) x^{2n} + std::vector>> t_poly; + + /// - t_poly_long: the quotient polynomial t(x) (see Round 3, pp28 + /// [GWC19]) + polynomial> t_poly_long; + + /// - t_poly_at_secret_g1: t(x) evaluated at the secret input zeta + /// i.e. t(zeta) + std::vector> t_poly_at_secret_g1; + + round_three_out_t( + std::vector> &&z_poly_xomega, + std::vector>> &&t_poly, + polynomial> &&t_poly_long, + std::vector> &&t_poly_at_secret_g1); +}; + +/// Prover round 4 output +template struct round_four_out_t { + + /// - a_zeta, b_zeta, c_zeta: the blinded witness polynomials a(x), + /// b(x), c(x) (denoted by W_polys_blinded[] output from Round 1) + /// evaluated at x=zeta i.e. a(z), b(z), c(z) + libff::Fr a_zeta; + libff::Fr b_zeta; + libff::Fr c_zeta; + + /// - S_0_zeta, S_1_zeta: the permutation polynomials S_sigma_1(x), + /// S_sigma_2(x) from the common preprocessed input (see [GWC19], + /// Sect. 8.1) evaluated at x=zeta i.e. S_sigma_1(z), S_sigma_2(z) + libff::Fr S_0_zeta; + libff::Fr S_1_zeta; + + /// - z_poly_xomega_zeta: the polynomial z(x*w) i.e. z(x) shifted by w + /// (output from Round 3) evaluated at x=zeta i.e. z(zeta*w) + libff::Fr z_poly_xomega_zeta; + + /// - t_zeta: the quotient polynomial t(x) output from Round 3, see + /// pp28 [GWC19]) evaluated at x=zeta i.e. t(z). IMPORTANT! the + /// original Plonk proposal [GWC19] does not output this parameter + /// t_zeta. The Python reference implementation does, so we do the + /// same in order to match the test vectors. TODO can remove t_zeta + /// in the future + libff::Fr t_zeta; + + round_four_out_t( + libff::Fr &&a_zeta, + libff::Fr &&b_zeta, + libff::Fr &&c_zeta, + libff::Fr &&S_0_zeta, + libff::Fr &&S_1_zeta, + libff::Fr &&z_poly_xomega_zeta, + libff::Fr &&t_zeta); +}; + +/// Prover round 5 output +template struct round_five_out_t { + + /// - r_zeta: linearisation polynomial r(x) evaluated at x=zeta + /// ie. r(zeta) + libff::Fr r_zeta; + + /// - W_zeta_at_secret: commitment to opening proof polynomial + /// W_zeta(x) at secert input i.e. [W_zeta(secret)]_1 + libff::G1 W_zeta_at_secret; + + /// - W_zeta_omega_at_secret: commitment to opening proof polynomial + /// W_{zeta omega}(x) at secert input i.e. [W_{zeta omega}(secret)]_1 + libff::G1 W_zeta_omega_at_secret; + + round_five_out_t( + libff::Fr &&r_zeta, + libff::G1 &&W_zeta_at_secret, + libff::G1 &&W_zeta_omega_at_secret); +}; + +/// Plonk prover. Computes object of class plonk_proof. +template class plonk_prover +// template class plonk_prover +{ + using Field = libff::Fr; + +public: + /// Prover Round 0 initialization + /// + /// Initialization + /// + /// INPUT + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] round_zero_out_t + static round_zero_out_t round_zero(const srs &srs); + + /// Prover Round 1 + /// + /// INPUT + /// \param[in] round_zero_out: see round_zero_out_t + /// \param[in] blind_scalars: blinding scalars b1, b2, ..., b9 (only + /// b1-b6 used in round 1) (see Sect. 8.1, Round 1 in [GWC19]) + /// \param[in] witness: witness values + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// \param[in] domain: libfqfft evaluation domain (see + /// libfqfft/evaluation_domain/evaluation_domain.hpp) + /// + /// OUTPUT + /// \param[out] round_one_out: see round_one_out_t + /// \param[out] transcript_hasher: accumulates the communication + /// transcript into a buffer to be hashed after prover + /// rounds 1,2,3,4,5 (cf. fiat-shamir heuristic). + static round_one_out_t round_one( + const round_zero_out_t &round_zero_out, + const std::vector> &blind_scalars, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher); + + /// Prover Round 2 + /// + /// INPUT + /// \param[in] beta, gamma: permutation challenges -- hashes of + /// transcript (from round 2) + /// \param[in] round_zero_out: see round_zero_out_t + /// \param[in] blind_scalars: blinding scalars b1, b2, ..., b9 (only + /// b1-b6 used in round 1) (see Sect. 8.1, Round 1 in [GWC19]) + /// \param[in] witness: witness values + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// \param[in] domain: libfqfft evaluation domain (see + /// libfqfft/evaluation_domain/evaluation_domain.hpp) + /// + /// OUTPUT + /// \param[out] round_two_out: see round_two_out_t + /// \param[out] transcript_hasher: accumulates the communication + /// transcript into a buffer to be hashed after prover + /// rounds 1,2,3,4,5 (cf. fiat-shamir heuristic). + static round_two_out_t round_two( + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const std::vector> blind_scalars, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher); + + /// Prover Round 3 + /// + /// INPUT + /// \param[in] alpha: quotient challenge -- hash of transcript (from + /// round 3) + /// \param[in] beta, gamma: permutation challenges -- hashes of + /// transcript (from round 2) + /// \param[in] round_zero_out: see round_zero_out_t + /// \param[in] round_one_out: see round_one_out_t + /// \param[in] round_two_out: see round_two_out_t + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] round_three_out: see round_three_out_t + /// \param[out] transcript_hasher: accumulates the communication + /// transcript into a buffer to be hashed after prover + /// rounds 1,2,3,4,5 (cf. fiat-shamir heuristic). + static round_three_out_t round_three( + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const std::vector> &witness, + const srs &srs, + transcript_hasher &hasher); + + /// Prover Round 4 + /// + /// INPUT + /// \param[in] zeta: evaluation challenge -- hash of transcript (from + /// round 4) + /// \param[in] round_one_out: see round_one_out_t + /// \param[in] round_three_out: see round_three_out_t + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] round_four_out: see round_four_out_t + /// \param[out] transcript_hasher: accumulates the communication + /// transcript into a buffer to be hashed after prover + /// rounds 1,2,3,4,5 (cf. fiat-shamir heuristic). + static round_four_out_t round_four( + const libff::Fr &zeta, + const round_one_out_t &round_one_out, + const round_three_out_t &round_three_out, + const srs &srs, + transcript_hasher &hasher); + + /// Prover Round 5 + /// + /// INPUT + /// \param[in] alpha: quotient challenge -- hash of transcript (from + /// round 3) + /// \param[in] beta, gamma: permutation challenges -- hashes of + /// transcript (from round 2) + /// \param[in] zeta: evaluation challenge -- hash of transcript (from + /// round 4) + /// \param[in] nu: opening challenge -- hash of transcript (from round 5) + /// (denoted by v in [GWC19]) + /// \param[in] round_zero_out: see round_zero_out_t + /// \param[in] round_one_out: see round_one_out_t + /// \param[in] round_two_out: see round_two_out_t + /// \param[in] round_three_out: see round_three_out_t + /// \param[in] round_four_out: see round_four_out_t + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] round_five_out: see round_five_out_t + /// \param[out] transcript_hasher: accumulates the communication + /// transcript into a buffer to be hashed after prover + /// rounds 1,2,3,4,5 (cf. fiat-shamir heuristic). + static round_five_out_t round_five( + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const libff::Fr &zeta, + const libff::Fr &nu, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const round_three_out_t &round_three_out, + const round_four_out_t &round_four_out, + const srs &srs, + transcript_hasher &hasher); + + /// Prover compute SNARK proof + /// + /// Pi ([a]_1, [b]_1, [c]_1, [z]_1, + /// [t_lo]_1, [t_mi]_1, [t_hi]_1, + /// \bar{a}, \bar{b}, \bar{c}, + /// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w}, + /// [W_zeta]_1, [W_{zeta omega}]_1 + /// r_zeta) + /// + /// \note in the reference Python implementation, r_zeta (the + /// evaluation of the linearlization polynomial r(X) at zeta from + /// Prover round 5) is added to the pi-SNARK proof. In the paper this + /// is omitted, which seems to make the proof shorter by 1 element at + /// the expense of a slightly heavier computation on the verifier's + /// side. Here we follow the reference implementation to make sure we + /// match the test values. TODO: once all test vectors are verified, + /// we may remove r_zeta from the proof to be fully compliant with the + /// paper. + /// + /// Mapping code-to-paper quantities (format is 'code var : paper var') + /// + /// \param W_polys_blinded_at_secret_g1[a, b, c]: [a]_1, [b]_1, [c]_1 + /// (from Round 1) + /// \param z_poly_at_secret_g1: [z]_1 (from Round 2) + /// \param t_poly_at_secret_g1[lo, mi, hi]: [t_lo]_1, [t_mi]_1, + /// [t_hi]_1 (from Round 3) + /// \param a_zeta, b_zeta, c_zeta, S_0_zeta, S_1_zeta, + /// z_poly_xomega_zeta: \bar{a}, \bar{b}, \bar{c}, + /// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w} (from Round 4) + /// \param W_zeta_at_secret, W_zeta_omega_at_secret: [W_zeta]_1, + /// [W_{zeta omega}]_1 (from Round 5) + /// + /// INPUT + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// \param[in] witness: all internal values and public input + /// corresponding to the given circuit + /// \param[in] blind_scalars: random blinding scalars b1, b2, ..., b9 + /// used in prover rounds 1 and 2 (see Sect. 8.3, rounds + /// 1,2 [GWC19]) + /// \param[in] transcript_hasher: hashes of the communication + /// transcript after prover rounds 1,2,3,4,5. + /// + /// OUTPUT + /// \param[out] proof: SNARK proof Pi (see above) + static plonk_proof compute_proof( + const srs &srs, + const std::vector &witness, + const std::vector> &blind_scalars, + transcript_hasher &hasher); +}; + +} // namespace libsnark + +#include "libsnark/zk_proof_systems/plonk/prover.tcc" + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/prover.tcc b/libsnark/zk_proof_systems/plonk/prover.tcc new file mode 100644 index 000000000..a59577985 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/prover.tcc @@ -0,0 +1,992 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_TCC_ + +// Implementation of Prover interfaces for a ppzkSNARK for Plonk. See +// prover.hpp . + +namespace libsnark +{ + +template +round_zero_out_t::round_zero_out_t( + const std::vector> &&zh_poly, + const polynomial> &&null_poly, + const polynomial> &&neg_one_poly) + : zh_poly(zh_poly), null_poly(null_poly), neg_one_poly(neg_one_poly) +{ +} + +template +round_zero_out_t plonk_prover::round_zero( + const srs &srs) +{ + using Field = libff::Fr; + + // output from round 0 + std::vector> zh_poly; + polynomial> null_poly; + polynomial> neg_one_poly; + + // vanishing polynomial zh_poly(X) = x^n-1. vanishes on all n + // roots of unity srs.omega_roots + zh_poly.resize(srs.num_gates + 1, Field(0)); + zh_poly[0] = Field(-1); + zh_poly[srs.num_gates] = Field(1); + + null_poly = {Field(0)}; + neg_one_poly = {-Field("1")}; + + round_zero_out_t round_zero_out( + std::move(zh_poly), std::move(null_poly), std::move(neg_one_poly)); + + return round_zero_out; +} + +template +round_one_out_t::round_one_out_t( + const std::vector>> &&W_polys, + const std::vector>> &&W_polys_blinded, + const std::vector> &&W_polys_blinded_at_secret_g1) + : W_polys(W_polys) + , W_polys_blinded(W_polys_blinded) + , W_polys_blinded_at_secret_g1(W_polys_blinded_at_secret_g1) +{ +} + +template +round_one_out_t plonk_prover::round_one( + const round_zero_out_t &round_zero_out, + const std::vector> &blind_scalars, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher) +{ + using Field = libff::Fr; + const size_t nwitness = NUM_HSETS; + + // output from round 1 + std::vector>> W_polys; + std::vector>> W_polys_blinded; + std::vector> W_polys_blinded_at_secret_g1; + + // Compute witness polynomials via Lagrange interpolation. + W_polys.resize(nwitness, polynomial(srs.num_gates)); + for (size_t i = 0; i < nwitness; ++i) { + typename std::vector::const_iterator begin = + witness.begin() + (i * srs.num_gates); + typename std::vector::const_iterator end = + witness.begin() + (i * srs.num_gates) + (srs.num_gates); + std::vector W_points(begin, end); + plonk_interpolate_polynomial_from_points( + W_points, W_polys[i], domain); + } + + // Represent the blinding scalars b1, b2, ..., b9 as polynomials. + std::vector> blind_polys{ + {blind_scalars[1], blind_scalars[0]}, // b1 + b0 X + {blind_scalars[3], blind_scalars[2]}, // b3 + b2 X + {blind_scalars[5], blind_scalars[4]} // b5 + b4 X + }; + + // Compute blinded witness polynomials e.g. a_poly = + // blind_polys[0] * zh_poly + W_polys[0]. + W_polys_blinded.resize(nwitness); + for (size_t i = 0; i < nwitness; ++i) { + libfqfft::_polynomial_multiplication( + W_polys_blinded[i], blind_polys[i], round_zero_out.zh_poly); + libfqfft::_polynomial_addition( + W_polys_blinded[i], W_polys_blinded[i], W_polys[i]); + } + // Evaluate blinded witness polynomials at the secret input. + W_polys_blinded_at_secret_g1.resize(W_polys_blinded.size()); + plonk_evaluate_polys_at_secret_G1( + srs.secret_powers_g1, W_polys_blinded, W_polys_blinded_at_secret_g1); + + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(W_polys_blinded_at_secret_g1[a]); + hasher.add_element(W_polys_blinded_at_secret_g1[b]); + hasher.add_element(W_polys_blinded_at_secret_g1[c]); + + round_one_out_t round_one_out( + std::move(W_polys), + std::move(W_polys_blinded), + std::move(W_polys_blinded_at_secret_g1)); + + return round_one_out; +} + +template +round_two_out_t::round_two_out_t( + polynomial> &&z_poly, libff::G1 &&z_poly_at_secret_g1) + : z_poly(z_poly), z_poly_at_secret_g1(z_poly_at_secret_g1) +{ +} + +template +round_two_out_t plonk_prover::round_two( + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const std::vector> blind_scalars, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher) +{ + using Field = libff::Fr; + + polynomial> z_poly; + libff::G1 z_poly_at_secret_g1; + + // Compute permutation polynomial. + + // blinding polynomial: b8 + b7 X + b6 X^2 + std::vector z1_blind_poly{ + blind_scalars[8], blind_scalars[7], blind_scalars[6]}; + // Multiply by the vanishing polynomial: z1 = z1 * this->zh_poly. + libfqfft::_polynomial_multiplication( + z1_blind_poly, z1_blind_poly, round_zero_out.zh_poly); + + // A[0] = 1; ... A[i] = computed from (i-1) + std::vector A_vector = plonk_compute_accumulator( + srs.num_gates, beta, gamma, witness, srs.H_prime, srs.H_prime_permute); + + polynomial A_poly(srs.num_gates); + plonk_interpolate_polynomial_from_points(A_vector, A_poly, domain); + + // Add blinding polynomial z_1 to the accumulator polynomial A_poly. + libfqfft::_polynomial_addition(z_poly, z1_blind_poly, A_poly); + z_poly_at_secret_g1 = + plonk_evaluate_poly_at_secret_G1(srs.secret_powers_g1, z_poly); + + // Add outputs from Round 2 to the hash buffer. + hasher.add_element(z_poly_at_secret_g1); + + round_two_out_t round_two_out( + std::move(z_poly), std::move(z_poly_at_secret_g1)); + + return round_two_out; +} + +template +round_three_out_t::round_three_out_t( + std::vector> &&z_poly_xomega, + std::vector>> &&t_poly, + polynomial> &&t_poly_long, + std::vector> &&t_poly_at_secret_g1) + : z_poly_xomega(z_poly_xomega) + , t_poly(t_poly) + , t_poly_long(t_poly_long) + , t_poly_at_secret_g1(t_poly_at_secret_g1) +{ +} + +template +round_three_out_t plonk_prover::round_three( + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const std::vector> &witness, + const srs &srs, + transcript_hasher &hasher) +{ + using Field = libff::Fr; + int num_hgen = NUM_HSETS; + + // output from round 3 + std::vector> z_poly_xomega; + std::vector>> t_poly; + polynomial> t_poly_long; + std::vector> t_poly_at_secret_g1; + + // Computing the polynomial z(x*w) i.e. z(x) shifted by w where + // w=srs.omega_roots is the base root of unity and z is + // z_poly. we do this by multiplying the coefficients of z by w. + z_poly_xomega.resize(round_two_out.z_poly.size()); + std::fill(z_poly_xomega.begin(), z_poly_xomega.end(), Field(0)); + for (size_t i = 0; i < round_two_out.z_poly.size(); ++i) { + // omega_roots^i + Field omega_roots_i = + libff::power(srs.omega_roots[base][1], libff::bigint<1>(i)); + z_poly_xomega[i] = round_two_out.z_poly[i] * omega_roots_i; + } + + // Start computation of polynomial t(X) in round 3. we break t + // into 4 parts which we compute separately. each of the 4 parts + // is multiplied by 1/zh_poly in the paper. + std::vector> t_part(4); + + // --- Computation of t_part[0] + + // a(x)b(x)q_M(x) + polynomial abqM; + libfqfft::_polynomial_multiplication( + abqM, + round_one_out.W_polys_blinded[a], + round_one_out.W_polys_blinded[b]); + libfqfft::_polynomial_multiplication(abqM, abqM, srs.Q_polys[M]); + // a(x)q_L(x) + polynomial aqL; + libfqfft::_polynomial_multiplication( + aqL, round_one_out.W_polys_blinded[a], srs.Q_polys[L]); + // b(x)q_R(x) + polynomial bqR; + libfqfft::_polynomial_multiplication( + bqR, round_one_out.W_polys_blinded[b], srs.Q_polys[R]); + // c(x)q_O(x) + polynomial cqO; + libfqfft::_polynomial_multiplication( + cqO, round_one_out.W_polys_blinded[c], srs.Q_polys[O]); + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(srs.num_gates); + + // Compute PI polynomial from the PI_wire_indices (stored in the srs) and + // the PI_value (stored in the witness). By convention of this + // implementation the PI values are stored in the right input wire w_R (and + // not in the left input wire w_L as in [GWC19]). Recall that the witness w + // is composed of left input w_L, right input w_R and output wires w_O (in + // this order) and so w is the concatenation of w_L || w_R || w_O = w. Each + // vector w_L, w_R and w_O of wire values is of length srs.num_gates and so + // in order to get the value of the i-th PI[i] located at PI_wire_indices[i] + // of w we need to do a modulo srs.num_gates operation (to skip the first + // srs.num_gates values corresponding to w_L). This is the reason for the + // modulo srs.num_gates operation in the code below. + // + // EXAMPLE + // + // Suppose that we have a circuit of 8 gates and the witness is w = w_L || + // w_R || w_O where (leftmost position corresponds to index 0): + // + // w_L = [ 3, 9,27, 1, 1,30, 0, 0] + // w_R = [ 3, 3, 3, 5,35, 5, 0, 0] + // w_O = [ 9,27,30, 5,35,35, 0, 0] + // + // so (leftmost position corresponds to index 0): + // + // w = [ 3,9,27,1,1,30,0,0,3,3,3,5,35,5,0,0,9,27,30,5,35,35,0,0] + // + // In this example circuit we have a single public input (PI) 35 first + // appearing at position 12 in w (counting from 0). In this case the vector + // srs.PI_wire_indices will contain a single element srs.PI_wire_indices[0] + // = 12. + // + // The value of the PI is extracted from the witness as PI_value = + // w[srs.PI_wire_indices[0]] = w[12] = 35 + // + // To obtain the index of the PI in w_R we do a modulo num_gates (= 8) + // operation to skip the w_L vector (first 8 entries in w). + std::vector PI_points(srs.num_gates, Field(0)); + // Loop over all wire indices that correspond to PIs. + for (size_t i = 0; i < srs.PI_wire_indices.size(); i++) { + const Field PI_value = witness[srs.PI_wire_indices[i]]; + const size_t PI_coordinate_x = srs.PI_wire_indices[i] % srs.num_gates; + PI_points[PI_coordinate_x] = Field(-PI_value); + } + // Compute the PI polynomial from the list of points using iFFT. + polynomial PI_poly; + plonk_compute_public_input_polynomial(PI_points, PI_poly, domain); + + // t_part[0](x) = a(x)b(x)q_M(x) + a(x)q_L(x) + b(x)q_R(x) + c(x)q_O(x) + + // PI(x) + q_C(x) + polynomial poly_null{Field(0)}; + libfqfft::_polynomial_addition(t_part[0], poly_null, abqM); + libfqfft::_polynomial_addition(t_part[0], t_part[0], aqL); + libfqfft::_polynomial_addition(t_part[0], t_part[0], bqR); + libfqfft::_polynomial_addition(t_part[0], t_part[0], cqO); + libfqfft::_polynomial_addition(t_part[0], t_part[0], PI_poly); + libfqfft::_polynomial_addition(t_part[0], t_part[0], srs.Q_polys[C]); + + // --- Computation of t_part[1] + + // X*beta as polynomial in X + std::vector> xbeta_poly{ + {Field(0), beta}, // X*beta + {Field(0), beta * srs.k1}, // X*beta*k1 + {Field(0), beta * srs.k2} // X*beta*k2 + }; + // Represent gamma as polynomial in X, needed for prover Round 3. + polynomial gamma_poly{gamma}; // gamma + // Represent alpha as polynomial in X, needed for prover Round 3. + polynomial alpha_poly{alpha}; // alpha + + // a(x) + beta*x + gamma + polynomial a_xbeta_gamma; + libfqfft::_polynomial_addition( + a_xbeta_gamma, round_one_out.W_polys_blinded[a], xbeta_poly[base]); + libfqfft::_polynomial_addition( + a_xbeta_gamma, a_xbeta_gamma, gamma_poly); + // b(x) + beta_k1*x + gamma + polynomial b_xbeta_gamma_k1; + libfqfft::_polynomial_addition( + b_xbeta_gamma_k1, + round_one_out.W_polys_blinded[b], + xbeta_poly[base_k1]); + libfqfft::_polynomial_addition( + b_xbeta_gamma_k1, b_xbeta_gamma_k1, gamma_poly); + // c(x) + beta_k1*x + gamma + polynomial c_xbeta_gamma_k2; + libfqfft::_polynomial_addition( + c_xbeta_gamma_k2, + round_one_out.W_polys_blinded[c], + xbeta_poly[base_k2]); + libfqfft::_polynomial_addition( + c_xbeta_gamma_k2, c_xbeta_gamma_k2, gamma_poly); + // t_part[1] = (a(x) + beta*x + gamma)*(b(x) + beta_k1*x + + // gamma)*(c(x) + beta_k1*x + gamma)*z(x)*alpha + libfqfft::_polynomial_multiplication( + t_part[1], a_xbeta_gamma, b_xbeta_gamma_k1); + libfqfft::_polynomial_multiplication( + t_part[1], t_part[1], c_xbeta_gamma_k2); + libfqfft::_polynomial_multiplication( + t_part[1], t_part[1], round_two_out.z_poly); + libfqfft::_polynomial_multiplication( + t_part[1], t_part[1], alpha_poly); + + // --- Computation of t_part[2] + + // Represent beta as polynomial in X, needed for prover Round 3. + polynomial beta_poly{beta}; + // S*beta as polynomial + // S_sigma1(x)*beta, S_sigma2(x)*beta, S_sigma3(x)*beta + std::vector> sbeta_poly(num_hgen); + for (int i = 0; i < num_hgen; ++i) { + libfqfft::_polynomial_multiplication( + sbeta_poly[i], srs.S_polys[i], beta_poly); + } + // a(x) + S_sigma1(x)*beta + gamma + polynomial a_sbeta_gamma; + libfqfft::_polynomial_addition( + a_sbeta_gamma, round_one_out.W_polys_blinded[a], sbeta_poly[base]); + libfqfft::_polynomial_addition( + a_sbeta_gamma, a_sbeta_gamma, gamma_poly); + // b(x) + S_sigma2(x)*beta + gamma + polynomial b_sbeta_gamma_k1; + libfqfft::_polynomial_addition( + b_sbeta_gamma_k1, + round_one_out.W_polys_blinded[b], + sbeta_poly[base_k1]); + libfqfft::_polynomial_addition( + b_sbeta_gamma_k1, b_sbeta_gamma_k1, gamma_poly); + // b(x) + S_sigma2(x)*beta + gamma + polynomial c_sbeta_gamma_k2; + libfqfft::_polynomial_addition( + c_sbeta_gamma_k2, + round_one_out.W_polys_blinded[c], + sbeta_poly[base_k2]); + libfqfft::_polynomial_addition( + c_sbeta_gamma_k2, c_sbeta_gamma_k2, gamma_poly); + // t_part[2] = (a(x) + S_sigma1(x)*beta + gamma)*(b(x) + + // S_sigma2(x)*beta + gamma)*(b(x) + S_sigma2(x)*beta + + // gamma)*z(x*srs.omega_roots)*alpha + libfqfft::_polynomial_multiplication( + t_part[2], a_sbeta_gamma, b_sbeta_gamma_k1); + libfqfft::_polynomial_multiplication( + t_part[2], t_part[2], c_sbeta_gamma_k2); + libfqfft::_polynomial_multiplication( + t_part[2], t_part[2], z_poly_xomega); + libfqfft::_polynomial_multiplication( + t_part[2], t_part[2], alpha_poly); + // -t_part[2] + polynomial neg_one_poly = {-Field("1")}; + libfqfft::_polynomial_multiplication( + t_part[2], t_part[2], neg_one_poly); + + // --- Computation of t_part[3] + + // z(x) - 1 + polynomial z_neg_one; + libfqfft::_polynomial_addition( + z_neg_one, round_two_out.z_poly, neg_one_poly); + // (z(x)-1) * L_1(x) + libfqfft::_polynomial_multiplication( + t_part[3], z_neg_one, srs.L_basis_zero); + // (z(x)-1) * L_1(x) * alpha + libfqfft::_polynomial_multiplication( + t_part[3], t_part[3], alpha_poly); + // (z(x)-1) * L_1(x) * alpha * alpha + libfqfft::_polynomial_multiplication( + t_part[3], t_part[3], alpha_poly); + + // --- computation of t(x) + + // t(x) = (t[0] + t[1] + (-t[2]) + t[3]) / zh(x) + t_poly_long = {Field(0)}; + libfqfft::_polynomial_addition(t_poly_long, t_poly_long, t_part[0]); + libfqfft::_polynomial_addition(t_poly_long, t_poly_long, t_part[1]); + libfqfft::_polynomial_addition(t_poly_long, t_poly_long, t_part[2]); + libfqfft::_polynomial_addition(t_poly_long, t_poly_long, t_part[3]); + // t(x) = t(x) / zh(x): A/B = (Q, R) st. A = (Q * B) + R. + polynomial remainder; + libfqfft::_polynomial_division( + t_poly_long, remainder, t_poly_long, round_zero_out.zh_poly); + bool b_zero_remainder = libfqfft::_is_zero(remainder); + if (!b_zero_remainder) { + throw std::logic_error("Non-zero remainder in polynomial division"); + } + + // Break this->t_poly_long into three parts: lo, mid, hi, each of + // degree (num_gates-1). note: (srs.num_gates+3) is the length of + // the CRS = (srs.num_gates+2) powers of G1 + 1 power of G2. + t_poly.resize(num_hgen); + for (int i = 0; i < num_hgen; ++i) { + typename std::vector::iterator begin = + t_poly_long.begin() + (i * (srs.num_gates + 2)); + typename std::vector::iterator end = t_poly_long.begin() + + (i * (srs.num_gates + 2)) + + (srs.num_gates + 2); + std::vector tmp(begin, end); + t_poly[i] = tmp; + } + + // Evaluate each part of t_poly in the secret input. + t_poly_at_secret_g1.resize(num_hgen); + for (int i = 0; i < num_hgen; ++i) { + t_poly_at_secret_g1[i] = plonk_evaluate_poly_at_secret_G1( + srs.secret_powers_g1, t_poly[i]); + } + + // Add outputs from Round 3 to the hash buffer. + hasher.add_element(t_poly_at_secret_g1[lo]); + hasher.add_element(t_poly_at_secret_g1[mid]); + hasher.add_element(t_poly_at_secret_g1[hi]); + + round_three_out_t round_three_out( + std::move(z_poly_xomega), + std::move(t_poly), + std::move(t_poly_long), + std::move(t_poly_at_secret_g1)); + + return round_three_out; +} + +template +round_four_out_t::round_four_out_t( + libff::Fr &&a_zeta, + libff::Fr &&b_zeta, + libff::Fr &&c_zeta, + libff::Fr &&S_0_zeta, + libff::Fr &&S_1_zeta, + libff::Fr &&z_poly_xomega_zeta, + libff::Fr &&t_zeta) + : a_zeta(a_zeta) + , b_zeta(b_zeta) + , c_zeta(c_zeta) + , S_0_zeta(S_0_zeta) + , S_1_zeta(S_1_zeta) + , z_poly_xomega_zeta(z_poly_xomega_zeta) + , t_zeta(t_zeta) +{ +} + +template +round_four_out_t plonk_prover::round_four( + const libff::Fr &zeta, + const round_one_out_t &round_one_out, + const round_three_out_t &round_three_out, + const srs &srs, + transcript_hasher &hasher) +{ + using Field = libff::Fr; + + // output from round 4 + libff::Fr a_zeta; + libff::Fr b_zeta; + libff::Fr c_zeta; + libff::Fr S_0_zeta; + libff::Fr S_1_zeta; + libff::Fr z_poly_xomega_zeta; + libff::Fr t_zeta; + + a_zeta = libfqfft::evaluate_polynomial( + srs.num_gates + 2, round_one_out.W_polys_blinded[a], zeta); + b_zeta = libfqfft::evaluate_polynomial( + srs.num_gates + 2, round_one_out.W_polys_blinded[b], zeta); + c_zeta = libfqfft::evaluate_polynomial( + srs.num_gates + 2, round_one_out.W_polys_blinded[c], zeta); + S_0_zeta = libfqfft::evaluate_polynomial( + srs.num_gates, srs.S_polys[0], zeta); + S_1_zeta = libfqfft::evaluate_polynomial( + srs.num_gates, srs.S_polys[1], zeta); + t_zeta = libfqfft::evaluate_polynomial( + round_three_out.t_poly_long.size(), round_three_out.t_poly_long, zeta); + z_poly_xomega_zeta = libfqfft::evaluate_polynomial( + round_three_out.z_poly_xomega.size(), + round_three_out.z_poly_xomega, + zeta); + + // Add outputs from Round 4 to the hash buffer. + hasher.add_element(a_zeta); + hasher.add_element(b_zeta); + hasher.add_element(c_zeta); + hasher.add_element(S_0_zeta); + hasher.add_element(S_1_zeta); + hasher.add_element(z_poly_xomega_zeta); + + round_four_out_t round_four_out( + std::move(a_zeta), + std::move(b_zeta), + std::move(c_zeta), + std::move(S_0_zeta), + std::move(S_1_zeta), + std::move(z_poly_xomega_zeta), + std::move(t_zeta)); + + return round_four_out; +} + +template +round_five_out_t::round_five_out_t( + libff::Fr &&r_zeta, + libff::G1 &&W_zeta_at_secret, + libff::G1 &&W_zeta_omega_at_secret) + : r_zeta(r_zeta) + , W_zeta_at_secret(W_zeta_at_secret) + , W_zeta_omega_at_secret(W_zeta_omega_at_secret) +{ +} + +template +round_five_out_t plonk_prover::round_five( + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const libff::Fr &zeta, + const libff::Fr &nu, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const round_three_out_t &round_three_out, + const round_four_out_t &round_four_out, + const srs &srs, + transcript_hasher &hasher) +{ + using Field = libff::Fr; + polynomial remainder; + + // output from round 5 + libff::Fr r_zeta; + libff::G1 W_zeta_at_secret; + libff::G1 W_zeta_omega_at_secret; + + // Compute linerisation polynomial r in five parts. + std::vector> r_part(5); + + // --- Computation of r_part[0] + + // Represent values as constant term polynomials in orderto use + // the functions in the libfqfft library on polynomials. + polynomial a_zeta_poly{round_four_out.a_zeta}; + polynomial b_zeta_poly{round_four_out.b_zeta}; + polynomial c_zeta_poly{round_four_out.c_zeta}; + // a(z)b(z)q_M(x) + polynomial abqM_zeta; + libfqfft::_polynomial_multiplication( + abqM_zeta, srs.Q_polys[M], a_zeta_poly); + libfqfft::_polynomial_multiplication( + abqM_zeta, abqM_zeta, b_zeta_poly); + // a(z)q_L(x) + polynomial aqL_zeta; + libfqfft::_polynomial_multiplication( + aqL_zeta, srs.Q_polys[L], a_zeta_poly); + // b(z)q_R(x) + polynomial bqR_zeta; + libfqfft::_polynomial_multiplication( + bqR_zeta, srs.Q_polys[R], b_zeta_poly); + // c(z)q_O(x) + polynomial cqO_zeta; + libfqfft::_polynomial_multiplication( + cqO_zeta, srs.Q_polys[O], c_zeta_poly); + // a(z)b(z)q_M(x) + a(z)q_L(x) + b(z)q_R(x) + c(z)q_O(x) + q_C(x) + libfqfft::_polynomial_addition( + r_part[0], round_zero_out.null_poly, abqM_zeta); + libfqfft::_polynomial_addition(r_part[0], r_part[0], aqL_zeta); + libfqfft::_polynomial_addition(r_part[0], r_part[0], bqR_zeta); + libfqfft::_polynomial_addition(r_part[0], r_part[0], cqO_zeta); + libfqfft::_polynomial_addition(r_part[0], r_part[0], srs.Q_polys[C]); + + // --- Computation of r_part[1] + + polynomial r1_const_poly{ + (round_four_out.a_zeta + (beta * zeta) + gamma) * + (round_four_out.b_zeta + (beta * srs.k1 * zeta) + gamma) * + (round_four_out.c_zeta + (beta * srs.k2 * zeta) + gamma) * alpha}; + libfqfft::_polynomial_multiplication( + r_part[1], r1_const_poly, round_two_out.z_poly); + + // --- Computation of r_part[2] + + polynomial r2_const_poly{ + (round_four_out.a_zeta + (beta * round_four_out.S_0_zeta) + gamma) * + (round_four_out.b_zeta + (beta * round_four_out.S_1_zeta) + gamma) * + (alpha * beta * round_four_out.z_poly_xomega_zeta)}; + libfqfft::_polynomial_multiplication( + r_part[2], r2_const_poly, srs.S_polys[2]); + // -r_part[2] + libfqfft::_polynomial_multiplication( + r_part[2], r_part[2], round_zero_out.neg_one_poly); + + // --- Computation of r_part[3] + + // r3 = accumulator_poly_ext3 * eval_poly(L_1, [zeta])[0] * alpha ** 2 + polynomial L_0_zeta_poly{libfqfft::evaluate_polynomial( + srs.L_basis_zero.size(), srs.L_basis_zero, zeta)}; + polynomial alpha_power2_poly{ + libff::power(alpha, libff::bigint<1>(2))}; + libfqfft::_polynomial_multiplication( + r_part[3], round_two_out.z_poly, L_0_zeta_poly); + libfqfft::_polynomial_multiplication( + r_part[3], r_part[3], alpha_power2_poly); + + // --- Computation of r_poly = (r0+r1-r2+r3) + + // Note: here the reference Python implementation differs from the + // paper where: + // + // r(x) = r(x) - zh(zeta) (t_lo(x) + zeta^n t_mid(x) + zeta^2n t_hi(x)) + // + // In the reference implementation \[PlonkPy], the missing term is + // added in the computation of the W_zeta(x) polynomial. + // + // linearisation polynomial r(x) + polynomial r_poly; + libfqfft::_polynomial_addition( + r_poly, round_zero_out.null_poly, r_part[0]); + libfqfft::_polynomial_addition(r_poly, r_poly, r_part[1]); + libfqfft::_polynomial_addition(r_poly, r_poly, r_part[2]); + libfqfft::_polynomial_addition(r_poly, r_poly, r_part[3]); + + // TODO: make a separate unit test for r_poly + // assert(r_poly == example.r_poly); + + // Evaluate the r-polynomial at zeta. Note: in the reference + // implementation, r_zeta is added to the pi-SNARK proof. In the + // paper this is omitted, which makes the proof shorter at the + // epxense of a slightly heavier computation on the verifier's + // side. + r_zeta = libfqfft::evaluate_polynomial(r_poly.size(), r_poly, zeta); + + // W_zeta polynomial is of degree 6 in the random element nu and + // hence has 7 terms. + std::vector> W_zeta_part(7); + + // --- compute W_zeta_part[0] + + // t_lo(x) + polynomial t_lo{round_three_out.t_poly[lo]}; + // t_mid(x) * zeta^(n+2) + polynomial t_mid_zeta_n; + polynomial zeta_powern_poly{ + libff::power(zeta, libff::bigint<1>(srs.num_gates + 2))}; + libfqfft::_polynomial_multiplication( + t_mid_zeta_n, round_three_out.t_poly[mid], zeta_powern_poly); + // t_hi(x) * zeta^(2(n+1)) + polynomial t_hi_zeta_2n; + polynomial zeta_power2n_poly{ + libff::power(zeta, libff::bigint<1>(2 * (srs.num_gates + 2)))}; + libfqfft::_polynomial_multiplication( + t_hi_zeta_2n, round_three_out.t_poly[hi], zeta_power2n_poly); + // -t_zeta as constant term polynomial + polynomial t_zeta_poly{-round_four_out.t_zeta}; + // t_lo(x) + (t_mid(x) * zeta^n) + (t_hi(x) * zeta^2n) + t_zeta_poly + libfqfft::_polynomial_addition( + W_zeta_part[0], round_zero_out.null_poly, t_lo); + libfqfft::_polynomial_addition( + W_zeta_part[0], W_zeta_part[0], t_mid_zeta_n); + libfqfft::_polynomial_addition( + W_zeta_part[0], W_zeta_part[0], t_hi_zeta_2n); + libfqfft::_polynomial_addition( + W_zeta_part[0], W_zeta_part[0], t_zeta_poly); + + // --- compute W_zeta_part[1] + + // -r_zeta as constant term polynomial + polynomial r_zeta_poly{-r_zeta}; + // r(x) - r_zeta + polynomial r_sub_rzeta; + libfqfft::_polynomial_addition(r_sub_rzeta, r_poly, r_zeta_poly); + // (r(x) - r_zeta) * nu + polynomial nu_poly{nu}; + libfqfft::_polynomial_multiplication( + W_zeta_part[1], r_sub_rzeta, nu_poly); + + // --- compute W_zeta_part[2] + + // -a_zeta as constant term polynomial + polynomial a_zeta_poly_neg; + libfqfft::_polynomial_multiplication( + a_zeta_poly_neg, a_zeta_poly, round_zero_out.neg_one_poly); + // a(x) - a_zeta + polynomial a_sub_azeta; + libfqfft::_polynomial_addition( + a_sub_azeta, round_one_out.W_polys_blinded[a], a_zeta_poly_neg); + // (a(x) - a_zeta) * nu^2 + Field nu2 = libff::power(nu, libff::bigint<1>(2)); + polynomial nu2_poly{nu2}; + libfqfft::_polynomial_multiplication( + W_zeta_part[2], a_sub_azeta, nu2_poly); + + // -b_zeta as constant term polynomial + polynomial b_zeta_poly_neg; + libfqfft::_polynomial_multiplication( + b_zeta_poly_neg, b_zeta_poly, round_zero_out.neg_one_poly); + // (b(x) - b_zeta) + polynomial b_sub_bzeta; + libfqfft::_polynomial_addition( + b_sub_bzeta, round_one_out.W_polys_blinded[b], b_zeta_poly_neg); + // (b(x) - b_zeta) * nu^3 + Field nu3 = libff::power(nu, libff::bigint<1>(3)); + polynomial nu3_poly{nu3}; + libfqfft::_polynomial_multiplication( + W_zeta_part[3], b_sub_bzeta, nu3_poly); + + // -c_zeta as constant term polynomial + polynomial c_zeta_poly_neg; + libfqfft::_polynomial_multiplication( + c_zeta_poly_neg, c_zeta_poly, round_zero_out.neg_one_poly); + // (c(x) - c_zeta) + polynomial c_sub_czeta; + libfqfft::_polynomial_addition( + c_sub_czeta, round_one_out.W_polys_blinded[c], c_zeta_poly_neg); + // (c(x) - c_zeta) * nu^4 + Field nu4 = libff::power(nu, libff::bigint<1>(4)); + polynomial nu4_poly{nu4}; + libfqfft::_polynomial_multiplication( + W_zeta_part[4], c_sub_czeta, nu4_poly); + + // -S_0_zeta as constant term polynomial + polynomial S_0_zeta_poly_neg{-round_four_out.S_0_zeta}; + // libfqfft::_polynomial_multiplication(S_0_zeta_poly_neg, + // S_0_zeta_poly, round_zero_out.neg_one_poly); + // (S0(x) - S_0_zeta) + polynomial S0_sub_szeta; + libfqfft::_polynomial_addition( + S0_sub_szeta, srs.S_polys[0], S_0_zeta_poly_neg); + // (S0(x) - S_0_zeta) * nu^5 + Field nu5 = libff::power(nu, libff::bigint<1>(5)); + polynomial nu5_poly{nu5}; + libfqfft::_polynomial_multiplication( + W_zeta_part[5], S0_sub_szeta, nu5_poly); + + // -S_1_zeta as constant term polynomial + polynomial S_1_zeta_poly_neg{-round_four_out.S_1_zeta}; + // libfqfft::_polynomial_multiplication(S_1_zeta_poly_neg, + // S_1_zeta_poly, neg_one_poly); + // (S1(x) - S_1_zeta) + polynomial S1_sub_szeta; + libfqfft::_polynomial_addition( + S1_sub_szeta, srs.S_polys[1], S_1_zeta_poly_neg); + // (S1(x) - S_1_zeta) * nu^6 + Field nu6 = libff::power(nu, libff::bigint<1>(6)); + polynomial nu6_poly{nu6}; + libfqfft::_polynomial_multiplication( + W_zeta_part[6], S1_sub_szeta, nu6_poly); + + // compute full zeta polynomial W_zeta = \sum W_zeta_part[i] + int nzeta = 7; + polynomial W_zeta(round_zero_out.null_poly); + for (int i = 0; i < nzeta; ++i) { + libfqfft::_polynomial_addition(W_zeta, W_zeta, W_zeta_part[i]); + } + + // compute 1/(X-zeta) * W_zeta + polynomial x_sub_zeta_poly{-zeta, Field(1)}; + libfqfft::_polynomial_division(W_zeta, remainder, W_zeta, x_sub_zeta_poly); + assert(libfqfft::_is_zero(remainder)); + + // Compute opening proof: + // W_zeta_omega = z(X) - z(zeta*srs.omega_roots) / X - + // (zeta*srs.omega_roots) + polynomial W_zeta_omega{round_zero_out.null_poly}; + + // -z(zeta*srs.omega_roots) + polynomial z_poly_xomega_zeta_neg{ + -round_four_out.z_poly_xomega_zeta}; + // z(X) - z(zeta*srs.omega_roots) + libfqfft::_polynomial_addition( + W_zeta_omega, round_two_out.z_poly, z_poly_xomega_zeta_neg); + // -zeta*srs.omega_roots; srs.omega_roots[base][1] = + // srs.omega_roots_base + polynomial x_sub_zeta_omega_roots{ + -(zeta * srs.omega_roots[base][1]), Field(1)}; + + // z(X) - z(zeta*srs.omega_roots) / X - + // (zeta*srs.omega_roots) + libfqfft::_polynomial_division( + W_zeta_omega, remainder, W_zeta_omega, x_sub_zeta_omega_roots); + assert(libfqfft::_is_zero(remainder)); + + // TODO: make a separate unit test for W_zeta, W_zeta_omega + // assert(W_zeta == example.W_zeta); + // assert(W_zeta_omega == example.W_zeta_omega); + + // Evaluate polynomials W_zeta and W_zeta_omega at the seceret + // input. + W_zeta_at_secret = + plonk_evaluate_poly_at_secret_G1(srs.secret_powers_g1, W_zeta); + W_zeta_omega_at_secret = plonk_evaluate_poly_at_secret_G1( + srs.secret_powers_g1, W_zeta_omega); + + // Add outputs from Round 5 to the hash buffer. + hasher.add_element(r_zeta); + hasher.add_element(W_zeta_at_secret); + hasher.add_element(W_zeta_omega_at_secret); + + round_five_out_t round_five_out( + std::move(r_zeta), + std::move(W_zeta_at_secret), + std::move(W_zeta_omega_at_secret)); + + return round_five_out; +} + +template +plonk_proof::plonk_proof( + std::vector> &W_polys_blinded_at_secret_g1, + libff::G1 &z_poly_at_secret_g1, + std::vector> &t_poly_at_secret_g1, + Field &a_zeta, + Field &b_zeta, + Field &c_zeta, + Field &S_0_zeta, + Field &S_1_zeta, + Field &z_poly_xomega_zeta, + libff::G1 &W_zeta_at_secret, + libff::G1 &W_zeta_omega_at_secret, + Field &r_zeta) + : W_polys_blinded_at_secret_g1(W_polys_blinded_at_secret_g1) + , z_poly_at_secret_g1(z_poly_at_secret_g1) + , t_poly_at_secret_g1(t_poly_at_secret_g1) + , a_zeta(a_zeta) + , b_zeta(b_zeta) + , c_zeta(c_zeta) + , S_0_zeta(S_0_zeta) + , S_1_zeta(S_1_zeta) + , z_poly_xomega_zeta(z_poly_xomega_zeta) + , W_zeta_at_secret(W_zeta_at_secret) + , W_zeta_omega_at_secret(W_zeta_omega_at_secret) + , r_zeta(r_zeta) +{ +} + +template +plonk_proof plonk_prover::compute_proof( + const srs &srs, + const std::vector &witness, + const std::vector> &blind_scalars, + transcript_hasher &hasher) +{ + std::shared_ptr>> domain = + libfqfft::get_evaluation_domain>(srs.num_gates); + + // Prover Round 0 (initialization) + printf("[%s:%d] Prover Round 0...\n", __FILE__, __LINE__); + round_zero_out_t round_zero_out = plonk_prover::round_zero(srs); + + // Prover Round 1 + printf("[%s:%d] Prover Round 1...\n", __FILE__, __LINE__); + round_one_out_t round_one_out = plonk_prover::round_one( + round_zero_out, blind_scalars, witness, srs, domain, hasher); + + printf("[%s:%d] Prover Round 2...\n", __FILE__, __LINE__); + // - beta: permutation challenges - hashes of transcript of round + // 1 (\attention the original protocl appends a 0, while we don't + // append anyhting to avoid making a copy of the buffer) + const libff::Fr beta = hasher.get_hash(); + // - gamma: permutation challenges - hashes of transcript of round + // 1 with 1 appended + hasher.add_element(libff::Fr::one()); + const libff::Fr gamma = hasher.get_hash(); + + round_two_out_t round_two_out = plonk_prover::round_two( + beta, + gamma, + round_zero_out, + blind_scalars, + witness, + srs, + domain, + hasher); + + printf("[%s:%d] Prover Round 3...\n", __FILE__, __LINE__); + // - alpha: quotient challenge - hash of transcript of rounds 1,2 + const libff::Fr alpha = hasher.get_hash(); + round_three_out_t round_three_out = plonk_prover::round_three( + alpha, + beta, + gamma, + round_zero_out, + round_one_out, + round_two_out, + witness, + srs, + hasher); + + printf("[%s:%d] Prover Round 4...\n", __FILE__, __LINE__); + // - zeta: evaluation challenge - hash of transcriptof rounds 1,2,3 + const libff::Fr zeta = hasher.get_hash(); + round_four_out_t round_four_out = plonk_prover::round_four( + zeta, round_one_out, round_three_out, srs, hasher); + + printf("[%s:%d] Prover Round 5...\n", __FILE__, __LINE__); + // - nu: opening challenge -- hash of transcript (denoted by v in + // [GWC19]) + const libff::Fr nu = hasher.get_hash(); + round_five_out_t round_five_out = plonk_prover::round_five( + alpha, + beta, + gamma, + zeta, + nu, + round_zero_out, + round_one_out, + round_two_out, + round_three_out, + round_four_out, + srs, + hasher); + + // u: multipoint evaluation challenge -- hash of transcript from + // rounds 1,2,3,4,5 + const libff::Fr u = hasher.get_hash(); + // get_hash may update the internal state of the + // transcript_hasher, depending on the implementation, therefore + // the prover and verifier must make exactly the same calls to + // both add_element and get_hash. that's why here we are computing + // u even if we are not using it. + libff::UNUSED(u); + + // construct proof + plonk_proof proof( + round_one_out.W_polys_blinded_at_secret_g1, + round_two_out.z_poly_at_secret_g1, + round_three_out.t_poly_at_secret_g1, + round_four_out.a_zeta, + round_four_out.b_zeta, + round_four_out.c_zeta, + round_four_out.S_0_zeta, + round_four_out.S_1_zeta, + round_four_out.z_poly_xomega_zeta, + round_five_out.W_zeta_at_secret, + round_five_out.W_zeta_omega_at_secret, + round_five_out.r_zeta); + + // return proof + return proof; +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_PROVER_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/srs.hpp b/libsnark/zk_proof_systems/plonk/srs.hpp new file mode 100644 index 000000000..1bfdbfe54 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/srs.hpp @@ -0,0 +1,209 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_HPP_ + +/// Declaration of SRS interfaces for ppzkSNARK proof system Plonk. This +/// includes: +/// +/// - class usrs +/// - class srs +/// - class for proving key (TODO: not implemented) +/// - class for verification key (TODO: not implemented) +/// - class for key pair (proving key & verification key) (TODO: not +/// implemented) +/// +/// Reference: +/// - \[GWC19]: +/// Title: "Plonk: Permutations over lagrange-bases for oecumenical +/// noninteractive arguments of knowledge", Ariel Gabizon, Zachary +/// J. Williamson, and Oana Ciobotaru, Cryptology ePrint Archive, +/// Report 2019/953, 2019, + +#include "libsnark/common/data_structures/polynomial.hpp" +#include "libsnark/zk_proof_systems/plonk/utils.hpp" + +namespace libsnark +{ + +/// +/// A note on the distinction between an srs and a universal srs. +/// +/// A universal srs (usrs) is composed *only* of monomials i.e. encoded +/// powers of the secret value in the group generator. Therefore a usrs +/// is independent of any particular circuit. +/// +/// The (plain) srs is a specialization of the usrs for one particular +/// circuit and is derived from the usrs e.g. as +/// +/// usrs = +/// srs = (proving_key, verificataion_key) = derive(usrs, circuit_description) +/// + +/// Universal srs (usrs). Contains secret encoded monomials with some +/// maximum degree and is so independent of any particular circuit. +template class usrs +{ +public: + /// Array of powers of secret \secret, encoded in G1: + /// [1]_1, [\secret]_1, [\secret^2]_1, ..., [\secret^{n+2}]_1 + std::vector> secret_powers_g1; + + /// Array of powers of secret \secret, encoded in G2: + /// [1]_2, [\secret]_2 + std::vector> secret_powers_g2; + + usrs( + std::vector> &&secret_powers_g1, + std::vector> &&secret_powers_g2); +}; + +/// Compute a universal srs (usrs). It is composed *only* of encoded +/// powers of the secret value in the group generator. Therefore a usrs +/// is independent of any particular circuit. +/// +/// \note only for debug +template +usrs plonk_usrs_derive_from_secret( + const libff::Fr &secret, const size_t max_degree); + +/// Plain (i.e. non-universal srs). Contains secret encoded monomials +/// with maximum degree equal to the number of gates of the analyzed +/// circuit + 2, plus circuit description. Dependent on the circuit. +template class srs +{ +public: + using Field = libff::Fr; + + /// number of gates in the analyzed circuit, denoted by "n" in + /// [GWC19] + size_t num_gates; + + /// number of selector polynomials (q-polynomials) (= 5 in the + /// vanilla Plonk proposal [GWC19]) + size_t num_qpolys; + + /// Vector of indices of wires corresponding to public inputs (PI) + std::vector PI_wire_indices; + + /// Circuit selector polynomials (Q-polynomials) + std::vector> Q_polys; + + /// Permutation polynomials S_sigma_1, S_sigma_2, S_sigma_2 (see + /// [GWC19], Sect. 8.1) + std::vector> S_polys; + + /// omega[0] are the n roots of unity, omega[1] are omega[0]*k1, + /// omega[2] are omega[0]*k2 + std::vector> omega_roots; + + /// H_prime contains the generators of H, k1 H and k2 H in one place + /// ie. omega, omega_k1 and omega_k2 + std::vector H_prime; + + /// H_prime permuted according to the wire permutation + std::vector H_prime_permute; + + /// constants for H, k1 H, k2 H + libff::Fr k1; + libff::Fr k2; + + /// Array of powers of secret \alpha, encoded in G1: [1]_1, + /// [\alpha]_1, [\alpha^2]_1, ..., [\alpha^{n+2}]_1 + std::vector> secret_powers_g1; + + /// Array of powers of secret \alpha, encoded in G2: [1]_2, + /// [\alpha]_2 + std::vector> secret_powers_g2; + + /// the 0-th polynomial of the Lagrange basis + polynomial L_basis_zero; + + srs(const size_t &num_gates, + const size_t &num_qpolys, + const std::vector &PI_wire_indices, + const std::vector> &Q_polys, + const std::vector> &S_polys, + const std::vector> &omega_roots, + const std::vector &H_prime, + const std::vector &H_prime_permute, + const libff::Fr &k1, + const libff::Fr &k2, + std::vector> &&secret_powers_g1, + std::vector> &&secret_powers_g2, + const polynomial &L_basis_zero); +}; + +/// Derive the (plain) SRS from the USRS, the gates matrix, the wire permutation +/// and the list of public input (PI) indices. The (plain) SRS is a +/// specialization of the USRS for one particular circuit +template +srs plonk_srs_derive_from_usrs_custom_PI_indices( + const usrs &usrs, + const std::vector>> gates_matrix, + const std::vector wire_permutation, + const std::vector PI_wire_indices); + +/// A wrapper for plonk_srs_derive_from_usrs_custom_PI_indices that +/// assumes that the PI indices are located in the top +/// num_public_inputs rows of the gates matrix. num_public_inputs is +/// the number of public inputs. +template +srs plonk_srs_derive_from_usrs( + const usrs &usrs, + const std::vector>> gates_matrix, + const std::vector wire_permutation, + const size_t num_public_inputs); + +/// A proving key for Plonk +template class plonk_proving_key +{ +public: + /// Array of powers of secret \alpha, encoded in G1: [1]_1, + /// [\alpha]_1, [\alpha^2]_1, ..., [\alpha^{n+2}]_1 + std::vector> secret_powers_g1; + + plonk_proving_key(){}; + plonk_proving_key(std::vector> &&secret_powers_g1) + : secret_powers_g1(std::move(secret_powers_g1)){}; +}; + +/// A verification key for Plonk +template class plonk_verification_key +{ +public: + /// Array of powers of secret \alpha, encoded in G2: [1]_2, + /// [\alpha]_2 + std::vector> secret_powers_g2; + + plonk_verification_key(); + plonk_verification_key(std::vector> &&secret_powers_g2); +}; + +/// A key pair for Plonk, which consists of a proving key and a +/// verification key. +template class plonk_keypair +{ +public: + plonk_proving_key pk; + plonk_verification_key vk; + + plonk_keypair() = default; + plonk_keypair(const plonk_keypair &other) = default; + plonk_keypair( + plonk_proving_key &&pk, plonk_verification_key &&vk); + + plonk_keypair(plonk_keypair &&other) = default; +}; + +} // namespace libsnark + +#include "libsnark/zk_proof_systems/plonk/srs.tcc" + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/srs.tcc b/libsnark/zk_proof_systems/plonk/srs.tcc new file mode 100644 index 000000000..e20df5d74 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/srs.tcc @@ -0,0 +1,275 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_TCC_ + +// Implementation of SRS interfaces for a ppzkSNARK for Plonk. See +// srs.hpp . + +namespace libsnark +{ + +template +usrs::usrs( + std::vector> &&secret_powers_g1, + std::vector> &&secret_powers_g2) + : secret_powers_g1(secret_powers_g1), secret_powers_g2(secret_powers_g2) +{ +} + +template +srs::srs( + const size_t &num_gates, + const size_t &num_qpolys, + const std::vector &PI_wire_indices, + const std::vector> &Q_polys, + const std::vector> &S_polys, + const std::vector> &omega_roots, + const std::vector &H_prime, + const std::vector &H_prime_permute, + const libff::Fr &k1, + const libff::Fr &k2, + std::vector> &&secret_powers_g1, + std::vector> &&secret_powers_g2, + const polynomial &L_basis_zero) + : num_gates(num_gates) + , num_qpolys(num_qpolys) + , PI_wire_indices(PI_wire_indices) + , Q_polys(Q_polys) + , S_polys(S_polys) + , omega_roots(omega_roots) + , H_prime(H_prime) + , H_prime_permute(H_prime_permute) + , k1(k1) + , k2(k2) + , secret_powers_g1(secret_powers_g1) + , secret_powers_g2(secret_powers_g2) + , L_basis_zero(L_basis_zero) +{ +} + +template +plonk_verification_key::plonk_verification_key( + std::vector> &&secret_powers_g2) + : secret_powers_g2(std::move(secret_powers_g2)){}; + +template +plonk_keypair::plonk_keypair( + plonk_proving_key &&pk, plonk_verification_key &&vk) + : pk(std::move(pk)), vk(std::move(vk)) +{ +} + +template +usrs plonk_usrs_derive_from_secret( + const libff::Fr &secret, const size_t max_degree) +{ + // Compute powers of secret times G1: 1*G1, secret^1*G1, secret^2*G1, ... + const libff::bigint::num_limbs> secret_bigint = + secret.as_bigint(); + const size_t window_size = std::max( + libff::wnaf_opt_window_size>(secret_bigint.num_bits()), + 1ul); + const std::vector naf = + libff::find_wnaf::num_limbs>(window_size, secret_bigint); + + std::vector> secret_powers_g1; + secret_powers_g1.reserve(max_degree); + libff::G1 secret_i_g1 = libff::G1::one(); + secret_powers_g1.push_back(secret_i_g1); + for (size_t i = 1; i < max_degree; ++i) { + // secret^i * G1 + secret_i_g1 = libff::fixed_window_wnaf_exp>( + window_size, secret_i_g1, naf); + secret_powers_g1.push_back(secret_i_g1); + } + + // Compute powers of secret times G2: 1*G2, secret^1*G2. + // Note: in Plonk we *always* have 2 encoded elemnts in G2 + std::vector> secret_powers_g2; + secret_powers_g2.reserve(2); + // secret^0 * G2 = G2 + libff::G2 secret_0_g2 = libff::G2::one(); + secret_powers_g2.push_back(secret_0_g2); + // secret^1 * G2 + libff::G2 secret_1_g2 = secret * libff::G2::one(); + secret_powers_g2.push_back(secret_1_g2); + + return usrs(std::move(secret_powers_g1), std::move(secret_powers_g2)); +} + +template +srs plonk_srs_derive_from_usrs_custom_PI_indices( + const usrs &usrs, + const std::vector>> gates_matrix, + const std::vector wire_permutation, + const std::vector PI_wire_indices) +{ + using Field = libff::Fr; + const std::vector> gates_matrix_transpose = + plonk_gates_matrix_transpose(gates_matrix); + + // The number of gates is equal to the number of columns in the + // transposed gates matrix. + size_t num_gates = gates_matrix_transpose[0].size(); + // Ensure that num_gates is not 0. + assert(num_gates > 0); + // Ensure num_gates is power of 2. + assert((num_gates & (num_gates - 1)) == 0); + + // The number of Q-polynomials (aka selector polynomials) is equal + // to the number of rows in the transposed gates matrix. + size_t num_qpolys = gates_matrix_transpose.size(); + + // The constraints q_L, q_R, q_O, q_M, q_C and the + // witness w_L, w_R, w_O are represented as polynomials in the roots of + // unity e.g. f_{q_L}(omega_i) = q_L[i], 0\le{i}<8. + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(num_gates); + + // Compute the selector polynomials (q-polynomials) from the + // transposed gates matrix over the Lagrange basis q_poly = \sum_i + // q[i] * L[i] where q[i] is a coefficient (a scalar Field + // element) and L[i] is a polynomial with Field coefficients. + std::vector> Q_polys = + plonk_compute_selector_polynomials( + num_gates, num_qpolys, gates_matrix_transpose, domain); + + // An explanation of the constants k1,k2 from [GWC19], Section 8, + // page 26 : + // + // "We explicitly define the multiplicative subgroup H as + // containing the n-th roots of unity in F_p , where w (omega) is + // a primitive n-th root of unity and a generator of H i.e: H = + // {1, w, ... , w^{n-1}}. We assume that the number of gates in a + // circuit is no more than n. + // + // For the moment k1,k2 are fixed (see below) to the test vector + // values from the plonk_example class for debug purposes. Note + // that these test values are specific to the BLS12-381 curve and + // hence they may ONLY satisfy the requirements for + // BLS12-381. TODO: choose k1,k2 according to the requirements in + // [GWC19] + libff::Fr k1 = + Field("706987411474581393682955260879121390206111740035659671471" + "3673571023200548519"); + libff::Fr k2 = libff::power(k1, libff::bigint<1>(2)); + + // From [GWC19], Section 8, page 26: + // + // "We also include an optimisation suggested by Vitalik Buterin, + // to define the identity permutations through degree-1 + // polynomials. The identity permutations must map each wire value + // to a unique element \in F. This can be done by defining + // S_ID1(X) = X, S_ID2 (X) = k1 X, S_ID3(X) = k2 X [see below for + // more on S_ID1, S_ID2, S_ID3], where k1, k2 are quadratic + // non-residues \in F. This effectively maps each wire value to a + // root of unity in H, with right and output wires having an + // additional multiplicative factor of k1, k2 applied + // respectively. By representing the identity permutation via + // degree-1 polynomials, their evaluations can be directly + // computed by the verifier. This reduces the size of the proof by + // 1 F element, as well as reducing the number of + // Fast-Fourier-Transforms required by the prover." + // + // Further in Sect. 8.1 [GWC19]: + // + // "S_ID1(X) = X, S_ID2(X) = k 1 X, S ID3 (X) = k 2 X: the + // identity permutation applied to a, b, c [the wire polynomials, + // see Round 1, p.27 [GWC19]]. k1, k2 \in F are chosen such that + // H, k1 H, k2 H are distinct cosets of H in F*, and thus consist + // of 3n distinct elements. (For example, when w (omega) is a + // quadratic residue in F, take k1 to be any quadratic + // non-residue, and k2 to be a quadratic non-residue not contained + // in k1 H.)" + + // omega[0] are the n roots of unity; omega[1] are omega[0]*k1; + // omega[2] are omega[0]*k2 + std::vector> omega_roots; + plonk_compute_roots_of_unity_omega(num_gates, k1, k2, omega_roots); + + // H_prime contains the generators of H, k1 H and k2 H in one + // place ie. omega_roots, omega_roots_k1 and omega_roots_k2 + std::vector H_prime; + plonk_compute_cosets_H_k1H_k2H(num_gates, k1, k2, H_prime); + + // TODO: write unit test for plonk_compute_cosets_H_k1H_k2H + // assert(H_prime[i] == example.H_prime[i]); + + // Permute H_prime according to the wire permutation. + std::vector H_prime_permute = + plonk_permute_subgroup_H(H_prime, wire_permutation, num_gates); + + // TODO: write unit test for plonk_permute_subgroup_H + // assert(H_prime_permute[i] == example.H_prime_permute[i]); + + // Compute the permutation polynomials S_sigma_1, S_sigma_2, + // S_sigma_3 (see [GWC19], Sect. 8.1) (our indexing starts from 0). + std::vector> S_polys = + plonk_compute_permutation_polynomials( + H_prime_permute, num_gates, domain); + + // secret^i * G1 + std::vector> secret_powers_g1( + usrs.secret_powers_g1.begin(), + usrs.secret_powers_g1.begin() + num_gates + 3); + + for (size_t i = 0; i < (num_gates + 3); ++i) { + secret_powers_g1.push_back(usrs.secret_powers_g1[i]); + } + // secret^i * G2 + std::vector> secret_powers_g2; + secret_powers_g2.reserve(2); + for (size_t i = 0; i < 2; ++i) { + secret_powers_g2.push_back(usrs.secret_powers_g2[i]); + } + + // Compute 0-th Lagrange basis vector via inverse FFT. + polynomial> L_basis_zero(num_gates, libff::Fr(0)); + L_basis_zero[0] = libff::Fr(1); + domain->iFFT(L_basis_zero); + + srs srs( + std::move(num_gates), + std::move(num_qpolys), + std::move(PI_wire_indices), + std::move(Q_polys), + std::move(S_polys), + std::move(omega_roots), + std::move(H_prime), + std::move(H_prime_permute), + std::move(k1), + std::move(k2), + std::move(secret_powers_g1), + std::move(secret_powers_g2), + std::move(L_basis_zero)); + + return srs; +} + +template +srs plonk_srs_derive_from_usrs( + const usrs &usrs, + const std::vector>> gates_matrix, + const std::vector wire_permutation, + const size_t num_public_inputs) +{ + std::vector PI_wire_indices; + // store the indices of the PIs + for (size_t i = 0; i < num_public_inputs; ++i) { + PI_wire_indices.push_back(i); + } + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, gates_matrix, wire_permutation, PI_wire_indices); + return srs; +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_SRS_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.cpp b/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.cpp new file mode 100644 index 000000000..1af67ffc5 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.cpp @@ -0,0 +1,129 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_CPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_CPP_ + +#include "libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.hpp" + +// Implementation of the transcript hasher interface specific to the BLS12-381 +// curve. See bls12_381_test_vector_transcript_hasher.hpp +namespace libsnark +{ + +bls12_381_test_vector_transcript_hasher:: + bls12_381_test_vector_transcript_hasher() + : length_array({288, 320, 416, 704, 896, 1120}) + , challenge_array({"beta", "gamma", "alpha", "zeta", "nu", "u"}) +{ + assert(length_array.size() == challenge_array.size()); + + plonk_example example; + + // initialize to empty vector + buffer.clear(); + // test array containing the expected hash values of the communication + // transcript i.e. the communication challenges (in this order): beta, + // gamma, alpha, zeta, nu, u WARNING! specific to curve BLS12-381 + hash_values = { + example.beta, + example.gamma, + example.alpha, + example.zeta, + example.nu, + example.u, + }; +} + +void bls12_381_test_vector_transcript_hasher::reset() { this->buffer.clear(); } + +void bls12_381_test_vector_transcript_hasher::add_element( + const libff::Fr &element) +{ + // convert the Fr element into a string + std::string str; + { + std::ostringstream ss; + libff::field_write( + element, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +void bls12_381_test_vector_transcript_hasher::add_element( + const libff::G1 &element) +{ + libff::G1 element_aff(element); + element_aff.to_affine_coordinates(); + + // convert the affine coordinates of the curve point into a string + std::string str; + { + std::ostringstream ss; + libff::group_write< + libff::encoding_binary, + libff::form_plain, + libff::compression_off>(element_aff, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +void bls12_381_test_vector_transcript_hasher::add_element( + const libff::G2 &element) +{ + libff::G2 element_aff(element); + element_aff.to_affine_coordinates(); + + // convert the affine coordinates of the curve point into a string + std::string str; + { + std::ostringstream ss; + libff::group_write< + libff::encoding_binary, + libff::form_plain, + libff::compression_off>(element_aff, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +libff::Fr bls12_381_test_vector_transcript_hasher:: + get_hash() +{ + const size_t buffer_len = this->buffer.size(); + + // find the matching index and return the corresponding challenge. + for (size_t i = 0; i < length_array.size(); ++i) { + if (buffer_len == length_array[i]) { + printf( + "[%s:%d] buffer_len %d: %s\n", + __FILE__, + __LINE__, + (int)buffer_len, + challenge_array[i].c_str()); + + return hash_values[i]; + } + } + + // If we are here, then the hasher buffer has invalid length so + // throw an exception + throw std::logic_error("Error: invalid length of transcript hasher buffer"); +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_CPP_ diff --git a/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.hpp b/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.hpp new file mode 100644 index 000000000..baa34d32b --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.hpp @@ -0,0 +1,122 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_HPP_ + +#include "libsnark/zk_proof_systems/plonk/utils.hpp" + +#include + +namespace libsnark +{ + +/// Implementation of the transcript hasher interface (see +/// transcript_hasher.hpp), returning hard-coded test vector values +/// for the plonk proof system on a specific circuit, using the +/// BLS12-381 curve. Also performs a simple check on the transcript to +/// be hashed to verify correct operation of the plonk prover and +/// verifier. +/// +/// the hasher works in the Prover as follows: +/// +/// round 1 +/// +/// add_element(buffer, a_eval_exp) +/// add_element(buffer, b_eval_exp) +/// add_element(buffer, c_eval_exp) +/// // buffer = first_output +/// +/// round 2 +/// +/// beta = hash(str(buffer) + "0") +/// gamma = hash(str(buffer) + "1") +/// +/// acc_eval = evaluate_in_exponent(CRS, accumulator_poly.to_coeffs()) +/// +/// add_element(buffer, acc_eval) +/// // buffer = first_output + second_output +/// +/// round 3 +/// +/// alpha = hash(str(buffer)) +/// +/// add_element(buffer, t_lo_eval_exp) +/// add_element(buffer, t_mid_eval_exp) +/// add_element(buffer, t_hi_eval_exp) +/// // buffer = first_output + second_output + third_output +/// +/// round 4 +/// +/// zeta = hash(str(buffer)) +/// +/// add_element(buffer, a_zeta) +/// add_element(buffer, b_zeta) +/// add_element(buffer, c_zeta) +/// add_element(buffer, S_0_zeta) +/// add_element(buffer, S_1_zeta) +/// add_element(buffer, accumulator_shift_zeta) +/// add_element(buffer, t_zeta) +/// add_element(buffer, r_zeta) +/// // buffer = first_output + second_output + third_output + fourth_output +/// +/// round 5 +/// +/// nu = hash(str(buffer)) +/// +/// 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()) +/// +/// add_element(buffer, W_zeta_eval_exp) +/// add_element(buffer, W_zeta_omega_eval_exp) +/// +/// // buffer = first_output + second_output + third_output + fourth_output +/// // + fifth_output +/// +/// u = hash(str(buffer)) +/// +class bls12_381_test_vector_transcript_hasher +{ +private: + // buffer accumulating data to be hashed + std::vector buffer; + // array containing the hash values of the communication transcript + // i.e. the six challenges (in this order): beta, gamma, alpha, zeta, nu, u + std::array, 6> hash_values; + // vector of valid lengths (\attention specific to BLS12-381) + const std::array length_array; + // map the index length=0,1...5 to the challenge string=beta, + // gamma, ...; used to print explicitly the challenge string for debug + const std::array challenge_array; + +public: + bls12_381_test_vector_transcript_hasher(); + + // add an Fr element to the transcript buffer for hashing + void add_element(const libff::Fr &element); + // add the coordinates of a G1 curve point to the transcript buffer for + // hashing + void add_element(const libff::G1 &element); + // add the coordinates of a G2 curve point to the transcript buffer for + // hashing + void add_element(const libff::G2 &element); + + // dummy implementation of get_hash that directly returns the + // expected hard-coded hashes for the purposes of unit testing TODO + // to be replaced by a call to a proper hash function e.g. SHA2, + // BLAKE, etc. + libff::Fr get_hash(); + + // clear the buffer (for now only for testing) + void reset(); +}; + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_BLS12_381_TEST_VECTOR_TRANSCRIPT_HASHER_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp new file mode 100644 index 000000000..328165719 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp @@ -0,0 +1,56 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_HPP_ + +#include "libsnark/zk_proof_systems/plonk/utils.hpp" + +#include + +namespace libsnark +{ + +/// Implementation of a dummy transcript hasher interface (see +/// transcript_hasher.hpp). It returns the number of bytes in the hash +/// buffer as an Fr element. Specialized over the curve field. See +/// also class bls12_381_test_vector_transcript_hasher, which is +/// specific to the BLS12_381 curve. +template class dummy_transcript_hasher +{ +private: + // buffer accumulating data to be hashed + std::vector buffer; + +public: + dummy_transcript_hasher(); + + // Add an Fr element to the transcript buffer for hashing. + void add_element(const libff::Fr &element); + // Add the coordinates of a G1 curve point to the transcript buffer for + // hashing. + void add_element(const libff::G1 &element); + // Add the coordinates of a G2 curve point to the transcript buffer for + // hashing. + void add_element(const libff::G2 &element); + + // Dummy implementation of get_hash that simply returns the number + // bytes in the buffer as an Fr value for the purposes of unit + // testing. TODO: to be replaced by a call to a proper hash + // function e.g. SHA2, BLAKE, etc. + libff::Fr get_hash(); + + // clear the buffer (for now only for testing) + void reset(); +}; + +} // namespace libsnark + +#include "libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc" + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_DUMMY_TRANSCRIPT_HASHER_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc new file mode 100644 index 000000000..85542a259 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc @@ -0,0 +1,94 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_TCC_ + +#include "libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp" + +// Implementation of the dummy transcript hasher interface. See +// dummy_transcript_hasher.hpp. +namespace libsnark +{ + +template dummy_transcript_hasher::dummy_transcript_hasher() +{ +} + +template void dummy_transcript_hasher::reset() +{ + this->buffer.clear(); +} + +template +void dummy_transcript_hasher::add_element(const libff::Fr &element) +{ + // convert the Fr element into a string + std::string str; + { + std::ostringstream ss; + libff::field_write( + element, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +template +void dummy_transcript_hasher::add_element(const libff::G1 &element) +{ + libff::G1 element_aff(element); + element_aff.to_affine_coordinates(); + + // convert the affine coordinates of the curve point into a string + std::string str; + { + std::ostringstream ss; + libff::group_write< + libff::encoding_binary, + libff::form_plain, + libff::compression_off>(element_aff, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +template +void dummy_transcript_hasher::add_element(const libff::G2 &element) +{ + libff::G2 element_aff(element); + element_aff.to_affine_coordinates(); + + // convert the affine coordinates of the curve point into a string + std::string str; + { + std::ostringstream ss; + libff::group_write< + libff::encoding_binary, + libff::form_plain, + libff::compression_off>(element_aff, ss); + str = ss.str(); + } + // copy the string as a sequence of uint8_t elements at the end of + // the buffer + std::copy(str.begin(), str.end(), std::back_inserter(this->buffer)); +} + +template libff::Fr dummy_transcript_hasher::get_hash() +{ + libff::Fr buffer_len = libff::Fr(this->buffer.size()); + return buffer_len; +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/tests/example.cpp b/libsnark/zk_proof_systems/plonk/tests/example.cpp new file mode 100644 index 000000000..2677ee524 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/example.cpp @@ -0,0 +1,941 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_TCC_ + +// Instantiation of the test vector values from the Python implementation +// of the Plonk protocol. \see example.hpp . +#include "libsnark/zk_proof_systems/plonk/tests/example.hpp" + +namespace libsnark +{ + +// the example class is defined specifically for the BLS12-381 +// curve. This function checks that the template parameter ppT indeed +// corresponds to the bls12_381 curev i.e. is equal to +// libff::bls12_381_pp +template void plonk_exception_assert_curve_bls12_381() +{ + const bool b_bls12_381 = std::is_same::value; + if (!b_bls12_381) { + throw std::domain_error("Curve is not BLS12-381"); + } +} + +plonk_example::plonk_example() +{ + using Field = libff::Fr; + using BaseField = libff::Fq; + + // Circuit data + + // number of gates / constraints. we have 6 gates for the example + // circuit + 2 dummy gates to make it a power of 2 (for the fft) + this->num_gates = 8; + + // number of q-polynomials + this->num_qpolys = 5; + + // hard-coded gates matrix for the example circuit P(x) = x**3 + x + // + 5 = 3 Each column is a q-vector + this->gates_matrix = { + // q_L q_R q_M q_O q_C + {Field(0), Field(0), Field(1), -Field("1"), Field(0)}, // mul + {Field(0), Field(0), Field(1), -Field("1"), Field(0)}, // mul + {Field(1), Field(1), Field(0), -Field("1"), Field(0)}, // add + {Field(0), Field(1), Field(0), Field(0), -Field("5")}, // con5 + {Field(0), Field(1), Field(0), Field(0), Field(0)}, // PI + {Field(1), Field(1), Field(0), -Field("1"), Field(0)}, // add + {Field(0), Field(0), Field(0), Field(0), Field(0)}, // dummy + {Field(0), Field(0), Field(0), Field(0), Field(0)}, // dummy + }; + + this->gates_matrix_transpose = { + // mul mul add con5 PI add dum dum + {Field(0), + Field(0), + Field(1), + Field(0), + Field(0), + Field(1), + Field(0), + Field(0)}, // q_L + {Field(0), + Field(0), + Field(1), + Field(1), + Field(1), + Field(1), + Field(0), + Field(0)}, // q_R + {Field(1), + Field(1), + Field(0), + Field(0), + Field(0), + Field(0), + Field(0), + Field(0)}, // q_M + {-Field("1"), + -Field("1"), + -Field("1"), + Field(0), + Field(0), + -Field("1"), + Field(0), + Field(0)}, // q_O + {Field(0), + Field(0), + Field(0), + -Field("5"), + Field(0), + Field(0), + Field(0), + Field(0)}, // q_C + }; + + // witness values + // w_L = a = [ 3, 9, 27, 1, 1, 30, 0, 0] + // w_R = b = [ 3, 3, 3, 5, 35, 5, 0, 0] + // w_O = c = [ 9, 27, 30, 5, 35, 35, 0, 0] + // W = w_L + w_R + w_O + this->witness = { + 3, 9, 27, 1, 1, 30, 0, 0, // w_L + 3, 3, 3, 5, 35, 5, 0, 0, // w_R + 9, 27, 30, 5, 35, 35, 0, 0 // w_O + }; + + // wire permutation (TODO: add function plonk_compute_permutation()) + this->wire_permutation = {9, 17, 18, 5, 4, 19, 7, 8, 10, 11, 1, 14, + 21, 20, 15, 16, 2, 3, 6, 12, 22, 13, 23, 24}; + + // public input (PI) + this->PI_value = Field(35); + + // index of the row of the PI in the non-transposed gates_matrix + this->PI_gates_matrix_irow = 4; + + // the example has 1 public input with wire index 12 (counting from 0) and + // value 35. it corresponds to the 4-th componnet of the w_R witness vector + // (counting from 0). in other words w_R[4]=35. recall that the full witness + // is = w_L + w_R + w_O (where '+' denotes concatentation). w_L has 8 + // components (since we have 8 gates == 6 "real" + 2 dummy) and the 4-th + // component of w_R is the PI (counting from 0), so it has wire index 8+4=12 + // (counting from 0). + this->PI_wire_indices.push_back(12); + + // n-th root of unity omega in Fq (n=8 is the number of constraints + // in the example). omega is a generator of the multiplicative + // subgroup H. Example (2**32)-th primitive root of unity in the + // base field Fq of bls12-381 i.e. such that omega_base**(2**32) = + // 1. The bls12-381 prime q is such that any power of 2 divides + // (q-1). In particular 2**32|(q-1) and so the 2**32-th root of + // unity exists. + this->omega_base = Field("2367469443165877065961295211566080294796737370150" + "6253797663184111817857449850"); + + // Constants k1,k2 to generate domains on which to evaluate the witness + // polynomials. k can be random, but we fix it for debug to match + // against the test vector values + this->k1 = Field("706987411474581393682955260879121390206111740035659671471" + "3673571023200548519"); + // Similarly, k2 can be random, but we fix it to match the test + // vectors + this->k2 = libff::power(k1, libff::bigint<1>(2)); + + // H_prime contains the generators of H, k1 H and K2 H in one place + // ie. omega, omega_k1 and omega_k2 + this->H_prime = { + Field("1"), + Field("2367469443165877065961295211566080294796737370150625379766318411" + "1817857449850"), + Field("3465144826073652318776269530687742778270252468765361963008"), + Field("8685283084174350996472453922654922162880456818468779543064782192" + "722679779374"), + Field("5243587517512619047944774050818596583769055250052763782260365869" + "9938581184512"), + Field("2876118074346741981983478839252516288972317879902138402494047458" + "8120723734663"), + Field("5243587517512619047598259568211231351891428296983989504433340623" + "1173219221505"), + Field("4375059209095183948297528658553104367481009568205885827953887650" + "7215901405139"), + Field("7069874114745813936829552608791213902061117400356596714713673571" + "023200548519"), + Field("6296993470737855411634321870255436387440388789676314980338138891" + "26570754409"), + Field("2134293230218433692027649469636827607837588105510789904753879922" + "6396168459088"), + Field("1267882965986156059121750719885063580789301664257248207931748295" + "050157094180"), + Field("4536600106038037654261818789939475193562943510017104110788998512" + "8915380635994"), + Field("5180617582805240493828430832116042219894651362156000632456984481" + "0812010430104"), + Field("3109294287294185355917124581181768975931467144541973877506485947" + "3542412725425"), + Field("5116799220914003442032598978830090225690125083627038961467191040" + "4888424090333"), + Field("1838592104783212022667877350171394113611392647210607993375145291" + "4767234401351"), + Field("3787649880178070211080878147767178500088988709179240616011656188" + "3724775274403"), + Field("2013895375313748245055634445236271583185979532535422332242782401" + "9686639989507"), + Field("2345700989319520162686183217527414661504655478998796606881532728" + "3610815221310"), + Field("3404995412729407025276896700647202470157662602842155788885220578" + "5171346783162"), + Field("1455937637334548836863895903051418083680066540873523166248709681" + "6213805910110"), + Field("3229692142198870802889139605582325000583075717517341450017583468" + "0251941195006"), + Field("2897886528193098885258590833291181922264399771053967175378833141" + "6327765963203")}; + + // H_prime permuted according to the wire permutation + this->H_prime_permute = { + Field("7069874114745813936829552608791213902061117400356596714713673571" + "023200548519"), + Field("1838592104783212022667877350171394113611392647210607993375145291" + "4767234401351"), + Field("3787649880178070211080878147767178500088988709179240616011656188" + "3724775274403"), + Field("5243587517512619047944774050818596583769055250052763782260365869" + "9938581184512"), + Field("8685283084174350996472453922654922162880456818468779543064782192" + "722679779374"), + Field("2013895375313748245055634445236271583185979532535422332242782401" + "9686639989507"), + Field("5243587517512619047598259568211231351891428296983989504433340623" + "1173219221505"), + Field("4375059209095183948297528658553104367481009568205885827953887650" + "7215901405139"), + Field("6296993470737855411634321870255436387440388789676314980338138891" + "26570754409"), + Field("2134293230218433692027649469636827607837588105510789904753879922" + "6396168459088"), + Field("1"), + Field("5180617582805240493828430832116042219894651362156000632456984481" + "0812010430104"), + Field("3404995412729407025276896700647202470157662602842155788885220578" + "5171346783162"), + Field("2345700989319520162686183217527414661504655478998796606881532728" + "3610815221310"), + Field("3109294287294185355917124581181768975931467144541973877506485947" + "3542412725425"), + Field("5116799220914003442032598978830090225690125083627038961467191040" + "4888424090333"), + Field("2367469443165877065961295211566080294796737370150625379766318411" + "1817857449850"), + Field("3465144826073652318776269530687742778270252468765361963008"), + Field("2876118074346741981983478839252516288972317879902138402494047458" + "8120723734663"), + Field("1267882965986156059121750719885063580789301664257248207931748295" + "050157094180"), + Field("1455937637334548836863895903051418083680066540873523166248709681" + "6213805910110"), + Field("4536600106038037654261818789939475193562943510017104110788998512" + "8915380635994"), + Field("3229692142198870802889139605582325000583075717517341450017583468" + "0251941195006"), + Field("2897886528193098885258590833291181922264399771053967175378833141" + "6327765963203")}; + + // random hidden element secret (toxic waste). we fix it to a + // constant in order to match against the test vectors + this->secret = Field("13778279493383315901513166932749987230291710199728570" + "152123261818328463629146"); + + this->secret_powers_g1 = { + {BaseField("36854167537133870167810883151830777579616207957825464098945" + "78378688607592378376318836054947676345821548104185464507"), + BaseField("13395065449444764730204713799419212215849338759383496204265" + "43736416511423956333506472724655353366534992391756441569")}, + {BaseField("20749965662226492945615097978198189221739861329213084113581" + "40896113079536003850694012630431408673580733705096617791"), + BaseField("20776169388666778595331629779008748452369857341460416596178" + "70201535410847474163664062006824876754586584291291275540")}, + {BaseField("29908066896405034961181068491184887680953048361228849374589" + "15361027713460409591689021671430152609090653721640806775"), + BaseField("15069105631190064801231642485088185662289730777561049481656" + "05526387811452704182227509354555265743592891256699177447")}, + {BaseField("11623994229753380094658096850046421770851169520142342404026" + "11534721365805950787073304245916047514747507357062412420"), + BaseField("32439913241842824306660774309390158013390581471164196275365" + "28847445203118923037149866561100103780253903700544760551")}, + {BaseField("66595550477645955422317691187591228045792600941665863117655" + "0711534449750755438228569302251439190379809780541605589"), + BaseField("15639754518865509584520548319903391700028182018162323764862" + "89252647203460244570019694049681767056597843416540080200")}, + {BaseField("13997155334738944346453484434520404070385952861192139915240" + "09101772100073848392561767527055222979107182734281429257"), + BaseField("24801967186118020827532424272070678408955817575006204546556" + "73428668098605801043836809939785138234969928618554184939")}, + {BaseField("72198996603356233208233142438023880002035034269949947481094" + "0866633976625729399263772964415588210693897376315032210"), + BaseField("23909713088018334455114179971652675642764792282949811409915" + "37842761274654400117722746265926005170825975983654738210")}, + {BaseField("24946452033393671727656887869287695560429335312866283157804" + "59840863253114375261836717471927002510598950105128289874"), + BaseField("22308947402682432087653424099864122493269262968494186222854" + "0161286780230155004903395236049749437555611923506858957")}, + {BaseField("13692091156184100189040529930284250299924138421820151450177" + "24608932835902017735132442291326610507957006806305596987"), + BaseField("16697997752614730182918384485633392374605603738670095512703" + "85685110329728891027501463328761827882531243313994834792")}, + {BaseField("32324617157201419767841199840089167892367982261722591009915" + "24828275951110441373038297570085682311463031127657083499"), + BaseField("21748585940969090447471146438241363898290310715892941128918" + "97914222472713836544371512541030476560224224399906695672")}, + {BaseField("32945190541547333935734520835897537392840680022512314688362" + "06591341493107913789146528048543749767535356614865447074"), + BaseField( + "10784239041438369956852608259577689512593965753533688873687516562" + "19548634950458729521798081562106307219496322329732")}}; + + // blinding scalars b1, b2, ..., b9. random but fixed to match + // the python test vectors + prover_blind_scalars = { + Field("80633968928703880558063703697897048577551160" + "44327394765020751373651916505604"), + Field("68270264301200565976794531113706823063169483" + "63643792417785314991392447377909"), + Field("20903799562102387073359556962112335230020378" + "309596765284572286455779329747315"), + Field("27445824854335787523979734401573136947589999" + "159092723101543900479804718923773"), + Field("52164479755085410212907573804852358855974754" + "07449078898274648785082871817687"), + Field("48720156268681305476740160454555587712391923" + "971264646500554531948708930944069"), + Field("21318915166515188286980897071631919826831011" + "87444208330829153527689737950718"), + Field("47548532878000795436471885496554996210469829" + "388180983864669623532585348412472"), + Field("25345997198725001609008178533933158854206333" + "20379105447254598708515031311667")}; + + // Hashes of transcript (Fiat-Shamir heuristic) + this->beta = Field("3710899868510394644410941212967766116886736137326022751" + "891187938298987182388"); + this->gamma = Field("110379303840831945879077096653321168432672740458288022" + "49545114995763715746939"); + this->alpha = Field("379799789992747238930717819864848384921111623418803600" + "22719385400306128734648"); + this->zeta = Field("4327197228921839935583364394550235027071910395980312641" + "5018065799136107272465"); + this->nu = Field("275158598338697752421507265080923414294782807831923791651" + "55175653098691426347"); + this->u = Field("1781751143954696684632449211212056577828855388109883650570" + "6049265393896966778"); + + // Prover Round 1 + + // Witness polynomials + this->W_polys = {// W_polys[a] + { + Field("65544843968907738099309675635232457297113190625" + "65954727825457337492322648073"), // t^0 + + Field("32948500085781872641455643871559485192618005894" + "853042154825967834821350910602"), // t^1 + + Field("45881390778235416653057335020812871593791953167" + "194904897994502135810789212158"), // t^2 + + Field("21733506116237836259034000970136844665656294154" + "820722285692118576560701570202"), // t^3 + + Field("65544843968907738099309675635232457297113190625" + "65954727825457337492322648063"), // t^4 + + Field("45705312676907413054326239314722310412178003523" + "796150825755316050920327615864"), // t^5 + + Field("45881390778235416685976210868512568622166513708" + "728461291561900589081727860734"), // t^6 + + Field("44844314713252590040795968599532914049288014275" + "85360378933914937574782272359") // t^7 + }, + // W_polys[a] + { + Field("13108968793781547619861935127046491459422638125" + "131909455650914674984645296135"), // t^0 + + Field("26701543132601498945146394479737583456967318942" + "563611087328104827461434190940"), // t^1 + + Field("32772421984453869048355408507838609029015494238" + "821870097275942011674602504197"), // t^2 + + Field("13599340474087270913269318764079985464155297112" + "973389232502566508274445532606"), // t^3 + + Field("26217937587563095239723870254092982918845276250" + "263818911301829349969290592260"), // t^4 + + Field("25734332042524691531702487408893143141641031409" + "948219651572864520903125521309"), // t^5 + + Field("32772421984453869050954267127393848268097696386" + "837677180978631363248623976453"), // t^6 + + Field("38836534701038919568777280363661219612617457535" + "570055673803781543238157124155") // t^7 + }, + // W_polys[a] + { + Field("19663453190672321429792902690569737189133957187" + "697864183476372012476967944210"), // t^0 + + Field("20106536651950714560943936006683707043835113765" + "212154412313674714935702104191"), // t^1 + + Field("13108968793781547595172778241271718688141717718" + "981742160475365835031441309698"), // t^2 + + Field("18246392504049801299811961511777672791578098930" + "042302001761141999236289949018"), // t^3 + + Field("65544843968907738099309675635232457297113190625" + "65954727825457337492322648065"), // t^4 + + Field("61114009356123806527913480518568834841881410048" + "93593661961261119293373765499"), // t^5 + + Field("13108968793781547644551092012821264230703558531" + "282076750826463514937849282562"), // t^6 + + Field("79715450835132939659004949378677025180891988003" + "79587746567580866473215365792") // t^7 + }}; + // vanishing_poly + // 52435875175126190479447740508185965837690552500527637822603658699938581184512 + //+ 0 t^1 + 0 t^2 + 0 t^3 + 0 t^4 + 0 t^5 + 0 t^6 + 0 t^7 + 1 t^8 + this->zh_poly = { + Field("5243587517512619047944774050818596583769055250052763782260365869" + "9938581184512"), // t^0 + + Field("0"), // t^1 + + Field("0"), // t^2 + + Field("0"), // t^3 + + Field("0"), // t^4 + + Field("0"), // t^5 + + Field("0"), // t^6 + + Field("0"), // t^7 + + Field("1"), // t^8 + }; + // Witness polynomials blinded by b constants + // a_poly = blind_polys[0] * zh_poly + W_polys[0] + this->W_polys_blinded = { + // W_polys_blinded[a] + { + Field("521633331418969076916992549603385292610849231994498001326438" + "01046038456454677"), // t^0 + + Field("248851031929114845856492735017697803348628898505256473898052" + "16461169434404998"), // t^1 + + Field("458813907782354166530573350208128715937919531671949048979945" + "02135810789212158"), // t^2 + + Field("217335061162378362590340009701368446656562941548207222856921" + "18576560701570202"), // t^3 + + Field("655448439689077380993096756352324572971131906256595472782545" + "7337492322648063"), // t^4 + + Field("457053126769074130543262393147223104121780035237961508257553" + "16050920327615864"), // t^5 + + Field("458813907782354166859762108685125686221665137087284612915619" + "00589081727860734"), // t^6 + + Field("448443147132525900407959685995329140492880142758536037893391" + "4937574782272359"), // t^7 + + Field("682702643012005659767945311137068230631694836364379241778531" + "4991392447377909"), // t^8 + + Field("806339689287038805580637036978970485775511604432739476502075" + "1373651916505604") // t^9 + }, + // W_polys_blinded[b] + { + Field("380990191145719505753299412336593203495231914665668241767106" + "72895118507556875"), // t^0 + + Field("579774357049911187178683751762524822694694063296684580275581" + "8371682104443625"), // t^1 + + Field("327724219844538690483554085078386090290154942388218700972759" + "42011674602504197"), // t^2 + + Field("135993404740872709132693187640799854641552971129733892325025" + "66508274445532606"), // t^3 + + Field("262179375875630952397238702540929829188452762502638189113018" + "29349969290592260"), // t^4 + + Field("257343320425246915317024874088931431416410314099482196515728" + "64520903125521309"), // t^5 + + Field("327724219844538690509542671273938482680976963868376771809786" + "31363248623976453"), // t^6 + + Field("388365347010389195687772803636612196126174575355700556738037" + "81543238157124155"), // t^7 + + Field("274458248543357875239797344015731369475899991590927231015439" + "00479804718923773"), // t^8 + + Field("209037995621023870733595569621123352300203783095967652845722" + "86455779329747315") // t^9 + }, + // W_polys_blinded[c] + { + Field("233791720971172064325004827442001153144325857169608555055254" + "98763706618184654"), // t^0 + + Field("148900886764421735396531786261984711582376383577630755140390" + "25929852830286504"), // t^1 + + Field("131089687937815475951727782412717186881417177189817421604753" + "65835031441309698"), // t^2 + + Field("182463925040498012998119615117776727915780989300423020017611" + "41999236289949018"), // t^3 + + Field("655448439689077380993096756352324572971131906256595472782545" + "7337492322648065"), // t^4 + + Field("611140093561238065279134805185688348418814100489359366196126" + "1119293373765499"), // t^5 + + Field("131089687937815476445510920128212642307035585312820767508264" + "63514937849282562"), // t^6 + + Field("797154508351329396590049493786770251808919880037958774656758" + "0866473215365792"), // t^7 + + Field("487201562686813054767401604545555877123919239712646465005545" + "31948708930944069"), // t^8 + + Field("521644797550854102129075738048523588559747540744907889827464" + "8785082871817687") // t^9 + }}; + // blinded witness polynomials evaluate at secret input + this->W_polys_blinded_at_secret_g1 = { + // W_polys_blinded_at_secret_g1[a] (x,y) + {BaseField("14812287891036618330676930721693823329702411523226156376233" + "17748273515137146902630034361568804034167204753099284501"), + BaseField("17964750299387068157283823706566054419626655811998147128959" + "26933741011033298072747963710561309706677692173630366237")}, + // W_polys_blinded_at_secret_g1[b] (x,y) + {BaseField("29432220588068977731172413919004940878073922894991625118592" + "59126805842169805910975063699386916233854515174105508725"), + BaseField("14617147990844911049525808735959595358829529410173698594487" + "62515518178805418879678173039509762385815865920800988196")}, + // W_polys_blinded_at_secret_g1[c] (x,y) + {BaseField("14391627324702662166442733543984684422882998027578504520373" + "12698084674043069817398172864897193109985787550989102559"), + BaseField( + "29011633872445735455259638237873043070766218287795562103418244491" + "41545494290892417899030834624333222271719003612974")}}; + // Prover Round 2 + // accumulatro polynomial + this->A_poly = { + Field("2518625392737982296343610325189127807488571368919632517794638553" + "2342220728117"), // t^0 + + Field("3343004226366800205137468684843812026286862538920524966872869345" + "3463660552682"), // t^1 + + Field("1204771288456474927231408409787544475988184671052023508676578841" + "2377967852364"), // t^2 + + Field("1654220606090652573608134811761648464025584860436467340851483101" + "8718382023477"), // t^3 + + Field("2783712845267402774526728481246187236057799933701726441152811280" + "4611642359798"), // t^4 + + Field("3732208461409147234788564051800623612946480973700210337990448546" + "1155139003575"), // t^5 + + Field("6413652480339839192085204403325965541536359421783588217290146845" + "390811835496"), // t^6 + + Field("5096442001688032260934660998312846158129100711302111193973619127" + "1694500382544"), // t^7 + }; + // blinded accumulator poly z(x) + this->z_poly = { + Field("2265165420750732280253528539849796218946508036881721973069178682" + "3827189416450"), // t^0 + + Field("3831738456079339709435054186006908989008934850155190362666272862" + "0816893324723"), // t^1 + + Field("9915821367913230443615994390712252777198745523076026755936634884" + "688229901646"), // t^2 + + Field("1654220606090652573608134811761648464025584860436467340851483101" + "8718382023477"), // t^3 + + Field("2783712845267402774526728481246187236057799933701726441152811280" + "4611642359798"), // t^4 + + Field("3732208461409147234788564051800623612946480973700210337990448546" + "1155139003575"), // t^5 + + Field("6413652480339839192085204403325965541536359421783588217290146845" + "390811835496"), // t^6 + + Field("5096442001688032260934660998312846158129100711302111193973619127" + "1694500382544"), // t^7 + + Field("2534599719872500160900817853393315885420633320379105447254598708" + "515031311667"), // t^8 + + Field("4754853287800079543647188549655499621046982938818098386466962353" + "2585348412472"), // t^9 + + Field("2131891516651518828698089707163191982683101187444208330829153527" + "689737950718"), // t^10 + }; + // blinded accumulator poly z(x) evaluated at secret + this->z_poly_at_secret_g1 = { + BaseField("218229772328487280905090401999839431733408937653957673318017" + "4184484710730756128092226245721766481877396312865768304"), + BaseField("292507928406344683037670492963483727823750442012637110834805" + "8274997491709275417542814945756255382474849922144871524")}; + // Prover Round 3 + // t_poly_at_secret_g1: t(x) evaluated at the secret input zeta i.e. t(zeta) + this->t_poly_at_secret_g1 = { + // t_poly_at_secret_g1[0] (x,y) + {BaseField("36334753045440395809371680338918211810312700289483151569664" + "30357290637750912918602224358395819959043217498580613188"), + BaseField("14280901549512618100167591929669036233606392208611617045103" + "58440208878251190328471919089961503194904379492282570328")}, + // t_poly_at_secret_g1[1] (x,y) + {BaseField("76363409032225954376660766997910850260552039791217261961132" + "3329140740033948682915660599655604319492439350037062593"), + BaseField("28136783837059300064723980127085168126317661898643574293043" + "41222779755096333176883586053913173384834727806732577514")}, + // t_poly_at_secret_g1[2] (x,y) + {BaseField("11337733321199745710063881143204871341221284322923746136104" + "71191239740936855771046194807037399513728603857921779020"), + BaseField( + "23717433852493404330471742080754816727740110188452404228212413264" + "03735375534578397825283190840736410689009347296342")}}; + // z_poly_xomega: the polynomial z(x*w) i.e. z(x) shifted by w + this->z_poly_xomega = { + Field("2265165420750732280253528539849796218946508036881721973069178682" + "3827189416450"), // t^0 + + Field("3522707332061080835798112609163881083507261400618189899676704579" + "1350279877954"), // t^1 + + Field("3309030535278527195951493880213970747872492878590839259324563659" + "7703335115887"), // t^2 + + Field("4088099918379397647346118061563173960283766613891904952309372247" + "5099935064612"), // t^3 + + Field("2459874672245216273418045569572409347711255316351037341107554589" + "5326938824715"), // t^4 + + Field("3130512625781403637138050922833186963879103219436289731932477576" + "1009602152629"), // t^5 + + Field("5012222053129571007016945989949647333098134542190693273050557821" + "1550198923184"), // t^6 + + Field("3064349268741886919262515885662041664089860259376571962820647628" + "4834360312181"), // t^7 + + Field("2534599719872500160900817853393315885420633320379105447254598708" + "515031311667"), // t^8 + + Field("1419615120215068486878184007202189531439457176459524671730274215" + "4033633385750"), // t^9 + + Field("1019842429205845332898215495550756855277887015561016627161000987" + "5240306916807") // t^10 + }; + // t_poly: the quotient polynomial t(x) (see Round 3, pp28 [GWC19]), + this->t_poly_long = { + Field("1613850149506400547772007762591425736533070021337736890758518750" + "3110317398551"), // t^0 + + Field("3040368902288718889983204820945380652817775664997775098304580225" + "9681526185231"), // t^1 + + Field("1042102470564703322665398547899923945819513306900065157914357111" + "3800640238933"), // t^2 + + Field("1633462087831609539968808827529008330124698299502354144702821124" + "4300088022199"), // t^3 + + Field("3632784646360485930553771159655097618380845063905226267905837784" + "5835048276810"), // t^4 + + Field("3656196858094264844695875765748620979140837444590225078292098611" + "0855788988433"), // t^5 + + Field("4523255421893679400053241195748570388235240684403702464129656520" + "043856004349"), // t^6 + + Field("1002647493544172661013146016776665819687146794207805535060529180" + "6843077939272"), // t^7 + + Field("3548121107888953018916580962039584646709578854362089670366763654" + "2576580345734"), // t^8 + + Field("2297931724833055633330038364258628456490770251522597328783342991" + "5658557329963"), // t^9 + + Field("6252854478478019668247055899364396160278584256715602663448250419" + "916088342384"), // t^10 + + Field("2407613143858867391555076078532805439108651463880076324787621056" + "1552682158710"), // t^11 + + Field("3674667163620429559371781226732735616050875001211632077958321530" + "694677958113"), // t^12 + + Field("1942412489095028244973144301361474632107814650518722125151606411" + "7233674898523"), // t^13 + + Field("4005762677304860945679708501660985182956740054316240488989084561" + "2848784733039"), // t^14 + + Field("1931303367871217529389379259233466201562729351133418057091284445" + "3652385236582"), // t^15 + + Field("2221817730104195391229605776246015275856933020723204442957788785" + "1871298997907"), // t^16 + + Field("3588159848730797308072409316494361587812978320894884007923341012" + "0898701096353"), // t^17 + + Field("1310205747596608722863298127271181852811202233900209260215326001" + "1136972622921"), // t^18 + + Field("8560623145505212661032016828864500149385317605488145618652908867" + "288785811948"), // t^19 + + Field("4065894564211273790799140230700764854860230755995745210191240729" + "3426046132023"), // t^20 + + Field("5081908704900564950804424995622495124460340656509539811225297312" + "5211073271073"), // t^21 + + Field("4666457525759582528402241552791202565704683710647774067806134699" + "2623383015707"), // t^22 + + Field("4306907993838530907933054915935228091472808577800496469450130926" + "3755728838047"), // t^23 + + Field("4947724791243297456277917191608867362506589718656153051429472063" + "9066541459749"), // t^24 + + Field("2087040818294080218293142955185975472708718151197964942361522282" + "2038546839608"), // t^25 + + Field("1458546107620775338054728063563476188812102450800490940500969838" + "6466329925772"), // t^26 + + Field("7078989131341788220225651051808493470990658902234100299184940135" + "97364414823"), // t^27 + + Field("2984093618809324090683329124882124934996621572654997040583232120" + "5298097143557"), // t^28 + + Field("6188816264874193895800450614104712986342233687836446073160843161" + "893236587105") // t^29 + }; + // t_poly_long: t(x), divided in three parts t(x), = t_lo(x), + + // t_mid(x), x^n + t_hi(x), x^{2n} + this->t_poly = {// ft_lo + { + Field("161385014950640054777200776259142573653307002133" + "77368907585187503110317398551"), // t^0 + + Field("304036890228871888998320482094538065281777566499" + "77750983045802259681526185231"), // t^1 + + Field("104210247056470332266539854789992394581951330690" + "00651579143571113800640238933"), // t^2 + + Field("163346208783160953996880882752900833012469829950" + "23541447028211244300088022199"), // t^3 + + Field("363278464636048593055377115965509761838084506390" + "52262679058377845835048276810"), // t^4 + + Field("365619685809426484469587576574862097914083744459" + "02250782920986110855788988433"), // t^5 + + Field("452325542189367940005324119574857038823524068440" + "3702464129656520043856004349"), // t^6 + + Field("100264749354417266101314601677666581968714679420" + "78055350605291806843077939272"), // t^7 + + Field("354812110788895301891658096203958464670957885436" + "20896703667636542576580345734"), // t^8 + + Field("229793172483305563333003836425862845649077025152" + "25973287833429915658557329963") // t^9 + }, + // t_mid + { + Field("625285447847801966824705589936439616027858425671" + "5602663448250419916088342384"), // t^0 + + Field("240761314385886739155507607853280543910865146388" + "00763247876210561552682158710"), // t^1 + + Field("367466716362042955937178122673273561605087500121" + "1632077958321530694677958113"), // t^2 + + Field("194241248909502824497314430136147463210781465051" + "87221251516064117233674898523"), // t^3 + + Field("400576267730486094567970850166098518295674005431" + "62404889890845612848784733039"), // t^4 + + Field("193130336787121752938937925923346620156272935113" + "34180570912844453652385236582"), // t^5 + + Field("222181773010419539122960577624601527585693302072" + "32044429577887851871298997907"), // t^6 + + Field("358815984873079730807240931649436158781297832089" + "48840079233410120898701096353"), // t^7 + + Field("131020574759660872286329812727118185281120223390" + "02092602153260011136972622921"), // t^8 + + Field("856062314550521266103201682886450014938531760548" + "8145618652908867288785811948") // t^9 + }, + // t_hi + { + Field("406589456421127379079914023070076485486023075599" + "57452101912407293426046132023"), // t^0 + + Field("508190870490056495080442499562249512446034065650" + "95398112252973125211073271073"), // t^1 + + Field("466645752575958252840224155279120256570468371064" + "77740678061346992623383015707"), // t^2 + + Field("430690799383853090793305491593522809147280857780" + "04964694501309263755728838047"), // t^3 + + Field("494772479124329745627791719160886736250658971865" + "61530514294720639066541459749"), // t^4 + + Field("208704081829408021829314295518597547270871815119" + "79649423615222822038546839608"), // t^5 + + Field("145854610762077533805472806356347618881210245080" + "04909405009698386466329925772"), // t^6 + + Field("707898913134178822022565105180849347099065890223" + "410029918494013597364414823"), // t^7 + + Field("298409361880932409068332912488212493499662157265" + "49970405832321205298097143557"), // t^8 + + Field("618881626487419389580045061410471298634223368783" + "6446073160843161893236587105") // t^9 + }}; + // Prover Round 4 + this->a_zeta = Field("89018754633263103134565706528698737767467674290012895" + "06712732994487869455294"); + this->b_zeta = Field("17059370482702287697833061796226204248201565415155528" + "923232473993993212080397"); + this->c_zeta = Field("24097569659300085566963716541699131253974499863725226" + "36184003898699439708220"); + this->S_0_zeta = Field("461436261558032879183302794283908482860766454284773" + "53060129573054942492588828"); + this->S_1_zeta = Field("243927046358912523431432696335637683451450085201403" + "60299402842967762646340846"); + this->t_zeta = Field("17704211079697158667898451781925539666888780633357685" + "549668669638883218786797"); + this->z_poly_xomega_zeta = Field("28842520748921749858267479462666161290723" + "351257502457358354355079408206613634"); + // Prover Round 5 + // linearisation polynomial r(x) + this->r_poly = { + Field("2603512055252796692071737904087507556829684549510721336678959715" + "7169046865340"), // t^0 + + Field("3942753965961071766143624447190676478891460844964486462800593298" + "0154778950407"), // t^1 + + Field("1743566792151933472376808485105574264437105927288539768954931379" + "3693366950787"), // t^2 + + Field("4942865003799481471749027521378523306396518062429387236185665079" + "8295465747357"), // t^3 + + Field("4415918367620933880836129889969775566105477399880154139724106878" + "707122735424"), // t^4 + + Field("8948900433568314567249974508827746248263244175570022612023712197" + "372700744447"), // t^5 + + Field("2960702973715822632300470900105704342347090742964455410397076921" + "9991866925475"), // t^6 + + Field("4140950340042081861784876195481408717940244394208041020815793396" + "987141178934"), // t^7 + + Field("4030038061106409199760524044467210289705742219665913488385521773" + "2857702756316"), // t^8 + + Field("4793378445006658536717368078811814461230636700375498945907637919" + "9822825835746"), // t^9 + + Field("2288087964390040455243247170114287577194614314265017877490312785" + "1353091457234") // t^10 + }; + // linearisation polynomial evaluated at x=zeta ie. r(zeta) + this->r_zeta = Field("98401833553540757648601291397401878521368727319456218" + "53688663309524905254695"); + // opening proof polynomial W_zeta(x) + this->W_zeta = { + Field("2425208075861302430388734364060671625126106358899275231917791230" + "8896977829530"), // x^0 + + Field("1060760789872469863243912303046696427113635606636800367079996536" + "1976876241434"), // x^1 + + Field("4945570090835204155867856741605638033127077675733215306305597163" + "4526190286005"), // x^2 + + Field("3332027100358956385527160855090111765264327736597228066927830862" + "6702032462766"), // x^3 + + Field("1585236647926224968598276457268065881132907825354517423594189230" + "0400403939930"), // x^4 + + Field("2768243031239845689523160572736932993927651703690421976097490318" + "4897420916090"), // x^5 + + Field("3408971485363305489368119320301696605838015578921377320829539984" + "918748131464"), // x^6 + + Field("6170013496654113309991805083384693399451071882671661442964650981" + "706324964718"), // x^7 + + Field("4046651207853235565358275015621115513666336518059676997543319340" + "6332496460952"), // x^8 + + Field("2779755100662473972439074429356997821277920509470931427485728915" + "1379644029144") // x^9 + }; + // opening proof polynomial W_{zeta omega}(x) + this->W_zeta_omega = { + Field("4053156463693497745052164515731838082731509222453505556234840603" + "5588261107649"), // x^0 + + Field("4058277206829669744576668736724647055016570296582250369803518901" + "3689327598242"), // x^1 + + Field("3469229733620729618347120395255874659028106129936335068581145823" + "3238031608744"), // x^2 + + Field("3672976997032667925860661999685787149672966747083938789879739560" + "7660186144417"), // x^3 + + Field("3920037723815253535315325165819311372867884025491460418913281378" + "9688481911843"), // x^4 + + Field("3780677159511070124578530692688093256358861446957732007938945605" + "6927918503907"), // x^5 + + Field("3008905681484411520937149759836469023029665120031639147269349017" + "3328399559224"), // x^6 + + Field("5019223596666837703964065504139865465420595258667351312020460215" + "9086894893773"), // x^7 + + Field("5046455272607465584094271013540888943906468773469964673357231746" + "9624082573673"), // x^8 + + Field("2131891516651518828698089707163191982683101187444208330829153527" + "689737950718") // x^9 + }; + // commitment to opening proof polynomial W_zeta(x) at secert input + // i.e. [W_zeta(secret)]_1 (represented as a point on the curve as a + // pair of X,Y coordinates (values in the base field)) + this->W_zeta_at_secret = { + BaseField("870060484392950318936407436339281693869104302033433437217724" + "352106918879203859031480272360445121364037832041952039"), + BaseField("861299420568581680683022801956519699850698057581115592449001" + "068617883576936792838745784184017332399410405735556637")}; + // commitment to opening proof polynomial W_{zeta omega}(x) at + // secert input i.e. [W_{zeta omega}(secret)]_1 + this->W_zeta_omega_at_secret = { + BaseField("655495718420813899433022178744291341055707361726913326276228" + "363256669650441492160760400814686050651569669287015855"), + BaseField("229481443404822103284044189219285257403125043021601137826232" + "4574002174077316300644184560707652291246729455056384738")}; + // Verifier precomputation + this->Q_polys_at_secret_g1 = { + // Q_polys_at_secret_G1[0](x, y) + {BaseField("37872638117646271814730035813573941475040188186517949668804" + "5184488721729111812849671928978444183306485908524284480"), + BaseField("79183321462482392539235348002489945030682397254523366395010" + "2448253555712343824442312037763251986444707559468789167")}, + // Q_polys_at_secret_G1[1](x, y) + {BaseField("34631210648960787533875729378285482931236025064470116389646" + "95206791719047814189335281941216929475385990343236354767"), + BaseField("37560055274848745758731228857694331283955625702810481826014" + "69211783316382910994832150136370603197748628978251617855")}, + // Q_polys_at_secret_G1[2](x, y) + {BaseField("38929455936321242573458158228059689667920715759118216184118" + "50268364131846825179296155052328167376852966904585186447"), + BaseField("14387651874046990029377825249761966997493481734049336060559" + "94266712582628388073773207681042085983472030360245506053")}, + // Q_polys_at_secret_G1[3](x, y) + {BaseField("21395203688591242318834236925149705288598122582999309763678" + "81008205428239812556102432321082924215659317599549354780"), + BaseField("31608016701108926123111249555539915826675093054796279958192" + "54406979123526107121808691993608981196593224342875940517")}, + // Q_polys_at_secret_G1[4](x, y) + {BaseField("14947061974880505484736471897005901630187100836190498942013" + "40042906428954701970245565963571442231938653450961032544"), + BaseField( + "28246023002329815495890687586581650096148528539495759217955996750" + "03541734658829364117937951492488210759581945412529")}}; + this->S_polys_at_secret_g1 = { + // S_polys_at_secret_G1[0](x, y) + {BaseField("36368078628695840142519314182027223097391859322500348523037" + "5359988783103495068430872040401356145672343538149573956"), + BaseField("38086117155279067619957746135328045145411168060473709058159" + "23618967460517724494838454007652229237779324980791026678")}, + // S_polys_at_secret_G1[1](x, y) + {BaseField("22692902817920796240439217704871520306588775874145333548088" + "21851134353908789851247370093633774252241702882912887085"), + BaseField("12029919948961427716735907593948285124581150685050417216921" + "00348489247274098109255301291346677656642600399089495236")}, + // S_polys_at_secret_G1[2](x, y) + {BaseField("14092796445922828479110756772232507484316624225197532585861" + "36046888366320164223401617201095933133577003578716884726"), + BaseField( + "17392526753346659131838574196457588111148361438835640062331721065" + "01577251543276680000254892836167925142291524692240")}}; + // Verifier Step 5: vanishing polynomial evaluation at zeta + this->zh_zeta = Field("3231291541068425176363304614853753627837086743398580" + "603659947674812058489341"); + // Verifier Step 6: compute Lagrange polynomial evaluation L1(zeta) + this->L_0_zeta = Field("249023502880157597838915072978532235843469796520075" + "92864411578966192407598153"); + // Verifier Step 7: evaluate public input polynomial at zeta + this->PI_zeta = Field("5213191382864624158721462223282140052666923997678483" + "8364162926303506898992750"); + // Verifier Step 8: compute quotient polynomial evaluation r'(zeta) = + // r(zeta) - r0, where r0 is a constant term + this->r_prime_zeta = Field("17704211079697158667898451781925539666888780633" + "357685549668669638883218786797"); + // Verifier Step 9 + this->D1 = { + // (x,y) + BaseField("295055128993514571465051427628530985500804633619823829912294" + "3701060700368614623093700969937886225712332445422779232"), + BaseField("177357457743368679564228713000738451950615223042556119935832" + "0811214796879879223471447420612658036440896892253660321")}; + // Verifier Step 10: compute full batched polynomial commitment + this->F1 = { + // (x,y) + BaseField("182674249211586861258228800954744156123303054902439656689558" + "7306861582709499257146203756040438377200227370973259986"), + BaseField("317469800320460430597039038149214739328961296782892898627772" + "0440012247826580102584515518061223894220403192017952478")}; + // Verifier Step 11: compute group-encoded batch evaluation [E]_1 + this->E1 = { + // (x,y) + BaseField("896211057343888615494168233046980277322670342780717794143414" + "541657336321268995118530971981505762603044924183423639"), + BaseField("243066919022843377501447737580021700365442316598462908640891" + "9423216148134888225770566675462506053600277852687652642")}; + // Verifier Step 12: batch validate all evaluations via pairing + this->pairing_first_lhs = { + // (x,y) + BaseField("259441502792996125537510028389383648449414919567783080548752" + "7382596454063206138678026974672787696177127251762793677"), + BaseField("235250982894607804932611639285546907420234209359655517101542" + "1209941442208769351281087086741535791209871063622971343")}; + this->pairing_first_rhs = { + // (x,y) +#if 0 // pairing check e() = e() + BaseField("2786086768974850272772873952573647917824800608941506526890251802946860616723974417792544909526285497672211326145554"), + BaseField("3322299130191197851913727020573383402853736418546119609247136782819928908204641650829975455690084120791575793280234") +#endif // #if 0 // pairing check e() = e() + // (x,y)^-1 +#if 1 // pairing check e() * e()^-1 = 1 + BaseField("278608676897485027277287395257364791782480060894150652689025" + "1802946860616723974417792544909526285497672211326145554"), + BaseField("680110425030469541504062805162520753703146401392888276084921" + "353304102742286196213612712173438931543246318479279553") +#endif // #if 1 // pairing check e() * e()^-1 = 1 + }; +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/tests/example.hpp b/libsnark/zk_proof_systems/plonk/tests/example.hpp new file mode 100644 index 000000000..4ee3e5966 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/example.hpp @@ -0,0 +1,322 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_HPP_ + +/// Test vectors for the implementation of the PlonK protocol \[GWC19] +/// (\see utils.hpp) produced using the Python implementation of Plonk +/// available at [PlonkPy]. +/// +/// The test vector values trace the execution of the Plonk protocol for +/// the example ciruit P(x) = x**3 + x + 5 = 35 discussed in a blog post +/// by Vitalik Buterin [VB19]. +/// +/// References: +/// - \[PlonkPy] +/// Title: Implementation of the Plonk ZKSNARK proof system in +/// Python, accessed in May 2022 on GitHub +/// https://github.com/ETHorHIL/Plonk_Py +/// - \[VB19] +/// Title: "Understanding PLONK", Vitalik Buterin, personal blog +/// post from 22 Sep 2019, +/// https://vitalik.ca/general/2019/09/22/plonk.html + +#include "libsnark/common/data_structures/polynomial.hpp" + +#include +#include + +namespace libsnark +{ + +/// Example Plonk circuit from [VB19]: +/// +/// P(x) = x**3 + x + 5 = 35 +/// +/// circuit has 6 gates + 2 dummy gates (to make power of 2 for the FFT) +/// +/// gates / constraints +/// +/// L R O +/// 1 mul x * x = v1 +/// 2 mul v1 * x = v2 +/// 3 add v2 + x = v3 +/// 4 con5 1 * 5 = 5 +/// 5 pin 1 * 35 = 35 +/// 6 add v3 + 5 = 35 +/// 7 dum / / / +/// 8 dum / / / +/// +/// wire polynomials +/// +/// w_L = [ x, v1, v2, 1, 1, v3, /, /] = [a1, a2, a3, a4, a5, a6, a7, a8] = a +/// w_R = [ x, x, x, 5, 35, 5, /, /] = [b1, b2, b3, b4, b5, b6, b7, b8] = b +/// w_O = [v1, v2, v3, 5, 35, 35, /, /] = [c1, c2, c3, c4, c5, c6, c7, c8] = c +/// +/// wires = [a1, a2, a3, a4, a5, a6, a7, a8, b1, b2, b3, b4, b5, b6, b7, b8, c1, +/// c2, c3, c4, c5, c6, c7, c8] +/// +/// index = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, +/// 18, 19, 20, 21, 22, 23, 24] +/// +/// perm = [ 9, 17, 18, 5, 4, 19, 7, 8, 10, 11, 1, 14, 21, 20, 15, 16, 2, +/// 3, 6, 12, 22, 13, 23, 24] +/// +/// witness +/// +/// x = 3 => v1 = 9, v2 = 27, v3 = 30 +/// +/// w_L = a = [ 3, 9, 27, 1, 1, 30, 0, 0] +/// w_R = b = [ 3, 3, 3, 5, 35, 5, 0, 0] +/// w_O = c = [ 9, 27, 30, 5, 35, 35, 0, 0] +/// +/// W = w_L + w_R + w_O +/// +/// encoding of plonk gates +/// +/// add = [1, 1,-1, 0, 0] +/// mul = [0, 0,-1, 1, 0] +/// con5 = [0, 1, 0, 0, 5] +/// pi = [0, 1, 0, 0, 0] +/// dum = [0, 0, 0, 0, 0] +/// +/// gates matrix +/// +/// (q_L * a) + (q_R * b) + (q_O * c) + (q_M * a * b) + (q_C) = 0 +/// +/// gates q_L q_R q_O q_M q_C +/// 1 mul x * x = v1 : [ 0, 0, -1, 1, 0] +/// 2 mul v1 * x = v2 : [ 0, 0, -1, 1, 0] +/// 3 add v2 + x = v3 : [ 1, 1, -1, 0, 0] +/// 4 con5 1 * 5 = 5 : [ 0, 1, 0, 0, -5] +/// 5 pin 1 * 35 = 35 : [ 0, 1, 0, 0, 0] +/// 6 add v2 + 5 = 35 : [ 1, 1, -1, 0, 0] +/// 7 dum / / / : [ 0, 0, 0, 0, 0] +/// 8 dum / / / : [ 0, 0, 0, 0, 0] +/// +/// q_L = [ 0, 0, 1, 0, 0, 1, 0, 0] +/// q_R = [ 0, 0, 1, 1, 1, 1, 0, 0] +/// q_O = [-1, -1, -1, 0, 0, 0, 0, 0] +/// q_M = [ 1, 1, 0, 0, 0, -1, 0, 0] +/// q_C = [ 0, 0, 0, -1, 0, 0, 0, 0] +/// +/// \attention The convention for selector vector values corresponding +/// to public inputs (PI) used here is different from the one used in +/// plonk_prepare_gates_matrix which is consistent with [GWC19], namely: +/// +/// (q_L[i], q_R[i], q_O[i], q_M[i], q_C[i]) = (1, 0, 0, 0, 0) +/// +/// For comparsion, here (i.e. in the example class) q_R[i]=1 and so +/// +/// (q_L[i], q_R[i], q_O[i], q_M[i], q_C[i]) = (0, 1, 0, 0, 0) +class plonk_example +{ +public: + using Field = libff::Fr; + using BaseField = libff::Fq; + + /// Circuit data + + /// number of gates / constraints. we have 6 gates for the example + /// circuit + 2 dummy gates to make it a power of 2 (for the fft) + size_t num_gates; + + /// number of q-polynomials + size_t num_qpolys; + + /// hard-coded gates matrix for the example circuit P(x) = x**3 + + /// x + 5 = 3 Each column is a q-vector + std::vector> gates_matrix; + + /// Transposed gates matrix: each row is a q-vector WARN: rows 2 + /// q_O and 3 q_M are swapped to match the Plonk_Py test vectors + /// implementation (reason unclear) + std::vector> gates_matrix_transpose; + + /// witness values + /// w_L = a = [ 3, 9, 27, 1, 1, 30, 0, 0] + /// w_R = b = [ 3, 3, 3, 5, 35, 5, 0, 0] + /// w_O = c = [ 9, 27, 30, 5, 35, 35, 0, 0] + /// W = w_L + w_R + w_O + std::vector witness; + + /// wire permutation (TODO: add function + /// plonk_compute_permutation()) + std::vector wire_permutation; + + /// public input (PI) + Field PI_value; + + /// index of the row of the PI in the non-transposed gates_matrix + size_t PI_gates_matrix_irow; + + /// Vector of indices of wires corresponding to public inputs (PI) + std::vector PI_wire_indices; + + /// n-th root of unity omega in Fq (n=8 is the number of + /// constraints in the example). omega is a generator of the + /// multiplicative subgroup H. Example (2**32)-th primitive root + /// of unity in the base field Fq of bls12-381 i.e. such that + /// omega_base**(2**32) = 1. The bls12-381 prime q is such that + /// any power of 2 divides (q-1). In particular 2**32|(q-1) and so + /// the 2**32-th root of unity exists. + Field omega_base; + + /// Constants k1,k2 to generate domains on which to evaluate the + /// witness polynomials. k can be random, but we fix it for debug + /// to match against the test vector values + Field k1; + + /// Similarly, k2 can be random, but we fix it to match the test + /// vectors + Field k2; + + /// H_prime (i.e. H' in [GWC19], e.g. see Sect. 8) contains the + /// generators of H, k1 H and K2 H in one place ie. omega, + /// omega_k1 and omega_k2 + std::vector H_prime; + + /// H_prime permuted according to the wire permutation + std::vector H_prime_permute; + + /// random hidden element secret (toxic waste). we fix it to a + /// constant in order to match against the test vectors + Field secret; + /// powers of secret times G1: 1*G1, secret^1*G1, secret^2*G1, ... + std::vector> secret_powers_g1; + + /// powers of secret times G2: 1*G2, secret^1*G2 + std::vector> secret_powers_g2; + + /// blinding scalars b1, b2, ..., b9. random but fixed to match + /// the python test vectors + std::vector prover_blind_scalars; + + /// Hashes of transcript (Fiat-Shamir heuristic) + Field beta; + Field gamma; + Field alpha; + Field zeta; + Field nu; /// v in the paper + Field u; + + /// Prover Round 1 + std::vector> W_polys; + + /// vanishing polynomial zh_poly(X) = x^n-1. vanishes on all n + /// roots of unity omega_roots + std::vector zh_poly; + + /// Witness polynomials blinded by b constants a_poly = + /// blind_polys[0] * zh_poly + W_polys[0] + std::vector> W_polys_blinded; + + /// blinded witness polynomials evaluate at secret input + std::vector> W_polys_blinded_at_secret_g1; + + /// Prover Round 2 + + /// accumulator polynomial + std::vector A_poly; + + /// blinded accumulator poly z(x) + std::vector z_poly; + + /// blinded accumulator poly z(x) evaluated at secret + std::vector z_poly_at_secret_g1; + + /// Prover Round 3 + + /// z_poly_xomega: the polynomial z(x*w) i.e. z(x) shifted by w + std::vector z_poly_xomega; + + /// t_poly: the quotient polynomial t(x) (see Round 3, pp28 + /// [GWC19]) + std::vector> t_poly; + + /// t_poly_long: t(x) divided in three parts t(x) = t_lo(x) + + /// t_mid(x) x^n + t_hi(x) x^{2n} + polynomial t_poly_long; + + /// t_poly_at_secret_g1: t(x) evaluated at the secret input zeta + /// i.e. t(zeta) + std::vector> t_poly_at_secret_g1; + + /// Prover Round 4 + + Field a_zeta; + Field b_zeta; + Field c_zeta; + Field S_0_zeta; + Field S_1_zeta; + Field t_zeta; + Field z_poly_xomega_zeta; + + /// Prover Round 5 + + /// linearisation polynomial r(x) + polynomial r_poly; + + /// linearisation polynomial evaluated at x=zeta ie. r(zeta) + Field r_zeta; + + /// opening proof polynomial W_zeta(x) + polynomial W_zeta; + + /// opening proof polynomial W_{zeta omega}(x) + polynomial W_zeta_omega; + + /// commitment to opening proof polynomial W_zeta(x) at secert + /// input i.e. [W_zeta(secret)]_1 (represented as a point on the + /// curve as a pair of X,Y coordinates (values in the base field)) + std::vector W_zeta_at_secret; + + /// commitment to opening proof polynomial W_{zeta omega}(x) at + /// secert input i.e. [W_{zeta omega}(secret)]_1 + std::vector W_zeta_omega_at_secret; + + /// Verifier precomputation + std::vector> Q_polys_at_secret_g1; + std::vector> S_polys_at_secret_g1; + + /// Verifier Step 5: vanishing polynomial evaluation at zeta + Field zh_zeta; + + /// Verifier Step 6: compute Lagrange polynomial evaluation + /// L1(zeta) + Field L_0_zeta; + + /// Verifier Step7: evaluate public input polynomial at zeta + Field PI_zeta; + + /// Verifier Step 8: compute quotient polynomial evaluation + /// r'(zeta) = r(zeta) - r0, where r0 is a constant term + Field r_prime_zeta; + + /// Verifier Step 9 + std::vector D1; + + /// Verifier Step 10: compute full batched polynomial commitment + std::vector F1; + + /// Verifier Step 11: compute group-encoded batch evaluation [E]_1 + std::vector E1; + + /// Verifier Step 12: batch validate all evaluations via pairing + std::vector pairing_first_lhs; + std::vector pairing_first_rhs; + // std::vector pairing_second_lhs; + // std::vector pairing_second_rhs; + + plonk_example(); +}; + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_EXAMPLE_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp new file mode 100644 index 000000000..f24a78a0f --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp @@ -0,0 +1,1429 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#include "libsnark/zk_proof_systems/plonk/prover.hpp" +#include "libsnark/zk_proof_systems/plonk/tests/bls12_381_test_vector_transcript_hasher.hpp" +#include "libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp" +#include "libsnark/zk_proof_systems/plonk/verifier.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Test program that exercises the Plonk protocol (first setup, then +/// prover, then verifier) on a synthetic R1CS instance. + +namespace libsnark +{ +#define PLONK_MAX_DEGREE 245 + +// Manipulate elements of a valid proof to assert that proof +// verification fails. +// +// Plonk proof Pi is composed of the following elements: +// +// Pi ([a]_1, [b]_1, [c]_1, [z]_1, +// [t_lo]_1, [t_mi]_1, [t_hi]_1, +// \bar{a}, \bar{b}, \bar{c}, +// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w}, +// [W_zeta]_1, [W_{zeta omega_roots}]_1 +// r_zeta (*)) +template +void test_verify_invalid_proof( + const plonk_proof &valid_proof, + const srs &srs, + const std::vector> &PI_value_list, + transcript_hasher &hasher) +{ + // initialize verifier + plonk_verifier verifier; + bool b_accept = true; + + // random element on the curve initialized to zero + libff::G1 G1_noise = libff::G1::zero(); + // random element in the scalar field initialized to zero + libff::Fr Fr_noise = libff::Fr::zero(); + // initialize the manipulated proof to the valid one + plonk_proof proof = valid_proof; + + // manipulate [a]_1, [b]_1, [c]_1 + for (size_t i = 0; i < valid_proof.W_polys_blinded_at_secret_g1.size(); + ++i) { + // re-initialize the manipulated proof + hasher.reset(); + proof = valid_proof; + G1_noise = libff::G1::random_element(); + proof.W_polys_blinded_at_secret_g1[i] = + proof.W_polys_blinded_at_secret_g1[i] + G1_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + } + // manipulate [z]_1 + hasher.reset(); + proof = valid_proof; + G1_noise = libff::G1::random_element(); + proof.z_poly_at_secret_g1 = proof.z_poly_at_secret_g1 + G1_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate [t_lo]_1, [t_mi]_1, [t_hi]_1 + for (size_t i = 0; i < valid_proof.t_poly_at_secret_g1.size(); ++i) { + // re-initialize the manipulated proof + hasher.reset(); + proof = valid_proof; + G1_noise = libff::G1::random_element(); + proof.t_poly_at_secret_g1[i] = proof.t_poly_at_secret_g1[i] + G1_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + } + // manipulate \bar{a} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.a_zeta = proof.a_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate \bar{b} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.b_zeta = proof.b_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate \bar{c} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.c_zeta = proof.c_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate \bar{S_sigma1} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.S_0_zeta = proof.S_0_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate \bar{S_sigma2} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.S_1_zeta = proof.S_1_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate \bar{z_w} + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.z_poly_xomega_zeta = proof.z_poly_xomega_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate [W_zeta]_1 + hasher.reset(); + proof = valid_proof; + G1_noise = libff::G1::random_element(); + proof.W_zeta_at_secret = proof.W_zeta_at_secret + G1_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate [W_{zeta omega_roots}]_1 + hasher.reset(); + proof = valid_proof; + G1_noise = libff::G1::random_element(); + proof.W_zeta_omega_at_secret = proof.W_zeta_omega_at_secret + G1_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); + // manipulate r_zeta + hasher.reset(); + proof = valid_proof; + Fr_noise = libff::Fr::random_element(); + proof.r_zeta = proof.r_zeta + Fr_noise; + b_accept = verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_FALSE(b_accept); +} + +template +void test_plonk_compute_accumulator( + const plonk_example &example, + const libff::Fr &beta, + const libff::Fr &gamma, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain) +{ + using Field = libff::Fr; + // A[0] = 1; ... A[i] = computed from (i-1) + std::vector A_vector = plonk_compute_accumulator( + srs.num_gates, beta, gamma, witness, srs.H_prime, srs.H_prime_permute); + polynomial A_poly(srs.num_gates); + plonk_interpolate_polynomial_from_points(A_vector, A_poly, domain); + + // initialize hard-coded values from example circuit + printf("[%s:%d] A_poly\n", __FILE__, __LINE__); + libff::print_vector(A_poly); + ASSERT_EQ(A_poly, example.A_poly); +} + +template +void test_plonk_prover_round_one( + const plonk_example &example, + const round_zero_out_t &round_zero_out, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher) +{ + std::vector> blind_scalars = example.prover_blind_scalars; + round_one_out_t round_one_out = + plonk_prover::round_one( + round_zero_out, blind_scalars, witness, srs, domain, hasher); + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("[%s:%d] this->W_polys[%d]\n", __FILE__, __LINE__, (int)i); + libff::print_vector(round_one_out.W_polys[i]); + ASSERT_EQ(round_one_out.W_polys[i], example.W_polys[i]); + } + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("[%s:%d] W_polys_blinded[%d]\n", __FILE__, __LINE__, i); + libff::print_vector(round_one_out.W_polys_blinded[i]); + ASSERT_EQ(round_one_out.W_polys_blinded[i], example.W_polys_blinded[i]); + } + printf("[%s:%d] Output from Round 1\n", __FILE__, __LINE__); + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("W_polys_at_secret_g1[%d]\n", i); + round_one_out.W_polys_blinded_at_secret_g1[i].print(); + libff::G1 W_polys_blinded_at_secret_g1_i( + round_one_out.W_polys_blinded_at_secret_g1[i]); + W_polys_blinded_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ( + W_polys_blinded_at_secret_g1_i.X, + example.W_polys_blinded_at_secret_g1[i][0]); + ASSERT_EQ( + W_polys_blinded_at_secret_g1_i.Y, + example.W_polys_blinded_at_secret_g1[i][1]); + } +} + +template +void test_plonk_prover_round_two( + const plonk_example &example, + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const std::vector> &blind_scalars, + const std::vector> &witness, + const srs &srs, + std::shared_ptr>> domain, + transcript_hasher &hasher) +{ + round_two_out_t round_two_out = + plonk_prover::round_two( + beta, + gamma, + round_zero_out, + blind_scalars, + witness, + srs, + domain, + hasher); + printf("[%s:%d] z_poly\n", __FILE__, __LINE__); + libff::print_vector(round_two_out.z_poly); + ASSERT_EQ(round_two_out.z_poly, example.z_poly); + printf("[%s:%d] Output from Round 2\n", __FILE__, __LINE__); + printf("[%s:%d] z_poly_at_secret_g1\n", __FILE__, __LINE__); + round_two_out.z_poly_at_secret_g1.print(); + libff::G1 z_poly_at_secret_g1_aff(round_two_out.z_poly_at_secret_g1); + z_poly_at_secret_g1_aff.to_affine_coordinates(); + ASSERT_EQ(z_poly_at_secret_g1_aff.X, example.z_poly_at_secret_g1[0]); + ASSERT_EQ(z_poly_at_secret_g1_aff.Y, example.z_poly_at_secret_g1[1]); +} + +template +void test_plonk_prover_round_three( + const plonk_example &example, + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const std::vector> &witness, + const srs &srs, + transcript_hasher &hasher) +{ + round_three_out_t round_three_out = + plonk_prover::round_three( + alpha, + beta, + gamma, + round_zero_out, + round_one_out, + round_two_out, + witness, + srs, + hasher); + printf("[%s:%d] Output from Round 3\n", __FILE__, __LINE__); + printf("[%s:%d] t_poly_long\n", __FILE__, __LINE__); + libff::print_vector(round_three_out.t_poly_long); + ASSERT_EQ(round_three_out.t_poly_long, example.t_poly_long); + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("[%s:%d] t_poly[%d]\n", __FILE__, __LINE__, i); + libff::print_vector(round_three_out.t_poly[i]); + ASSERT_EQ(round_three_out.t_poly[i], example.t_poly[i]); + } + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("[%s:%d] t_poly_at_secret_g1[%d]\n", __FILE__, __LINE__, i); + round_three_out.t_poly_at_secret_g1[i].print(); + libff::G1 t_poly_at_secret_g1_i( + round_three_out.t_poly_at_secret_g1[i]); + t_poly_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ(t_poly_at_secret_g1_i.X, example.t_poly_at_secret_g1[i][0]); + ASSERT_EQ(t_poly_at_secret_g1_i.Y, example.t_poly_at_secret_g1[i][1]); + } +} + +template +void test_plonk_prover_round_four( + const plonk_example &example, + const libff::Fr &zeta, + const round_one_out_t &round_one_out, + const round_three_out_t &round_three_out, + const srs &srs, + transcript_hasher &hasher) +{ + round_four_out_t round_four_out = + plonk_prover::round_four( + zeta, round_one_out, round_three_out, srs, hasher); + // Prover Round 4 output check against test vectors. + printf("[%s:%d] Output from Round 4\n", __FILE__, __LINE__); + printf("a_zeta "); + round_four_out.a_zeta.print(); + ASSERT_EQ(round_four_out.a_zeta, example.a_zeta); + printf("b_zeta "); + round_four_out.b_zeta.print(); + ASSERT_EQ(round_four_out.b_zeta, example.b_zeta); + printf("c_zeta "); + round_four_out.c_zeta.print(); + ASSERT_EQ(round_four_out.c_zeta, example.c_zeta); + printf("S_0_zeta "); + round_four_out.S_0_zeta.print(); + ASSERT_EQ(round_four_out.S_0_zeta, example.S_0_zeta); + printf("S_1_zeta "); + round_four_out.S_1_zeta.print(); + ASSERT_EQ(round_four_out.S_1_zeta, example.S_1_zeta); + printf("t_zeta "); + round_four_out.t_zeta.print(); + ASSERT_EQ(round_four_out.t_zeta, example.t_zeta); + printf("z_poly_xomega_zeta "); + round_four_out.z_poly_xomega_zeta.print(); + ASSERT_EQ(round_four_out.z_poly_xomega_zeta, example.z_poly_xomega_zeta); +} + +template +void test_plonk_prover_round_five( + const plonk_example &example, + const libff::Fr &alpha, + const libff::Fr &beta, + const libff::Fr &gamma, + const libff::Fr &zeta, + const libff::Fr &nu, + const round_zero_out_t &round_zero_out, + const round_one_out_t &round_one_out, + const round_two_out_t &round_two_out, + const round_three_out_t &round_three_out, + const round_four_out_t &round_four_out, + const srs &srs, + transcript_hasher &hasher) +{ + round_five_out_t round_five_out = + plonk_prover::round_five( + alpha, + beta, + gamma, + zeta, + nu, + round_zero_out, + round_one_out, + round_two_out, + round_three_out, + round_four_out, + srs, + hasher); + + printf("[%s:%d] Outputs from Prover round 5\n", __FILE__, __LINE__); + printf("r_zeta "); + round_five_out.r_zeta.print(); + ASSERT_EQ(round_five_out.r_zeta, example.r_zeta); + printf("[%s:%d] W_zeta_at_secret \n", __FILE__, __LINE__); + round_five_out.W_zeta_at_secret.print(); + libff::G1 W_zeta_at_secret_aff(round_five_out.W_zeta_at_secret); + W_zeta_at_secret_aff.to_affine_coordinates(); + ASSERT_EQ(W_zeta_at_secret_aff.X, example.W_zeta_at_secret[0]); + ASSERT_EQ(W_zeta_at_secret_aff.Y, example.W_zeta_at_secret[1]); + printf("[%s:%d] W_zeta_omega_at_secret \n", __FILE__, __LINE__); + round_five_out.W_zeta_omega_at_secret.print(); + libff::G1 W_zeta_omega_at_secret_aff( + round_five_out.W_zeta_omega_at_secret); + W_zeta_omega_at_secret_aff.to_affine_coordinates(); + ASSERT_EQ(W_zeta_omega_at_secret_aff.X, example.W_zeta_omega_at_secret[0]); + ASSERT_EQ(W_zeta_omega_at_secret_aff.Y, example.W_zeta_omega_at_secret[1]); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve. +template void test_plonk_prover_rounds() +{ + using Field = libff::Fr; + + ppT::init_public_params(); + + // Load test vector values from example circuit. + plonk_example example; + // random hidden element secret (toxic waste) + Field secret = example.secret; + // example witness + std::vector witness = example.witness; + + // hard-coded values for the "random" blinding constants from + // example circuit + std::vector> blind_scalars = example.prover_blind_scalars; + // maximum degree of the encoded monomials in the usrs + size_t max_degree = PLONK_MAX_DEGREE; + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(example.num_gates); + + // prepare srs + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, + example.gates_matrix, + example.wire_permutation, + example.PI_wire_indices); + + // initialize hasher + transcript_hasher hasher; + + // Prover Round 0 (initialization) + round_zero_out_t round_zero_out = + plonk_prover::round_zero(srs); + + // --- Unit test Prover Round 1 --- + // Reset buffer at the start of the round (needed for testing only). + printf("[%s:%d] Unit test Prover Round 1...\n", __FILE__, __LINE__); + test_plonk_prover_round_one( + example, round_zero_out, witness, srs, domain, hasher); + + // --- Unit test Prover Round 2 --- + // Reset buffer at the start of the round (needed for testing only). + printf("[%s:%d] Unit test Prover Round 2...\n", __FILE__, __LINE__); + round_one_out_t round_one_out = + plonk_prover::round_one( + round_zero_out, blind_scalars, witness, srs, domain, hasher); + // clear hash buffer + hasher.reset(); + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[a]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[b]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[c]); + const libff::Fr beta = hasher.get_hash(); + hasher.add_element(libff::Fr::one()); + const libff::Fr gamma = hasher.get_hash(); + test_plonk_prover_round_two( + example, + beta, + gamma, + round_zero_out, + blind_scalars, + witness, + srs, + domain, + hasher); + + // Unit test plonk_compute_accumulator + test_plonk_compute_accumulator( + example, beta, gamma, witness, srs, domain); + + // --- Unit test Prover Round 3 --- + // Reset buffer at the start of the round (needed for testing only). + printf("[%s:%d] Prover Round 3...\n", __FILE__, __LINE__); + round_two_out_t round_two_out = + plonk_prover::round_two( + beta, + gamma, + round_zero_out, + blind_scalars, + witness, + srs, + domain, + hasher); + // Clear hash buffer. + hasher.reset(); + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[a]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[b]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[c]); + hasher.add_element(libff::Fr::one()); + // Add outputs from Round 2 to the hash buffer. + hasher.add_element(round_two_out.z_poly_at_secret_g1); + const libff::Fr alpha = hasher.get_hash(); + test_plonk_prover_round_three( + example, + alpha, + beta, + gamma, + round_zero_out, + round_one_out, + round_two_out, + witness, + srs, + hasher); + + // --- Unit test Prover Round 4 --- + printf("[%s:%d] Prover Round 4...\n", __FILE__, __LINE__); + round_three_out_t round_three_out = + plonk_prover::round_three( + alpha, + beta, + gamma, + round_zero_out, + round_one_out, + round_two_out, + witness, + srs, + hasher); + // Clear hash buffer. + hasher.reset(); + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[a]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[b]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[c]); + hasher.add_element(libff::Fr::one()); + // Add outputs from Round 2 to the hash buffer. + hasher.add_element(round_two_out.z_poly_at_secret_g1); + // Add outputs from Round 3 to the hash buffer. + hasher.add_element(round_three_out.t_poly_at_secret_g1[lo]); + hasher.add_element(round_three_out.t_poly_at_secret_g1[mid]); + hasher.add_element(round_three_out.t_poly_at_secret_g1[hi]); + const libff::Fr zeta = hasher.get_hash(); + test_plonk_prover_round_four( + example, zeta, round_one_out, round_three_out, srs, hasher); + + // --- Unit test Prover Round 5 --- + printf("[%s:%d] Unit test Prover Round 5...\n", __FILE__, __LINE__); + round_four_out_t round_four_out = + plonk_prover::round_four( + zeta, round_one_out, round_three_out, srs, hasher); + // Clear hash buffer. + hasher.reset(); + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[a]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[b]); + hasher.add_element(round_one_out.W_polys_blinded_at_secret_g1[c]); + hasher.add_element(libff::Fr::one()); + // Add outputs from Round 2 to the hash buffer. + hasher.add_element(round_two_out.z_poly_at_secret_g1); + // Add outputs from Round 3 to the hash buffer. + hasher.add_element(round_three_out.t_poly_at_secret_g1[lo]); + hasher.add_element(round_three_out.t_poly_at_secret_g1[mid]); + hasher.add_element(round_three_out.t_poly_at_secret_g1[hi]); + // Add outputs from Round 4 to the hash buffer. + hasher.add_element(round_four_out.a_zeta); + hasher.add_element(round_four_out.b_zeta); + hasher.add_element(round_four_out.c_zeta); + hasher.add_element(round_four_out.S_0_zeta); + hasher.add_element(round_four_out.S_1_zeta); + hasher.add_element(round_four_out.z_poly_xomega_zeta); + const libff::Fr nu = hasher.get_hash(); + test_plonk_prover_round_five( + example, + alpha, + beta, + gamma, + zeta, + nu, + round_zero_out, + round_one_out, + round_two_out, + round_three_out, + round_four_out, + srs, + hasher); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template void test_plonk_srs() +{ + using Field = libff::Fr; + + ppT::init_public_params(); + + // Load test vector values from example circuit. + plonk_example example; + // random hidden element secret (toxic waste) + Field secret = example.secret; + // maximum degree of the encoded monomials in the usrs + size_t max_degree = PLONK_MAX_DEGREE; + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(example.num_gates); + + // --- USRS --- + // Compute SRS = powers of secret times G1: 1*G1, secret^1*G1, + // secret^2*G1, ... and secret times G2: 1*G2, secret^1*G2. + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + // --- SRS --- + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, + example.gates_matrix, + example.wire_permutation, + example.PI_wire_indices); + // Compare SRS against reference test values. + printf("[%s:%d] secret ", __FILE__, __LINE__); + secret.print(); + for (int i = 0; i < (int)srs.num_gates + 3; ++i) { + printf("secret_power_G1[%2d] ", i); + srs.secret_powers_g1[i].print(); + // test from generator + libff::G1 srs_secret_powers_g1_i(srs.secret_powers_g1[i]); + srs_secret_powers_g1_i.to_affine_coordinates(); + ASSERT_EQ(srs_secret_powers_g1_i.X, example.secret_powers_g1[i][0]); + ASSERT_EQ(srs_secret_powers_g1_i.Y, example.secret_powers_g1[i][1]); + } + for (int i = 0; i < 2; ++i) { + printf("secret_power_G2[%2d] ", i); + srs.secret_powers_g2[i].print(); + } +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template void test_plonk_prover() +{ + using Field = libff::Fr; + + ppT::init_public_params(); + // Load test vector values from example circuit. + plonk_example example; + // Random hidden element secret (toxic waste). + Field secret = example.secret; + // example witness + std::vector witness = example.witness; + // hard-coded values for the "random" blinding constants from + // example circuit + std::vector> blind_scalars = example.prover_blind_scalars; + // maximum degree of the encoded monomials in the usrs + size_t max_degree = PLONK_MAX_DEGREE; + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(example.num_gates); + + // Perepare srs. + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, + example.gates_matrix, + example.wire_permutation, + example.PI_wire_indices); + + // Initialize hasher. + transcript_hasher hasher; + + // Initialize prover. + plonk_prover prover; + // Compute proof. + plonk_proof proof = + prover.compute_proof(srs, witness, blind_scalars, hasher); + // Compare proof against test vector values (debug). + ASSERT_EQ(proof.a_zeta, example.a_zeta); + ASSERT_EQ(proof.b_zeta, example.b_zeta); + ASSERT_EQ(proof.c_zeta, example.c_zeta); + ASSERT_EQ(proof.S_0_zeta, example.S_0_zeta); + ASSERT_EQ(proof.S_1_zeta, example.S_1_zeta); + ASSERT_EQ(proof.z_poly_xomega_zeta, example.z_poly_xomega_zeta); + ASSERT_EQ(proof.r_zeta, example.r_zeta); + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("W_polys_at_secret_g1[%d]\n", i); + proof.W_polys_blinded_at_secret_g1[i].print(); + libff::G1 W_polys_blinded_at_secret_g1_i( + proof.W_polys_blinded_at_secret_g1[i]); + W_polys_blinded_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ( + W_polys_blinded_at_secret_g1_i.X, + example.W_polys_blinded_at_secret_g1[i][0]); + ASSERT_EQ( + W_polys_blinded_at_secret_g1_i.Y, + example.W_polys_blinded_at_secret_g1[i][1]); + } + proof.z_poly_at_secret_g1.print(); + libff::G1 z_poly_at_secret_g1_aff(proof.z_poly_at_secret_g1); + z_poly_at_secret_g1_aff.to_affine_coordinates(); + ASSERT_EQ(z_poly_at_secret_g1_aff.X, example.z_poly_at_secret_g1[0]); + ASSERT_EQ(z_poly_at_secret_g1_aff.Y, example.z_poly_at_secret_g1[1]); + for (int i = 0; i < (int)NUM_HSETS; ++i) { + printf("[%s:%d] t_poly_at_secret_g1[%d]\n", __FILE__, __LINE__, i); + proof.t_poly_at_secret_g1[i].print(); + libff::G1 t_poly_at_secret_g1_i(proof.t_poly_at_secret_g1[i]); + t_poly_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ(t_poly_at_secret_g1_i.X, example.t_poly_at_secret_g1[i][0]); + ASSERT_EQ(t_poly_at_secret_g1_i.Y, example.t_poly_at_secret_g1[i][1]); + } + proof.W_zeta_at_secret.print(); + libff::G1 W_zeta_at_secret_aff(proof.W_zeta_at_secret); + W_zeta_at_secret_aff.to_affine_coordinates(); + ASSERT_EQ(W_zeta_at_secret_aff.X, example.W_zeta_at_secret[0]); + ASSERT_EQ(W_zeta_at_secret_aff.Y, example.W_zeta_at_secret[1]); + proof.W_zeta_omega_at_secret.print(); + libff::G1 W_zeta_omega_at_secret_aff(proof.W_zeta_omega_at_secret); + W_zeta_omega_at_secret_aff.to_affine_coordinates(); + ASSERT_EQ(W_zeta_omega_at_secret_aff.X, example.W_zeta_omega_at_secret[0]); + ASSERT_EQ(W_zeta_omega_at_secret_aff.Y, example.W_zeta_omega_at_secret[1]); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template +void test_plonk_verifier_preprocessed_input( + const plonk_example &example, const srs &srs) +{ + // Compute verifier preprocessed input. + const verifier_preprocessed_input_t preprocessed_input = + plonk_verifier::preprocessed_input(srs); + + for (int i = 0; i < (int)srs.Q_polys.size(); ++i) { + printf("srs.Q_polys_at_secret_G1[%d] \n", i); + preprocessed_input.Q_polys_at_secret_g1[i].print(); + libff::G1 Q_poly_at_secret_g1_i( + preprocessed_input.Q_polys_at_secret_g1[i]); + Q_poly_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ(Q_poly_at_secret_g1_i.X, example.Q_polys_at_secret_g1[i][0]); + ASSERT_EQ(Q_poly_at_secret_g1_i.Y, example.Q_polys_at_secret_g1[i][1]); + } + for (int i = 0; i < (int)srs.S_polys.size(); ++i) { + printf("S_polys_at_secret_G1[%d] \n", i); + preprocessed_input.S_polys_at_secret_g1[i].print(); + libff::G1 S_poly_at_secret_g1_i( + preprocessed_input.S_polys_at_secret_g1[i]); + S_poly_at_secret_g1_i.to_affine_coordinates(); + ASSERT_EQ(S_poly_at_secret_g1_i.X, example.S_polys_at_secret_g1[i][0]); + ASSERT_EQ(S_poly_at_secret_g1_i.Y, example.S_polys_at_secret_g1[i][1]); + } +} + +template +void test_plonk_verifier_step_five( + const plonk_example &example, + const step_four_out_t &step_four_out, + std::shared_ptr>> domain) +{ + const step_five_out_t step_five_out = + plonk_verifier::step_five( + step_four_out, domain); + printf("[%s:%d] zh_zeta ", __FILE__, __LINE__); + step_five_out.zh_zeta.print(); + ASSERT_EQ(step_five_out.zh_zeta, example.zh_zeta); +} + +template +void test_plonk_verifier_step_six( + const plonk_example &example, + const step_four_out_t &step_four_out, + const srs &srs) +{ + const step_six_out_t step_six_out = + plonk_verifier::step_six(step_four_out, srs); + printf("L_0_zeta "); + step_six_out.L_0_zeta.print(); + ASSERT_EQ(step_six_out.L_0_zeta, example.L_0_zeta); +} + +template +void test_plonk_verifier_step_seven( + const plonk_example &example, + const step_four_out_t &step_four_out, + const std::vector> &PI_value_list, + const srs &srs) +{ + const step_seven_out_t step_seven_out = + plonk_verifier::step_seven( + step_four_out, PI_value_list, srs); + printf("PI_zeta "); + step_seven_out.PI_zeta.print(); + ASSERT_EQ(step_seven_out.PI_zeta, example.PI_zeta); +} + +template +void test_plonk_verifier_step_eight( + const plonk_example &example, + const step_four_out_t &step_four_out, + const step_five_out_t &step_five_out, + const step_six_out_t &step_six_out, + const step_seven_out_t &step_seven_out, + const plonk_proof &proof) +{ + const step_eight_out_t step_eight_out = + plonk_verifier::step_eight( + step_four_out, step_five_out, step_six_out, step_seven_out, proof); + ASSERT_EQ(step_eight_out.r_prime_zeta, example.r_prime_zeta); +} + +template +void test_plonk_verifier_step_nine( + const plonk_example &example, + const step_four_out_t &step_four_out, + const step_six_out_t &step_six_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs) +{ + step_nine_out_t step_nine_out = + plonk_verifier::step_nine( + step_four_out, step_six_out, proof, preprocessed_input, srs); + step_nine_out.D1.print(); + libff::G1 D1_aff(step_nine_out.D1); + D1_aff.to_affine_coordinates(); + ASSERT_EQ(D1_aff.X, example.D1[0]); + ASSERT_EQ(D1_aff.Y, example.D1[1]); +} + +template +void test_plonk_verifier_step_ten( + const plonk_example &example, + const step_four_out_t &step_four_out, + const step_nine_out_t &step_nine_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs) +{ + step_ten_out_t step_ten_out = + plonk_verifier::step_ten( + step_four_out, step_nine_out, proof, preprocessed_input, srs); + printf("[%s:%d] F1\n", __FILE__, __LINE__); + step_ten_out.F1.print(); + libff::G1 F1_aff(step_ten_out.F1); + F1_aff.to_affine_coordinates(); + ASSERT_EQ(F1_aff.X, example.F1[0]); + ASSERT_EQ(F1_aff.Y, example.F1[1]); +} + +template +void test_plonk_verifier_step_eleven( + const plonk_example &example, + const step_four_out_t &step_four_out, + const step_eight_out_t &step_eight_out, + const plonk_proof &proof) +{ + const step_eleven_out_t step_eleven_out = + plonk_verifier::step_eleven( + step_four_out, step_eight_out, proof); + printf("[%s:%d] E1\n", __FILE__, __LINE__); + step_eleven_out.E1.print(); + libff::G1 E1_aff(step_eleven_out.E1); + E1_aff.to_affine_coordinates(); + ASSERT_EQ(E1_aff.X, example.E1[0]); + ASSERT_EQ(E1_aff.Y, example.E1[1]); +} + +/// test verifier step 12 +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template +void test_plonk_verifier_pairing( + const plonk_example &example, + const libff::Fr &zeta, // step 4 + const libff::Fr &u, // step 4 + const libff::G1 &F1, // step 10 + const libff::G1 &E1, // step 11 + const plonk_proof &proof, + const srs &srs) +{ + using Field = libff::Fr; + std::vector> curve_points_lhs{ + proof.W_zeta_at_secret, proof.W_zeta_omega_at_secret}; + std::vector> scalar_elements_lhs{Field(1), u}; + libff::G1 pairing_first_lhs = + plonk_multi_exp_G1(curve_points_lhs, scalar_elements_lhs); + libff::G2 pairing_second_lhs = srs.secret_powers_g2[1]; + std::vector> curve_points_rhs{ + proof.W_zeta_at_secret, proof.W_zeta_omega_at_secret, F1, E1}; + std::vector> scalar_elements_rhs{ + // Warning! raise to the power of -1 to check e() * e()^-1 = 1 + Field(-1) * zeta, + Field(-1) * u * zeta * srs.omega_roots[base][1], + Field(-1) * Field(1), + Field(-1) * Field(-1)}; + + libff::G1 pairing_first_rhs = + plonk_multi_exp_G1(curve_points_rhs, scalar_elements_rhs); + libff::G2 pairing_second_rhs = srs.secret_powers_g2[0]; + + const libff::G1_precomp _A = ppT::precompute_G1(pairing_first_lhs); + const libff::G2_precomp _B = ppT::precompute_G2(pairing_second_lhs); + const libff::G1_precomp _C = ppT::precompute_G1(pairing_first_rhs); + const libff::G2_precomp _D = ppT::precompute_G2(pairing_second_rhs); + const libff::Fqk miller_result = + ppT::double_miller_loop(_A, _B, _C, _D); + const libff::GT result = ppT::final_exponentiation(miller_result); + bool b_accept = (result == libff::GT::one()); + + printf("[%s:%d] pairing_first_lhs\n", __FILE__, __LINE__); + pairing_first_lhs.print(); + libff::G1 pairing_first_lhs_aff(pairing_first_lhs); + pairing_first_lhs_aff.to_affine_coordinates(); + ASSERT_EQ(pairing_first_lhs_aff.X, example.pairing_first_lhs[0]); + ASSERT_EQ(pairing_first_lhs_aff.Y, example.pairing_first_lhs[1]); + + printf("[%s:%d] pairing_first_rhs\n", __FILE__, __LINE__); + pairing_first_rhs.print(); + libff::G1 pairing_first_rhs_aff(pairing_first_rhs); + pairing_first_rhs_aff.to_affine_coordinates(); + ASSERT_EQ(pairing_first_rhs_aff.X, example.pairing_first_rhs[0]); + ASSERT_EQ(pairing_first_rhs_aff.Y, example.pairing_first_rhs[1]); + + ASSERT_TRUE(b_accept); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template void test_plonk_verifier_steps() +{ + using Field = libff::Fr; + + ppT::init_public_params(); + // Load test vector values from example circuit. + plonk_example example; + // Random hidden element secret (toxic waste). + Field secret = example.secret; + // Example witness + std::vector witness = example.witness; + // Hard-coded values for the "random" blinding constants from. + // example circuit + std::vector> blind_scalars = example.prover_blind_scalars; + // Maximum degree of the encoded monomials in the usrs. + size_t max_degree = PLONK_MAX_DEGREE; + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(example.num_gates); + + // Prepare srs. + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, + example.gates_matrix, + example.wire_permutation, + example.PI_wire_indices); + + // Initialize hasher. + transcript_hasher hasher; + + // Initialize prover. + plonk_prover prover; + // Compute proof. + plonk_proof proof = + prover.compute_proof(srs, witness, blind_scalars, hasher); + + // Clear the hasher buffer in order to re-use the same + // transcript_hasher object for the verifier. + hasher.reset(); + + // Unit test verifier preprocessed input. + test_plonk_verifier_preprocessed_input( + example, srs); + + // Prepare the list of PI values for the example circuit. + std::vector PI_value_list; + for (size_t i = 0; i < example.PI_wire_indices.size(); i++) { + Field PI_value = example.witness[example.PI_wire_indices[i]]; + PI_value_list.push_back(PI_value); + } + + // Compute step 4 + const step_four_out_t step_four_out = + plonk_verifier::step_four(proof, hasher); + + // unit test verifier step 5 + test_plonk_verifier_step_five( + example, step_four_out, domain); + + // unit test verifier step 6 + test_plonk_verifier_step_six( + example, step_four_out, srs); + + // unit test verifier step 7 + test_plonk_verifier_step_seven( + example, step_four_out, PI_value_list, srs); + + // unit test verifier step 8 + const step_five_out_t step_five_out = + plonk_verifier::step_five( + step_four_out, domain); + const step_six_out_t step_six_out = + plonk_verifier::step_six(step_four_out, srs); + const step_seven_out_t step_seven_out = + plonk_verifier::step_seven( + step_four_out, PI_value_list, srs); + test_plonk_verifier_step_eight( + example, + step_four_out, + step_five_out, + step_six_out, + step_seven_out, + proof); + + // unit test verifier step 9 + const verifier_preprocessed_input_t preprocessed_input = + plonk_verifier::preprocessed_input(srs); + test_plonk_verifier_step_nine( + example, step_four_out, step_six_out, proof, preprocessed_input, srs); + + // unit test verifier step 10 + step_nine_out_t step_nine_out = + plonk_verifier::step_nine( + step_four_out, step_six_out, proof, preprocessed_input, srs); + test_plonk_verifier_step_ten( + example, step_four_out, step_nine_out, proof, preprocessed_input, srs); + + // unit test verifier step 11 + const step_eight_out_t step_eight_out = + plonk_verifier::step_eight( + step_four_out, step_five_out, step_six_out, step_seven_out, proof); + test_plonk_verifier_step_eleven( + example, step_four_out, step_eight_out, proof); + + // unit test verifier pairing (step 12) + step_ten_out_t step_ten_out = + plonk_verifier::step_ten( + step_four_out, step_nine_out, proof, preprocessed_input, srs); + const step_eleven_out_t step_eleven_out = + plonk_verifier::step_eleven( + step_four_out, step_eight_out, proof); + test_plonk_verifier_pairing( + example, + step_four_out.zeta, + step_four_out.u, + step_ten_out.F1, + step_eleven_out.E1, + proof, + srs); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template void test_plonk_verifier() +{ + using Field = libff::Fr; + + ppT::init_public_params(); + // Load test vector values from example circuit. + plonk_example example; + // Random hidden element secret (toxic waste). + Field secret = example.secret; + // Example witness. + std::vector witness = example.witness; + // Hard-coded values for the "random" blinding constants from + // example circuit. + std::vector> blind_scalars = example.prover_blind_scalars; + // Maximum degree of the encoded monomials in the usrs. + size_t max_degree = PLONK_MAX_DEGREE; + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(example.num_gates); + + // Prepare srs. + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( + usrs, + example.gates_matrix, + example.wire_permutation, + example.PI_wire_indices); + + // Initialize hasher. + transcript_hasher hasher; + + // Initialize prover. + plonk_prover prover; + // Compute proof. + plonk_proof proof = + prover.compute_proof(srs, witness, blind_scalars, hasher); + + // Clear the hasher buffer in order to re-use the same + // transcript_hasher object for the verifier. + hasher.reset(); + + // Initialize verifier. + plonk_verifier verifier; + // Prepare the list of PI values for the example circuit. + std::vector PI_value_list; + for (size_t i = 0; i < example.PI_wire_indices.size(); i++) { + Field PI_value = example.witness[example.PI_wire_indices[i]]; + PI_value_list.push_back(PI_value); + } + // Verify proof. + bool b_valid_proof = + verifier.verify_proof(proof, srs, PI_value_list, hasher); + ASSERT_TRUE(b_valid_proof); + + // Clear the hasher buffer in order to re-use the same + // transcript_hasher object. + hasher.reset(); + // Assert that proof verification fails when the proof is + // manipulated. + test_verify_invalid_proof(proof, srs, PI_value_list, hasher); +} + +/// \attention the example class is defined specifically for the BLS12-381 +/// curve, so make sure we are using this curve +template void test_plonk_gates_matrix_transpose() +{ + using Field = libff::Fr; + // Load gates matrix from example circuit. + plonk_example example; + std::vector> gates_matrix_transpose = + plonk_gates_matrix_transpose(example.gates_matrix); + ASSERT_EQ(gates_matrix_transpose, example.gates_matrix_transpose); +} + +// We test the example circuit y^2 = x mod r where x is a public input +// and y is the witness. Thus the circuit shows that x is a quadratic +// residue in the field Fr. For example x=49, y=7. +// +// The circuit is represented by one public input (PI) and one +// multiplication gate. According to the Plonk arithmetization rules, +// each gate (including the PI) is represented as: +// +// qL a + qR b + qO c + qM ab + qC = 0 +// +// where qL,qR,qO,qM,qC are the selector polynomials and +// a,b,c are respectively the vectors of left inputs, right inputs +// and outputs to the gates. +// +// Using the above representation, the PI and the multiplication +// gates are given resp. as +// +// PI: 1 a1 + 0 b1 + 0 c1 + 0 a1 b1 + 0 ( + PI) = 0 +// MUL: 0 a2 + 0 b2 + (-1) c2 + 1 a2 b2 + 0 = 0 +// +// where ("/" means "unused"): +// +// a = (a1, a2) = (x, y) +// b = (b1, b2) = (/, y) +// c = (c1, c2) = (/, x) +// qL = (1, 0) +// qR = (0, 0) +// qO = (0, -1) +// qM = (0, 1) +// qC = (0, 0) +// +// The selector polynomials as given above define the following gates +// matrix: +// +// qL qR qO qM qC +// 1 0 0 0 0 +// 0 0 -1 1 0 +// +// To compute the permutation of inputs and outputs that would reflect +// the copy-constraints, note the following. Of all the elements of +// the input/output vector V = (a1, a2, b1, b2, c1, c2), the circuit +// uses a1=x, a2=y, b2=a2=y and c2=a1=x, while b1 and c1 are not used +// (see the variables with non-zero coefficients in the PI and MUL +// equations above). If we number the elements of V from 1 to 6, the +// permutation reflecting the copy constraints should be (6, 4, 3, 2, +// 5, 1) i.e. 6 and 1 are permuted to reflect c2=a1; 4 and 2 are +// permuted to reflect b2=a2 and 3 and 5 remain in place to reflect +// that b1 and c1 are not used. Equivalently, the permutation is ("/" +// means "unused"): +// +// ( x y / y / x) +// (a1 a2 b1 b2 c1 c2) -> (1 2 3 4 5 6) +// (c2 b2 b1 a2 c1 a1) -> (6 4 3 2 5 1) +template void test_plonk_simple_circuit() +{ + ppT::init_public_params(); + + using Field = libff::Fr; + + // 0 Arithmetization of test circuit y^2 = x mod r + + // The number of gates is 2: one public input (PI) gate and one + // multiplication gate. This number is also conveniently a power of + // 2 (needed for the FFT/iFFT). + const size_t num_gates = 2; + // The example circuit has 1 public input + const size_t num_public_inputs = 1; + std::vector> gates_matrix = + plonk_prepare_gates_matrix(num_public_inputs); + ASSERT_EQ(gates_matrix.size(), num_public_inputs); + // Add the multiplication gate + const std::vector MUL_gate{0, 0, -1, 1, 0}; + gates_matrix.push_back(MUL_gate); + ASSERT_EQ(gates_matrix.size(), 2); + ASSERT_EQ(gates_matrix[0].size(), 5); + // Hard-code the wire permutation for the tested circuit. Note + // that counting of indices starts from 1. TODO: implement a + // general function to compute the wire permutation for any + // circuit. + std::vector wire_permutation{6, 4, 3, 2, 5, 1}; + // maximum degree of the encoded monomials in the usrs + const size_t max_degree = PLONK_MAX_DEGREE; + // Random hidden element kept secret (toxic waste) + Field secret = Field::random_element(); + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(num_gates); + + // 1 Generate SRS + + // Compute usrs. + usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); + // Compute srs. + srs srs = plonk_srs_derive_from_usrs( + usrs, gates_matrix, wire_permutation, num_public_inputs); + + // 2 Compute proof + + // Initialize prover hasher. + transcript_hasher prover_hasher; + // Prepare witness vector w = a+b+c. + std::vector witness{49, 7, 1, 7, 49, 49}; + // Nine random blinding constants for the prover polynomials. + std::vector> blind_scalars; + const size_t nscalars = 9; + for (size_t i = 0; i < nscalars; ++i) { + Field r = Field::random_element(); + blind_scalars.push_back(r); + } + // initialize prover + plonk_prover prover; + // compute proof + plonk_proof proof = + prover.compute_proof(srs, witness, blind_scalars, prover_hasher); + + // 3 Verify proof + + // Initialize verifier hasher. + transcript_hasher verifier_hasher; + // Prepare the list of PI values for the example circuit. + std::vector PI_value_list = + plonk_public_input_values(witness, num_public_inputs); + // Initialize verifier. + plonk_verifier verifier; + // Verify proof. + bool b_valid_proof = + verifier.verify_proof(proof, srs, PI_value_list, verifier_hasher); + ASSERT_TRUE(b_valid_proof); +} + +template void test_plonk_prepare_gates_matrix() +{ + using Field = libff::Fr; + // Test for 10 random values of num_public_inputs + const size_t ntests = 10; + for (size_t i = 0; i < ntests; ++i) { + const size_t num_public_inputs = random() % 100; + std::vector> gates_matrix = + plonk_prepare_gates_matrix(num_public_inputs); + ASSERT_EQ(gates_matrix.size(), num_public_inputs); + // Assert that the PI gates are located at the top N rows of the gates + // matrix + const std::vector PI_gate{1, 0, 0, 0, 0}; + for (size_t i = 0; i < num_public_inputs; ++i) { + ASSERT_EQ(gates_matrix[i], PI_gate); + } + } +} + +// generic test for all curves +template void test_plonk_constants_k1_k2() +{ + ppT::init_public_params(); + + using Field = libff::Fr; + + // Check that plonk_generate_constants_k1_k2 generates valid k1 + // and k2. + Field k1, k2; + plonk_generate_constants_k1_k2(k1, k2); + ASSERT_TRUE(plonk_are_valid_constants_k1_k2(k1, k2)); + + // Store the valid choices for k1,k2. + const Field k1_valid = k1; + const Field k2_valid = k2; + // Example value for the number of gates (must be power of 2). + const size_t num_gates = 8; + // Generate the n-th root of unity used as a generator of the + // multiplicative group H of size num_gates. + const Field omega_base = libff::get_root_of_unity(num_gates); + + // Check that plonk_are_valid_constants_k1_k2 correctly detects + // invalid combinations of k1 and k2. + for (size_t ipower = 1; ipower <= num_gates; ++ipower) { + // Set k2 to an element of k1H: k2=k1*(omega^i). + k1 = k1_valid; + k2 = k1 * (omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k1 to an element of k2H: k1=k2*(omega^i). + k2 = k2_valid; + k1 = k2 * (omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k1 to an element of H: k1=omega^i. + k1 = (omega_base ^ ipower); + k2 = k2_valid; + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k2 to an element of H: k2=omega^i. + k1 = k1_valid; + k2 = (omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + } +} + +// generic test for all curves for an alternative method to generate +// constants k1,k2 using randomization +template void test_plonk_random_constants_k1_k2() +{ + ppT::init_public_params(); + + using Field = libff::Fr; + Field k1, k2; + plonk_generate_random_constants_k1_k2(k1, k2); + ASSERT_TRUE(plonk_are_valid_constants_k1_k2(k1, k2)); +} + +// test specific to BLS12-381 (uses class example specific to +// BLS12-381) +void test_plonk_constants_k1_k2_bls12_381() +{ + using ppT = libff::bls12_381_pp; + using Field = libff::Fr; + Field k1, k2; + + // Check that the example k1 and k2 are valid (according to + // plonk_are_valid_constants_k1_k2. + plonk_example example; + k1 = example.k1; + k2 = example.k2; + ASSERT_TRUE(plonk_are_valid_constants_k1_k2(k1, k2)); + + // Check that plonk_are_valid_constants_k1_k2 correctly detects + // invalid combinations of k1 and k2. + for (size_t i = 1; i <= example.num_gates; ++i) { + size_t ipower = i; + // Set k2 to an element of k1H: k2=k1*(omega^i). + k1 = example.k1; + k2 = k1 * (example.omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k1 to an element of k2H: k1=k2*(omega^i). + k2 = example.k2; + k1 = k2 * (example.omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k1 to an element of H: k1=omega^i. + k1 = (example.omega_base ^ ipower); + k2 = example.k2; + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + // Set k2 to an element of H: k2=omega^i. + k1 = example.k1; + k2 = (example.omega_base ^ ipower); + ASSERT_FALSE(plonk_are_valid_constants_k1_k2(k1, k2)); + } +} + +TEST(TestPlonk, Edwards) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + // TODO add test_plonk_simple_circuit +} + +TEST(TestPlonk, BN128) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + // TODO add test_plonk_simple_circuit +} + +TEST(TestPlonk, Mnt4) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_simple_circuit< + libff::mnt4_pp, + dummy_transcript_hasher>(); +} + +TEST(TestPlonk, Mnt6) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_simple_circuit< + libff::mnt6_pp, + dummy_transcript_hasher>(); +} + +TEST(TestPlonk, BW6_761) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_simple_circuit< + libff::bw6_761_pp, + dummy_transcript_hasher>(); +} + +TEST(TestPlonk, ALT_BN128) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_simple_circuit< + libff::alt_bn128_pp, + dummy_transcript_hasher>(); +} + +TEST(TestPlonk, BLS12_377) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_simple_circuit< + libff::bls12_377_pp, + dummy_transcript_hasher>(); +} + +TEST(TestPlonk, BLS12_381) +{ + test_plonk_constants_k1_k2(); + test_plonk_random_constants_k1_k2(); + test_plonk_constants_k1_k2_bls12_381(); + test_plonk_srs(); + test_plonk_prover_rounds< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_prover< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_verifier_steps< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_verifier< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_gates_matrix_transpose(); + test_plonk_prepare_gates_matrix(); + test_plonk_simple_circuit< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_simple_circuit< + libff::bls12_381_pp, + dummy_transcript_hasher>(); +} + +} // namespace libsnark diff --git a/libsnark/zk_proof_systems/plonk/utils.hpp b/libsnark/zk_proof_systems/plonk/utils.hpp new file mode 100644 index 000000000..52a7bd1f6 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/utils.hpp @@ -0,0 +1,259 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_HPP_ + +#include "libsnark/zk_proof_systems/plonk/tests/example.hpp" + +#include +#include +#include +#include + +/// Declaration of common untility functions for ppzkSNARK proof +/// system Plonk. The implementation instantiates the protocol of +/// PlonK \[GWC19], +/// +/// References: +/// - \[GWC19]: +/// Title: "Plonk: Permutations over lagrange-bases for oecumenical +/// noninteractive arguments of knowledge", Ariel Gabizon, Zachary +/// J. Williamson, and Oana Ciobotaru, Cryptology ePrint Archive, +/// Report 2019/953, 2019, + +/// number of H-sets: H, Hk1 and Hk2, where H is a subgroup, while Hk1 +/// and Hk2 are cosets +const size_t NUM_HSETS = 3; + +namespace libsnark +{ + +enum W_polys_id { a, b, c }; +enum Q_polys_id { L, R, M, O, C }; +enum t_polys_id { lo, mid, hi }; +enum omega_id { base, base_k1, base_k2 }; + +/***************************** Main algorithms *******************************/ + +/// print the elements of a vector +template void print_vector(const std::vector &v); + +/// Interpolate a polynomial from a set of points through inverse FFT +/// +/// INPUT: +/// +/// \param[in] f_points[0..n-1]: a set of points (0,y0), (1,y1), +/// ... (n-1,y_{n-1}) s.t. y0=f_points[0], y1=f_points[1], +/// ... which we want to interpolate as a polynomial +/// \param[in] domain: libfqfft evaluation domain (see +/// libfqfft/evaluation_domain/evaluation_domain.hpp) +/// +/// OUTPUT: +/// +/// \param[out] f_poly[0..n-1]: the coefficients [a0, a1, ..., a_{n-1}] +/// of the polynomial f(x) interpolating the set of points +/// f_points. For example if f_poly[0..n-1] = [a0, a1, ..., +/// a_{n-1}] then this represents the polynomial f(x) = +/// a0+a1x+a1x^2+...+a_{n-1}x^{n-1} such that +/// f(omega_i)=f_points[i], where omega_0, ..., omega_{n-1} +/// are the n roots of unity. +/// +/// \note uses libfqfft iFFT for the interpolation +template +void plonk_interpolate_polynomial_from_points( + const std::vector &f_points, + polynomial &f_poly, + std::shared_ptr> domain); + +/// Compute the selector polynomials of the given circuit (also +/// called here "q-polynomials"). See Sect. 8.1. The matrix +/// gates_matrix_transpose has 5 rows, each corresponding to the +/// values L, R, M, O and C for each gate; the number of columns is +/// equal to the number of gates. L_basis is the Lagrange basis. +template +std::vector> plonk_compute_selector_polynomials( + const size_t &num_gates, + const std::vector> &gates_matrix_transpose, + std::shared_ptr> domain); + +/// This function computes the sets H, k1H, k2H. H is a +/// multiplicative subgroup containing the n-th roots of unity in Fr, +/// where \omega is a primitive n-th root of unity and a generator of +/// H i.e H = {1, \omega, ..., \omega^{n-1}}. k1, k2 \in Fr are chosen +/// such that H, H k1, H k2 are distinct cosets of H in Fr, and thus +/// consist of 3n distinct elements. \see [GWC19] pp26 (top). +template +void plonk_compute_roots_of_unity_omega( + const size_t num_gates, + const FieldT k1, + const FieldT k2, + std::vector> &omega); + +/// This function computes the sets H, k1H, k2H, where H is a +/// multiplicative subgroup containing the n-th roots of unity in Fr and +/// \omega is a primitive n-th root of unity and a generator of +/// H ie. H = {1, \omega, ..., \omega^{n-1}}. k1, k2 \in Fr are chosen +/// such that H, H k1, H k2 are distinct cosets of H in Fr, and thus +/// consist of 3n distinct elements. \see [GWC19] pp26 (top) and Sect. 8. +/// +/// \note uses plonk_compute_roots_of_unity_omega +template +void plonk_compute_cosets_H_k1H_k2H( + const size_t num_gates, + const FieldT k1, + const FieldT k2, + std::vector &H_prime); + +/// Permute the multiplicative subgroup H according to the wire +/// permutation: (see [GWC19] Sect. 8), \see +/// plonk_compute_roots_of_unity_omega, \see +/// plonk_roots_of_unity_omega_to_subgroup_H +template +std::vector plonk_permute_subgroup_H( + const std::vector &H_prime, + const std::vector &wire_permutation, + const size_t num_gates); + +/// Compute the permutation polynomials S_sigma_1, S_sigma_2, +/// S_sigma_2 (see [GWC19], Sect. 8.1). +template +std::vector> plonk_compute_permutation_polynomials( + const std::vector &H_prime_permute, + const size_t num_gates, + std::shared_ptr> domain); + +// A wrapper for multi_exp_method_BDLO12_signed() dot-product a +// vector of group elements in G1 (curve points) with a vector of +// scalar elements in Fr +template +libff::G1 plonk_multi_exp_G1( + const std::vector> &curve_points, + const std::vector> &scalar_elements); + +/// Evaluate a polynomial F at the encrypted secret input +/// \secret^i*G_1 ie. compute f(\secret)*G1 = [f(\secret)]_i +/// +/// INPUT +/// +/// \param[in] secret_powers_g1: \secret^i*G1: +/// 0\le{i} +libff::G1 plonk_evaluate_poly_at_secret_G1( + const std::vector> &secret_powers_g1, + const polynomial> &f_poly); + +/// Evaluate a list of polynomials in the encrypted secret input: see +/// plonk_evaluate_poly_at_secret_G1. +template +void plonk_evaluate_polys_at_secret_G1( + const std::vector> &secret_powers_g1, + const std::vector>> &Q_polys, + std::vector> &Q_polys_at_secret_g1); + +/// Compute the factors in the product of the permutation polynomial +/// z(X) in Prover Round 2. Note that accumulator A[0]=1 and A[i], i>0 +/// is computed from values at i-1 for witness[i-1], H_prime[i-1], +/// H_prime_permute[i-1]m etc. +template +FieldT plonk_compute_accumulator_factor( + const size_t i, + const size_t n, // nconstraimts + const FieldT beta, + const FieldT gamma, + const std::vector &witness, + const std::vector &H_prime, // H, Hk1, Hk2 + const std::vector &H_prime_permute, + const std::vector &A); + +/// A: accumulatro vector +template +std::vector plonk_compute_accumulator( + const size_t num_gates, + const FieldT beta, + const FieldT gamma, + const std::vector &witness, + const std::vector &H_prime, // H, Hk1, Hk2 + const std::vector &H_prime_permute); + +/// transpose the gates matrix +template +std::vector> plonk_gates_matrix_transpose( + const std::vector> &gates_matrix, + const size_t &nrows, + const size_t &ncols); + +/// Deterministically compute values for the constants k1,k2 \in Fr +/// (section 8.1 [GWC19]) as k1=g^{2s}, k2=nqr, where s is the largest +/// power of 2 such that 2^s < p and 2^{s+1} > p, p is the prime +/// modulus of Fr and nqr is a quadratic nonresidue in Fr^*. Both s +/// and nqr are members of the class Fp_model defined in +/// libff/libff/algebra/fields/fp.hpp . +template +void plonk_generate_constants_k1_k2(FieldT &k1_result, FieldT &k2_result); + +/// Generate values for the constants k1,k2 \in Fr (section 8.1 +/// [GWC19]) by trying random values until the following three +/// conditions are simultaneously satisfied: +/// +/// 1) k1^n != 1 ensuring that k1 \notin H +/// 2) k2^n != 1 ensuring that k2 \notin H +/// 3) (k1 k2^-1)^n != 1 ensuring that k2H \notin k1H (and vice-versa) +template +void plonk_generate_random_constants_k1_k2( + FieldT &k1_result, FieldT &k2_result); + +/// Check that the constants k1,k2 \in Fr (section 8.1 [GWC19]) are +/// valid. This is the case if the sets H, k1H, k2H are +/// non-overlapping, where H = {w, w^1, w^2, ..., w^n} is a +/// multiplicative subgroup of Fr of order n with generator w (denoted +/// as \omega in [GWC19]). w is a primitive n-th root of unity in Fr +/// and n is of the form 2^s where s is the largest power of 2 such +/// that 2^s | (r-1), where r is the prime modulus of Fr. See also +/// Section 8.1 in [GWC19]. +template +bool plonk_are_valid_constants_k1_k2(const FieldT &k1, const FieldT &k2); + +/// Return a matrix with top N rows filled in with the public inputs +/// selector vectors where N = num_public_inputs is the number of +/// public inputs passed as an input parameter by the caller. +template +std::vector>> plonk_prepare_gates_matrix( + const size_t &num_public_inputs); + +/// Extract the values corresponing to the public inputs from the +/// witness using the respective wire indices passed as input. Those +/// values are passed on to the verifier together with the proof. +template +std::vector> plonk_public_input_values_from_indices( + const std::vector> &witness, + const std::vector &PI_wire_indices); + +/// A wrapper for plonk_public_input_values_from_indices. Extracts the +/// values corresponing to the public inputs from the witness, assuming +/// that they are in the first num_public_inputs positions. In other +/// words extracts the values of the public inputs given the *length* of +/// the public input vector. +template +std::vector> plonk_public_input_values( + const std::vector> &witness, + const size_t &num_public_inputs); + +} // namespace libsnark + +#include "libsnark/zk_proof_systems/plonk/utils.tcc" + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/utils.tcc b/libsnark/zk_proof_systems/plonk/utils.tcc new file mode 100644 index 000000000..bb91002c6 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/utils.tcc @@ -0,0 +1,442 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_TCC_ + +// Implementation of interfaces for a ppzkSNARK for Plonk. See +// utils.hpp . + +namespace libsnark +{ + +template void print_vector(const std::vector &v) +{ + for (size_t i = 0; i < v.size(); ++i) { + printf("[%2d]: ", (int)i); + v[i].print(); + } +} + +template +void plonk_compute_lagrange_basis( + const size_t npoints, std::vector> &L) +{ + assert(L.size() != 0); + assert(L.size() == L[0].size()); + assert(L.size() == npoints); + + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(npoints); + for (size_t i = 0; i < npoints; ++i) { + polynomial u(npoints, FieldT(0)); + u[i] = FieldT(1); + // compute i-th Lagrange basis vector via inverse FFT + domain->iFFT(u); + L[i] = u; + } +} + +template +void plonk_interpolate_polynomial_from_points( + const std::vector &f_points, + polynomial &f_poly, + std::shared_ptr> domain) +{ + f_poly = f_points; + domain->iFFT(f_poly); +} + +template +std::vector> plonk_compute_selector_polynomials( + const size_t &num_gates, + const size_t &num_qpolys, + const std::vector> &gates_matrix_transpose, + std::shared_ptr> domain) +{ + assert(gates_matrix_transpose.size() == num_qpolys); + assert(gates_matrix_transpose[0].size() == num_gates); + + std::vector> Q_polys; + Q_polys.resize(num_qpolys, polynomial(num_gates)); + for (size_t i = 0; i < num_qpolys; ++i) { + std::vector q_vec = gates_matrix_transpose[i]; + plonk_interpolate_polynomial_from_points( + q_vec, Q_polys[i], domain); + } + return Q_polys; +}; + +template +void plonk_compute_public_input_polynomial( + const std::vector &PI_points, + polynomial &PI_poly, + std::shared_ptr> domain) +{ + plonk_interpolate_polynomial_from_points( + PI_points, PI_poly, domain); +}; + +template +void plonk_compute_roots_of_unity_omega( + const size_t num_gates, + const FieldT k1, + const FieldT k2, + std::vector> &omega) +{ + // Ensure that num_gates is not 0 and is power of 2. + // TODO: check also that it's less than 2^(ppT::s) + bool b_nonzero = (num_gates > 0); + bool b_is_power2 = ((num_gates & (num_gates - 1)) == 0); + if (!(b_nonzero && b_is_power2)) { + throw std::invalid_argument( + "Number of gates not power of 2 or is zero."); + } + omega.resize(NUM_HSETS, std::vector(num_gates)); + + // Get the n-th root of unity omega in Fq (n=8 is the number of + // constraints in the example). omega is a generator of the + // multiplicative subgroup H. Example (2**32)-th primitive root + // of unity in the base field Fq of bls12-381 i.e. such that + // omega_base**(2**32) = 1. The bls12-381 prime q is such that any + // power of 2 divides (q-1). In particular 2**32|(q-1) and so the + // 2**32-th root of unity exists. + FieldT omega_base = libff::get_root_of_unity(num_gates); + FieldT omega_i = 1; + for (size_t i = 0; i < num_gates; ++i) { + omega[base][i] = omega_i; + FieldT omega_k1_i = omega[base][i] * k1; + FieldT omega_k2_i = omega[base][i] * k2; + omega[base_k1][i] = omega_k1_i; + omega[base_k2][i] = omega_k2_i; + omega_i *= omega_base; + } +} + +template +void plonk_compute_cosets_H_k1H_k2H( + const size_t num_gates, + const FieldT k1, + const FieldT k2, + std::vector &H_prime) +{ + // Ensure that num_gates is not 0 and is power of 2. + bool b_nonzero = (num_gates > 0); + bool b_is_power2 = ((num_gates & (num_gates - 1)) == 0); + if (!(b_nonzero && b_is_power2)) { + throw std::invalid_argument( + "Number of gates not power of 2 or is zero."); + } + + // omega[0] are the n roots of unity, omega[1] are omega[0]*k1, + // omega[2] are omega[0]*k2 + std::vector> omega; + plonk_compute_roots_of_unity_omega(num_gates, k1, k2, omega); + + std::copy(omega[base].begin(), omega[base].end(), back_inserter(H_prime)); + std::copy( + omega[base_k1].begin(), omega[base_k1].end(), back_inserter(H_prime)); + std::copy( + omega[base_k2].begin(), omega[base_k2].end(), back_inserter(H_prime)); +} + +template +std::vector plonk_permute_subgroup_H( + const std::vector &H_prime, + const std::vector &wire_permutation, + const size_t num_gates) +{ + assert(H_prime.size() > 0); + std::vector H_prime_permute; + H_prime_permute.resize(NUM_HSETS * num_gates, FieldT(0)); + for (size_t i = 0; i < H_prime.size(); ++i) { + H_prime_permute[i] = H_prime[wire_permutation[i] - 1]; + } + return H_prime_permute; +} + +template +std::vector> plonk_compute_permutation_polynomials( + const std::vector &H_prime_permute, + const size_t num_gates, + std::shared_ptr> domain) +{ + assert(H_prime_permute.size() == (NUM_HSETS * num_gates)); + std::vector> S_polys; + S_polys.resize(NUM_HSETS, polynomial(num_gates)); + for (size_t i = 0; i < NUM_HSETS; ++i) { + typename std::vector::const_iterator begin = + H_prime_permute.begin() + (i * num_gates); + typename std::vector::const_iterator end = + H_prime_permute.begin() + (i * num_gates) + (num_gates); + std::vector S_points(begin, end); + plonk_interpolate_polynomial_from_points( + S_points, S_polys[i], domain); + } + return S_polys; +} + +template +libff::G1 plonk_multi_exp_G1( + const std::vector> &curve_points, + const std::vector> &scalar_elements) +{ + assert(curve_points.size() == scalar_elements.size()); + const size_t chunks = 1; + libff::G1 product = libff::multi_exp< + libff::G1, + libff::Fr, + libff::multi_exp_method_BDLO12_signed>( + curve_points.begin(), + curve_points.end(), + scalar_elements.begin(), + scalar_elements.end(), + chunks); + return product; +} + +template +libff::G1 plonk_evaluate_poly_at_secret_G1( + const std::vector> &secret_powers_g1, + const polynomial> &f_poly) +{ + assert(f_poly.size() <= secret_powers_g1.size()); + const size_t chunks = 1; + const size_t num_coefficients = f_poly.size(); + libff::G1 f_poly_at_secret_g1 = libff::multi_exp< + libff::G1, + libff::Fr, + libff::multi_exp_method_BDLO12_signed>( + secret_powers_g1.begin(), + secret_powers_g1.begin() + num_coefficients, + f_poly.begin(), + f_poly.end(), + chunks); + return f_poly_at_secret_g1; +} + +template +void plonk_evaluate_polys_at_secret_G1( + const std::vector> &secret_powers_g1, + const std::vector>> &Q_polys, + std::vector> &Q_polys_at_secret_g1) +{ + assert(secret_powers_g1.size()); + assert(Q_polys.size()); + assert(Q_polys.size() <= secret_powers_g1.size()); + const size_t npolys = Q_polys.size(); + for (size_t i = 0; i < npolys; ++i) { + Q_polys_at_secret_g1[i] = + plonk_evaluate_poly_at_secret_G1(secret_powers_g1, Q_polys[i]); + } +} + +template +FieldT plonk_compute_accumulator_factor( + const size_t i, + const size_t num_gates, + const FieldT beta, + const FieldT gamma, + const std::vector &witness, + const std::vector &H_prime, // H, Hk1, Hk2 + const std::vector &H_prime_permute, + const std::vector &A) +{ + assert(num_gates); + assert(i < num_gates); + assert(witness.size() == (NUM_HSETS * num_gates)); + assert(H_prime.size() == (NUM_HSETS * num_gates)); + assert(H_prime_permute.size() == (NUM_HSETS * num_gates)); + assert(A.size() == num_gates); + FieldT res = FieldT(1); + if (i > 0) { + FieldT nom_1 = witness[i - 1] + (beta * H_prime[i - 1]) + gamma; + FieldT den_1 = witness[i - 1] + (beta * H_prime_permute[i - 1]) + gamma; + + FieldT nom_2 = witness[num_gates + i - 1] + + (beta * H_prime[num_gates + i - 1]) + gamma; + FieldT den_2 = witness[num_gates + i - 1] + + (beta * H_prime_permute[num_gates + i - 1]) + gamma; + + FieldT nom_3 = witness[2 * num_gates + i - 1] + + (beta * H_prime[2 * num_gates + i - 1]) + gamma; + FieldT den_3 = witness[2 * num_gates + i - 1] + + (beta * H_prime_permute[2 * num_gates + i - 1]) + gamma; + + FieldT nom = nom_1 * nom_2 * nom_3; + FieldT den = den_1 * den_2 * den_3; + + res = nom * den.inverse() * A[i - 1]; + } + return res; +} + +template +std::vector plonk_compute_accumulator( + const size_t num_gates, + const FieldT beta, + const FieldT gamma, + const std::vector &witness, + const std::vector &H_prime, // H, Hk1, Hk2 + const std::vector &H_prime_permute) +{ + assert(num_gates); + assert(witness.size() == (NUM_HSETS * num_gates)); + assert(H_prime.size() == (NUM_HSETS * num_gates)); + assert(H_prime_permute.size() == (NUM_HSETS * num_gates)); + std::vector A(num_gates, FieldT(0)); + for (size_t i = 0; i < num_gates; ++i) { + A[i] = plonk_compute_accumulator_factor( + i, num_gates, beta, gamma, witness, H_prime, H_prime_permute, A); + } + return A; +} + +template +std::vector> plonk_gates_matrix_transpose( + const std::vector> &gates_matrix) +{ + const size_t nrows = gates_matrix.size(); + const size_t ncols = gates_matrix[0].size(); + const size_t nrows_transpose = ncols; + const size_t ncols_transpose = nrows; + std::vector> gates_matrix_transpose( + nrows_transpose, std::vector(ncols_transpose)); + for (size_t irow = 0; irow < nrows; ++irow) { + assert(gates_matrix[irow].size() == ncols); + for (size_t icol = 0; icol < ncols; ++icol) { + gates_matrix_transpose[icol][irow] = gates_matrix[irow][icol]; + } + } + return gates_matrix_transpose; +} + +template +void plonk_generate_constants_k1_k2(FieldT &k1, FieldT &k2) +{ + // n = 2^s: maximum order of the H subgroup that is power of 2 + const size_t n = std::pow(2, FieldT::s); + // generator of Fr^* + const FieldT g = FieldT::multiplicative_generator; + // Set k1 = g^{2s} \notin H. + k1 = g ^ n; + // Set k2 to a quadratic nonresidue of Fr^* . + k2 = FieldT::nqr; + // assert k1,k2 are valid + assert(plonk_are_valid_constants_k1_k2(k1, k2)); +} + +template +void plonk_generate_random_constants_k1_k2(FieldT &k1_result, FieldT &k2_result) +{ + // n = 2^s: maximum order of the H subgroup that is power of 2 + const size_t n = std::pow(2, FieldT::s); + FieldT k1, k2; + // choose k1 + do { + k1 = FieldT::random_element(); + } while ((k1 ^ n) == FieldT::one()); + // choose k2 + FieldT k1_over_k2; + do { + k2 = FieldT::random_element(); + k1_over_k2 = k1 * k2.inverse(); + } while (((k2 ^ n) == FieldT::one()) || + (((k1_over_k2) ^ n) == FieldT::one())); + k1_result = k1; + k2_result = k2; + // assert k1,k2 are valid + assert(plonk_are_valid_constants_k1_k2(k1, k2)); +} + +template +bool plonk_are_valid_constants_k1_k2(const FieldT &k1, const FieldT &k2) +{ + // The function checks if the following three conditions are + // simultaneously satisfied: + // + // 1) k1^n != 1 ensuring that k1 \notin H + // 2) k2^n != 1 ensuring that k2 \notin H + // 3) (k1 k2^-1)^n != 1 ensuring that k2H \notin k1H (and vice-versa) + // + // To clarify 3), note that if (k1 k2^-1)^n == 1 then \exists i: 1 <= + // i <= n: k1 = k2 w^i and so k1 \in k2H. This is because k1 = k2 w^i + // is equivalent to k1 k2^-1 = w^i, equivalent to (k1 k2^-1)^n = + // (w^i)^n = 1. The latter follows from the fact that w^i is an n-th + // root of unity in Fr (for any i: 1<=i<=n). + // + // conditions 1) and 2) are special cases of 3) for which resp. k1=1, + // k2=k1 and k1=1, k2=k2 + + // n = 2^s: maximum order of the H subgroup that is power of 2 + const size_t n = std::pow(2, FieldT::s); + const bool k1_outside_H = ((k1 ^ n) != FieldT::one()); + const bool k2_outside_H = ((k2 ^ n) != FieldT::one()); + const bool k1_over_k2_outside_H = + (((k1 * k2.inverse()) ^ n) != FieldT::one()); + + return (k1_outside_H && k2_outside_H && k1_over_k2_outside_H); +} + +// In general, the i-th row of the gates matrix contains the i-th +// component of the selector vectors q_L, q_R, q_O, q_M, q_C (see +// Section 6 [GWC19]). The i-th compoment of each selector vector is +// determined by the i-th gate of the arithmetic circuit, which can be +// one of the following: addition, multiplication, multiplication by +// constant, public input. In particular, when the i-th gate is a +// public input, the i-th components of the selector vectors are: +// +// (q_L[i], q_R[i], q_O[i], q_M[i], q_C[i]) = (1, 0, 0, 0, 0) +// +// Therefore the top N rows of the initialized gates matrix will have +// the above form. See also Section 6 [GWC19]. +template +std::vector>> plonk_prepare_gates_matrix( + const size_t &num_public_inputs) +{ + using FieldT = libff::Fr; + std::vector> gates_matrix_init; + const std::vector PI_selector_vector{1, 0, 0, 0, 0}; + for (size_t i = 0; i < num_public_inputs; ++i) { + gates_matrix_init.push_back(PI_selector_vector); + } + return gates_matrix_init; +} + +// Extract the values corresponing to the public inputs from the +// witness using the respective wire indices passed as input. Those +// values are passed on to the verifier together with the proof. +template +std::vector> plonk_public_input_values_from_indices( + const std::vector> &witness, + const std::vector &PI_wire_indices) +{ + assert(PI_wire_indices.size() <= witness.size()); + + using FieldT = libff::Fr; + std::vector PI_value_list; + for (size_t i = 0; i < PI_wire_indices.size(); i++) { + assert(PI_wire_indices[i] < witness.size()); + FieldT PI_value = witness[PI_wire_indices[i]]; + PI_value_list.push_back(PI_value); + } + return PI_value_list; +} + +template +std::vector> plonk_public_input_values( + const std::vector> &witness, const size_t &num_public_inputs) +{ + assert(num_public_inputs <= witness.size()); + return std::vector>( + witness.begin(), witness.begin() + num_public_inputs); +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_UTILS_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/verifier.hpp b/libsnark/zk_proof_systems/plonk/verifier.hpp new file mode 100644 index 000000000..6cf1babaa --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/verifier.hpp @@ -0,0 +1,392 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_HPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_HPP_ + +/// Declaration of Verifier interfaces for ppzkSNARK proof system Plonk. This +/// includes: +/// +/// - class for verifier +/// +/// References: +/// +/// - \[GWC19]: +/// Title: "Plonk: Permutations over lagrange-bases for oecumenical +/// noninteractive arguments of knowledge", Ariel Gabizon, Zachary +/// J. Williamson, and Oana Ciobotaru, Cryptology ePrint Archive, +/// Report 2019/953, 2019, + +namespace libsnark +{ + +/// SNARK proof +/// +/// (\see plonk_prover::compute_proof) +/// +/// Pi ([a]_1, [b]_1, [c]_1, [z]_1, +/// [t_lo]_1, [t_mi]_1, [t_hi]_1, +/// \bar{a}, \bar{b}, \bar{c}, +/// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w}, +/// [W_zeta]_1, [W_{zeta omega}]_1 +/// r_zeta (*)) +/// +/// Mapping code-to-paper quantities (format code : paper) +/// +/// \param W_polys_blinded_at_secret_g1[a, b, c]: [a]_1, [b]_1, [c]_1 +/// (from Round 1) +/// \param z_poly_at_secret_g1: [z]_1 (from Round 2) +/// \param t_poly_at_secret_g1[lo, mi, hi]: [t_lo]_1, [t_mi]_1, +/// [t_hi]_1 (from Round 3) +/// \param a_zeta, b_zeta, c_zeta, S_0_zeta, S_1_zeta, +/// z_poly_xomega_zeta: \bar{a}, \bar{b}, \bar{c}, +/// \bar{S_sigma1}, \bar{S_sigma2}, \bar{z_w} (from Round 4) +/// \param W_zeta_at_secret, W_zeta_omega_at_secret: [W_zeta]_1, +/// [W_{zeta omega}]_1 (from Round 5) +/// +/// Verifier preprocessed input +/// +/// \param Q_polys_at_secret_g1[0..3]: [q_M]_1, [q_L]_1, [q_R]_1, +/// [q_O]_1 +/// \param S_polys_at_secret_g1[0..2]: [S_sigma1]_1, [S_sigma2]_1, +/// [S_sigma3]_1 +/// \param srs.secret_powers_g2[1]: [secret]_2 = secret^1 * G2 +/// +/// Public input polynomial +/// +/// srs.PI_poly: w_i, 0\le{i} struct verifier_preprocessed_input_t { + /// circuit selector polynomials Q evaluated at the secret input + std::vector> Q_polys_at_secret_g1; + /// permutation polynomials S evaluated at the secret input + std::vector> S_polys_at_secret_g1; + + verifier_preprocessed_input_t( + std::vector> &&Q_polys_at_secret_g1, + std::vector> &&S_polys_at_secret_g1); +}; + +/// Verifier step 4 output +template struct step_four_out_t { + /// permutation challenge - hashes of transcript (round 1) + const libff::Fr beta; + /// permutation challenge - hashes of transcript (round 1) + const libff::Fr gamma; + /// quotinet challenge - hash of transcript (round 2) + const libff::Fr alpha; + /// evaluation challenge - hash of transcript (round 3) + const libff::Fr zeta; + /// opening challenge - hash of transcript (round 4) (denoted by v in + /// [GWC19]) + const libff::Fr nu; + /// multipoint evaluation challenge - hash of transcript (round 5) + const libff::Fr u; + step_four_out_t( + const libff::Fr &beta, + const libff::Fr &gamma, + const libff::Fr &alpha, + const libff::Fr &zeta, + const libff::Fr &nu, + const libff::Fr &u); +}; + +/// Verifier step 5 output +template struct step_five_out_t { + /// evaluation of vanishing polynomial Zh at x=zeta i.e. Zh(zeta) + libff::Fr zh_zeta; + step_five_out_t(libff::Fr &&zh_zeta); +}; + +/// Verifier step 6 output +template struct step_six_out_t { + /// Lagrange polynomial evaluation of polynomial L1 at x=zeta + /// i.e. L1(zeta) + libff::Fr L_0_zeta; + step_six_out_t(libff::Fr &&L_0_zeta); +}; + +/// Verifier step 7 output +template struct step_seven_out_t { + /// Public input polynomial PI evaluated at x=zeta .e. PI(zeta) + libff::Fr PI_zeta; + step_seven_out_t(libff::Fr &&PI_zeta); +}; + +/// Verifier step 8 output +template struct step_eight_out_t { + /// compute quotient polynomial evaluation r'(zeta) = r(zeta) - r0, + /// where r0 is a constant term Note: + libff::Fr r_prime_zeta; + step_eight_out_t(libff::Fr &&r_prime_zeta); +}; + +/// Verifier step 9 output +template struct step_nine_out_t { + /// first part of batched polynomial commitment [D]_1 + libff::G1 D1; + step_nine_out_t(libff::G1 &&D1); +}; + +/// Verifier step 10 output +template struct step_ten_out_t { + /// full batched polynomial commitment [F]_1 = [D]_1 + v [a]_1 + v^2 + /// [b]_1 + v^3 [c]_1 + v^4 [s_sigma_1]_1 + v^5 [s_sigma_2]_1 + libff::G1 F1; + step_ten_out_t(libff::G1 &&F1); +}; + +/// Verifier step 11 output +template struct step_eleven_out_t { + /// group-encoded batch evaluation [E]_1 + libff::G1 E1; + step_eleven_out_t(libff::G1 &&E1); +}; + +/// Plonk verifier. Verifies object of class plonk_proof. +template class plonk_verifier +{ + using Field = libff::Fr; + +public: + /// Verifier precomputation + /// + /// INPUT + /// \param[in] srs: structured reference string + /// + /// OUTPUT + /// \param[out] verifier_preprocessed_input: see + /// verifier_preprocessed_input_t + static verifier_preprocessed_input_t preprocessed_input( + const srs &srs); + + /// Verifier Step 1: validate that elements belong to group G1 + /// + /// \attention This validation MUST be done by the caller. Empty + /// function here for consistency with the description in [GWC19] + static void step_one(const plonk_proof &proof); + + /// Verifier Step 2: validate that elements belong to scalar field Fr + /// + /// \attention This validation MUST be done by the caller. Empty + /// function here for consistency with the description in [GWC19] + static void step_two(const plonk_proof &proof); + + /// Verifier Step 3: validate that the public input belongs to scalar + /// field Fr + /// + /// \attention This validation MUST be done by the caller. Empty + /// function here for consistency with the description in [GWC19] + static void step_three(const srs &srs); + + /// Verifier Step 4: compute challenges hashed transcript as in prover + /// description, from the common inputs, public input, and elements of + /// pi-SNARK. TODO: fixed to the test vectors for now + /// + /// INPUT + /// \param[in] proof: SNARK proof produced by the prover + /// \param[in] transcript_hasher: hashes of the communication + /// transcript after prover rounds 1,2,3,4,5. + /// + /// OUTPUT + /// \param[out] step_four_out: see step_four_out + static step_four_out_t step_four( + const plonk_proof &proof, transcript_hasher &hasher); + + /// Verifier Step 5: compute zero polynomial evaluation + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] domain: libfqfft evaluation domain (see + /// libfqfft/evaluation_domain/evaluation_domain.hpp) + /// + /// OUTPUT + /// \param[out] step_five_out: see step_five_out_t + static step_five_out_t step_five( + const step_four_out_t &step_four_out, + std::shared_ptr>> domain); + + /// Verifier Step 6: Compute Lagrange polynomial evaluation L1(zeta) + /// Note: the paper counts the L-polynomials from 1; we count from 0 + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] step_six_out: see step_six_out_t + static step_six_out_t step_six( + const step_four_out_t &step_four_out, const srs &srs); + + /// Verifier Step 7: compute public input polynomial evaluation + /// PI(zeta) + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] step_seven_out: see step_seven_out_t + static step_seven_out_t step_seven( + const step_four_out_t &step_four_out, + const std::vector &PI_value_list, + const srs &srs); + + /// Verifier Step 8: compute quotient polynomial evaluation r'(zeta) = + /// r(zeta) - r0, where r0 is a constant term \note follows the Python + /// reference implementation, which slightly deviates from the paper + /// due to the presence of the r_zeta term in the proof (not present + /// in the paper). In particular, the reference code computes and + /// uses r'(zeta) in step 8, while the paper uses r0. In addition, the + /// reference code divides r'(zeta) by the vanishing polynomial at + /// zeta zh_zeta, while the paper does not do that (see also Step 9). + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] step_five_out: see step_five_out_t + /// \param[in] step_six_out: see step_six_out_t + /// \param[in] step_seven_out: see step_seven_out_t + /// \param[in] proof: SNARK proof produced by the prover + /// + /// OUTPUT + /// \param[out] step_eight_out: see step_eight_out_t + static step_eight_out_t step_eight( + const step_four_out_t &step_four_out, + const step_five_out_t &step_five_out, + const step_six_out_t &step_six_out, + const step_seven_out_t &step_seven_out, + const plonk_proof &proof); + + /// Verifier Step 9: compute first part of batched polynomial + /// commitment [D]_1 Note: the reference implemention differs from the + /// paper -- it does not add the following term to D1, but to F1 (Step + /// 10): -Zh(zeta)([t_lo]_1 + zeta^n [t_mid]_1 + zeta^2n + /// [t_hi]_1). Instead ([t_lo]_1 + zeta^n [t_mid]_1 + zeta^2n + /// [t_hi]_1) is added to F1 in Step 10 and the multiplication by + /// Zh(zeta) is accounted for by dividing by Zh(zeta) of r'(zeta) in + /// Step 8. + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] step_six_out: see step_six_out_t + /// \param[in] proof: SNARK proof produced by the prover + /// \param[in] preprocessed_input: verifier preprocessed input + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] step_nine_out: see step_nine_out_t + static step_nine_out_t step_nine( + const step_four_out_t &step_four_out, + const step_six_out_t &step_six_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs); + + /// Verifier Step 10: compute full batched polynomial commitment [F]_1 + /// = [D]_1 + v [a]_1 + v^2 [b]_1 + v^3 [c]_1 + v^4 [s_sigma_1]_1 + + /// v^5 [s_sigma_2]_1 Note: to [F]_1 the erefernce code also adds the + /// term ([t_lo]_1 + zeta^n [t_mid]_1 + zeta^2n [t_hi]_1) which is + /// addedto [D]_1 in the paper (see commenst to Steps 8,9) + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] step_nine_out: see step_nine_out_t + /// \param[in] proof: SNARK proof produced by the prover + /// \param[in] preprocessed_input: verifier preprocessed input + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] step_ten_out: see step_ten_out_t + static step_ten_out_t step_ten( + const step_four_out_t &step_four_out, + const step_nine_out_t &step_nine_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs); + + /// Verifier Step 11: compute group-encoded batch evaluation [E]_1 + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] step_eight_out: see step_eight_out_t + /// \param[in] proof: SNARK proof produced by the prover + /// + /// OUTPUT + /// \param[out] step_eleven_out: see step_eleven_out_t + static step_eleven_out_t step_eleven( + const step_four_out_t &step_four_out, + const step_eight_out_t &step_eight_out, + const plonk_proof &proof); + + /// Verifier Step 12: batch validate all evaluations + /// + /// Checks the following equality + /// + /// e( [W_zeta]_1 + u [W_{zeta srs.omega_roots}]_1, [x]_2 ) * e( -zeta + /// [W_zeta ]_1 - u zeta srs.omega_roots [W_{zeta srs.omega_roots}]_1 + /// - [F]_1 + [E]_1, [1]_2 ) = Field(1) + /// + /// Denoted as: + /// + /// e(first_lhs, second_lhs) * e(first_rhs, second_rhs) = 1 + /// + /// INPUT + /// \param[in] step_four_out: see step_four_out_t + /// \param[in] step_ten_out: see step_ten_out_t + /// \param[in] step_eleven_out: see step_eleven_out_t + /// \param[in] proof: SNARK proof produced by the prover + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// + /// OUTPUT + /// \param[out] boolean 1/0 = valid/invalid proof + static bool step_twelve( + const step_four_out_t &step_four_out, + const step_ten_out_t &step_ten_out, + const step_eleven_out_t &step_eleven_out, + const plonk_proof &proof, + const srs &srs); + + /// \attention The first three steps (as given in [GWC19] -- see + /// below) MUST be executed by the caller: + /// + /// Verifier Step 1: validate that elements belong to group G1 + /// Verifier Step 2: validate that elements belong to scalar field Fr + /// Verifier Step 3: validate that the public input belongs to scalar field + /// Fr + /// + /// Therefore verification starts from Step 4 of [GWC19] + /// + /// INPUT + /// \param[in] proof: SNARK proof produced by the prover + /// \param[in] srs: structured reference string containing also + /// circuit-specific information + /// \param[in] PI_value_list: list of values corresponding to + /// public inputs + /// \param[in] transcript_hasher: hashes of the communication + /// transcript after prover rounds 1,2,3,4,5. + /// + /// OUTPUT + /// \param[out] boolean 1/0 = valid/invalid proof + bool verify_proof( + const plonk_proof &proof, + const srs &srs, + const std::vector &PI_value_list, + transcript_hasher &hasher); +}; + +} // namespace libsnark + +#include "libsnark/zk_proof_systems/plonk/verifier.tcc" + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_HPP_ diff --git a/libsnark/zk_proof_systems/plonk/verifier.tcc b/libsnark/zk_proof_systems/plonk/verifier.tcc new file mode 100644 index 000000000..bc320e7b0 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/verifier.tcc @@ -0,0 +1,481 @@ +/** @file + ***************************************************************************** + * @author This file is part of libff, developed by Clearmatics Ltd + * (originally developed by SCIPR Lab) and contributors + * (see AUTHORS). + * @copyright MIT license (see LICENSE file) + *****************************************************************************/ + +#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_TCC_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_TCC_ + +// Implementation of Verifier interfaces for a ppzkSNARK for Plonk. See +// verifier.hpp . + +namespace libsnark +{ + +template +verifier_preprocessed_input_t::verifier_preprocessed_input_t( + std::vector> &&Q_polys_at_secret_g1, + std::vector> &&S_polys_at_secret_g1) + : Q_polys_at_secret_g1(Q_polys_at_secret_g1) + , S_polys_at_secret_g1(S_polys_at_secret_g1) +{ +} + +template +verifier_preprocessed_input_t plonk_verifier:: + preprocessed_input(const srs &srs) +{ + std::vector> Q_polys_at_secret_g1; + Q_polys_at_secret_g1.resize(srs.Q_polys.size()); + plonk_evaluate_polys_at_secret_G1( + srs.secret_powers_g1, srs.Q_polys, Q_polys_at_secret_g1); + + std::vector> S_polys_at_secret_g1; + S_polys_at_secret_g1.resize(srs.S_polys.size()); + plonk_evaluate_polys_at_secret_G1( + srs.secret_powers_g1, srs.S_polys, S_polys_at_secret_g1); + + verifier_preprocessed_input_t preprocessed_input( + std::move(Q_polys_at_secret_g1), std::move(S_polys_at_secret_g1)); + return preprocessed_input; +} + +template +step_four_out_t::step_four_out_t( + const libff::Fr &beta, + const libff::Fr &gamma, + const libff::Fr &alpha, + const libff::Fr &zeta, + const libff::Fr &nu, + const libff::Fr &u) + : beta(beta), gamma(gamma), alpha(alpha), zeta(zeta), nu(nu), u(u) +{ +} + +template +step_four_out_t plonk_verifier::step_four( + const plonk_proof &proof, transcript_hasher &hasher) +{ + // Add outputs from Round 1 to the hash buffer. + hasher.add_element(proof.W_polys_blinded_at_secret_g1[a]); + hasher.add_element(proof.W_polys_blinded_at_secret_g1[b]); + hasher.add_element(proof.W_polys_blinded_at_secret_g1[c]); + // - beta: permutation challenge - hashes of transcript of round + // 1 (\attention the original protocl appends a 0, while we don't + // append anyhting to avoid making a copy of the buffer) + libff::Fr beta = hasher.get_hash(); + // - gamma: permutation challenge - hashes of transcript of round + // 1 with 1 appended + hasher.add_element(libff::Fr::one()); + libff::Fr gamma = hasher.get_hash(); + + // Add outputs from Round 2 to the hash buffer. + hasher.add_element(proof.z_poly_at_secret_g1); + // - alpha: quotient challenge - hash of transcript of rounds 1,2 + libff::Fr alpha = hasher.get_hash(); + + // Add outputs from Round 3 to the hash buffer. + hasher.add_element(proof.t_poly_at_secret_g1[lo]); + hasher.add_element(proof.t_poly_at_secret_g1[mid]); + hasher.add_element(proof.t_poly_at_secret_g1[hi]); + // - zeta: evaluation challenge - hash of transcriptof rounds + // - 1,2,3 + libff::Fr zeta = hasher.get_hash(); + + // Add outputs from Round 4 to the hash buffer. + hasher.add_element(proof.a_zeta); + hasher.add_element(proof.b_zeta); + hasher.add_element(proof.c_zeta); + hasher.add_element(proof.S_0_zeta); + hasher.add_element(proof.S_1_zeta); + hasher.add_element(proof.z_poly_xomega_zeta); + // - nu: opening challenge -- hash of transcript (denoted by v in + // [GWC19]) + libff::Fr nu = hasher.get_hash(); + + // Add outputs from Round 5 to the hash buffer. + hasher.add_element(proof.r_zeta); + hasher.add_element(proof.W_zeta_at_secret); + hasher.add_element(proof.W_zeta_omega_at_secret); + // u: multipoint evaluation challenge -- hash of transcript from + // rounds 1,2,3,4,5 + libff::Fr u = hasher.get_hash(); + + // step 4 output + step_four_out_t step_four_out(beta, gamma, alpha, zeta, nu, u); + + return step_four_out; +} + +template +step_five_out_t::step_five_out_t(libff::Fr &&zh_zeta) + : zh_zeta(zh_zeta) +{ +} + +template +step_five_out_t plonk_verifier::step_five( + const step_four_out_t &step_four_out, + std::shared_ptr>> domain) +{ + libff::Fr zh_zeta = + domain->compute_vanishing_polynomial(step_four_out.zeta); + step_five_out_t step_five_out(std::move(zh_zeta)); + return step_five_out; +} + +/// struct step_six_out_t constructor +template +step_six_out_t::step_six_out_t(libff::Fr &&L_0_zeta) + : L_0_zeta(L_0_zeta) +{ +} + +template +step_six_out_t plonk_verifier::step_six( + const step_four_out_t &step_four_out, const srs &srs) +{ + libff::Fr L_0_zeta = libfqfft::evaluate_polynomial( + srs.L_basis_zero.size(), srs.L_basis_zero, step_four_out.zeta); + step_six_out_t step_six_out(std::move(L_0_zeta)); + return step_six_out; +} + +template +step_seven_out_t::step_seven_out_t(libff::Fr &&PI_zeta) + : PI_zeta(PI_zeta) +{ +} + +template +step_seven_out_t plonk_verifier::step_seven( + const step_four_out_t &step_four_out, + const std::vector &PI_value_list, + const srs &srs) +{ + std::shared_ptr> domain = + libfqfft::get_evaluation_domain(srs.num_gates); + // Construct the PI polynomial from the vector of PI values + // (received as input to the verifier) and the PI wire indices + // (stored in the srs). + std::vector PI_points(srs.num_gates, Field(0)); + for (size_t i = 0; i < PI_value_list.size(); i++) { + size_t PI_coordinate_x = srs.PI_wire_indices[i] % srs.num_gates; + PI_points[PI_coordinate_x] = Field(-PI_value_list[i]); + } + + // Compute PI polynomial. + polynomial PI_poly; + plonk_compute_public_input_polynomial(PI_points, PI_poly, domain); + + libff::Fr PI_zeta; + PI_zeta = libfqfft::evaluate_polynomial( + PI_poly.size(), PI_poly, step_four_out.zeta); + step_seven_out_t step_seven_out(std::move(PI_zeta)); + return step_seven_out; +} + +template +step_eight_out_t::step_eight_out_t(libff::Fr &&r_prime_zeta) + : r_prime_zeta(r_prime_zeta) +{ +} + +template +step_eight_out_t plonk_verifier::step_eight( + const step_four_out_t &step_four_out, + const step_five_out_t &step_five_out, + const step_six_out_t &step_six_out, + const step_seven_out_t &step_seven_out, + const plonk_proof &proof) +{ + libff::Fr r_prime_zeta; + Field alpha_power2 = libff::power(step_four_out.alpha, libff::bigint<1>(2)); + + // Compute polynomial r'(zeta) = r(zeta) - r_0 . + std::vector r_prime_parts(5); + r_prime_parts[0] = proof.r_zeta + step_seven_out.PI_zeta; + r_prime_parts[1] = + (proof.a_zeta + (step_four_out.beta * proof.S_0_zeta) + + step_four_out.gamma); + r_prime_parts[2] = + (proof.b_zeta + (step_four_out.beta * proof.S_1_zeta) + + step_four_out.gamma); + r_prime_parts[3] = (proof.c_zeta + step_four_out.gamma) * + proof.z_poly_xomega_zeta * step_four_out.alpha; + r_prime_parts[4] = (step_six_out.L_0_zeta * alpha_power2); + r_prime_zeta = (r_prime_parts[0] - + (r_prime_parts[1] * r_prime_parts[2] * r_prime_parts[3]) - + r_prime_parts[4]) * + step_five_out.zh_zeta.inverse(); + + step_eight_out_t step_eight_out(std::move(r_prime_zeta)); + return step_eight_out; +} + +template +step_nine_out_t::step_nine_out_t(libff::G1 &&D1) : D1(D1) +{ +} + +template +step_nine_out_t plonk_verifier::step_nine( + const step_four_out_t &step_four_out, + const step_six_out_t &step_six_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs) +{ + libff::G1 D1; + + // D1 is computed in 3 parts + std::vector> D1_part(3); + + Field alpha_power2 = libff::power(step_four_out.alpha, libff::bigint<1>(2)); + + // Compute D1_part[0]: + // (a_bar b_bar [q_M]_1 + a_bar [q_L]_1 + b_bar [q_R]_1 + c_bar [q_O]_1 + + // [q_C]_1) nu Note: the paper omits the final multiplication by nu + std::vector> curve_points{ + preprocessed_input.Q_polys_at_secret_g1[M], + preprocessed_input.Q_polys_at_secret_g1[L], + preprocessed_input.Q_polys_at_secret_g1[R], + preprocessed_input.Q_polys_at_secret_g1[O], + preprocessed_input.Q_polys_at_secret_g1[C]}; + std::vector> scalar_elements{ + proof.a_zeta * proof.b_zeta * step_four_out.nu, + proof.a_zeta * step_four_out.nu, + proof.b_zeta * step_four_out.nu, + proof.c_zeta * step_four_out.nu, + step_four_out.nu}; + D1_part[0] = plonk_multi_exp_G1(curve_points, scalar_elements); + + // Compute D1_part[1]: + // ((a_bar + beta zeta + gamma)(b_bar + beta k1 zeta + gamma)(c_bar + beta + // k2 zeta + gamma) alpha + L1(zeta) alpha^2 + u) [z]_1 + Field D1_part1_scalar = + (proof.a_zeta + (step_four_out.beta * step_four_out.zeta) + + step_four_out.gamma) * + (proof.b_zeta + (step_four_out.beta * srs.k1 * step_four_out.zeta) + + step_four_out.gamma) * + (proof.c_zeta + (step_four_out.beta * srs.k2 * step_four_out.zeta) + + step_four_out.gamma) * + step_four_out.alpha * step_four_out.nu + + step_six_out.L_0_zeta * alpha_power2 * step_four_out.nu + + step_four_out.u; + D1_part[1] = D1_part1_scalar * proof.z_poly_at_secret_g1; + + // Compute D1_part[2]: + // (a_bar + beta s_sigma1_bar + gamma)(b_bar + beta s_sigma2_bar + + // gamma)alpha beta z_preprocessed_input.omega_roots_bar [s_sigma3]_1 + Field D1_part2_scalar = + ((proof.a_zeta + (step_four_out.beta * proof.S_0_zeta) + + step_four_out.gamma) * + (proof.b_zeta + (step_four_out.beta * proof.S_1_zeta) + + step_four_out.gamma) * + step_four_out.alpha * step_four_out.beta * proof.z_poly_xomega_zeta * + step_four_out.nu) * + Field(-1); + D1_part[2] = D1_part2_scalar * preprocessed_input.S_polys_at_secret_g1[2]; + + // Compute D1 = D1_part[0] + D1_part[1] + D1_part[2] . + D1 = D1_part[0] + D1_part[1] + D1_part[2]; + + step_nine_out_t step_nine_out(std::move(D1)); + return step_nine_out; +} + +template +step_ten_out_t::step_ten_out_t(libff::G1 &&F1) : F1(F1) +{ +} + +template +step_ten_out_t plonk_verifier::step_ten( + const step_four_out_t &step_four_out, + const step_nine_out_t &step_nine_out, + const plonk_proof &proof, + const verifier_preprocessed_input_t &preprocessed_input, + const srs &srs) +{ + libff::G1 F1; + Field zeta_power_n = + libff::power(step_four_out.zeta, libff::bigint<1>(srs.num_gates + 2)); + Field zeta_power_2n = libff::power( + step_four_out.zeta, libff::bigint<1>(2 * (srs.num_gates + 2))); + std::vector nu_power(7); + for (size_t i = 0; i < nu_power.size(); ++i) { + nu_power[i] = libff::power(step_four_out.nu, libff::bigint<1>(i)); + } + std::vector> curve_points{ + proof.t_poly_at_secret_g1[lo], // nu^0 + proof.t_poly_at_secret_g1[mid], // nu^0 + proof.t_poly_at_secret_g1[hi], // nu^0 + step_nine_out.D1, // nu^1 + proof.W_polys_blinded_at_secret_g1[a], // nu^2 + proof.W_polys_blinded_at_secret_g1[b], // nu^3 + proof.W_polys_blinded_at_secret_g1[c], // nu^4 + preprocessed_input.S_polys_at_secret_g1[0], // nu^5 + preprocessed_input.S_polys_at_secret_g1[1] // nu^6 + }; + std::vector> scalar_elements{ + Field(1), + zeta_power_n, // zeta^(n+2), + zeta_power_2n, // zeta^(2*(n+2)), + Field(1), + nu_power[2], // nu^2, + nu_power[3], // nu^3, + nu_power[4], // nu^4, + nu_power[5], // nu^5, + nu_power[6] // nu^6 + }; + F1 = plonk_multi_exp_G1(curve_points, scalar_elements); + step_ten_out_t step_ten_out(std::move(F1)); + return step_ten_out; +} + +template +step_eleven_out_t::step_eleven_out_t(libff::G1 &&E1) : E1(E1) +{ +} + +template +step_eleven_out_t plonk_verifier::step_eleven( + const step_four_out_t &step_four_out, + const step_eight_out_t &step_eight_out, + const plonk_proof &proof) +{ + libff::G1 E1; + std::vector nu_power(7); + for (size_t i = 0; i < nu_power.size(); ++i) { + nu_power[i] = libff::power(step_four_out.nu, libff::bigint<1>(i)); + } + std::vector> curve_points{libff::G1::one()}; + std::vector> scalar_elements{ + step_eight_out.r_prime_zeta + nu_power[1] * proof.r_zeta + // v^1 + nu_power[2] * proof.a_zeta + // v^2 + nu_power[3] * proof.b_zeta + // v^3 + nu_power[4] * proof.c_zeta + // v^4 + nu_power[5] * proof.S_0_zeta + // v^5 + nu_power[6] * proof.S_1_zeta + // v^6 + step_four_out.u * proof.z_poly_xomega_zeta}; + + E1 = plonk_multi_exp_G1(curve_points, scalar_elements); + + step_eleven_out_t step_eleven_out(std::move(E1)); + return step_eleven_out; +} + +template +bool plonk_verifier::step_twelve( + const step_four_out_t &step_four_out, + const step_ten_out_t &step_ten_out, + const step_eleven_out_t &step_eleven_out, + const plonk_proof &proof, + const srs &srs) +{ + std::vector> curve_points_lhs{ + proof.W_zeta_at_secret, proof.W_zeta_omega_at_secret}; + std::vector> scalar_elements_lhs{Field(1), step_four_out.u}; + libff::G1 pairing_first_lhs = + plonk_multi_exp_G1(curve_points_lhs, scalar_elements_lhs); + libff::G2 pairing_second_lhs = srs.secret_powers_g2[1]; + + std::vector> curve_points_rhs{ + proof.W_zeta_at_secret, + proof.W_zeta_omega_at_secret, + step_ten_out.F1, + step_eleven_out.E1}; + std::vector> scalar_elements_rhs{ + // Warning! raise to the power of -1 to check e() * e()^-1 = 1 + Field(-1) * step_four_out.zeta, + Field(-1) * step_four_out.u * step_four_out.zeta * + srs.omega_roots[base][1], + Field(-1) * Field(1), + Field(-1) * Field(-1)}; + + libff::G1 pairing_first_rhs = + plonk_multi_exp_G1(curve_points_rhs, scalar_elements_rhs); + libff::G2 pairing_second_rhs = srs.secret_powers_g2[0]; + + const libff::G1_precomp _A = ppT::precompute_G1(pairing_first_lhs); + const libff::G2_precomp _B = ppT::precompute_G2(pairing_second_lhs); + const libff::G1_precomp _C = ppT::precompute_G1(pairing_first_rhs); + const libff::G2_precomp _D = ppT::precompute_G2(pairing_second_rhs); + const libff::Fqk miller_result = + ppT::double_miller_loop(_A, _B, _C, _D); + const libff::GT result = ppT::final_exponentiation(miller_result); + bool b_accept = (result == libff::GT::one()); + return b_accept; +} + +template +bool plonk_verifier::verify_proof( + const plonk_proof &proof, + const srs &srs, + const std::vector &PI_value_list, + transcript_hasher &hasher) +{ + std::shared_ptr>> domain = + libfqfft::get_evaluation_domain>(srs.num_gates); + + // Compute verifier preprocessed input. + const verifier_preprocessed_input_t preprocessed_input = + plonk_verifier::preprocessed_input(srs); + + // Verifier Step 1: validate that elements belong to group G1 + // Verifier Step 2: validate that elements belong to scalar field Fr + // Verifier Step 3: validate that the public input belongs to scalar field + // Fr + // + // Executed by the caller + + // Verifier Step 4: compute challenges hashed transcript as in + // prover description, from the common inputs, public input, and + // elements of pi-SNARK (fixed to the test vectors for now) + const step_four_out_t step_four_out = this->step_four(proof, hasher); + + // Verifier Step 5: compute zero polynomial evaluation + const step_five_out_t step_five_out = + this->step_five(step_four_out, domain); + + // Verifier Step 6: Compute Lagrange polynomial evaluation L1(zeta) + // Note: the paper counts the L-polynomials from 1; we count from 0 + const step_six_out_t step_six_out = this->step_six(step_four_out, srs); + + // Verifier Step 7: compute public input polynomial evaluation PI(zeta) + const step_seven_out_t step_seven_out = + this->step_seven(step_four_out, PI_value_list, srs); + + // Verifier Step 8: compute quotient polynomial evaluation + // r'(zeta) = r(zeta) - r0, where r0 is a constant term + const step_eight_out_t step_eight_out = this->step_eight( + step_four_out, step_five_out, step_six_out, step_seven_out, proof); + + // Verifier Step 9: compute first part of batched polynomial + // commitment [D]_1 + step_nine_out_t step_nine_out = this->step_nine( + step_four_out, step_six_out, proof, preprocessed_input, srs); + + // Verifier Step 10: compute full batched polynomial commitment + // [F]_1 + step_ten_out_t step_ten_out = this->step_ten( + step_four_out, step_nine_out, proof, preprocessed_input, srs); + + // Verifier Step 11: compute group-encoded batch evaluation [E]_1 + const step_eleven_out_t step_eleven_out = + this->step_eleven(step_four_out, step_eight_out, proof); + + // Verifier Step 12: batch validate all evaluations (check + // pairing) + bool b_accept = this->step_twelve( + step_four_out, step_ten_out, step_eleven_out, proof, srs); + return b_accept; +} + +} // namespace libsnark + +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_VERIFIER_TCC_