Skip to content
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

Merged
merged 26 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

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

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdym by montgomery switch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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;
}
113 changes: 92 additions & 21 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

namespace bb {
// clang-format off

/**
* @brief IPA (inner product argument) commitment scheme class.
Expand All @@ -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
*
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where exactly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transcript Transcript?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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$
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Copy link
Contributor

Choose a reason for hiding this comment

The 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 throw_or_abort

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also we don't document anywhere why these cannot be zero right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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
Expand All @@ -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$
Expand All @@ -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));
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Loading
Loading