go-blockchain/crypto/upstream/range_proof_bppe.h
Claude 1416a6714a
feat(crypto): Phase 2a scaffold — vendored C++ and CMake build
Extract CryptoNote crypto sources from upstream (fa1608cf).
Build as static libcryptonote.a via CMake with compat stubs for
external dependencies (warnings, logging, varint, profiling).

37 upstream files, 10 compat stubs, 680KB static library.

Co-Authored-By: Charon <charon@lethean.io>
2026-02-20 18:21:44 +00:00

759 lines
30 KiB
C++
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Boolberry developers
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// The EUPL is a copyleft licence that is compatible with the MIT/X11
// licence used by the original projects; the MIT terms are therefore
// considered “grandfathered” under the EUPL for this code.
//
// SPDXLicenseIdentifier: EUPL-1.2
//
#pragma once
//
// This file contains the implementation of range proof protocol.
// Namely, Bulletproofs+ https://eprint.iacr.org/2020/735
// Double-blinded commitments extension implemented as in Appendix D in the Zarcanum whitepaper: https://eprint.iacr.org/2021/1478
namespace crypto
{
struct bppe_signature
{
std::vector<public_key> L; // size = log_2(m * n)
std::vector<public_key> R;
public_key A0;
public_key A;
public_key B;
scalar_t r;
scalar_t s;
scalar_t delta_1;
scalar_t delta_2;
};
#if 0
# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl
# define DBG_PRINT(x) std::cout << x << std::endl
#else
# define DBG_VAL_PRINT(x) (void(0))
# define DBG_PRINT(x) (void(0))
#endif
#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \
if (!(cond)) { LOG_PRINT_RED("bppe_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \
if (p_err) { *p_err = err_code; } return false; }
template<typename CT>
bool bppe_gen(const scalar_vec_t& values, const scalar_vec_t& masks, const scalar_vec_t& masks2, const std::vector<const crypto::public_key*>& commitments_1div8, bppe_signature& sig, uint8_t* p_err = nullptr)
{
// Note: commitments_1div8 are supposed to be already calculated
static_assert(CT::c_bpp_n <= 255, "too big N");
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= CT::c_bpp_values_max && values.size() == masks.size() && masks.size() == masks2.size() && values.size() == commitments_1div8.size(), 1);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced() && masks2.is_reduced(), 3);
const size_t c_bpp_log2_m = constexpr_ceil_log2(values.size());
const size_t c_bpp_m = 1ull << c_bpp_log2_m;
const size_t c_bpp_mn = c_bpp_m * CT::c_bpp_n;
const size_t c_bpp_log2_mn = c_bpp_log2_m + CT::c_bpp_log2_n;
#ifndef NDEBUG
for(size_t i = 0; i < values.size(); ++i)
{
point_t V{};
CT::calc_pedersen_commitment_2(values[i], masks[i], masks2[i], V);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(point_t(*commitments_1div8[i]).modify_mul8() == V, 4);
}
#endif
// s.a. BP+ paper, page 15, eq. 11
// decompose v into aL and aR:
// v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product
// aR = aL - (1, 1, ... 1)
// aR o aL = 0
// aLs = (aL_0, aL_1, ..., aL_m-1) -- `bit` matrix of c_bpp_m x c_bpp_n, each element is a scalar
scalar_mat_t<CT::c_bpp_n> aLs(c_bpp_mn), aRs(c_bpp_mn);
aLs.zero();
aRs.zero();
// m >= values.size, first set up [0..values.size-1], then -- [values.size..m-1] (padding area)
for (size_t i = 0; i < values.size(); ++i)
{
const scalar_t& v = values[i];
for (uint8_t j = 0; j < CT::c_bpp_n; ++j)
{
if (v.get_bit(j))
aLs(i, j) = c_scalar_1; // aL = 1, aR = 0
else
aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1
}
}
for (size_t i = values.size(); i < c_bpp_m; ++i)
for (size_t j = 0; j < CT::c_bpp_n; ++j)
aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1
// using e as Fiat-Shamir transcript
scalar_t e = CT::get_initial_transcript();
DBG_PRINT("initial transcript: " << e);
hash_helper_t::hs_t hsc;
CT::update_transcript(hsc, e, commitments_1div8);
// Zarcanum paper, page 33, Fig. D.3: The prover chooses alpha_1, alpha_2 and computes A = g^aL h^aR h_1^alpha_1 h_2^alpha_2
// so we calculate A0 = alpha_1 * H + alpha_2 * H_2 + SUM(aL_i * G_i) + SUM(aR_i * H_i)
scalar_t alpha_1 = scalar_t::random(), alpha_2 = scalar_t::random();
point_t A0 = alpha_1 * CT::bpp_H + alpha_2 * CT::bpp_H2;
for (size_t i = 0; i < c_bpp_mn; ++i)
A0 += aLs[i] * CT::get_generator(false, i) + aRs[i] * CT::get_generator(true, i);
// part of 1/8 defense scheme
A0 *= c_scalar_1div8;
A0.to_public_key(sig.A0);
DBG_VAL_PRINT(alpha_1);
DBG_VAL_PRINT(alpha_2);
DBG_VAL_PRINT(A0);
// calculate scalar challenges y and z
hsc.add_scalar(e);
hsc.add_pub_key(sig.A0);
scalar_t y = hsc.calc_hash();
scalar_t z = hash_helper_t::hs(y);
e = z; // transcript for further steps
DBG_VAL_PRINT(y);
DBG_VAL_PRINT(z);
// Computing vector d for aggregated version of the protocol (BP+ paper, page 17)
// (note: elements are stored column-by-column in memory)
// d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) |
// | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) |
// | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) |
// | ....................................................................................... |
// | 2^(n-1) * z^(2*1), 2^(n-1) * z^(2*2), 2^(n-1) * z^(2*3), ..., 2^(n-1) * z^(2*m)) |
// Note: sum(d_i) = (2^n - 1) * ((z^2)^1 + (z^2)^2 + ... (z^2)^m)) = (2^n-1) * sum_of_powers(x^2, log(m))
scalar_t z_sq = z * z;
scalar_mat_t<CT::c_bpp_n> d(c_bpp_mn);
d(0, 0) = z_sq;
// first row
for (size_t i = 1; i < c_bpp_m; ++i)
d(i, 0) = d(i - 1, 0) * z_sq;
// all rows
for (size_t j = 1; j < CT::c_bpp_n; ++j)
for (size_t i = 0; i < c_bpp_m; ++i)
d(i, j) = d(i, j - 1) + d(i, j - 1);
DBG_PRINT("Hs(d): " << d.calc_hs());
// calculate extended Vandermonde vector y = (1, y, y^2, ..., y^(mn+1)) (BP+ paper, page 18, Fig. 3)
// (calculate two more elements (1 and y^(mn+1)) for convenience)
scalar_vec_t y_powers(c_bpp_mn + 2);
y_powers[0] = 1;
for (size_t i = 1; i <= c_bpp_mn + 1; ++i)
y_powers[i] = y_powers[i - 1] * y;
const scalar_t& y_mn_p1 = y_powers[c_bpp_mn + 1];
DBG_PRINT("Hs(y_powers): " << y_powers.calc_hs());
// aL_hat = aL - 1*z
scalar_vec_t aLs_hat = aLs - z;
// aR_hat = aR + d o y^leftarr + 1*z where y^leftarr = (y^n, y^(n-1), ..., y) (BP+ paper, page 18, Fig. 3)
scalar_vec_t aRs_hat = aRs + z;
for (size_t i = 0; i < c_bpp_mn; ++i)
aRs_hat[i] += d[i] * y_powers[c_bpp_mn - i];
DBG_PRINT("Hs(aLs_hat): " << aLs_hat.calc_hs());
DBG_PRINT("Hs(aRs_hat): " << aRs_hat.calc_hs());
// calculate alpha_hat
// alpha_hat_1 = alpha_1 + SUM(z^(2j) * gamma_1,j * y^(mn+1)) for j = 1..m
// alpha_hat_2 = alpha_2 + SUM(z^(2j) * gamma_2,j * y^(mn+1)) for j = 1..m
// i.e. \hat{\alpha} = \alpha + y^{m n+1} \sum_{j = 1}^{m} z^{2j} \gamma_j
scalar_t alpha_hat_1 = 0, alpha_hat_2 = 0;
for (size_t i = 0; i < masks.size(); ++i)
{
alpha_hat_1 += d(i, 0) * masks[i];
alpha_hat_2 += d(i, 0) * masks2[i];
}
alpha_hat_1 = alpha_1 + y_mn_p1 * alpha_hat_1;
alpha_hat_2 = alpha_2 + y_mn_p1 * alpha_hat_2;
DBG_VAL_PRINT(alpha_hat_1);
DBG_VAL_PRINT(alpha_hat_2);
// calculate 1, y^-1, y^-2, ...
const scalar_t y_inverse = y.reciprocal();
scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round)
y_inverse_powers[0] = 1;
for (size_t i = 1, size = y_inverse_powers.size(); i < size; ++i)
y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inverse;
// prepare generator's vector
std::vector<point_t> g(c_bpp_mn), h(c_bpp_mn);
for (size_t i = 0; i < c_bpp_mn; ++i)
{
g[i] = CT::get_generator(false, i);
h[i] = CT::get_generator(true, i);
}
// WIP zk-argument called with zk-WIP(g, h, G, H, H2, A_hat, aL_hat, aR_hat, alpha_hat_1, alpha_hat_2)
scalar_vec_t& a = aLs_hat;
scalar_vec_t& b = aRs_hat;
sig.L.resize(c_bpp_log2_mn);
sig.R.resize(c_bpp_log2_mn);
// zk-WIP reduction rounds (s.a. Zarcanum preprint page 24 Fig. D.1)
for (size_t n = c_bpp_mn / 2, ni = 0; n >= 1; n /= 2, ++ni)
{
DBG_PRINT(ENDL << "#" << ni);
// zk-WIP(g, h, G, H, H2, P, a, b, alpha_1, alpha_2)
scalar_t dL = scalar_t::random(), dL2 = scalar_t::random();
DBG_VAL_PRINT(dL); DBG_VAL_PRINT(dL2);
scalar_t dR = scalar_t::random(), dR2 = scalar_t::random();
DBG_VAL_PRINT(dR); DBG_VAL_PRINT(dR2);
// a = (a1, a2), b = (b1, b2) -- vectors of scalars
// cL = <a1, ((y, y^2, ...) o b2)> -- scalar
scalar_t cL = 0;
for (size_t i = 0; i < n; ++i)
cL += a[i] * y_powers[i + 1] * b[n + i];
DBG_VAL_PRINT(cL);
// cR = <a2, ((y, y^2, ...) o b1)> * y^n -- scalar
scalar_t cR = 0;
for (size_t i = 0; i < n; ++i)
cR += a[n + i] * y_powers[i + 1] * b[i];
cR *= y_powers[n];
DBG_VAL_PRINT(cR);
// L = y^-n * a1 * g2 + b2 * h1 + cL * G + dL * H + dL2 * H2 -- point
point_t sum = c_point_0;
for (size_t i = 0; i < n; ++i)
sum += a[i] * g[n + i];
point_t L;
CT::calc_pedersen_commitment_2(cL, dL, dL2, L);
for (size_t i = 0; i < n; ++i)
L += b[n + i] * h[i];
L += y_inverse_powers[n] * sum;
L *= c_scalar_1div8;
DBG_VAL_PRINT(L);
// R = y^n * a2 * g1 + b1 * h2 + cR * G + dR * H + dR2 * H2 -- point
sum.zero();
for (size_t i = 0; i < n; ++i)
sum += a[n + i] * g[i];
point_t R;
CT::calc_pedersen_commitment_2(cR, dR, dR2, R);
for (size_t i = 0; i < n; ++i)
R += b[i] * h[n + i];
R += y_powers[n] * sum;
R *= c_scalar_1div8;
DBG_VAL_PRINT(R);
// put L, R to the sig
L.to_public_key(sig.L[ni]);
R.to_public_key(sig.R[ni]);
// update the transcript
hsc.add_scalar(e);
hsc.add_pub_key(sig.L[ni]);
hsc.add_pub_key(sig.R[ni]);
e = hsc.calc_hash();
DBG_VAL_PRINT(e);
// recalculate arguments for the next round
scalar_t e_squared = e * e;
scalar_t e_inverse = e.reciprocal();
scalar_t e_inverse_squared = e_inverse * e_inverse;
scalar_t e_y_inv_n = e * y_inverse_powers[n];
scalar_t e_inv_y_n = e_inverse * y_powers[n];
// g_hat = e^-1 * g1 + (e * y^-n) * g2 -- vector of points
for (size_t i = 0; i < n; ++i)
g[i] = e_inverse * g[i] + e_y_inv_n * g[n + i];
// h_hat = e * h1 + e^-1 * h2 -- vector of points
for (size_t i = 0; i < n; ++i)
h[i] = e * h[i] + e_inverse * h[n + i];
// P_hat = e^2 * L + P + e^-2 * R -- point
// a_hat = e * a1 + e^-1 * y^n * a2 -- vector of scalars
for (size_t i = 0; i < n; ++i)
a[i] = e * a[i] + e_inv_y_n * a[n + i];
// b_hat = e^-1 * b1 + e * b2 -- vector of scalars
for (size_t i = 0; i < n; ++i)
b[i] = e_inverse * b[i] + e * b[n + i];
// alpha_hat_1 = e^2 * dL + alpha_1 + e^-2 * dR -- scalar
// alpha_hat_2 = e^2 * dL2 + alpha_2 + e^-2 * dR2 -- scalar
alpha_hat_1 += e_squared * dL + e_inverse_squared * dR;
alpha_hat_2 += e_squared * dL2 + e_inverse_squared * dR2;
// run next iteraton zk-WIP(g_hat, h_hat, G, H, H2, P_hat, a_hat, b_hat, alpha_hat_1, alpha_hat_2)
}
DBG_PRINT("");
// zk-WIP last round
scalar_t r = scalar_t::random();
scalar_t s = scalar_t::random();
scalar_t delta_1 = scalar_t::random(), delta_2 = scalar_t::random();
scalar_t eta_1 = scalar_t::random(), eta_2 = scalar_t::random();
DBG_VAL_PRINT(r);
DBG_VAL_PRINT(s);
DBG_VAL_PRINT(delta_1); DBG_VAL_PRINT(delta_2);
DBG_VAL_PRINT(eta_1); DBG_VAL_PRINT(eta_2);
// A = r * g + s * h + (r y b + s y a) * G + delta_1 * H + delta_2 * H2 -- point
point_t A = c_point_0;
CT::calc_pedersen_commitment_2(y * (r * b[0] + s * a[0]), delta_1, delta_2, A);
A += r * g[0] + s * h[0];
A *= c_scalar_1div8;
A.to_public_key(sig.A);
DBG_VAL_PRINT(A);
// B = (r * y * s) * G + eta_1 * H + eta_2 * H2
point_t B = c_point_0;
CT::calc_pedersen_commitment_2(r * y * s, eta_1, eta_2, B);
B *= c_scalar_1div8;
B.to_public_key(sig.B);
DBG_VAL_PRINT(B);
// update the transcript
hsc.add_scalar(e);
hsc.add_pub_key(sig.A);
hsc.add_pub_key(sig.B);
e = hsc.calc_hash();
DBG_VAL_PRINT(e);
// finalize the signature
sig.r = r + e * a[0];
sig.s = s + e * b[0];
sig.delta_1 = eta_1 + e * delta_1 + e * e * alpha_hat_1;
sig.delta_2 = eta_2 + e * delta_2 + e * e * alpha_hat_2;
DBG_VAL_PRINT(sig.r);
DBG_VAL_PRINT(sig.s);
DBG_VAL_PRINT(sig.delta_1);
DBG_VAL_PRINT(sig.delta_2);
return true;
} // bppe_gen()
// convenient overload for tests
template<typename CT>
bool bppe_gen(const scalar_vec_t& values, const scalar_vec_t& masks, const scalar_vec_t& masks2, bppe_signature& sig, std::vector<point_t>& commitments_1div8_to_be_generated, uint8_t* p_err = nullptr)
{
// calc commitments vector as commitments[i] = 1/8 * values[i] * G + 1/8 * masks[i] * H + 1/8 * masks2[i] * H2
commitments_1div8_to_be_generated.resize(values.size());
std::vector<crypto::public_key> commitments_1div8(values.size());
std::vector<const crypto::public_key*> commitments_1div8_pointers(values.size());
for (size_t i = 0; i < values.size(); ++i)
{
CT::calc_pedersen_commitment_2(values[i] * c_scalar_1div8, masks[i] * c_scalar_1div8, masks2[i] * c_scalar_1div8, commitments_1div8_to_be_generated[i]);
commitments_1div8[i] = (commitments_1div8_to_be_generated[i]).to_public_key();
commitments_1div8_pointers[i] = &commitments_1div8[i];
}
return bppe_gen<CT>(values, masks, masks2, commitments_1div8_pointers, sig, p_err);
}
#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE
struct bppe_sig_commit_ref_t
{
bppe_sig_commit_ref_t(const bppe_signature& sig, const std::vector<point_t>& commitments)
: sig(sig)
, commitments(commitments)
{}
const bppe_signature& sig;
const std::vector<point_t>& commitments; // assumed to be premultiplied by 1/8
};
template<typename CT>
bool bppe_verify(const std::vector<bppe_sig_commit_ref_t>& sigs, uint8_t* p_err = nullptr)
{
#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \
if (!(cond)) { LOG_PRINT_RED("bppe_verify: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \
if (p_err) { *p_err = err_code; } return false; }
DBG_PRINT(ENDL << " . . . . bppe_verify() . . . . ");
static_assert(CT::c_bpp_n <= 255, "too big N");
const size_t kn = sigs.size();
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1);
struct intermediate_element_t
{
scalar_t y;
scalar_t z;
scalar_t z_sq;
scalar_vec_t e;
scalar_vec_t e_sq;
scalar_t e_final;
scalar_t e_final_sq;
size_t inv_e_offset; // offset in batch_for_inverse
size_t inv_y_offset; // offset in batch_for_inverse
size_t c_bpp_log2_m;
size_t c_bpp_m;
size_t c_bpp_mn;
point_t A;
point_t A0;
point_t B;
std::vector<point_t> L;
std::vector<point_t> R;
};
std::vector<intermediate_element_t> interms(kn);
size_t c_bpp_log2_m_max = 0;
for (size_t k = 0; k < kn; ++k)
{
const bppe_sig_commit_ref_t& bsc = sigs[k];
const bppe_signature& sig = bsc.sig;
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bsc.commitments.size() > 0, 2);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() > 0 && sig.L.size() == sig.R.size(), 3);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.r.is_reduced() && sig.s.is_reduced() && sig.delta_1.is_reduced() && sig.delta_2.is_reduced(), 4);
intermediate_element_t& interm = interms[k];
interm.c_bpp_log2_m = constexpr_ceil_log2(bsc.commitments.size());
if (c_bpp_log2_m_max < interm.c_bpp_log2_m)
c_bpp_log2_m_max = interm.c_bpp_log2_m;
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() == interm.c_bpp_log2_m + CT::c_bpp_log2_n, 5);
interm.c_bpp_m = 1ull << interm.c_bpp_log2_m;
interm.c_bpp_mn = interm.c_bpp_m * CT::c_bpp_n;
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A0.from_public_key(sig.A0), 6);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A.from_public_key(sig.A), 7);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.B.from_public_key(sig.B), 8);
interm.L.resize(sig.L.size());
interm.R.resize(sig.R.size());
for (size_t i = 0; i < interm.L.size(); ++i)
{
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.L[i].from_public_key(sig.L[i]), 9);
CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.R[i].from_public_key(sig.R[i]), 10);
}
}
const size_t c_bpp_m_max = 1ull << c_bpp_log2_m_max;
const size_t c_bpp_mn_max = c_bpp_m_max * CT::c_bpp_n;
const size_t c_bpp_LR_size_max = c_bpp_log2_m_max + CT::c_bpp_log2_n;
//
// prepare stuff
//
/*
std::vector<point_t> g(c_bpp_mn_max), h(c_bpp_mn_max);
for (size_t i = 0; i < c_bpp_mn_max; ++i)
{
g[i] = CT::get_generator(false, i);
h[i] = CT::get_generator(true, i);
}
*/
scalar_vec_t batch_for_inverse;
batch_for_inverse.reserve(kn + kn * c_bpp_LR_size_max);
for (size_t k = 0; k < kn; ++k)
{
DBG_PRINT(ENDL << "SIG #" << k);
const bppe_sig_commit_ref_t& bsc = sigs[k];
const bppe_signature& sig = bsc.sig;
intermediate_element_t& interm = interms[k];
// restore y and z
// using e as Fiat-Shamir transcript
scalar_t e = CT::get_initial_transcript();
DBG_PRINT("initial transcript: " << e);
hash_helper_t::hs_t hsc;
CT::update_transcript(hsc, e, bsc.commitments);
// calculate scalar challenges y and z
hsc.add_scalar(e);
hsc.add_pub_key(sig.A0);
hsc.assign_calc_hash(interm.y);
interm.z = hash_helper_t::hs(interm.y);
interm.z_sq = interm.z * interm.z;
DBG_VAL_PRINT(interm.y);
DBG_VAL_PRINT(interm.z);
e = interm.z; // transcript for further steps
interm.inv_y_offset = batch_for_inverse.size();
batch_for_inverse.push_back(interm.y);
interm.inv_e_offset = batch_for_inverse.size();
interm.e.resize(sig.L.size());
interm.e_sq.resize(sig.L.size());
for (size_t i = 0; i < sig.L.size(); ++i)
{
hsc.add_scalar(e);
hsc.add_pub_key(sig.L[i]);
hsc.add_pub_key(sig.R[i]);
hsc.assign_calc_hash(e);
interm.e[i] = e;
interm.e_sq[i] = e * e;
DBG_PRINT("e[" << i << "]: " << e);
batch_for_inverse.push_back(e);
}
hsc.add_scalar(e);
hsc.add_pub_key(sig.A);
hsc.add_pub_key(sig.B);
hsc.assign_calc_hash(interm.e_final);
interm.e_final_sq = interm.e_final * interm.e_final;
DBG_VAL_PRINT(interm.e_final);
}
batch_for_inverse.invert();
// Notation:
// 1_vec ^ n = (1, 1, 1, ..., 1)
// 2_vec ^ n = (2^0, 2^1, 2^2, ..., 2^(n-1))
// -1_vec ^ n = ((-1)^0, (-1)^1, (-1)^2, ... (-1)^(n-1)) = (1, -1, 1, -1, ...)
// y<^n = (y^n, y^(n-1), ..., y^1)
// y>^n = (y^1, y^2, ..., y^n)
// from Zarcanum page 24, Fig D.1:
// Verifier outputs Accept IFF the following holds:
// P^e^2 * A^e * B == g ^ (r' e) * h ^ (s' e) * G ^ (r' y s') * H ^ delta'_1 * H2 ^ delta'_2
// (where g and h are calculated in each round)
// The same equation in additive notation:
// e^2 * P + e * A + B == (r' * e) * g + (s' * e) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2
// <=>
// (r' * e) * g + (s' * e) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 - e^2 * P - e * A - B == 0 (*)
// where A, B, r', s', delta'_1, delta'_2 is taken from the signature
// and P_{k+1} = e^2 * L_k + P_k + e^-2 * R_k for all rounds
//
// from Zarcanum preprint page 33, Fig D.3:
// P and V computes:
// A_hat = A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h +
// + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) +
// + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G
// (calculated once)
//
// As suggested in BPP preprint Section 6.1 "Practical Optimizations":
// 1) g and h exponentianions can be optimized in order not to be calculated at each round
// as the following (page 20, with delta'_2 and H2 added):
//
// (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 -
// - e^2 * A_hat
// - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j)
// - e * A - B = 0 (**)
//
// where:
// g, h - vector of fixed generators
// s_vec_i = y^(1-i) * PROD{j=1..log(n)}(e_j ^ b(i,j))
// s'_vec_i = PROD{j=1..log(n)}(e_j ^ -b(i,j))
// b(i, j) = { 2 * ((1<<(j-1)) & (i-1)) - 1) (counting both from 1) (page 20)
// b(i, j) = { 2 * ((1<<j) & i) - 1) (counting both from 0)
//
// 2) we gonna aggregate all (**) for each round by multiplying them to a random weights and then sum up
// insert A_hat into (**) =>
// (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 -
// - e^2 * (A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h +
// + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) +
// + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G
// )
// - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j)
// - e * A - B = 0
// =>
// (for single signature)
//
// (r' * e * s_vec - e^2 * (- 1_vec^(mn) * z)) * g | these are
// + (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) * h | fixed generators
// + (r' y s' - e^2 * ((z - z^2)*SUM(y^>mn) - z*y^(mn+1)*SUM(d)) * G | across all
// + delta'_1 * H | the signatures
// + delta'_2 * H2 | the signatures
//
// - e^2 * A0
// - e^2 * y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j))
// - e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j)
// - e * A - B = 0 (***)
//
// All (***) will be muptiplied by random weightning factor and then summed up.
// Calculate cummulative sclalar multiplicand for fixed generators across all the sigs.
scalar_vec_t g_scalars;
g_scalars.resize(c_bpp_mn_max, 0);
scalar_vec_t h_scalars;
h_scalars.resize(c_bpp_mn_max, 0);
scalar_t G_scalar = 0;
scalar_t H_scalar = 0;
scalar_t H2_scalar = 0;
point_t summand = c_point_0;
for (size_t k = 0; k < kn; ++k)
{
DBG_PRINT(ENDL << "SIG #" << k);
const bppe_sig_commit_ref_t& bsc = sigs[k];
const bppe_signature& sig = bsc.sig;
intermediate_element_t& interm = interms[k];
// random weightning factor for speed-optimized batch verification (preprint page 20)
const scalar_t rwf = scalar_t::random();
DBG_PRINT("rwf: " << rwf);
// prepare d vector (see also d structure description in proof function)
scalar_mat_t<CT::c_bpp_n> d(interm.c_bpp_mn);
d(0, 0) = interm.z_sq;
// first row
for (size_t i = 1; i < interm.c_bpp_m; ++i)
d(i, 0) = d(i - 1, 0) * interm.z_sq;
// all rows
for (size_t j = 1; j < CT::c_bpp_n; ++j)
for (size_t i = 0; i < interm.c_bpp_m; ++i)
d(i, j) = d(i, j - 1) + d(i, j - 1);
// sum(d) (see also note in proof function for this)
const scalar_t sum_d = CT::get_2_to_the_power_of_N_minus_1() * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m);
DBG_PRINT("Hs(d): " << d.calc_hs());
DBG_PRINT("sum(d): " << sum_d);
const scalar_t& y_inv = batch_for_inverse[interm.inv_y_offset];
auto get_e_inv = [&](size_t i) { return batch_for_inverse[interm.inv_e_offset + i]; }; // i belongs to [0; L.size()-1]
// prepare s_vec (unlike the paper here we moved y-component out of s_vec for convenience, so s_vec'[x] = s_vec[~x & (MN-1)])
// complexity (sc_mul's): MN+2*log2(MN)-2
// the idea is the following:
// s_vec[00000b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^-1 * (e_1)^-1 * (e_0)^-1
// s_vec[00101b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^+1 * (e_1)^-1 * (e_0)^+1
const size_t log2_mn = sig.L.size(); // at the beginning we made sure that sig.L.size() == c_bpp_log2_m + c_bpp_log2_n
scalar_vec_t s_vec(interm.c_bpp_mn);
s_vec[0] = get_e_inv(0);
for (size_t i = 1; i < log2_mn; ++i)
s_vec[0] *= get_e_inv(i); // s_vec[0] = (e_0)^-1 * (e_1)^-1 * .. (e_{log2_mn-1})^-1
DBG_PRINT("[0] " << s_vec[0]);
for (size_t i = 1; i < interm.c_bpp_mn; ++i)
{
size_t base_el_index = i & (i - 1); // base element index: 0, 0, 2, 0, 4, 4, 6, 0, 8, 8, 10... base element differs in one bit (0) from the current one (1)
size_t bit_index = log2_mn - calc_lsb_32((uint32_t)i) - 1; // the bit index where current element has the difference with the base
s_vec[i] = s_vec[base_el_index] * interm.e_sq[bit_index]; // (e_j)^-1 * (e_j)^2 = (e_j)^+1
DBG_PRINT("[" << i << "] " << " " << base_el_index << ", " << bit_index << " : " << s_vec[i]);
}
// prepare y_inv vector
scalar_vec_t y_inverse_powers(interm.c_bpp_mn);
y_inverse_powers[0] = 1;
for (size_t i = 1; i < interm.c_bpp_mn; ++i)
y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inv;
// y^(mn+1)
scalar_t y_power_mnp1 = interm.y;
for (size_t i = 0; i < log2_mn; ++i)
y_power_mnp1 *= y_power_mnp1;
y_power_mnp1 *= interm.y;
DBG_VAL_PRINT(y_power_mnp1);
// now calculate all multiplicands for common generators
// g vector multiplicands:
// rwf * (r' * e * (1, y^-1, y^-2, ...) o s_vec + e^2 * z) =
// rwf * r' * e * ((1, y^-1, ...) o s_vec) + rwf * e^2 * z * (1, 1, ...)
scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z;
scalar_t rwf_r_e = rwf * interm.e_final * sig.r;
for (size_t i = 0; i < interm.c_bpp_mn; ++i)
g_scalars[i] += rwf_r_e * y_inverse_powers[i] * s_vec[i] + rwf_e_sq_z;
DBG_PRINT("Hs(g_scalars): " << g_scalars.calc_hs());
// h vector multiplicands:
// rwf * (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z))
// rwf * s' * e * s'_vec - rwf * e^2 * z * (1, 1...) - rwf * e^2 * (d o y<^(mn))
//scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z;
scalar_t rwf_s_e = rwf * sig.s * interm.e_final;
scalar_t rwf_e_sq_y = rwf * interm.e_final_sq * interm.y;
for (size_t i = interm.c_bpp_mn - 1; i != SIZE_MAX; --i)
{
h_scalars[i] += rwf_s_e * s_vec[interm.c_bpp_mn - 1 - i] - rwf_e_sq_z - rwf_e_sq_y * d[i];
rwf_e_sq_y *= interm.y;
}
DBG_PRINT("Hs(h_scalars): " << h_scalars.calc_hs());
// G point multiplicands:
// rwf * (r' y s' - e ^ 2 * ((z - z ^ 2)*SUM(y^>mn) - z * y^(mn+1) * SUM(d)) =
// = rwf * r' y s' - rwf * e^2 * (z - z ^ 2)*SUM(y^>mn) + rwf * e^2 * z * y^(mn+1) * SUM(d)
G_scalar += rwf * sig.r * interm.y * sig.s + rwf_e_sq_y * sum_d * interm.z;
G_scalar -= rwf * interm.e_final_sq * (interm.z - interm.z_sq) * sum_of_powers(interm.y, log2_mn);
DBG_PRINT("sum_y: " << sum_of_powers(interm.y, log2_mn));
DBG_PRINT("G_scalar: " << G_scalar);
// H point multiplicands:
// rwf * delta_1
H_scalar += rwf * sig.delta_1;
DBG_PRINT("H_scalar: " << H_scalar);
// H2 point multiplicands:
// rwf * delta_2
H2_scalar += rwf * sig.delta_2;
DBG_PRINT("H2_scalar: " << H2_scalar);
// uncommon generators' multiplicands
point_t summand_8 = c_point_0; // this summand to be multiplied by 8 before adding to the main summand
// - rwf * e^2 * A0
summand_8 -= rwf * interm.e_final_sq * interm.A0;
DBG_PRINT("A0_scalar: " << c_scalar_Lm1 * interm.e_final_sq * rwf);
// - rwf * e^2 * y^(mn+1) * (SUM{j=1..m} (z^2)^j * V_j))
scalar_t e_sq_y_mn1_z_sq_power = rwf * interm.e_final_sq * y_power_mnp1;
for (size_t j = 0; j < bsc.commitments.size(); ++j)
{
e_sq_y_mn1_z_sq_power *= interm.z_sq;
summand_8 -= e_sq_y_mn1_z_sq_power * bsc.commitments[j];
DBG_PRINT("V_scalar[" << j << "]: " << c_scalar_Lm1 * e_sq_y_mn1_z_sq_power);
}
// - rwf * e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j)
scalar_t rwf_e_sq = rwf * interm.e_final_sq;
for (size_t j = 0; j < log2_mn; ++j)
{
summand_8 -= rwf_e_sq * (interm.e_sq[j] * interm.L[j] + get_e_inv(j) * get_e_inv(j) * interm.R[j]);
DBG_PRINT("L_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * interm.e_sq[j]);
DBG_PRINT("R_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * get_e_inv(j) * get_e_inv(j));
}
// - rwf * e * A - rwf * B = 0
summand_8 -= rwf * interm.e_final * interm.A + rwf * interm.B;
DBG_PRINT("A_scalar: " << c_scalar_Lm1 * rwf * interm.e_final);
DBG_PRINT("B_scalar: " << c_scalar_Lm1 * rwf);
summand_8.modify_mul8();
summand += summand_8;
}
point_t GH_exponents = c_point_0;
CT::calc_pedersen_commitment_2(G_scalar, H_scalar, H2_scalar, GH_exponents);
bool result = msm_and_check_zero<CT>(g_scalars, h_scalars, summand + GH_exponents);
if (result)
DBG_PRINT(ENDL << " . . . . bppe_verify() -- SUCCEEDED!!!" << ENDL);
return result;
#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE
}
#undef DBG_VAL_PRINT
#undef DBG_PRINT
} // namespace crypto