// Copyright (c) 2017-2023, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Adapted from Java code by Sarang Noether // Paper references are to https://eprint.iacr.org/2017/1066 (revision 1 July 2018) #include #include #include #include "misc_log_ex.h" #include "span.h" #include "common/perf_timer.h" #include "cryptonote_config.h" extern "C" { #include "crypto/crypto-ops.h" } #include "rctOps.h" #include "multiexp.h" #include "bulletproofs.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "bulletproofs" //#define DEBUG_BP #if 0 #define PERF_TIMER_START_BP(x) PERF_TIMER_START_UNIT(x, 1000000) #define PERF_TIMER_STOP_BP(x) PERF_TIMER_STOP(x) #else #define PERF_TIMER_START_BP(x) ((void)0) #define PERF_TIMER_STOP_BP(x) ((void)0) #endif #define STRAUS_SIZE_LIMIT 232 #define PIPPENGER_SIZE_LIMIT 0 namespace rct { static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); static rct::keyV vector_powers(const rct::key &x, size_t n); static rct::keyV vector_dup(const rct::key &x, size_t n); static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr straus_HiGi_cache; static std::shared_ptr pippenger_HiGi_cache; static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); static const rct::key ip12 = inner_product(oneN, twoN); static boost::mutex init_mutex; static inline rct::key multiexp(const std::vector &data, size_t HiGi_size) { if (HiGi_size > 0) { static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT"); return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size())); } else return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size())); } static inline bool is_reduced(const rct::key &scalar) { return sc_check(scalar.bytes) == 0; } static rct::key get_exponent(const rct::key &base, size_t idx) { std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_EXPONENT + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); ge_p3_tobytes(e.bytes, &e_p3); CHECK_AND_ASSERT_THROW_MES(!(e == rct::identity()), "Exponent is point at infinity"); return e; } static void init_exponents() { boost::lock_guard lock(init_mutex); static bool init_done = false; if (init_done) return; std::vector data; data.reserve(maxN*maxM*2); for (size_t i = 0; i < maxN*maxM; ++i) { const rct::key Hi = get_exponent(rct::H, i * 2); CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed"); const rct::key Gi = get_exponent(rct::H, i * 2 + 1); CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed"); data.push_back({rct::zero(), Gi_p3[i]}); data.push_back({rct::zero(), Hi_p3[i]}); } straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB"); MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB"); MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB"); size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Total cache size: " << cache_size/1024 << "kB"); init_done = true; } /* Given two scalar arrays, construct a vector commitment */ static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b) { CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN"); std::vector multiexp_data; multiexp_data.reserve(a.size()*2); for (size_t i = 0; i < a.size(); ++i) { multiexp_data.emplace_back(a[i], Gi_p3[i]); multiexp_data.emplace_back(b[i], Hi_p3[i]); } return multiexp(multiexp_data, 2 * a.size()); } /* Compute a custom vector-scalar commitment */ static rct::key cross_vector_exponent8(size_t size, const std::vector &A, size_t Ao, const std::vector &B, size_t Bo, const rct::keyV &a, size_t ao, const rct::keyV &b, size_t bo, const rct::keyV *scale, const ge_p3 *extra_point, const rct::key *extra_scalar) { CHECK_AND_ASSERT_THROW_MES(size + Ao <= A.size(), "Incompatible size for A"); CHECK_AND_ASSERT_THROW_MES(size + Bo <= B.size(), "Incompatible size for B"); CHECK_AND_ASSERT_THROW_MES(size + ao <= a.size(), "Incompatible size for a"); CHECK_AND_ASSERT_THROW_MES(size + bo <= b.size(), "Incompatible size for b"); CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large"); CHECK_AND_ASSERT_THROW_MES(!scale || size == scale->size() / 2, "Incompatible size for scale"); CHECK_AND_ASSERT_THROW_MES(!!extra_point == !!extra_scalar, "only one of extra point/scalar present"); std::vector multiexp_data; multiexp_data.resize(size*2 + (!!extra_point)); for (size_t i = 0; i < size; ++i) { sc_mul(multiexp_data[i*2].scalar.bytes, a[ao+i].bytes, INV_EIGHT.bytes); multiexp_data[i*2].point = A[Ao+i]; sc_mul(multiexp_data[i*2+1].scalar.bytes, b[bo+i].bytes, INV_EIGHT.bytes); if (scale) sc_mul(multiexp_data[i*2+1].scalar.bytes, multiexp_data[i*2+1].scalar.bytes, (*scale)[Bo+i].bytes); multiexp_data[i*2+1].point = B[Bo+i]; } if (extra_point) { sc_mul(multiexp_data.back().scalar.bytes, extra_scalar->bytes, INV_EIGHT.bytes); multiexp_data.back().point = *extra_point; } return multiexp(multiexp_data, 0); } /* Given a scalar, construct a vector of powers */ static rct::keyV vector_powers(const rct::key &x, size_t n) { rct::keyV res(n); if (n == 0) return res; res[0] = rct::identity(); if (n == 1) return res; res[1] = x; for (size_t i = 2; i < n; ++i) { sc_mul(res[i].bytes, res[i-1].bytes, x.bytes); } return res; } /* Given a scalar, return the sum of its powers from 0 to n-1 */ static rct::key vector_power_sum(rct::key x, size_t n) { if (n == 0) return rct::zero(); rct::key res = rct::identity(); if (n == 1) return res; const bool is_power_of_2 = (n & (n - 1)) == 0; if (is_power_of_2) { sc_add(res.bytes, res.bytes, x.bytes); while (n > 2) { sc_mul(x.bytes, x.bytes, x.bytes); sc_muladd(res.bytes, x.bytes, res.bytes, res.bytes); n /= 2; } } else { rct::key prev = x; for (size_t i = 1; i < n; ++i) { if (i > 1) sc_mul(prev.bytes, prev.bytes, x.bytes); sc_add(res.bytes, res.bytes, prev.bytes); } } return res; } /* Given two scalar arrays, construct the inner product */ static rct::key inner_product(const epee::span &a, const epee::span &b) { CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); rct::key res = rct::zero(); for (size_t i = 0; i < a.size(); ++i) { sc_muladd(res.bytes, a[i].bytes, b[i].bytes, res.bytes); } return res; } static rct::key inner_product(const rct::keyV &a, const rct::keyV &b) { return inner_product(epee::span(a.data(), a.size()), epee::span(b.data(), b.size())); } /* Given two scalar arrays, construct the Hadamard product */ static rct::keyV hadamard(const rct::keyV &a, const rct::keyV &b) { CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); rct::keyV res(a.size()); for (size_t i = 0; i < a.size(); ++i) { sc_mul(res[i].bytes, a[i].bytes, b[i].bytes); } return res; } /* folds a curvepoint array using a two way scaled Hadamard product */ static void hadamard_fold(std::vector &v, const rct::keyV *scale, const rct::key &a, const rct::key &b) { CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even"); const size_t sz = v.size() / 2; for (size_t n = 0; n < sz; ++n) { ge_dsmp c[2]; ge_dsm_precomp(c[0], &v[n]); ge_dsm_precomp(c[1], &v[sz + n]); rct::key sa, sb; if (scale) sc_mul(sa.bytes, a.bytes, (*scale)[n].bytes); else sa = a; if (scale) sc_mul(sb.bytes, b.bytes, (*scale)[sz + n].bytes); else sb = b; ge_double_scalarmult_precomp_vartime2_p3(&v[n], sa.bytes, c[0], sb.bytes, c[1]); } v.resize(sz); } /* Add two vectors */ static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b) { CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); rct::keyV res(a.size()); for (size_t i = 0; i < a.size(); ++i) { sc_add(res[i].bytes, a[i].bytes, b[i].bytes); } return res; } /* Add a scalar to all elements of a vector */ static rct::keyV vector_add(const rct::keyV &a, const rct::key &b) { rct::keyV res(a.size()); for (size_t i = 0; i < a.size(); ++i) { sc_add(res[i].bytes, a[i].bytes, b.bytes); } return res; } /* Subtract a scalar from all elements of a vector */ static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b) { rct::keyV res(a.size()); for (size_t i = 0; i < a.size(); ++i) { sc_sub(res[i].bytes, a[i].bytes, b.bytes); } return res; } /* Multiply a scalar and a vector */ static rct::keyV vector_scalar(const epee::span &a, const rct::key &x) { rct::keyV res(a.size()); for (size_t i = 0; i < a.size(); ++i) { sc_mul(res[i].bytes, a[i].bytes, x.bytes); } return res; } static rct::keyV vector_scalar(const rct::keyV &a, const rct::key &x) { return vector_scalar(epee::span(a.data(), a.size()), x); } /* Create a vector from copies of a single value */ static rct::keyV vector_dup(const rct::key &x, size_t N) { return rct::keyV(N, x); } static rct::key sm(rct::key y, int n, const rct::key &x) { while (n--) sc_mul(y.bytes, y.bytes, y.bytes); sc_mul(y.bytes, y.bytes, x.bytes); return y; } /* Compute the inverse of a scalar, the clever way */ static rct::key invert(const rct::key &x) { rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111; _1 = x; sc_mul(_10.bytes, _1.bytes, _1.bytes); sc_mul(_100.bytes, _10.bytes, _10.bytes); sc_mul(_11.bytes, _10.bytes, _1.bytes); sc_mul(_101.bytes, _10.bytes, _11.bytes); sc_mul(_111.bytes, _10.bytes, _101.bytes); sc_mul(_1001.bytes, _10.bytes, _111.bytes); sc_mul(_1011.bytes, _10.bytes, _1001.bytes); sc_mul(_1111.bytes, _100.bytes, _1011.bytes); rct::key inv; sc_mul(inv.bytes, _1111.bytes, _1.bytes); inv = sm(inv, 123 + 3, _101); inv = sm(inv, 2 + 2, _11); inv = sm(inv, 1 + 4, _1111); inv = sm(inv, 1 + 4, _1111); inv = sm(inv, 4, _1001); inv = sm(inv, 2, _11); inv = sm(inv, 1 + 4, _1111); inv = sm(inv, 1 + 3, _101); inv = sm(inv, 3 + 3, _101); inv = sm(inv, 3, _111); inv = sm(inv, 1 + 4, _1111); inv = sm(inv, 2 + 3, _111); inv = sm(inv, 2 + 2, _11); inv = sm(inv, 1 + 4, _1011); inv = sm(inv, 2 + 4, _1011); inv = sm(inv, 6 + 4, _1001); inv = sm(inv, 2 + 2, _11); inv = sm(inv, 3 + 2, _11); inv = sm(inv, 3 + 2, _11); inv = sm(inv, 1 + 4, _1001); inv = sm(inv, 1 + 3, _111); inv = sm(inv, 2 + 4, _1111); inv = sm(inv, 1 + 4, _1011); inv = sm(inv, 3, _101); inv = sm(inv, 2 + 4, _1111); inv = sm(inv, 3, _101); inv = sm(inv, 1 + 2, _11); #ifdef DEBUG_BP rct::key tmp; sc_mul(tmp.bytes, inv.bytes, x.bytes); CHECK_AND_ASSERT_THROW_MES(tmp == rct::identity(), "invert failed"); #endif return inv; } static rct::keyV invert(rct::keyV x) { rct::keyV scratch; scratch.reserve(x.size()); rct::key acc = rct::identity(); for (size_t n = 0; n < x.size(); ++n) { scratch.push_back(acc); if (n == 0) acc = x[0]; else sc_mul(acc.bytes, acc.bytes, x[n].bytes); } acc = invert(acc); rct::key tmp; for (int i = x.size(); i-- > 0; ) { sc_mul(tmp.bytes, acc.bytes, x[i].bytes); sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes); acc = tmp; } return x; } /* Compute the slice of a vector */ static epee::span slice(const rct::keyV &a, size_t start, size_t stop) { CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index"); CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index"); CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices"); return epee::span(&a[start], stop - start); } static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1) { rct::key data[3]; data[0] = hash_cache; data[1] = mash0; data[2] = mash1; rct::hash_to_scalar(hash_cache, data, sizeof(data)); return hash_cache; } static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2) { rct::key data[4]; data[0] = hash_cache; data[1] = mash0; data[2] = mash1; data[3] = mash2; rct::hash_to_scalar(hash_cache, data, sizeof(data)); return hash_cache; } static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2, const rct::key &mash3) { rct::key data[5]; data[0] = hash_cache; data[1] = mash0; data[2] = mash1; data[3] = mash2; data[4] = mash3; rct::hash_to_scalar(hash_cache, data, sizeof(data)); return hash_cache; } /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ Bulletproof bulletproof_PROVE(const rct::key &sv, const rct::key &gamma) { return bulletproof_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); } Bulletproof bulletproof_PROVE(uint64_t v, const rct::key &gamma) { return bulletproof_PROVE(std::vector(1, v), rct::keyV(1, gamma)); } /* Given a set of values v (0..2^N-1) and masks gamma, construct a range proof */ Bulletproof bulletproof_PROVE(const rct::keyV &sv, const rct::keyV &gamma) { CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma"); CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty"); for (const rct::key &sve: sv) CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input"); for (const rct::key &g: gamma) CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input"); init_exponents(); PERF_TIMER_UNIT(PROVE, 1000000); constexpr size_t logN = 6; // log2(64) constexpr size_t N = 1< 0; ) { if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8)))) { aL[j*N+i] = rct::identity(); aL8[j*N+i] = INV_EIGHT; aR[j*N+i] = aR8[j*N+i] = rct::zero(); } else { aL[j*N+i] = aL8[j*N+i] = rct::zero(); aR[j*N+i] = MINUS_ONE; aR8[j*N+i] = MINUS_INV_EIGHT; } } } PERF_TIMER_STOP_BP(PROVE_aLaR); // DEBUG: Test to ensure this recovers the value #ifdef DEBUG_BP for (size_t j = 0; j < M; ++j) { uint64_t test_aL = 0, test_aR = 0; for (size_t i = 0; i < N; ++i) { if (aL[j*N+i] == rct::identity()) test_aL += ((uint64_t)1)< Gprime(MN); std::vector Hprime(MN); rct::keyV aprime(MN); rct::keyV bprime(MN); const rct::key yinv = invert(y); rct::keyV yinvpow(MN); yinvpow[0] = rct::identity(); yinvpow[1] = yinv; for (size_t i = 0; i < MN; ++i) { Gprime[i] = Gi_p3[i]; Hprime[i] = Hi_p3[i]; if (i > 1) sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes); aprime[i] = l[i]; bprime[i] = r[i]; } rct::keyV L(logMN); rct::keyV R(logMN); int round = 0; rct::keyV w(logMN); // this is the challenge x in the inner product protocol PERF_TIMER_STOP_BP(PROVE_step3); PERF_TIMER_START_BP(PROVE_step4); const rct::keyV *scale = &yinvpow; while (nprime > 1) { // PAPER LINE 20 nprime /= 2; // PAPER LINES 21-22 PERF_TIMER_START_BP(PROVE_inner_product); rct::key cL = inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size())); rct::key cR = inner_product(slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime)); PERF_TIMER_STOP_BP(PROVE_inner_product); // PAPER LINES 23-24 PERF_TIMER_START_BP(PROVE_LR); sc_mul(tmp.bytes, cL.bytes, x_ip.bytes); L[round] = cross_vector_exponent8(nprime, Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, scale, &ge_p3_H, &tmp); sc_mul(tmp.bytes, cR.bytes, x_ip.bytes); R[round] = cross_vector_exponent8(nprime, Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, scale, &ge_p3_H, &tmp); PERF_TIMER_STOP_BP(PROVE_LR); // PAPER LINES 25-27 w[round] = hash_cache_mash(hash_cache, L[round], R[round]); if (w[round] == rct::zero()) { PERF_TIMER_STOP_BP(PROVE_step4); MINFO("w[round] is 0, trying again"); goto try_again; } // PAPER LINES 29-30 const rct::key winv = invert(w[round]); if (nprime > 1) { PERF_TIMER_START_BP(PROVE_hadamard2); hadamard_fold(Gprime, NULL, winv, w[round]); hadamard_fold(Hprime, scale, w[round], winv); PERF_TIMER_STOP_BP(PROVE_hadamard2); } // PAPER LINES 33-34 PERF_TIMER_START_BP(PROVE_prime); aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), w[round]), vector_scalar(slice(aprime, nprime, aprime.size()), winv)); bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), winv), vector_scalar(slice(bprime, nprime, bprime.size()), w[round])); PERF_TIMER_STOP_BP(PROVE_prime); scale = NULL; ++round; } PERF_TIMER_STOP_BP(PROVE_step4); return Bulletproof(std::move(V), A, S, T1, T2, taux, mu, std::move(L), std::move(R), aprime[0], bprime[0], t); } Bulletproof bulletproof_PROVE(const std::vector &v, const rct::keyV &gamma) { CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma"); // vG + gammaH PERF_TIMER_START_BP(PROVE_v); rct::keyV sv(v.size()); for (size_t i = 0; i < v.size(); ++i) { sv[i] = rct::zero(); sv[i].bytes[0] = v[i] & 255; sv[i].bytes[1] = (v[i] >> 8) & 255; sv[i].bytes[2] = (v[i] >> 16) & 255; sv[i].bytes[3] = (v[i] >> 24) & 255; sv[i].bytes[4] = (v[i] >> 32) & 255; sv[i].bytes[5] = (v[i] >> 40) & 255; sv[i].bytes[6] = (v[i] >> 48) & 255; sv[i].bytes[7] = (v[i] >> 56) & 255; } PERF_TIMER_STOP_BP(PROVE_v); return bulletproof_PROVE(sv, gamma); } struct proof_data_t { rct::key x, y, z, x_ip; std::vector w; size_t logM, inv_offset; }; /* Given a range proof, determine if it is valid * This uses the method in PAPER LINES 95-105, * weighted across multiple proofs in a batch */ bool bulletproof_VERIFY(const std::vector &proofs) { init_exponents(); PERF_TIMER_START_BP(VERIFY); const size_t logN = 6; const size_t N = 1 << logN; // sanity and figure out which proof is longest size_t max_length = 0; size_t nV = 0; std::vector proof_data; proof_data.reserve(proofs.size()); size_t inv_offset = 0; std::vector to_invert; to_invert.reserve(11 * proofs.size()); size_t max_logM = 0; for (const Bulletproof *p: proofs) { const Bulletproof &proof = *p; // check scalar range CHECK_AND_ASSERT_MES(is_reduced(proof.taux), false, "Input scalar not in range"); CHECK_AND_ASSERT_MES(is_reduced(proof.mu), false, "Input scalar not in range"); CHECK_AND_ASSERT_MES(is_reduced(proof.a), false, "Input scalar not in range"); CHECK_AND_ASSERT_MES(is_reduced(proof.b), false, "Input scalar not in range"); CHECK_AND_ASSERT_MES(is_reduced(proof.t), false, "Input scalar not in range"); CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element"); CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes"); CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof"); max_length = std::max(max_length, proof.L.size()); nV += proof.V.size(); // Reconstruct the challenges PERF_TIMER_START_BP(VERIFY_start); proof_data.resize(proof_data.size() + 1); proof_data_t &pd = proof_data.back(); rct::key hash_cache = rct::hash_to_scalar(proof.V); pd.y = hash_cache_mash(hash_cache, proof.A, proof.S); CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0"); pd.z = hash_cache = rct::hash_to_scalar(pd.y); CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0"); pd.x = hash_cache_mash(hash_cache, pd.z, proof.T1, proof.T2); CHECK_AND_ASSERT_MES(!(pd.x == rct::zero()), false, "x == 0"); pd.x_ip = hash_cache_mash(hash_cache, pd.x, proof.taux, proof.mu, proof.t); CHECK_AND_ASSERT_MES(!(pd.x_ip == rct::zero()), false, "x_ip == 0"); PERF_TIMER_STOP_BP(VERIFY_start); size_t M; for (pd.logM = 0; (M = 1< 0, false, "Zero rounds"); PERF_TIMER_START_BP(VERIFY_line_21_22); // The inner product challenges are computed per round pd.w.resize(rounds); for (size_t i = 0; i < rounds; ++i) { pd.w[i] = hash_cache_mash(hash_cache, proof.L[i], proof.R[i]); CHECK_AND_ASSERT_MES(!(pd.w[i] == rct::zero()), false, "w[i] == 0"); } PERF_TIMER_STOP_BP(VERIFY_line_21_22); pd.inv_offset = inv_offset; for (size_t i = 0; i < rounds; ++i) to_invert.push_back(pd.w[i]); to_invert.push_back(pd.y); inv_offset += rounds + 1; } CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large"); size_t maxMN = 1u << max_length; rct::key tmp; std::vector multiexp_data; multiexp_data.reserve(nV + (2 * (max_logM + logN) + 4) * proofs.size() + 2 * maxMN); multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); const std::vector inverses = invert(std::move(to_invert)); to_invert.clear(); PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert); // setup weighted aggregates rct::key z1 = rct::zero(); rct::key z3 = rct::zero(); rct::keyV m_z4(maxMN, rct::zero()), m_z5(maxMN, rct::zero()); rct::key m_y0 = rct::zero(), y1 = rct::zero(); int proof_data_index = 0; rct::keyV w_cache; std::vector proof8_V, proof8_L, proof8_R; for (const Bulletproof *p: proofs) { const Bulletproof &proof = *p; const proof_data_t &pd = proof_data[proof_data_index++]; CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); const size_t M = 1 << pd.logM; const size_t MN = M*N; const rct::key weight_y = rct::skGen(); const rct::key weight_z = rct::skGen(); // pre-multiply some points by 8 proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]); proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]); proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]); ge_p3 proof8_T1; ge_p3 proof8_T2; ge_p3 proof8_S; ge_p3 proof8_A; rct::scalarmult8(proof8_T1, proof.T1); rct::scalarmult8(proof8_T2, proof.T2); rct::scalarmult8(proof8_S, proof.S); rct::scalarmult8(proof8_A, proof.A); PERF_TIMER_START_BP(VERIFY_line_61); sc_mulsub(m_y0.bytes, proof.taux.bytes, weight_y.bytes, m_y0.bytes); const rct::keyV zpow = vector_powers(pd.z, M+3); rct::key k; const rct::key ip1y = vector_power_sum(pd.y, MN); sc_mulsub(k.bytes, zpow[2].bytes, ip1y.bytes, rct::zero().bytes); for (size_t j = 1; j <= M; ++j) { CHECK_AND_ASSERT_MES(j+2 < zpow.size(), false, "invalid zpow index"); sc_mulsub(k.bytes, zpow[j+2].bytes, ip12.bytes, k.bytes); } PERF_TIMER_STOP_BP(VERIFY_line_61); PERF_TIMER_START_BP(VERIFY_line_61rl_new); sc_muladd(tmp.bytes, pd.z.bytes, ip1y.bytes, k.bytes); sc_sub(tmp.bytes, proof.t.bytes, tmp.bytes); sc_muladd(y1.bytes, tmp.bytes, weight_y.bytes, y1.bytes); for (size_t j = 0; j < proof8_V.size(); j++) { sc_mul(tmp.bytes, zpow[j+2].bytes, weight_y.bytes); multiexp_data.emplace_back(tmp, proof8_V[j]); } sc_mul(tmp.bytes, pd.x.bytes, weight_y.bytes); multiexp_data.emplace_back(tmp, proof8_T1); rct::key xsq; sc_mul(xsq.bytes, pd.x.bytes, pd.x.bytes); sc_mul(tmp.bytes, xsq.bytes, weight_y.bytes); multiexp_data.emplace_back(tmp, proof8_T2); PERF_TIMER_STOP_BP(VERIFY_line_61rl_new); PERF_TIMER_START_BP(VERIFY_line_62); multiexp_data.emplace_back(weight_z, proof8_A); sc_mul(tmp.bytes, pd.x.bytes, weight_z.bytes); multiexp_data.emplace_back(tmp, proof8_S); PERF_TIMER_STOP_BP(VERIFY_line_62); // Compute the number of rounds for the inner product const size_t rounds = pd.logM+logN; CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); PERF_TIMER_START_BP(VERIFY_line_24_25); // Compute the curvepoints from G[i] and H[i] rct::key yinvpow = rct::identity(); rct::key ypow = rct::identity(); const rct::key *winv = &inverses[pd.inv_offset]; const rct::key yinv = inverses[pd.inv_offset + rounds]; // precalc PERF_TIMER_START_BP(VERIFY_line_24_25_precalc); w_cache.resize(1< 0; --s) { sc_mul(w_cache[s].bytes, w_cache[s/2].bytes, pd.w[j].bytes); sc_mul(w_cache[s-1].bytes, w_cache[s/2].bytes, winv[j].bytes); } } PERF_TIMER_STOP_BP(VERIFY_line_24_25_precalc); for (size_t i = 0; i < MN; ++i) { rct::key g_scalar = proof.a; rct::key h_scalar; if (i == 0) h_scalar = proof.b; else sc_mul(h_scalar.bytes, proof.b.bytes, yinvpow.bytes); // Convert the index to binary IN REVERSE and construct the scalar exponent sc_mul(g_scalar.bytes, g_scalar.bytes, w_cache[i].bytes); sc_mul(h_scalar.bytes, h_scalar.bytes, w_cache[(~i) & (MN-1)].bytes); sc_add(g_scalar.bytes, g_scalar.bytes, pd.z.bytes); CHECK_AND_ASSERT_MES(2+i/N < zpow.size(), false, "invalid zpow index"); CHECK_AND_ASSERT_MES(i%N < twoN.size(), false, "invalid twoN index"); sc_mul(tmp.bytes, zpow[2+i/N].bytes, twoN[i%N].bytes); if (i == 0) { sc_add(tmp.bytes, tmp.bytes, pd.z.bytes); sc_sub(h_scalar.bytes, h_scalar.bytes, tmp.bytes); } else { sc_muladd(tmp.bytes, pd.z.bytes, ypow.bytes, tmp.bytes); sc_mulsub(h_scalar.bytes, tmp.bytes, yinvpow.bytes, h_scalar.bytes); } sc_mulsub(m_z4[i].bytes, g_scalar.bytes, weight_z.bytes, m_z4[i].bytes); sc_mulsub(m_z5[i].bytes, h_scalar.bytes, weight_z.bytes, m_z5[i].bytes); if (i == 0) { yinvpow = yinv; ypow = pd.y; } else if (i != MN-1) { sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes); sc_mul(ypow.bytes, ypow.bytes, pd.y.bytes); } } PERF_TIMER_STOP_BP(VERIFY_line_24_25); PERF_TIMER_START_BP(VERIFY_line_26_new); sc_muladd(z1.bytes, proof.mu.bytes, weight_z.bytes, z1.bytes); for (size_t i = 0; i < rounds; ++i) { sc_mul(tmp.bytes, pd.w[i].bytes, pd.w[i].bytes); sc_mul(tmp.bytes, tmp.bytes, weight_z.bytes); multiexp_data.emplace_back(tmp, proof8_L[i]); sc_mul(tmp.bytes, winv[i].bytes, winv[i].bytes); sc_mul(tmp.bytes, tmp.bytes, weight_z.bytes); multiexp_data.emplace_back(tmp, proof8_R[i]); } sc_mulsub(tmp.bytes, proof.a.bytes, proof.b.bytes, proof.t.bytes); sc_mul(tmp.bytes, tmp.bytes, pd.x_ip.bytes); sc_muladd(z3.bytes, tmp.bytes, weight_z.bytes, z3.bytes); PERF_TIMER_STOP_BP(VERIFY_line_26_new); } // now check all proofs at once PERF_TIMER_START_BP(VERIFY_step2_check); sc_sub(tmp.bytes, m_y0.bytes, z1.bytes); multiexp_data.emplace_back(tmp, rct::G); sc_sub(tmp.bytes, z3.bytes, y1.bytes); multiexp_data.emplace_back(tmp, rct::H); for (size_t i = 0; i < maxMN; ++i) { multiexp_data[i * 2] = {m_z4[i], Gi_p3[i]}; multiexp_data[i * 2 + 1] = {m_z5[i], Hi_p3[i]}; } if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity())) { PERF_TIMER_STOP_BP(VERIFY_step2_check); MERROR("Verification failure"); return false; } PERF_TIMER_STOP_BP(VERIFY_step2_check); PERF_TIMER_STOP_BP(VERIFY); return true; } bool bulletproof_VERIFY(const std::vector &proofs) { std::vector proof_pointers; proof_pointers.reserve(proofs.size()); for (const Bulletproof &proof: proofs) proof_pointers.push_back(&proof); return bulletproof_VERIFY(proof_pointers); } bool bulletproof_VERIFY(const Bulletproof &proof) { std::vector proofs; proofs.push_back(&proof); return bulletproof_VERIFY(proofs); } }