Skip to content
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
Expand Up @@ -933,5 +933,14 @@ g1::element pippenger_unsafe(fr* scalars,
{
return pippenger(scalars, points, num_initial_points, state, false);
}
g1::element pippenger_without_endomorphism_basis_points(fr* scalars,
g1::affine_element* points,
const size_t num_initial_points,
pippenger_runtime_state& state)
{
std::vector<g1::affine_element> G_mod(num_initial_points * 2);
barretenberg::scalar_multiplication::generate_pippenger_point_table(points, &G_mod[0], num_initial_points);
return pippenger(scalars, &G_mod[0], num_initial_points, state, false);
}
} // namespace scalar_multiplication
} // namespace barretenberg
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ g1::element pippenger_unsafe(fr* scalars,
g1::affine_element* points,
const size_t num_initial_points,
pippenger_runtime_state& state);
g1::element pippenger_without_endomorphism_basis_points(fr* scalars,
g1::affine_element* points,
const size_t num_initial_points,
pippenger_runtime_state& state);

} // namespace scalar_multiplication
} // namespace barretenberg
275 changes: 275 additions & 0 deletions cpp/src/aztec/honk/commitment_scheme/ipa/ipa.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#pragma once
#include <numeric>
#include <srs/reference_string/reference_string.hpp>
#include <ecc/curves/bn254/scalar_multiplication/scalar_multiplication.hpp>
#include <ecc/curves/bn254/pairing.hpp>
#include "../commitment_scheme.hpp"
#include "stdlib/primitives/curves/bn254.hpp"

// Suggested by Zac: Future optimisations
// 1: write a program that generates a large set of generator points (2^23?) and writes to a file on disk
// 2: create a SRS class for IPA similar to existing SRS class, that loads these points from disk
// and stores in struct *and* applies the pippenger point table endomorphism transforation
// 3: when constructing a InnerProductArgument class, pass std::shared_ptr<SRS> as input param and store as member
// variable
// 4: (SRS class should contain a `pippenger_runtime_state` object so it does not need to be repeatedly
// generated)

/**
* @brief IPA (inner-product argument) commitment scheme class. Conforms to the specification
* https://hackmd.io/q-A8y6aITWyWJrvsGGMWNA?view.
*
* @tparam Fr: The underlying field
* @tparam Fq: The field corresponding to the elliptic curve
* @tparam G1: The elliptic curve group
*/
template <typename Fr, typename Fq, typename G1> class InnerProductArgument {
public:
using element = typename G1::element;
using affine_element = typename G1::affine_element;
struct IpaProof {
std::vector<affine_element> L_vec;
std::vector<affine_element> R_vec;
Fr a_zero;
};
// To contain the public inputs for IPA proof
// For now we are including the aux_generator and round_challenges in public input. They will be computed by the
// prover and the verifier by Fiat-Shamir when the challengeGenerator is defined.
struct IpaPubInput {
element commitment;
Fr challenge_point;
Fr evaluation;
size_t poly_degree;
element aux_generator; // To be removed
std::vector<Fr> round_challenges; // To be removed
};

/**
* @brief Commit to a polynomial
*
* @param polynomial The input polynomial in the coefficient form
* @param poly_degree The degree of the polynomial
* @param G_vector The common set of generators required to compute the commitment, to be replaced by srs
*
* @return a group element
*/
static element commit(std::vector<Fr>& polynomial, const size_t poly_degree, std::vector<affine_element>& G_vector)
{
auto pippenger_runtime_state = barretenberg::scalar_multiplication::pippenger_runtime_state(poly_degree);
auto commitment = barretenberg::scalar_multiplication::pippenger_without_endomorphism_basis_points(
&polynomial[0], &G_vector[0], poly_degree, pippenger_runtime_state);
return commitment;
}

/**
* @brief Compute an IpaProof for opening a single polynomial at a single evaluation point
*
* @param ipa_pub_input Data required to compute the opening proof. See spec for more details
* @param polynomial The witness polynomial whose opening proof needs to be computed
* @param G_vector the common set of generators, to be replaced by the srs
*
* @return an IpaProof, containing information required to verify whether the commitment is computed correctly and
* the polynomial evaluation is correct in the given challenge point.
*/
static IpaProof ipa_prove(const IpaPubInput& ipa_pub_input,
const std::vector<Fr>& polynomial,
const std::vector<affine_element>& G_vector)
{
IpaProof proof;
auto& challenge_point = ipa_pub_input.challenge_point;
ASSERT(challenge_point != 0 && "The challenge point should not be zero");
const size_t poly_degree = ipa_pub_input.poly_degree;
// To check poly_degree is greater than zero and a power of two
// Todo: To accomodate non power of two poly_degree
ASSERT((poly_degree > 0) && (!(poly_degree & (poly_degree - 1))) &&
Comment thread
arijitdutta67 marked this conversation as resolved.
"The poly_degree should be positive and a power of two");
auto& aux_generator = ipa_pub_input.aux_generator;
auto a_vec = polynomial;
// Todo: to make it more efficient by directly using G_vector for the input points when i = 0 and write the
// output points to G_vec_local. Then use G_vec_local for rounds where i>0, this can be done after we use SRS
// instead of G_vector.
std::vector<affine_element> G_vec_local(poly_degree);
for (size_t i = 0; i < poly_degree; i++) {
G_vec_local[i] = G_vector[i];
Comment thread
arijitdutta67 marked this conversation as resolved.
}
// Construct b vector
// Todo: For round i=0, b_vec can be derived in-place.
// This means that the size of b_vec can be 50% of the current size (i.e. we only write values to b_vec at the
// end of round 0)
std::vector<Fr> b_vec(poly_degree);
Fr b_power = 1;
for (size_t i = 0; i < poly_degree; i++) {
b_vec[i] = b_power;
b_power *= challenge_point;
}
// Iterate for log_2(poly_degree) rounds to compute the round commitments.
const size_t log_poly_degree = static_cast<size_t>(numeric::get_msb(poly_degree));
std::vector<element> L_elements(log_poly_degree);
std::vector<element> R_elements(log_poly_degree);
size_t round_size = poly_degree;

for (size_t i = 0; i < log_poly_degree; i++) {
round_size >>= 1;
// Compute inner_prod_L := < a_vec_lo, b_vec_hi > and inner_prod_R := < a_vec_hi, b_vec_lo >
Fr inner_prod_L = Fr::zero();
Fr inner_prod_R = Fr::zero();
for (size_t j = 0; j < round_size; j++) {
inner_prod_L += a_vec[j] * b_vec[round_size + j];
inner_prod_R += a_vec[round_size + j] * b_vec[j];
Comment thread
arijitdutta67 marked this conversation as resolved.
}
// L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
// Todo: Remove usage of multiple runtime_state, pass it as an element of the SRS.
auto pippenger_runtime_state = barretenberg::scalar_multiplication::pippenger_runtime_state(round_size);
Comment thread
arijitdutta67 marked this conversation as resolved.
element partial_L = barretenberg::scalar_multiplication::pippenger_without_endomorphism_basis_points(
&a_vec[0], &G_vec_local[round_size], round_size, pippenger_runtime_state);
partial_L += aux_generator * inner_prod_L;

// R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
element partial_R = barretenberg::scalar_multiplication::pippenger_without_endomorphism_basis_points(
&a_vec[round_size], &G_vec_local[0], round_size, pippenger_runtime_state);
partial_R += aux_generator * inner_prod_R;

L_elements[i] = affine_element(partial_L);
R_elements[i] = affine_element(partial_R);

// Generate the round challenge. Todo: Use Fiat-Shamir
const Fr round_challenge = ipa_pub_input.round_challenges[i];
const Fr round_challenge_inv = round_challenge.invert();

// Update the vectors a_vec, b_vec and G_vec.
// a_vec_next = a_vec_lo * round_challenge + a_vec_hi * round_challenge_inv
// b_vec_next = b_vec_lo * round_challenge_inv + b_vec_hi * round_challenge
// G_vec_next = G_vec_lo * round_challenge_inv + G_vec_hi * round_challenge
for (size_t j = 0; j < round_size; j++) {
a_vec[j] *= round_challenge;
a_vec[j] += round_challenge_inv * a_vec[round_size + j];
b_vec[j] *= round_challenge_inv;
b_vec[j] += round_challenge * b_vec[round_size + j];

/*
Todo: (performance improvement suggested by Zac): We can improve performance here by using
element::batch_mul_with_endomorphism. This method takes a vector of input points points and a scalar x
and outputs a vector containing points[i]*x. It's 30% faster than a basic mul operation due to
performing group additions in 2D affine coordinates instead of 3D projective coordinates (affine point
additions are usually more expensive than projective additions due to the need to compute a modular
inverse. However we get around this by computing a single batch inverse. This only works if you are
adding a lot of independent point pairs so you can amortise the cost of the single batch inversion
across multiple points).
*/

auto G_lo = element(G_vec_local[j]) * round_challenge_inv;
Comment thread
arijitdutta67 marked this conversation as resolved.
auto G_hi = element(G_vec_local[round_size + j]) * round_challenge;
auto temp = G_lo + G_hi;
G_vec_local[j] = temp.normalize();
}
}
proof.L_vec = std::vector<affine_element>(log_poly_degree);
proof.R_vec = std::vector<affine_element>(log_poly_degree);
for (size_t i = 0; i < log_poly_degree; i++) {
proof.L_vec[i] = L_elements[i];
proof.R_vec[i] = R_elements[i];
}
proof.a_zero = a_vec[0];
return proof;
}

/**
* @brief Verify the correctness of an IpaProof
*
* @param ipa_proof The proof containg L_vec, R_vec and a_zero
* @param Ipa_pub_input Data required to verify the ipa_proof
* @param G_vector The common set of generators, to be replaced by the srs
*
* @return true/false depending on if the proof verifies
*/
static bool ipa_verify(const IpaProof& ipa_proof,
const IpaPubInput& ipa_pub_input,
const std::vector<affine_element>& G_vector)
{
// Local copies of public inputs
auto& a_zero = ipa_proof.a_zero;
auto& commitment = ipa_pub_input.commitment;
auto& challenge_point = ipa_pub_input.challenge_point;
auto& evaluation = ipa_pub_input.evaluation;
auto& poly_degree = ipa_pub_input.poly_degree;
auto& aux_generator = ipa_pub_input.aux_generator;

// Compute C_prime
element C_prime = commitment + (aux_generator * evaluation);

// Compute the round challeneges and their inverses.
const size_t log_poly_degree = static_cast<size_t>(numeric::get_msb(poly_degree));
std::vector<Fr> round_challenges(log_poly_degree);
for (size_t i = 0; i < log_poly_degree; i++) {
round_challenges[i] = ipa_pub_input.round_challenges[i];
}
std::vector<Fr> round_challenges_inv(log_poly_degree);
for (size_t i = 0; i < log_poly_degree; i++) {
round_challenges_inv[i] = ipa_pub_input.round_challenges[i];
}
Fr::batch_invert(&round_challenges_inv[0], log_poly_degree);
std::vector<affine_element> L_vec(log_poly_degree);
std::vector<affine_element> R_vec(log_poly_degree);
for (size_t i = 0; i < log_poly_degree; i++) {
L_vec[i] = ipa_proof.L_vec[i];
R_vec[i] = ipa_proof.R_vec[i];
}

// Compute C_zero = C_prime + ∑_{j ∈ [k]} u_j^2L_j + ∑_{j ∈ [k]} u_j^{-2}R_j
const size_t pippenger_size = 2 * log_poly_degree;
std::vector<affine_element> msm_elements(pippenger_size);
std::vector<Fr> msm_scalars(pippenger_size);
for (size_t i = 0; i < log_poly_degree; i++) {
msm_elements[size_t(2) * i] = L_vec[i];
msm_elements[size_t(2) * i + 1] = R_vec[i];
msm_scalars[size_t(2) * i] = round_challenges[i] * round_challenges[i];
msm_scalars[size_t(2) * i + 1] = round_challenges_inv[i] * round_challenges_inv[i];
}
auto pippenger_runtime_state_1 = barretenberg::scalar_multiplication::pippenger_runtime_state(pippenger_size);
element LR_sums = barretenberg::scalar_multiplication::pippenger_without_endomorphism_basis_points(
&msm_scalars[0], &msm_elements[0], pippenger_size, pippenger_runtime_state_1);
element C_zero = C_prime + LR_sums;

/**
* Compute b_zero where b_zero can be computed using the polynomial:
*
* g(X) = ∏_{i ∈ [k]} (u_{k-i}^{-1} + u_{k-i}.X^{2^{i-1}}).
*
* b_zero = g(evaluation) = ∏_{i ∈ [k]} (u_{k-i}^{-1} + u_{k-i}. (evaluation)^{2^{i-1}})
*/
Fr b_zero = Fr::one();
for (size_t i = 0; i < log_poly_degree; i++) {
auto exponent = static_cast<uint64_t>(Fr(2).pow(i));
b_zero *= round_challenges_inv[log_poly_degree - 1 - i] +
(round_challenges[log_poly_degree - 1 - i] * challenge_point.pow(exponent));
}
// Compute G_zero
// First construct s_vec
std::vector<Fr> s_vec(poly_degree);
for (size_t i = 0; i < poly_degree; i++) {
Fr s_vec_scalar = Fr::one();
for (size_t j = (log_poly_degree - 1); j != size_t(-1); j--) {
auto bit = (i >> j) & 1;
bool b = static_cast<bool>(bit);
if (b) {
s_vec_scalar *= round_challenges[log_poly_degree - 1 - j];
} else {
s_vec_scalar *= round_challenges_inv[log_poly_degree - 1 - j];
}
}
s_vec[i] = s_vec_scalar;
}
// Copy the G_vector to local memory.
std::vector<affine_element> G_vec_local(poly_degree);
for (size_t i = 0; i < poly_degree; i++) {
G_vec_local[i] = G_vector[i];
}
auto pippenger_runtime_state_2 = barretenberg::scalar_multiplication::pippenger_runtime_state(poly_degree);
auto G_zero = barretenberg::scalar_multiplication::pippenger_without_endomorphism_basis_points(
&s_vec[0], &G_vec_local[0], poly_degree, pippenger_runtime_state_2);
element right_hand_side = G_zero * a_zero;
Fr a_zero_b_zero = a_zero * b_zero;
right_hand_side += aux_generator * a_zero_b_zero;
return (C_zero.normalize() == right_hand_side.normalize());
}
};
66 changes: 66 additions & 0 deletions cpp/src/aztec/honk/commitment_scheme/ipa/ipa.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "ipa.hpp"
#include <common/mem.hpp>
#include <gtest/gtest.h>
#include "./polynomials/polynomial_arithmetic.hpp"
#include "./polynomials/polynomial.hpp"
#include <ecc/curves/bn254/fq12.hpp>
#include <ecc/curves/bn254/pairing.hpp>
using namespace barretenberg;

TEST(honk_commitment_scheme, ipa_commit)
{
constexpr size_t n = 1024;
std::vector<barretenberg::fr> scalars(n);
std::vector<barretenberg::g1::affine_element> points(n);

for (size_t i = 0; i < n; i++) {
scalars[i] = barretenberg::fr::random_element();
points[i] = barretenberg::g1::affine_element(barretenberg::g1::element::random_element());
}

barretenberg::g1::element expected = points[0] * scalars[0];
for (size_t i = 1; i < n; i++) {
expected += points[i] * scalars[i];
}

InnerProductArgument<barretenberg::fr, barretenberg::fq, barretenberg::g1> newIpa;
barretenberg::g1::element result = newIpa.commit(scalars, n, points);
EXPECT_EQ(expected.normalize(), result.normalize());
}

TEST(honk_commitment_scheme, ipa_open)
{
// generate a random polynomial coeff, degree needs to be a power of two
size_t n = 1024;
std::vector<barretenberg::fr> coeffs(n);
for (size_t i = 0; i < n; ++i) {
coeffs[i] = barretenberg::fr::random_element();
}
// generate random evaluation point x and the evaluation
auto x = barretenberg::fr::random_element();
auto eval = polynomial_arithmetic::evaluate(&coeffs[0], x, n);
// generate G_vec for testing, bypassing srs
std::vector<barretenberg::g1::affine_element> G_vec(n);
for (size_t i = 0; i < n; ++i) {
auto scalar = fr::random_element();
G_vec[i] = barretenberg::g1::affine_element(barretenberg::g1::one * scalar);
}
InnerProductArgument<barretenberg::fr, barretenberg::fq, barretenberg::g1> newIpa;
auto C = newIpa.commit(coeffs, n, G_vec);
InnerProductArgument<barretenberg::fr, barretenberg::fq, barretenberg::g1>::IpaPubInput pub_input;
pub_input.commitment = C;
pub_input.challenge_point = x;
pub_input.evaluation = eval;
pub_input.poly_degree = n;
auto aux_scalar = fr::random_element();
pub_input.aux_generator = barretenberg::g1::one * aux_scalar;
const size_t log_n = static_cast<size_t>(numeric::get_msb(n));
pub_input.round_challenges = std::vector<barretenberg::fr>(log_n);
for (size_t i = 0; i < log_n; i++) {
pub_input.round_challenges[i] = barretenberg::fr::random_element();
}
auto proof = newIpa.ipa_prove(pub_input, coeffs, G_vec);
auto result =
InnerProductArgument<barretenberg::fr, barretenberg::fq, barretenberg::g1>::ipa_verify(proof, pub_input, G_vec);
EXPECT_TRUE(result);
}