-
Notifications
You must be signed in to change notification settings - Fork 270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Extended IPA tests and fuzzing #5140
Changes from all commits
c5feab7
8c7a4bf
f93c3b6
6a239b0
36748e8
5f6a937
cb3aa08
bd4f989
9d90f1c
296aa07
2336bdc
f9cdaf0
30d5e55
5bb8af7
b4b447f
d07d9be
d9d2d8c
e76692a
a738148
201f083
ea28dc2
c9949bc
12813c7
dcf306d
76de62f
e296885
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CommitmentKey<Curve>> ck; | ||
std::shared_ptr<VerifierCommitmentKey<Curve>> vk; | ||
/** | ||
* @brief Class that allows us to call internal IPA methods, because it's friendly | ||
* | ||
*/ | ||
class ProxyCaller { | ||
public: | ||
template <typename Transcript> | ||
static void compute_opening_proof_internal(const std::shared_ptr<CommitmentKey<Curve>>& ck, | ||
const OpeningPair<Curve>& opening_pair, | ||
const Polynomial<Curve::ScalarField>& polynomial, | ||
const std::shared_ptr<Transcript>& transcript) | ||
{ | ||
IPA<Curve>::compute_opening_proof_internal(ck, opening_pair, polynomial, transcript); | ||
} | ||
template <typename Transcript> | ||
static bool verify_internal(const std::shared_ptr<VerifierCommitmentKey<Curve>>& vk, | ||
const OpeningClaim<Curve>& opening_claim, | ||
const std::shared_ptr<Transcript>& transcript) | ||
{ | ||
return IPA<Curve>::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<CommitmentKey<Curve>>(COMMITMENT_TEST_NUM_POINTS); | ||
auto crs_factory = std::make_shared<srs::factories::FileCrsFactory<curve::Grumpkin>>("../srs_db/grumpkin", | ||
COMMITMENT_TEST_NUM_POINTS); | ||
vk = std::make_shared<VerifierCommitmentKey<curve::Grumpkin>>(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<Fr>; | ||
// We need data | ||
if (size == 0) { | ||
return 0; | ||
} | ||
// Get the logarighmic size of polynomial | ||
const auto log_size = static_cast<size_t>(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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wdym by montgomery switch? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we do field(uint256_t(buffer)), underneath the value converts to montgomery form, after it is reduced several times. This means that the internal data of the field element is a map of the buffer, and so a single bit change of the buffer will lead to huge changes in internal representation. To be able to switch it off, I use flags from input to convert the field element back from montgomery form, so the internal form directly corresponds with what's in the buffer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thannks for explaining! |
||
// 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<MockTranscript>(); | ||
|
||
std::vector<uint256_t> 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<uint256_t> 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<Curve>{ x, poly.evaluate(x) }; | ||
auto const opening_claim = OpeningClaim<Curve>{ 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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
#include <vector> | ||
|
||
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,17 +72,28 @@ namespace bb { | |
documentation </a> | ||
*/ | ||
template <typename Curve> class IPA { | ||
// clang-fromat on | ||
using Fr = typename Curve::ScalarField; | ||
using GroupElement = typename Curve::Element; | ||
using Commitment = typename Curve::AffineElement; | ||
using CK = CommitmentKey<Curve>; | ||
using VK = VerifierCommitmentKey<Curve>; | ||
using Polynomial = bb::Polynomial<Fr>; | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where exactly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Transcript Transcript? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One is an automatic name of the parameter, the other is "Transcript type" |
||
* @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 <typename Curve> 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 <typename Curve> 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>& ck, | ||
const OpeningPair<Curve>& opening_pair, | ||
const Polynomial& polynomial, | ||
const std::shared_ptr<NativeTranscript>& transcript) | ||
template <typename Transcript> | ||
static void compute_opening_proof_internal(const std::shared_ptr<CK>& ck, | ||
const OpeningPair<Curve>& opening_pair, | ||
const Polynomial& polynomial, | ||
const std::shared_ptr<Transcript>& transcript) | ||
{ | ||
ASSERT(opening_pair.challenge != 0 && "The challenge point should not be zero"); | ||
// clang-format on | ||
auto poly_length = static_cast<size_t>(polynomial.size()); | ||
|
||
// Step 1. | ||
|
@@ -127,6 +141,10 @@ template <typename Curve> class IPA { | |
// Receive challenge for the auxiliary generator | ||
const Fr generator_challenge = transcript->template get_challenge<Fr>("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 <typename Curve> class IPA { | |
|
||
// Step 6.d | ||
// Receive the challenge from the verifier | ||
const Fr round_challenge = transcript->get_challenge<Fr>("IPA:round_challenge_" + index); | ||
const Fr round_challenge = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it seemed everywhere else we use ASSERTs. I know they only trigger in debug mode, but that should be fine for now, right? Or is there any specific reason why we want to go with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also we don't document anywhere why these cannot be zero right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is ASSERTs should be used to detect cases of programmers' mistakes. And in this case you'd want to abort in production,too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added docs |
||
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 <typename Curve> 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 <typename Curve> 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 <typename Curve> class IPA { | |
* | ||
* | ||
*/ | ||
static bool verify(const std::shared_ptr<VK>& vk, | ||
const OpeningClaim<Curve>& opening_claim, | ||
const std::shared_ptr<NativeTranscript>& transcript) | ||
template <typename Transcript> | ||
static bool verify_internal(const std::shared_ptr<VK>& vk, | ||
const OpeningClaim<Curve>& opening_claim, | ||
const std::shared_ptr<Transcript>& transcript) | ||
{ | ||
// Step 1. | ||
// Receive polynomial_degree + 1 = d from the prover | ||
auto poly_length = static_cast<uint32_t>(transcript->template receive_from_prover<typename Curve::BaseField>( | ||
"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<Fr>("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<size_t>(numeric::get_msb(poly_length)); | ||
|
@@ -340,6 +370,9 @@ template <typename Curve> class IPA { | |
auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index); | ||
auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index); | ||
round_challenges[i] = transcript->template get_challenge<Fr>("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 <typename Curve> class IPA { | |
// Construct vector s | ||
std::vector<Fr> 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 <typename Curve> 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>& ck, | ||
const OpeningPair<Curve>& opening_pair, | ||
const Polynomial& polynomial, | ||
const std::shared_ptr<NativeTranscript>& 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>& vk, | ||
const OpeningClaim<Curve>& opening_claim, | ||
const std::shared_ptr<NativeTranscript>& transcript) | ||
{ | ||
return verify_internal(vk, opening_claim, transcript); | ||
} | ||
}; | ||
|
||
} // namespace bb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's such a small crs btw :o