Skip to content

Commit

Permalink
feat: Extended IPA tests and fuzzing (#5140)
Browse files Browse the repository at this point in the history
This PR extends IPA testing with testing for specific cases such as:
1) Opening the polynomial at zero
2) Opening a polynomial  = 0
3) Opening with challenges that are zero (should fail)
4) Making the polynomial zero after one round (after one fold)

To this end it adds a mock transcript to provide artificial challenges.
It also adds a fuzzer for IPA and fixes a bug in group arithmetic found
during testing
  • Loading branch information
Rumata888 authored Mar 15, 2024
1 parent 7e8e8e5 commit 0ae5ace
Show file tree
Hide file tree
Showing 9 changed files with 584 additions and 34 deletions.
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
// 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
* @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);

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

0 comments on commit 0ae5ace

Please sign in to comment.