diff --git a/libsnark/zk_proof_systems/plonk/srs.hpp b/libsnark/zk_proof_systems/plonk/srs.hpp index 07c804849..1bfdbfe54 100644 --- a/libsnark/zk_proof_systems/plonk/srs.hpp +++ b/libsnark/zk_proof_systems/plonk/srs.hpp @@ -144,12 +144,23 @@ template class srs /// 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( +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 { diff --git a/libsnark/zk_proof_systems/plonk/srs.tcc b/libsnark/zk_proof_systems/plonk/srs.tcc index fb9c1e584..e20df5d74 100644 --- a/libsnark/zk_proof_systems/plonk/srs.tcc +++ b/libsnark/zk_proof_systems/plonk/srs.tcc @@ -105,7 +105,7 @@ usrs plonk_usrs_derive_from_secret( } template -srs plonk_srs_derive_from_usrs( +srs plonk_srs_derive_from_usrs_custom_PI_indices( const usrs &usrs, const std::vector>> gates_matrix, const std::vector wire_permutation, @@ -253,6 +253,23 @@ srs plonk_srs_derive_from_usrs( 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/example.hpp b/libsnark/zk_proof_systems/plonk/tests/example.hpp index 6d22456f0..4ee3e5966 100644 --- a/libsnark/zk_proof_systems/plonk/tests/example.hpp +++ b/libsnark/zk_proof_systems/plonk/tests/example.hpp @@ -105,6 +105,16 @@ namespace libsnark /// 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: diff --git a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp index 416de03ff..ad19495ca 100644 --- a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp +++ b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp @@ -406,7 +406,7 @@ template void test_plonk_prover_rounds() // prepare srs usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); - srs srs = plonk_srs_derive_from_usrs( + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( usrs, example.gates_matrix, example.wire_permutation, @@ -585,7 +585,7 @@ template void test_plonk_srs() // 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( + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( usrs, example.gates_matrix, example.wire_permutation, @@ -632,7 +632,7 @@ template void test_plonk_prover() // Perepare srs. usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); - srs srs = plonk_srs_derive_from_usrs( + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( usrs, example.gates_matrix, example.wire_permutation, @@ -919,7 +919,7 @@ template void test_plonk_verifier_steps() // Prepare srs. usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); - srs srs = plonk_srs_derive_from_usrs( + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( usrs, example.gates_matrix, example.wire_permutation, @@ -1043,7 +1043,7 @@ template void test_plonk_verifier() // Prepare srs. usrs usrs = plonk_usrs_derive_from_secret(secret, max_degree); - srs srs = plonk_srs_derive_from_usrs( + srs srs = plonk_srs_derive_from_usrs_custom_PI_indices( usrs, example.gates_matrix, example.wire_permutation, @@ -1095,6 +1095,154 @@ template void test_plonk_gates_matrix_transpose() 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_prepare_gates_matrix() +{ + 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() { @@ -1254,6 +1402,10 @@ TEST(TestPlonk, BLS12_381) libff::bls12_381_pp, bls12_381_test_vector_transcript_hasher>(); test_plonk_gates_matrix_transpose(); + test_plonk_prepare_gates_matrix< + libff::bls12_381_pp, + bls12_381_test_vector_transcript_hasher>(); + test_plonk_prepare_gates_matrix(); } } // namespace libsnark diff --git a/libsnark/zk_proof_systems/plonk/utils.hpp b/libsnark/zk_proof_systems/plonk/utils.hpp index a93787365..52a7bd1f6 100644 --- a/libsnark/zk_proof_systems/plonk/utils.hpp +++ b/libsnark/zk_proof_systems/plonk/utils.hpp @@ -227,6 +227,31 @@ void plonk_generate_random_constants_k1_k2( 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" diff --git a/libsnark/zk_proof_systems/plonk/utils.tcc b/libsnark/zk_proof_systems/plonk/utils.tcc index cc85aabe7..bb91002c6 100644 --- a/libsnark/zk_proof_systems/plonk/utils.tcc +++ b/libsnark/zk_proof_systems/plonk/utils.tcc @@ -308,8 +308,8 @@ std::vector> plonk_gates_matrix_transpose( 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) { - assert(gates_matrix[icol].size() == ncols); gates_matrix_transpose[icol][irow] = gates_matrix[irow][icol]; } } @@ -383,6 +383,60 @@ bool plonk_are_valid_constants_k1_k2(const FieldT &k1, const FieldT &k2) 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 index 391917ed6..6cf1babaa 100644 --- a/libsnark/zk_proof_systems/plonk/verifier.hpp +++ b/libsnark/zk_proof_systems/plonk/verifier.hpp @@ -371,6 +371,8 @@ template class plonk_verifier /// \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. ///