From eb2a1cffc0c66ad73b24d159fb5a852f883a13d9 Mon Sep 17 00:00:00 2001 From: Vesselin Velichkov Date: Fri, 27 Jan 2023 13:24:02 +0000 Subject: [PATCH] anemoi: implemented gadget for the full anemoi permutation (WIP) (https://github.com/clearmatics/libsnark/issues/104) --- .../hashes/anemoi/anemoi_components.hpp | 48 ++++- .../hashes/anemoi/anemoi_components.tcc | 181 +++++++++++++++++- .../hashes/anemoi/tests/anemoi_outputs.cpp | 60 ++++++ .../hashes/anemoi/tests/anemoi_outputs.hpp | 9 + .../anemoi/tests/test_anemoi_gadget.cpp | 135 ++++++++++++- 5 files changed, 425 insertions(+), 8 deletions(-) diff --git a/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.hpp b/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.hpp index ce9bf36b1..84ecdca8d 100644 --- a/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.hpp +++ b/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.hpp @@ -218,8 +218,9 @@ class anemoi_permutation_round_prime_field_gadget anemoi_permutation_round_prime_field_gadget( protoboard &pb, - const std::vector &C_const, - const std::vector &D_const, + // TODO: add round index + const std::vector &C_const, // remove + const std::vector &D_const, // remove const pb_linear_combination_array &X_left_input, const pb_linear_combination_array &X_right_input, const pb_variable_array &Y_left_output, @@ -263,6 +264,49 @@ template class anemoi_permutation_mds static anemoi_mds_matrix_t permutation_mds(const libff::Fr g); }; +/// Full Anemoi permutation mapping (Fr)^{2L} -> (Fr)^{2L} +/// see anemoi_permutation_round_prime_field_gadget +template< + typename ppT, + size_t NumStateColumns_L, + class parameters = anemoi_parameters>> +class anemoi_permutation_prime_field_gadget : public gadget> +{ + using FieldT = libff::Fr; + +private: + // C round constants for all rounds + std::vector> C_const_vec; + // D round constants for all rounds + std::vector> D_const_vec; + // vector of round gadgets + std::vector> + Round; + +public: + const pb_linear_combination_array X_left_input; + const pb_linear_combination_array X_right_input; + const pb_variable_array Y_left_output; + const pb_variable_array Y_right_output; + + anemoi_permutation_prime_field_gadget( + protoboard &pb, + // TODO: remove constants + const std::vector> &C_const, + const std::vector> &D_const, + const pb_linear_combination_array &X_left_input, + const pb_linear_combination_array &X_right_input, + const pb_variable_array &Y_left_output, + const pb_variable_array &Y_right_output, + const std::string &annotation_prefix); + + void generate_r1cs_constraints(); + void generate_r1cs_witness(); +}; + } // namespace libsnark #include "libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.tcc" diff --git a/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.tcc b/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.tcc index a317134cf..7dc57a773 100644 --- a/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.tcc +++ b/libsnark/gadgetlib1/gadgets/hashes/anemoi/anemoi_components.tcc @@ -621,10 +621,16 @@ void anemoi_permutation_round_prime_field_gadget< Flystel[i].generate_r1cs_witness(); } +#if 1 // DEBUG vpv-20230207 for (size_t i = 0; i < NumStateColumns_L; i++) { - this->pb.val(Y_left_output[i]) = this->pb.val(Flystel[i].output_y0); - this->pb.val(Y_right_output[i]) = this->pb.val(Flystel[i].output_y1); + assert( + this->pb.val(Y_left_output[i]) == + this->pb.val(Flystel[i].output_y0)); + assert( + this->pb.val(Y_right_output[i]) == + this->pb.val(Flystel[i].output_y1)); } +#endif // DEBUG } // TODO: consdier applying the following changes to all @@ -673,6 +679,177 @@ std::array, 4>, 4> anemoi_permutation_mds:: return M; } +template +anemoi_permutation_prime_field_gadget:: + anemoi_permutation_prime_field_gadget( + protoboard> &pb, + const std::vector> &C, + const std::vector> &D, + const pb_linear_combination_array &X_left, + const pb_linear_combination_array &X_right, + const pb_variable_array &Y_left, + const pb_variable_array &Y_right, + const std::string &annotation_prefix) + : gadget>(pb, annotation_prefix) + , C_const_vec(C) + , D_const_vec(D) + , X_left_input(X_left) + , X_right_input(X_right) + , Y_left_output(Y_left) + , Y_right_output(Y_right) +{ + // Number of columns can not be larger than rounds128 size + assert(NumStateColumns_L <= parameters::nrounds128.size()); + // Number of columns can not be larger than rounds256 size + assert(NumStateColumns_L <= parameters::nrounds256.size()); + + // Get the number of rounds for the given Anemoi instance + // (i.e. given number of columns in the state). Note: currently + // using 256-bit security instance by default. TODO add support + // for 128-bit security e.g. by adding a Boolean flag b_sec_128 in + // the tamplate parameters. + const size_t nrounds = parameters::nrounds256[NumStateColumns_L - 1]; + + // Left and right input to round i, outputs from round i-1 + std::vector> round_results_left; + round_results_left.resize(nrounds); + std::vector> round_results_right; + round_results_right.resize(nrounds); + + // Initialize Round[0] with input X_left_input, X_right_input and + // output round_results_left[0], round_results_right[0] + round_results_left[0].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_left[0]")); + round_results_right[0].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_right[0]")); + + Round.emplace_back(anemoi_permutation_round_prime_field_gadget< + ppT, + NumStateColumns_L, + parameters>( + pb, + C[0], + D[0], + X_left_input, + X_right_input, + round_results_left[0], + round_results_right[0], + FMT(this->annotation_prefix, " Round[0]"))); + + printf("[%s:%d] CHECKPOINT! i = 0\n", __FILE__, __LINE__); + + // Initialize Round[i>0] gadget with input round_results_left[i - + // 1], round_results_right[i - 1] and output + // round_results_left[i], round_results_right[i] + for (size_t i = 1; i < nrounds - 1; i++) { + + printf("[%s:%d] CHECKPOINT! i = %zu\n", __FILE__, __LINE__, i); + + round_results_left[i].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_left[%zu]", i)); + round_results_right[i].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_right[%zu]", i)); + + Round.emplace_back(anemoi_permutation_round_prime_field_gadget< + ppT, + NumStateColumns_L, + parameters>( + pb, + C[i], + D[i], + round_results_left[i - 1], + round_results_right[i - 1], + round_results_left[i], + round_results_right[i], + FMT(this->annotation_prefix, " Round[%zu]", i))); + } + + printf( + "[%s:%d] CHECKPOINT! final round %zu\n", + __FILE__, + __LINE__, + nrounds - 1); + + round_results_left[nrounds - 1].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_left[%zu]", nrounds - 1)); + round_results_right[nrounds - 1].allocate( + pb, + NumStateColumns_L, + FMT(this->annotation_prefix, " round_results_right[%zu]", nrounds - 1)); + + // For last round, copy the output as given by the caller + // Y_left_output, Y_right_output + round_results_left[nrounds - 1] = Y_left_output; + round_results_right[nrounds - 1] = Y_right_output; + + // Initialize the last round gadget + Round.emplace_back(anemoi_permutation_round_prime_field_gadget< + ppT, + NumStateColumns_L, + parameters>( + pb, + C[nrounds - 1], + D[nrounds - 1], + round_results_left[nrounds - 2], + round_results_right[nrounds - 2], + round_results_left[nrounds - 1], + round_results_right[nrounds - 1], + FMT(this->annotation_prefix, " Round[%zu]", nrounds - 1))); + + printf("[%s:%d] Exit %s()\n", __FILE__, __LINE__, __FUNCTION__); +} + +template +void anemoi_permutation_prime_field_gadget:: + generate_r1cs_constraints() +{ + printf("[%s:%d] Enter %s()\n", __FILE__, __LINE__, __FUNCTION__); + size_t nrounds = parameters::nrounds256[NumStateColumns_L - 1]; + for (size_t i = 0; i < nrounds; i++) { + Round[i].generate_r1cs_constraints(); + } +} + +template +void anemoi_permutation_prime_field_gadget:: + generate_r1cs_witness() +{ + printf("[%s:%d] Enter %s()\n", __FILE__, __LINE__, __FUNCTION__); + size_t nrounds = parameters::nrounds256[NumStateColumns_L - 1]; + for (size_t i = 0; i < nrounds; i++) { + Round[i].generate_r1cs_witness(); + } + +#if 0 // DEBUG + for (size_t i = 0; i < nrounds; i++) { + pb_variable_array Y_left_i = Round[i].Y_left_output; + pb_variable_array Y_right_i = Round[i].Y_right_output; + + printf("[%s:%d] Round %2zu\n", __FILE__, __LINE__, i); + // Print left output from round i + for (size_t j = 0; j < NumStateColumns_L; j++) { + FieldT Y_left_i_val_j = this->pb.val(Y_left_i[j]); + Y_left_i_val_j.print(); + } + // Print right output from round i + for (size_t j = 0; j < NumStateColumns_L; j++) { + FieldT Y_right_i_val_j = this->pb.val(Y_right_i[j]); + Y_right_i_val_j.print(); + } + } +#endif // #if 1 // DEBUG +} + } // namespace libsnark #endif // LIBSNARK_GADGETLIB1_GADGETS_HASHES_ANEMOI_COMPONENTS_TCC_ diff --git a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.cpp b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.cpp index afcf49d06..03af4a1ea 100644 --- a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.cpp +++ b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.cpp @@ -107,4 +107,64 @@ std::vector> anemoi_expected_output_one_round( return Y_expect_one_round; } +std::vector> anemoi_expected_output( + const size_t &NumStateColumns_L) +{ + std::vector> Y_expect; + + assert( + ((NumStateColumns_L == 1) || (NumStateColumns_L == 2) || + (NumStateColumns_L == 3) || (NumStateColumns_L == 4))); + + // Expected output for X rounds, L=1: Y_left || Y_right + if (NumStateColumns_L == 1) { + // TBD + Y_expect = { + libff::Fr("0"), + libff::Fr("0"), + }; + } + + // Expected output for X rounds, L=2: Y_left || Y_right + if (NumStateColumns_L == 2) { + // TBD + Y_expect = { + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + }; + } + + // Expected output for X rounds, L=3: Y_left || Y_right + if (NumStateColumns_L == 3) { + // TBD + Y_expect = { + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + }; + } + + // Expected output for X rounds, L=4: Y_left || Y_right + if (NumStateColumns_L == 4) { + // TBD + Y_expect = { + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + libff::Fr("0"), + }; + } + + return Y_expect; +} + } // namespace libsnark diff --git a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.hpp b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.hpp index e97861566..e87f5ba73 100644 --- a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.hpp +++ b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/anemoi_outputs.hpp @@ -29,6 +29,15 @@ template using expected_round_values_fn_t = std::function>(const size_t)>; +// Returns the expected outputs from the full Anemoi permutation for +// BLS12_381 +std::vector> anemoi_expected_output( + const size_t &NumStateColumns_L); + +template +using expected_values_fn_t = + std::function>(const size_t)>; + } // namespace libsnark #endif // LIBSNARK_GADGETLIB1_GADGETS_HASHES_ANEMOI_TESTS_ANEMOI_OUTPUTS_HPP_ diff --git a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/test_anemoi_gadget.cpp b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/test_anemoi_gadget.cpp index 4af878acd..22790a1ac 100644 --- a/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/test_anemoi_gadget.cpp +++ b/libsnark/gadgetlib1/gadgets/hashes/anemoi/tests/test_anemoi_gadget.cpp @@ -249,6 +249,7 @@ void test_anemoi_permutation_round_prime_field_gadget( pb_variable_array X_left; pb_variable_array X_right; + pb_variable_array Y_left; pb_variable_array Y_right; @@ -311,6 +312,113 @@ void test_anemoi_permutation_round_prime_field_gadget( "anemoi_permutation_round_prime_field_gadget tests successful"); } +template< + typename ppT, + size_t NumStateColumns_L, + class parameters = anemoi_parameters>> +void test_anemoi_permutation_prime_field_gadget( + expected_values_fn_t expected_values_fn) + +{ + using FieldT = libff::Fr; + + protoboard pb; + std::vector> C; + std::vector> D; + + pb_variable_array X_left; + pb_variable_array X_right; + pb_variable_array Y_left; + pb_variable_array Y_right; + + X_left.allocate(pb, NumStateColumns_L, "left inputs"); + X_right.allocate(pb, NumStateColumns_L, "right inputs"); + + Y_left.allocate(pb, NumStateColumns_L, "left outputs"); + Y_right.allocate(pb, NumStateColumns_L, "right outputs"); + + assert(NumStateColumns_L <= parameters::nrounds256.size()); + assert(NumStateColumns_L <= parameters::nrounds128.size()); + + // the number of rounds depends on the number of columns in the + // state + size_t nrounds = parameters::nrounds256[NumStateColumns_L - 1]; + + // Store C,D round constants from parameters class + for (size_t iround = 0; iround < nrounds; iround++) { + // C,D constants for one round + std::vector C_iround; + std::vector D_iround; + for (size_t icol = 0; icol < NumStateColumns_L; icol++) { + if (NumStateColumns_L == 1) { + C_iround.push_back( + parameters::C_constants_col_one[iround][icol]); + D_iround.push_back( + parameters::D_constants_col_one[iround][icol]); + } + if (NumStateColumns_L == 2) { + C_iround.push_back( + parameters::C_constants_col_two[iround][icol]); + D_iround.push_back( + parameters::D_constants_col_two[iround][icol]); + } + if (NumStateColumns_L == 3) { + C_iround.push_back( + parameters::C_constants_col_three[iround][icol]); + D_iround.push_back( + parameters::D_constants_col_three[iround][icol]); + } + if (NumStateColumns_L == 4) { + C_iround.push_back( + parameters::C_constants_col_four[iround][icol]); + D_iround.push_back( + parameters::D_constants_col_four[iround][icol]); + } + } + C.push_back(C_iround); + D.push_back(D_iround); + } + + anemoi_permutation_prime_field_gadget d( + pb, C, D, X_left, X_right, Y_left, Y_right, "anemoi permutation"); + + // generate constraints + d.generate_r1cs_constraints(); + + // Input values: X_left = 0,1,2...L-1 ; X_right = L, L+1, 2L-1 + for (size_t i = 0; i < NumStateColumns_L; i++) { + pb.val(X_left[i]) = FieldT(i); + pb.val(X_right[i]) = FieldT(NumStateColumns_L + i); + } + + // generate witness for the given input + d.generate_r1cs_witness(); + + for (size_t i = 0; i < NumStateColumns_L; i++) { + // ASSERT_EQ(Y_expect[i], pb.val(Y_left[i])); + // ASSERT_EQ(Y_expect[NumStateColumns_L + i], + // pb.val(Y_right[i])); + FieldT Y_left_val_i = pb.val(Y_left[i]); + FieldT Y_right_val_i = pb.val(Y_right[i]); + printf("[%s:%d] Y[%zu]\n", __FILE__, __LINE__, i); + Y_left_val_i.print(); + Y_right_val_i.print(); + } + + if (expected_values_fn) { + // TBD + ASSERT_EQ(0, 0); + } + + // TBD +#if 0 + ASSERT_TRUE(pb.is_satisfied()); + test_pb_verify_circuit(pb); +#endif + + libff::print_time("anemoi_permutation_prime_field_gadget tests successful"); +} + void test_anemoi_permutation_mds_bls12_381() { using ppT = libff::bls12_381_pp; @@ -365,10 +473,14 @@ void test_intermediate_gadgets_bls12_381() template void test_for_curve( - expected_round_values_fn_t expected_round_values_fn = 0) + expected_round_values_fn_t expected_round_values_fn = 0, + expected_values_fn_t expected_values_fn = 0) { // Use the original parameters for the full permutation using parameters = anemoi_parameters; + + // Test single round +#if 0 test_anemoi_permutation_round_prime_field_gadget( expected_round_values_fn); test_anemoi_permutation_round_prime_field_gadget( @@ -377,15 +489,29 @@ void test_for_curve( expected_round_values_fn); test_anemoi_permutation_round_prime_field_gadget( expected_round_values_fn); +#endif + // Test single round + test_anemoi_permutation_prime_field_gadget( + expected_values_fn); +#if 1 + test_anemoi_permutation_prime_field_gadget( + expected_values_fn); + test_anemoi_permutation_prime_field_gadget( + expected_values_fn); + test_anemoi_permutation_prime_field_gadget( + expected_values_fn); +#endif } -TEST(TestAnemoiGadget, BLS12_381) { test_intermediate_gadgets_bls12_381(); } - TEST(TestForCurve, BLS12_381) { - test_for_curve(&anemoi_expected_output_one_round); + test_for_curve( + &anemoi_expected_output_one_round, &anemoi_expected_output); } +#if 0 +TEST(TestAnemoiGadget, BLS12_381) { test_intermediate_gadgets_bls12_381(); } + TEST(TestForCurve, BLS12_377) { // TODO For BLS12_377 alpha = 11, which is the first value for @@ -415,6 +541,7 @@ TEST(TestForCurve, BW6_761) { test_for_curve(); } TEST(TestForCurve, BN128) { test_for_curve(); } TEST(TestForCurve, ALT_BN128) { test_for_curve(); } +#endif // #if 0 int main(int argc, char **argv) {