diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.fuzzer.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.fuzzer.cpp new file mode 100644 index 00000000000..f6d98bbd7fa --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.fuzzer.cpp @@ -0,0 +1,158 @@ +#define IPA_FUZZ_TEST +#include "ipa.hpp" +#include "./mock_transcript.hpp" +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/commitment_schemes/verification_key.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/srs/factories/file_crs_factory.hpp" + +namespace bb { + +// We actually only use 4, because fuzzing is very slow +constexpr size_t COMMITMENT_TEST_NUM_POINTS = 32; +using Curve = curve::Grumpkin; +std::shared_ptr> ck; +std::shared_ptr> vk; +/** + * @brief Class that allows us to call internal IPA methods, because it's friendly + * + */ +class ProxyCaller { + public: + template + static void compute_opening_proof_internal(const std::shared_ptr>& ck, + const OpeningPair& opening_pair, + const Polynomial& polynomial, + const std::shared_ptr& transcript) + { + IPA::compute_opening_proof_internal(ck, opening_pair, polynomial, transcript); + } + template + static bool verify_internal(const std::shared_ptr>& vk, + const OpeningClaim& opening_claim, + const std::shared_ptr& transcript) + { + return IPA::verify_internal(vk, opening_claim, transcript); + } +}; +} // namespace bb + +/** + * @brief Initialize SRS, commitment key, verification key + * + */ +extern "C" void LLVMFuzzerInitialize(int*, char***) +{ + srs::init_grumpkin_crs_factory("../srs_db/ignition"); + ck = std::make_shared>(COMMITMENT_TEST_NUM_POINTS); + auto crs_factory = std::make_shared>("../srs_db/grumpkin", + COMMITMENT_TEST_NUM_POINTS); + vk = std::make_shared>(COMMITMENT_TEST_NUM_POINTS, crs_factory); +} + +// This define is needed to make ProxyClass a friend of IPA +#define IPA_FUZZ_TEST +#include "ipa.hpp" + +/** + * @brief A fuzzer for the IPA primitive + * + * @details Parses the given data as a polynomial, a sequence of challenges for the transcript and the evaluation point, + * then opens the polynomial with IPA and verifies that the opening was correct + */ +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + using Fr = grumpkin::fr; + using Polynomial = Polynomial; + // We need data + if (size == 0) { + return 0; + } + // Get the logarighmic size of polynomial + const auto log_size = static_cast(data[0]); + // More than 4 is so bad + if (log_size == 0 || log_size > 2) { + return 0; + } + const auto* offset = data + 1; + const auto num_challenges = log_size + 1; + // How much data do we need? + // Challenges: sizeof(uint256_t) * num_challenges + 1 for montgomery switch + // Polynomial: sizeof(uint256_t) * size + 1 per size/8 + // Eval x: sizeof(uint256_t) + 1 + const size_t polynomial_size = (1 << log_size); + // Bytes controlling montgomery switching for polynomial coefficients + const size_t polynomial_control_bytes = (polynomial_size < 8 ? 1 : polynomial_size / 8); + const size_t expected_size = + sizeof(uint256_t) * (num_challenges + polynomial_size + 1) + 3 + polynomial_control_bytes; + if (size < expected_size) { + return 0; + } + + // Initialize transcript + auto transcript = std::make_shared(); + + std::vector challenges(num_challenges); + // Get the byte, where bits control if we parse challenges in montgomery form or not + const auto control_byte = offset[0]; + offset++; + // Get challenges one by one + for (size_t i = 0; i < num_challenges; i++) { + auto challenge = *(uint256_t*)(offset); + + if ((control_byte >> i) & 1) { + // If control byte says so, parse the value from input as if it's internal state of the field (already + // converted to montgomery). This allows modifying the state directly + auto field_challenge = Fr(challenge); + + challenge = field_challenge.from_montgomery_form(); + } + // Challenges can't be zero + if (Fr(challenge).is_zero()) { + return 0; + } + challenges[i] = challenge; + offset += sizeof(uint256_t); + } + + // Put challenges into the transcript + transcript->initialize(challenges); + + // Parse polynomial + std::vector polynomial_coefficients(polynomial_size); + for (size_t i = 0; i < polynomial_size; i++) { + polynomial_coefficients[i] = *(uint256_t*)(offset); + offset += sizeof(uint256_t); + } + Polynomial poly(polynomial_size); + + // Convert from montgomery if the appropriate bit is set + for (size_t i = 0; i < polynomial_size; i++) { + auto b = offset[i / 8]; + + poly[i] = polynomial_coefficients[i]; + if ((b >> (i % 8)) & 1) { + poly[i].self_from_montgomery_form(); + } + } + + offset += polynomial_control_bytes; + // Parse the x we are evaluating on + auto x = Fr(*(uint256_t*)offset); + offset += sizeof(uint256_t); + if ((offset[0] & 1) != 0) { + x.self_from_montgomery_form(); + } + auto const opening_pair = OpeningPair{ x, poly.evaluate(x) }; + auto const opening_claim = OpeningClaim{ opening_pair, ck->commit(poly) }; + ProxyCaller::compute_opening_proof_internal(ck, opening_pair, poly, transcript); + + // Reset challenge indices + transcript->reset_indices(); + + // Should verify + if (!ProxyCaller::verify_internal(vk, opening_claim, transcript)) { + return 1; + } + return 0; +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp index 7637cec8a67..dfe6f6d94f0 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp @@ -10,6 +10,7 @@ #include namespace bb { +// clang-format off /** * @brief IPA (inner product argument) commitment scheme class. @@ -26,8 +27,9 @@ namespace bb { *The opening and verification procedures expect that there already exists a commitment to \f$f(x)\f$ which is the *scalar product \f$[f(x)]=\langle\vec{f},\vec{G}\rangle\f$, where \f$\vec{f}=(f_0, f_1,..., f_{d-1})\f$​ * - * The opening procedure documentation can be found in the description of \link IPA::compute_opening_proof - compute_opening_proof \endlink. The verification procedure documentation is in \link IPA::verify verify \endlink + * The opening procedure documentation can be found in the description of \link IPA::compute_opening_proof_internal + compute_opening_proof_internal \endlink. The verification procedure documentation is in \link IPA::verify_internal + verify_internal \endlink * * @tparam Curve * @@ -70,6 +72,7 @@ namespace bb { documentation */ template class IPA { + // clang-fromat on using Fr = typename Curve::ScalarField; using GroupElement = typename Curve::Element; using Commitment = typename Curve::AffineElement; @@ -77,10 +80,20 @@ template class IPA { using VK = VerifierCommitmentKey; using Polynomial = bb::Polynomial; - public: +// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of IPA specifically +#ifdef IPA_TEST + FRIEND_TEST(IPATest, ChallengesAreZero); + FRIEND_TEST(IPATest, AIsZeroAfterOneRound); +#endif +#ifdef IPA_FUZZ_TEST + friend class ProxyCaller; +#endif + // clang-format off + /** - * @brief Compute an inner product argument proof for opening a single polynomial at a single evaluation point + * @brief Compute an inner product argument proof for opening a single polynomial at a single evaluation point. * + * @tparam Transcript Transcript type. Useful for testing * @param ck The commitment key containing srs and pippenger_runtime_state for computing MSM * @param opening_pair (challenge, evaluation) * @param polynomial The witness polynomial whose opening proof needs to be computed @@ -92,7 +105,7 @@ template class IPA { *as follows: * *1. Send the degree of \f$f(x)\f$ plus one, equal to \f$d\f$ to the verifier - *2. Receive the generator challenge \f$u\f$ from the verifier + *2. Receive the generator challenge \f$u\f$ from the verifier. If it is zero, abort *3. Compute the auxiliary generator \f$U=u\cdot G\f$, where \f$G\f$ is a generator of \f$E(\mathbb{F}_p)\f$​ *4. Set \f$\vec{G}_{k}=\vec{G}\f$, \f$\vec{a}_{k}=\vec{p}\f$ *5. Compute the vector \f$\vec{b}_{k}=(1,\beta,\beta^2,...,\beta^{d-1})\f$ @@ -104,19 +117,20 @@ template class IPA { *\f$R_{i-1}=\langle\vec{a}_{i\_high},\vec{G}_{i\_low}\rangle+\langle\vec{a}_{i\_high},\vec{b}_{i\_low}\rangle\cdot U\f$ * 3. Send \f$L_{i-1}\f$ and \f$R_{i-1}\f$ to the verifier - * 4. Receive round challenge \f$u_{i-1}\f$ from the verifier​ + * 4. Receive round challenge \f$u_{i-1}\f$ from the verifier​, if it is zero, abort * 5. Compute \f$\vec{G}_{i-1}=\vec{G}_{i\_low}+u_{i-1}^{-1}\cdot \vec{G}_{i\_high}\f$ * 6. Compute \f$\vec{a}_{i-1}=\vec{a}_{i\_low}+u_{i-1}\cdot \vec{a}_{i\_high}\f$ * 7. Compute \f$\vec{b}_{i-1}=\vec{b}_{i\_low}+u_{i-1}^{-1}\cdot \vec{b}_{i\_high}\f$​ * *7. Send the final \f$\vec{a}_{0} = (a_0)\f$ to the verifier */ - static void compute_opening_proof(const std::shared_ptr& ck, - const OpeningPair& opening_pair, - const Polynomial& polynomial, - const std::shared_ptr& transcript) + template + static void compute_opening_proof_internal(const std::shared_ptr& ck, + const OpeningPair& opening_pair, + const Polynomial& polynomial, + const std::shared_ptr& transcript) { - ASSERT(opening_pair.challenge != 0 && "The challenge point should not be zero"); + // clang-format on auto poly_length = static_cast(polynomial.size()); // Step 1. @@ -127,6 +141,10 @@ template class IPA { // Receive challenge for the auxiliary generator const Fr generator_challenge = transcript->template get_challenge("IPA:generator_challenge"); + if (generator_challenge.is_zero()) { + throw_or_abort("The generator challenge can't be zero"); + } + // Step 3. // Compute auxiliary generator U auto aux_generator = Commitment::one() * generator_challenge; @@ -246,7 +264,11 @@ template class IPA { // Step 6.d // Receive the challenge from the verifier - const Fr round_challenge = transcript->get_challenge("IPA:round_challenge_" + index); + const Fr round_challenge = transcript->template get_challenge("IPA:round_challenge_" + index); + + if (round_challenge.is_zero()) { + throw_or_abort("IPA round challenge is zero"); + } const Fr round_challenge_inv = round_challenge.invert(); // Step 6.e @@ -285,6 +307,7 @@ template class IPA { /** * @brief Verify the correctness of a Proof * + * @tparam Transcript Allows to specify a transcript class. Useful for testing * @param vk Verification_key containing srs and pippenger_runtime_state to be used for MSM * @param opening_claim Contains the commitment C and opening pair \f$(\beta, f(\beta))\f$ * @param transcript Transcript with elements from the prover and generated challenges @@ -294,9 +317,10 @@ template class IPA { * @details The procedure runs as follows: * *1. Receive \f$d\f$ (polynomial degree plus one) from the prover - *2. Receive the generator challenge \f$u\f$ and computes \f$U=u\cdot G\f$ + *2. Receive the generator challenge \f$u\f$, abort if it's zero, otherwise compute \f$U=u\cdot G\f$ *3. Compute \f$C'=C+f(\beta)\cdot U\f$ - *4. Receive \f$L_j, R_j\f$ and compute challenges \f$u_j\f$ for \f$j \in {k-1,..,0}\f$ + *4. Receive \f$L_j, R_j\f$ and compute challenges \f$u_j\f$ for \f$j \in {k-1,..,0}\f$, abort immediately on + receiving a \f$u_j=0\f$ *5. Compute \f$C_0 = C' + \sum_{j=0}^{k-1}(u_j^{-1}L_j + u_jR_j)\f$ *6. Compute \f$b_0=g(\beta)=\prod_{i=0}^{k-1}(1+u_{i}^{-1}x^{2^{i}})\f$ *7. Compute vector \f$\vec{s}=(1,u_{0}^{-1},u_{1}^{-1},u_{0}^{-1}u_{1}^{-1},...,\prod_{i=0}^{k-1}u_{i}^{-1})\f$ @@ -307,18 +331,24 @@ template class IPA { * * */ - static bool verify(const std::shared_ptr& vk, - const OpeningClaim& opening_claim, - const std::shared_ptr& transcript) + template + static bool verify_internal(const std::shared_ptr& vk, + const OpeningClaim& opening_claim, + const std::shared_ptr& transcript) { // Step 1. // Receive polynomial_degree + 1 = d from the prover auto poly_length = static_cast(transcript->template receive_from_prover( - "IPA:poly_degree_plus_1")); // note this is base field because this is a uint32_t, which should map to a - // bb::fr, not a grumpkin::fr, which is a BaseField element for Grumpkin + "IPA:poly_degree_plus_1")); // note this is base field because this is a uint32_t, which should map + // to a bb::fr, not a grumpkin::fr, which is a BaseField element for + // Grumpkin // Step 2. // Receive generator challenge u and compute auxiliary generator const Fr generator_challenge = transcript->template get_challenge("IPA:generator_challenge"); + + if (generator_challenge.is_zero()) { + throw_or_abort("The generator challenge can't be zero"); + } auto aux_generator = Commitment::one() * generator_challenge; auto log_poly_degree = static_cast(numeric::get_msb(poly_length)); @@ -340,6 +370,9 @@ template class IPA { auto element_L = transcript->template receive_from_prover("IPA:L_" + index); auto element_R = transcript->template receive_from_prover("IPA:R_" + index); round_challenges[i] = transcript->template get_challenge("IPA:round_challenge_" + index); + if (round_challenges[i].is_zero()) { + throw_or_abort("Round challenges can't be zero"); + } round_challenges_inv[i] = round_challenges[i].invert(); msm_elements[2 * i] = element_L; @@ -369,8 +402,8 @@ template class IPA { // Construct vector s std::vector s_vec(poly_length); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/857): This code is not efficient as its O(nlogn). - // This can be optimized to be linear by computing a tree of products. Its very readable, so we're + // TODO(https://github.com/AztecProtocol/barretenberg/issues/857): This code is not efficient as its + // O(nlogn). This can be optimized to be linear by computing a tree of products. Its very readable, so we're // leaving it unoptimized for now. run_loop_in_parallel_if_effective( poly_length, @@ -430,6 +463,44 @@ template class IPA { // Check if C_right == C₀ return (C_zero.normalize() == right_hand_side.normalize()); } + + public: + /** + * @brief Compute an inner product argument proof for opening a single polynomial at a single evaluation point. + * + * @param ck The commitment key containing srs and pippenger_runtime_state for computing MSM + * @param opening_pair (challenge, evaluation) + * @param polynomial The witness polynomial whose opening proof needs to be computed + * @param transcript Prover transcript + * + * @remark Detailed documentation can be found in \link IPA::compute_opening_proof_internal + * compute_opening_proof_internal \endlink. + */ + static void compute_opening_proof(const std::shared_ptr& ck, + const OpeningPair& opening_pair, + const Polynomial& polynomial, + const std::shared_ptr& transcript) + { + compute_opening_proof_internal(ck, opening_pair, polynomial, transcript); + } + + /** + * @brief Verify the correctness of a Proof + * + * @param vk Verification_key containing srs and pippenger_runtime_state to be used for MSM + * @param opening_claim Contains the commitment C and opening pair \f$(\beta, f(\beta))\f$ + * @param transcript Transcript with elements from the prover and generated challenges + * + * @return true/false depending on if the proof verifies + * + *@remark The verification procedure documentation is in \link IPA::verify_internal verify_internal \endlink + */ + static bool verify(const std::shared_ptr& vk, + const OpeningClaim& opening_claim, + const std::shared_ptr& transcript) + { + return verify_internal(vk, opening_claim, transcript); + } }; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp index 719be120fda..87f14d5630c 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -1,6 +1,6 @@ -#include "ipa.hpp" #include "../gemini/gemini.hpp" #include "../shplonk/shplonk.hpp" +#include "./mock_transcript.hpp" #include "barretenberg/commitment_schemes/commitment_key.test.hpp" #include "barretenberg/common/mem.hpp" #include "barretenberg/ecc/curves/bn254/fq12.hpp" @@ -8,6 +8,7 @@ #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/polynomials/polynomial_arithmetic.hpp" #include +#include using namespace bb; @@ -24,6 +25,9 @@ class IPATest : public CommitmentTest { }; } // namespace +#define IPA_TEST +#include "ipa.hpp" + TEST_F(IPATest, CommitOnManyZeroCoeffPolyWorks) { constexpr size_t n = 4; @@ -44,6 +48,149 @@ TEST_F(IPATest, CommitOnManyZeroCoeffPolyWorks) EXPECT_EQ(expected.normalize(), commitment.normalize()); } +// This test checks that we can correctly open a zero polynomial. Since we often have point at infinity troubles, it +// detects those. +TEST_F(IPATest, OpenZeroPolynomial) +{ + using IPA = IPA; + constexpr size_t n = 4; + Polynomial poly(n); + // Commit to a zero polynomial + GroupElement commitment = this->commit(poly); + EXPECT_TRUE(commitment.is_point_at_infinity()); + + auto [x, eval] = this->random_eval(poly); + EXPECT_EQ(eval, Fr::zero()); + const OpeningPair opening_pair = { x, eval }; + const OpeningClaim opening_claim{ opening_pair, commitment }; + + // initialize empty prover transcript + auto prover_transcript = std::make_shared(); + IPA::compute_opening_proof(this->ck(), opening_pair, poly, prover_transcript); + + // initialize verifier transcript from proof data + auto verifier_transcript = std::make_shared(prover_transcript->proof_data); + + auto result = IPA::verify(this->vk(), opening_claim, verifier_transcript); + EXPECT_TRUE(result); +} + +// This test makes sure that even if the whole vector \vec{b} generated from the x, at which we open the polynomial, is +// zero, IPA behaves +TEST_F(IPATest, OpenAtZero) +{ + using IPA = IPA; + // generate a random polynomial, degree needs to be a power of two + size_t n = 128; + auto poly = this->random_polynomial(n); + Fr x = Fr::zero(); + auto eval = poly.evaluate(x); + auto commitment = this->commit(poly); + const OpeningPair opening_pair = { x, eval }; + const OpeningClaim opening_claim{ opening_pair, commitment }; + + // initialize empty prover transcript + auto prover_transcript = std::make_shared(); + IPA::compute_opening_proof(this->ck(), opening_pair, poly, prover_transcript); + + // initialize verifier transcript from proof data + auto verifier_transcript = std::make_shared(prover_transcript->proof_data); + + auto result = IPA::verify(this->vk(), opening_claim, verifier_transcript); + EXPECT_TRUE(result); +} + +namespace bb { +#if !defined(__wasm__) +// This test ensures that IPA throws or aborts when a challenge is zero, since it breaks the logic of the argument +TEST_F(IPATest, ChallengesAreZero) +{ + using IPA = IPA; + // generate a random polynomial, degree needs to be a power of two + size_t n = 128; + auto poly = this->random_polynomial(n); + auto [x, eval] = this->random_eval(poly); + auto commitment = this->commit(poly); + const OpeningPair opening_pair = { x, eval }; + const OpeningClaim opening_claim{ opening_pair, commitment }; + + // initialize an empty mock transcript + auto transcript = std::make_shared(); + const size_t num_challenges = numeric::get_msb(n) + 1; + std::vector random_vector(num_challenges); + + // Generate a random element vector with challenges + for (size_t i = 0; i < num_challenges; i++) { + random_vector[i] = Fr::random_element(); + } + + // Compute opening proofs several times, where each time a different challenge is equal to zero. Should cause + // exceptions + for (size_t i = 0; i < num_challenges; i++) { + auto new_random_vector = random_vector; + new_random_vector[i] = Fr::zero(); + transcript->initialize(new_random_vector); + EXPECT_ANY_THROW(IPA::compute_opening_proof_internal(this->ck(), opening_pair, poly, transcript)); + } + // Fill out a vector of affine elements that the verifier receives from the prover with generators (we don't care + // about them right now) + std::vector lrs(num_challenges * 2); + for (size_t i = 0; i < num_challenges * 2; i++) { + lrs[i] = Curve::AffineElement::one(); + } + // Verify proofs several times, where each time a different challenge is equal to zero. Should cause + // exceptions + for (size_t i = 0; i < num_challenges; i++) { + auto new_random_vector = random_vector; + new_random_vector[i] = Fr::zero(); + transcript->initialize(new_random_vector, lrs, { uint256_t(n) }); + EXPECT_ANY_THROW(IPA::verify_internal(this->vk(), opening_claim, transcript)); + } +} + +// This test checks that if the vector \vec{a_new} becomes zero after one round, it doesn't break IPA. +TEST_F(IPATest, AIsZeroAfterOneRound) +{ + using IPA = IPA; + // generate a random polynomial, degree needs to be a power of two + size_t n = 4; + auto poly = Polynomial(n); + for (size_t i = 0; i < n / 2; i++) { + poly[i] = Fr::random_element(); + poly[i + (n / 2)] = poly[i]; + } + auto [x, eval] = this->random_eval(poly); + auto commitment = this->commit(poly); + const OpeningPair opening_pair = { x, eval }; + const OpeningClaim opening_claim{ opening_pair, commitment }; + + // initialize an empty mock transcript + auto transcript = std::make_shared(); + const size_t num_challenges = numeric::get_msb(n) + 1; + std::vector random_vector(num_challenges); + + // Generate a random element vector with challenges + for (size_t i = 0; i < num_challenges; i++) { + random_vector[i] = Fr::random_element(); + } + // Substitute the first folding challenge with -1 + random_vector[1] = -Fr::one(); + + // Put the challenges in the transcript + transcript->initialize(random_vector); + + // Compute opening proof + IPA::compute_opening_proof_internal(this->ck(), opening_pair, poly, transcript); + + // Reset indices + transcript->reset_indices(); + + // Verify + EXPECT_TRUE(IPA::verify_internal(this->vk(), opening_claim, transcript)); +} +#endif +} // namespace bb + TEST_F(IPATest, Commit) { constexpr size_t n = 128; diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/mock_transcript.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/mock_transcript.hpp new file mode 100644 index 00000000000..91f3b2d03b3 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/mock_transcript.hpp @@ -0,0 +1,100 @@ +#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +namespace bb { +/** + * @brief Mock transcript class used by IPA tests and fuzzer + * + * @details This transcript can send previously determined challenges instead of ones generated by Fiat Shamir. It can + * also store elements received from the prover + * + */ +class MockTranscript { + public: + // Vector of challenges sent to the verifier + std::vector challenges; + // Vector of group elements received from the prover / sent to the verifier + std::vector group_elements; + // Vector of field elements received from the prover / sent to the verifier. uint256_t is used to ignore field type + std::vector field_elements; + + // Indices of the elements being sampled + size_t current_challenge_index = 0; + size_t current_field_index = 0; + size_t current_group_index = 0; + + /** + * @brief Initialize the transcript (requires to submit the challenges) + * + * @param challenges_ Challenges that will be sent to the prover/verifier + * @param group_elements_ Group elements sent to the verifier + * @param field_elements_ Field elements sent to the verifier + */ + void initialize(std::vector challenges_, + std::vector group_elements_ = {}, + std::vector field_elements_ = {}) + { + challenges = std::move(challenges_); + current_challenge_index = 0; + current_field_index = 0; + current_group_index = 0; + group_elements = std::move(group_elements_); + field_elements = std::move(field_elements_); + } + /** + * @brief Reset the indices of elements sampled after using the transcript with the prover + * + * @details After the transcipt received elements from the prover, this method allows to reset counters so that the + * verifier can receive those elements + */ + void reset_indices() + { + current_challenge_index = 0; + current_field_index = 0; + current_challenge_index = 0; + } + /** + * @brief Send something that can be converted to uint256_t to the verifier (used for field elements) + * + */ + template void send_to_verifier(const std::string&, const T& element) + { + // GCC breaks explicit specialization, so I have to do this + if constexpr (std::is_same_v) { + + group_elements.push_back(element); + } else { + field_elements.push_back(static_cast(element)); + } + } + + /** + * @brief Get a challenge from the verifier + * + */ + template T get_challenge(const std::string&) + { + // No heap overreads, please + ASSERT(current_challenge_index < challenges.size()); + T result = static_cast(challenges[current_challenge_index]); + current_challenge_index++; + return result; + } + /** + * @brief Receive elements from the prover + * + */ + template T receive_from_prover(const std::string&) + { + if constexpr (std::is_same_v || + std::is_same_v) { + ASSERT(field_elements.size() > current_field_index); + return field_elements[current_field_index++]; + } + if constexpr (std::is_same_v) { + ASSERT(group_elements.size() > current_group_index); + return group_elements[current_group_index++]; + } + } +}; +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/common/mem.hpp b/barretenberg/cpp/src/barretenberg/common/mem.hpp index fe4a6351e9e..a26e615de8c 100644 --- a/barretenberg/cpp/src/barretenberg/common/mem.hpp +++ b/barretenberg/cpp/src/barretenberg/common/mem.hpp @@ -31,6 +31,10 @@ inline void* protected_aligned_alloc(size_t alignment, size_t size) { size += (size % alignment); void* t = nullptr; + // pad size to alignment + if (size % alignment != 0) { + size += alignment - (size % alignment); + } // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) t = aligned_alloc(alignment, size); if (t == nullptr) { diff --git a/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.hpp b/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.hpp index 003478324e5..e50677834e0 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.hpp @@ -160,7 +160,9 @@ template class alignas(64) affine_ } Fq x; Fq y; + // for serialization: update with new fields + // TODO(https://github.com/AztecProtocol/barretenberg/issues/908) point at inifinty isn't handled MSGPACK_FIELDS(x, y); }; } // namespace bb::group_elements diff --git a/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.test.cpp b/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.test.cpp index 38f0518fb34..4c36b16c9d4 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/groups/affine_element.test.cpp @@ -98,6 +98,44 @@ template class TestAffineElement : public testing::Test { affine_element R(0, P.y); ASSERT_FALSE(P == R); } + // Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie + // on the curve, depending on the y-coordinate. + static void test_infinity_ordering_regression() + { + affine_element P(0, 1); + affine_element Q(0, 1); + + P.self_set_infinity(); + EXPECT_NE(P < Q, Q < P); + } + + /** + * @brief Check that msgpack encoding is consistent with decoding + * + */ + static void test_msgpack_roundtrip() + { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/908) point at inifinty isn't handled + auto [actual, expected] = msgpack_roundtrip(affine_element{ 1, 1 }); + EXPECT_EQ(actual, expected); + } + + /** + * @brief A regression test to make sure the -1 case is covered + * + */ + static void test_batch_endomorphism_by_minus_one() + { + constexpr size_t num_points = 2; + std::vector affine_points(num_points, affine_element::one()); + + std::vector result = + element::batch_mul_with_endomorphism(affine_points, -affine_element::Fr::one()); + + for (size_t i = 0; i < num_points; i++) { + EXPECT_EQ(affine_points[i], -result[i]); + } + } }; using TestTypes = testing::Types; @@ -128,21 +166,14 @@ TYPED_TEST(TestAffineElement, PointCompressionUnsafe) } } -// Regression test to ensure that the point at infinity is not equal to its coordinate-wise reduction, which may lie -// on the curve, depending on the y-coordinate. -TEST(AffineElement, InfinityOrderingRegression) +TYPED_TEST(TestAffineElement, InfinityOrderingRegression) { - secp256k1::g1::affine_element P(0, 1); - secp256k1::g1::affine_element Q(0, 1); - - P.self_set_infinity(); - EXPECT_NE(P < Q, Q < P); + TestFixture::test_infinity_ordering_regression(); } -TEST(AffineElement, Msgpack) +TYPED_TEST(TestAffineElement, Msgpack) { - auto [actual, expected] = msgpack_roundtrip(secp256k1::g1::affine_element{ 1, 1 }); - EXPECT_EQ(actual, expected); + TestFixture::test_msgpack_roundtrip(); } namespace bb::group_elements { @@ -165,7 +196,7 @@ class TestElementPrivate { } // namespace bb::group_elements // Our endomorphism-specialized multiplication should match our generic multiplication -TEST(AffineElement, MulWithEndomorphismMatchesMulWithoutEndomorphism) +TYPED_TEST(TestAffineElement, MulWithEndomorphismMatchesMulWithoutEndomorphism) { for (int i = 0; i < 100; i++) { auto x1 = bb::group_elements::element(grumpkin::g1::affine_element::random_element()); @@ -176,6 +207,7 @@ TEST(AffineElement, MulWithEndomorphismMatchesMulWithoutEndomorphism) } } +// TODO(https://github.com/AztecProtocol/barretenberg/issues/909): These tests are not typed for no reason // Multiplication of a point at infinity by a scalar should be a point at infinity TEST(AffineElement, InfinityMulByScalarIsInfinity) { @@ -213,4 +245,13 @@ TEST(AffineElement, InfinityBatchMulByScalarIsInfinity) grumpkin::g1::element::batch_mul_with_endomorphism(affine_points, grumpkin::fr::random_element()); EXPECT_THAT(result, Each(Property(&grumpkin::g1::affine_element::is_point_at_infinity, Eq(true)))); +} + +TYPED_TEST(TestAffineElement, BatchEndomoprhismByMinusOne) +{ + if constexpr (TypeParam::USE_ENDOMORPHISM) { + TestFixture::test_batch_endomorphism_by_minus_one(); + } else { + GTEST_SKIP(); + } } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/groups/element.hpp b/barretenberg/cpp/src/barretenberg/ecc/groups/element.hpp index f73f7ab5fe0..cb7946beb0f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/groups/element.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/groups/element.hpp @@ -128,6 +128,7 @@ template class alignas(32) element { // return { x, y, Fq::one() }; // } // for serialization: update with new fields + // TODO(https://github.com/AztecProtocol/barretenberg/issues/908) point at inifinty isn't handled MSGPACK_FIELDS(x, y, z); static void conditional_negate_affine(const affine_element& in, diff --git a/barretenberg/cpp/src/barretenberg/ecc/groups/element_impl.hpp b/barretenberg/cpp/src/barretenberg/ecc/groups/element_impl.hpp index 78e3b6cccc3..5cc22a57dba 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/groups/element_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/groups/element_impl.hpp @@ -901,6 +901,32 @@ std::vector> element::batch_mul_with_endomo /*finite_field_additions_per_iteration=*/7, /*finite_field_multiplications_per_iteration=*/6); }; + + // We compute the resulting point through WNAF by evaluating (the (\sum_i (16ⁱ⋅ + // (a_i ∈ {-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15}))) - skew), where skew is 0 or 1. The result of the sum is + // always odd and skew is used to reconstruct an even scalar. This means that to construct scalar p-1, where p is + // the order of the scalar field, we first compute p through the sums and then subtract -1. Howver, since we are + // computing p⋅Point, we get a point at infinity, which is an edgecase, and we don't want to handle edgecases in the + // hot loop since the slow the computation down. So it's better to just handle it here. + if (scalar == -Fr::one()) { + + std::vector results(num_points); + run_loop_in_parallel_if_effective( + num_points, + [&results, &points](size_t start, size_t end) { + for (size_t i = start; i < end; ++i) { + results[i] = -points[i]; + } + }, + /*finite_field_additions_per_iteration=*/0, + /*finite_field_multiplications_per_iteration=*/0, + /*finite_field_inversions_per_iteration=*/0, + /*group_element_additions_per_iteration=*/0, + /*group_element_doublings_per_iteration=*/0, + /*scalar_multiplications_per_iteration=*/0, + /*sequential_copy_ops_per_iteration=*/1); + return results; + } // Compute wnaf for scalar const Fr converted_scalar = scalar.from_montgomery_form();