From be861805d14a9b80fd5f502a0094f5151106888e Mon Sep 17 00:00:00 2001 From: Vesselin Velichkov Date: Thu, 19 Jan 2023 14:17:08 +0000 Subject: [PATCH 1/2] plonk: added dummy transcript hasher specialized for all curves; added unit tests for the simple quadratic residue circuit for all curves (addresses issue https://github.com/clearmatics/libsnark/issues/103) --- .../plonk/tests/dummy_transcript_hasher.hpp | 59 +++++++++++ .../plonk/tests/dummy_transcript_hasher.tcc | 99 +++++++++++++++++++ .../plonk/tests/test_plonk.cpp | 60 +++++++---- 3 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp create mode 100644 libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc 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..25a8dfa04 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp @@ -0,0 +1,59 @@ +/** @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 the elemnts 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 + // of elements 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 buffer_clear(); + + // get buffer size + size_t buffer_size(); +}; + +} // 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..d1bc45275 --- /dev/null +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc @@ -0,0 +1,99 @@ +/** @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_CPP_ +#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_CPP_ + +#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::buffer_clear() +{ + this->buffer.clear(); +} + +template size_t dummy_transcript_hasher::buffer_size() +{ + return this->buffer.size(); +} + +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_CPP_ diff --git a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp index ad19495ca..9210db102 100644 --- a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp +++ b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp @@ -8,6 +8,7 @@ #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 @@ -1148,9 +1149,10 @@ template void test_plonk_gates_matrix_transpose() // ( 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() +template void test_plonk_simple_circuit() { + ppT::init_public_params(); + using Field = libff::Fr; // 0 Arithmetization of test circuit y^2 = x mod r @@ -1337,57 +1339,70 @@ void test_plonk_constants_k1_k2_bls12_381() } } -TEST(TestPlonkConstantsK1K2, Edwards) +TEST(TestPlonk, Edwards) { test_plonk_constants_k1_k2(); test_plonk_random_constants_k1_k2(); + // TODO add test_plonk_simple_circuit } -TEST(TestPlonkConstantsK1K2, Mnt4) +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(TestPlonkConstantsK1K2, Mnt6) +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(TestPlonkConstantsK1K2, BW6_761) +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(TestPlonkConstantsK1K2, BN128) -{ - test_plonk_constants_k1_k2(); - test_plonk_random_constants_k1_k2(); -} - -TEST(TestPlonkConstantsK1K2, ALT_BN128) +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(TestPlonkConstantsK1K2, BLS12_377) +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(TestPlonkConstantsK1K2, BLS12_381) +TEST(TestPlonk, BLS12_381) { test_plonk_constants_k1_k2(); test_plonk_random_constants_k1_k2(); test_plonk_constants_k1_k2_bls12_381(); -} - -TEST(TestPlonk, BLS12_381) -{ test_plonk_srs(); test_plonk_prover_rounds< libff::bls12_381_pp, @@ -1402,10 +1417,13 @@ TEST(TestPlonk, BLS12_381) libff::bls12_381_pp, bls12_381_test_vector_transcript_hasher>(); test_plonk_gates_matrix_transpose(); - test_plonk_prepare_gates_matrix< + test_plonk_prepare_gates_matrix(); + test_plonk_simple_circuit< libff::bls12_381_pp, bls12_381_test_vector_transcript_hasher>(); - test_plonk_prepare_gates_matrix(); + test_plonk_simple_circuit< + libff::bls12_381_pp, + dummy_transcript_hasher>(); } } // namespace libsnark From 3603f95445c00eb56a00299fd1e2730d08dfb21b Mon Sep 17 00:00:00 2001 From: Vesselin Velichkov Date: Fri, 3 Feb 2023 12:50:26 +0000 Subject: [PATCH 2/2] plonk: removed unused method buffer_size (https://github.com/clearmatics/libsnark/pull/107#discussion_r1095727785); renamed method buffer_clear to reset (https://github.com/clearmatics/libsnark/pull/107#discussion_r1095728696); minor edits in comments (https://github.com/clearmatics/libsnark/pull/107#discussion_r1095722640). --- ...ls12_381_test_vector_transcript_hasher.cpp | 10 +---- ...ls12_381_test_vector_transcript_hasher.hpp | 5 +-- .../plonk/tests/dummy_transcript_hasher.hpp | 17 ++++----- .../plonk/tests/dummy_transcript_hasher.tcc | 13 ++----- .../plonk/tests/test_plonk.cpp | 38 +++++++++---------- 5 files changed, 32 insertions(+), 51 deletions(-) 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 index fb9094bad..1af67ffc5 100644 --- 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 @@ -40,15 +40,7 @@ bls12_381_test_vector_transcript_hasher:: }; } -void bls12_381_test_vector_transcript_hasher::buffer_clear() -{ - this->buffer.clear(); -} - -size_t bls12_381_test_vector_transcript_hasher::buffer_size() -{ - return this->buffer.size(); -} +void bls12_381_test_vector_transcript_hasher::reset() { this->buffer.clear(); } void bls12_381_test_vector_transcript_hasher::add_element( const libff::Fr &element) 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 index 027afff2b..baa34d32b 100644 --- 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 @@ -114,10 +114,7 @@ class bls12_381_test_vector_transcript_hasher libff::Fr get_hash(); // clear the buffer (for now only for testing) - void buffer_clear(); - - // get buffer size - size_t buffer_size(); + void reset(); }; } // namespace libsnark diff --git a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp index 25a8dfa04..328165719 100644 --- a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.hpp @@ -17,10 +17,10 @@ namespace libsnark { /// Implementation of a dummy transcript hasher interface (see -/// transcript_hasher.hpp). It returns the number of the elemnts 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. +/// 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: @@ -40,16 +40,13 @@ template class dummy_transcript_hasher void add_element(const libff::G2 &element); // Dummy implementation of get_hash that simply returns the number - // of elements in the buffer as an Fr value for the purposes of - // unit testing. TODO: to be replaced by a call to a proper hash + // 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 buffer_clear(); - - // get buffer size - size_t buffer_size(); + void reset(); }; } // namespace libsnark diff --git a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc index d1bc45275..85542a259 100644 --- a/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc +++ b/libsnark/zk_proof_systems/plonk/tests/dummy_transcript_hasher.tcc @@ -6,8 +6,8 @@ * @copyright MIT license (see LICENSE file) *****************************************************************************/ -#ifndef LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_CPP_ -#define LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_CPP_ +#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" @@ -20,16 +20,11 @@ template dummy_transcript_hasher::dummy_transcript_hasher() { } -template void dummy_transcript_hasher::buffer_clear() +template void dummy_transcript_hasher::reset() { this->buffer.clear(); } -template size_t dummy_transcript_hasher::buffer_size() -{ - return this->buffer.size(); -} - template void dummy_transcript_hasher::add_element(const libff::Fr &element) { @@ -96,4 +91,4 @@ template libff::Fr dummy_transcript_hasher::get_hash() } // namespace libsnark -#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_CPP_ +#endif // LIBSNARK_ZK_PROOF_SYSTEMS_PLONK_TESTS_DUMMY_TRANSCRIPT_HASHER_TCC_ diff --git a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp index 9210db102..f24a78a0f 100644 --- a/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp +++ b/libsnark/zk_proof_systems/plonk/tests/test_plonk.cpp @@ -65,7 +65,7 @@ void test_verify_invalid_proof( for (size_t i = 0; i < valid_proof.W_polys_blinded_at_secret_g1.size(); ++i) { // re-initialize the manipulated proof - hasher.buffer_clear(); + hasher.reset(); proof = valid_proof; G1_noise = libff::G1::random_element(); proof.W_polys_blinded_at_secret_g1[i] = @@ -74,7 +74,7 @@ void test_verify_invalid_proof( ASSERT_FALSE(b_accept); } // manipulate [z]_1 - hasher.buffer_clear(); + 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; @@ -83,7 +83,7 @@ void test_verify_invalid_proof( // 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.buffer_clear(); + 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; @@ -91,63 +91,63 @@ void test_verify_invalid_proof( ASSERT_FALSE(b_accept); } // manipulate \bar{a} - hasher.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + 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.buffer_clear(); + hasher.reset(); proof = valid_proof; Fr_noise = libff::Fr::random_element(); proof.r_zeta = proof.r_zeta + Fr_noise; @@ -433,7 +433,7 @@ template void test_plonk_prover_rounds() plonk_prover::round_one( round_zero_out, blind_scalars, witness, srs, domain, hasher); // clear hash buffer - hasher.buffer_clear(); + 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]); @@ -470,7 +470,7 @@ template void test_plonk_prover_rounds() domain, hasher); // Clear hash buffer. - hasher.buffer_clear(); + 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]); @@ -505,7 +505,7 @@ template void test_plonk_prover_rounds() srs, hasher); // Clear hash buffer. - hasher.buffer_clear(); + 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]); @@ -527,7 +527,7 @@ template void test_plonk_prover_rounds() plonk_prover::round_four( zeta, round_one_out, round_three_out, srs, hasher); // Clear hash buffer. - hasher.buffer_clear(); + 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]); @@ -937,7 +937,7 @@ template void test_plonk_verifier_steps() // Clear the hasher buffer in order to re-use the same // transcript_hasher object for the verifier. - hasher.buffer_clear(); + hasher.reset(); // Unit test verifier preprocessed input. test_plonk_verifier_preprocessed_input( @@ -1061,7 +1061,7 @@ template void test_plonk_verifier() // Clear the hasher buffer in order to re-use the same // transcript_hasher object for the verifier. - hasher.buffer_clear(); + hasher.reset(); // Initialize verifier. plonk_verifier verifier; @@ -1078,7 +1078,7 @@ template void test_plonk_verifier() // Clear the hasher buffer in order to re-use the same // transcript_hasher object. - hasher.buffer_clear(); + hasher.reset(); // Assert that proof verification fails when the proof is // manipulated. test_verify_invalid_proof(proof, srs, PI_value_list, hasher);