From 90b8d9f271d84f6003209d505c53504dff86ba0e Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 30 Nov 2017 19:59:10 +0000 Subject: [PATCH] add bulletproofs to the build, with basic unit tests Based on Java code from Sarang Noether --- src/ringct/CMakeLists.txt | 7 +- src/ringct/bulletproofs.cc | 760 ++++++++++++++++++++++++++++++ src/ringct/bulletproofs.h | 78 +++ tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/bulletproofs.cpp | 70 +++ 5 files changed, 914 insertions(+), 2 deletions(-) create mode 100644 src/ringct/bulletproofs.cc create mode 100644 src/ringct/bulletproofs.h create mode 100644 tests/unit_tests/bulletproofs.cpp diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index f9862ac80..1452e5367 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -30,14 +30,16 @@ set(ringct_sources rctOps.cpp rctSigs.cpp rctTypes.cpp - rctCryptoOps.c) + rctCryptoOps.c + bulletproofs.cc) set(ringct_headers) set(ringct_private_headers rctOps.h rctSigs.h - rctTypes.h) + rctTypes.h + bulletproofs.h) monero_private_headers(ringct ${crypto_private_headers}) @@ -51,4 +53,5 @@ target_link_libraries(ringct cncrypto cryptonote_basic PRIVATE + ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc new file mode 100644 index 000000000..7b4ac12a7 --- /dev/null +++ b/src/ringct/bulletproofs.cc @@ -0,0 +1,760 @@ +// Copyright (c) 2017, 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 + +#include +#include +#include +#include "misc_log_ex.h" +#include "common/perf_timer.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "rctOps.h" +#include "bulletproofs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bulletproofs" + +//#define DEBUG_BP + +#define PERF_TIMER_START_BP(x) PERF_TIMER_START_UNIT(x, 1000000) + +namespace rct +{ + +static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); +static rct::keyV vector_powers(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 rct::key Hi[maxN], Gi[maxN]; +static ge_dsmp Gprecomp[64], Hprecomp[64]; +static const 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 rct::keyV oneN = vector_powers(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 rct::key get_exponent(const rct::key &base, size_t idx) +{ + static const std::string salt("bulletproof"); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + salt + tools::get_varint_data(idx); + return rct::hashToPoint(rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); +} + +static void init_exponents() +{ + boost::lock_guard lock(init_mutex); + + static bool init_done = false; + if (init_done) + return; + for (size_t i = 0; i < maxN; ++i) + { + Hi[i] = get_exponent(rct::H, i * 2); + rct::precomp(Hprecomp[i], Hi[i]); + Gi[i] = get_exponent(rct::H, i * 2 + 1); + rct::precomp(Gprecomp[i], Gi[i]); + } + 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, "Incompatible sizes of a and maxN"); + rct::key res = rct::identity(); + for (size_t i = 0; i < a.size(); ++i) + { + rct::key term; + rct::addKeys3(term, a[i], Gprecomp[i], b[i], Hprecomp[i]); + rct::addKeys(res, res, term); + } + return res; +} + +/* Compute a custom vector-scalar commitment */ +static rct::key vector_exponent_custom(const rct::keyV &A, const rct::keyV &B, 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() == b.size(), "Incompatible sizes of a and b"); + CHECK_AND_ASSERT_THROW_MES(a.size() == A.size(), "Incompatible sizes of a and A"); + CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN, "Incompatible sizes of a and maxN"); + rct::key res = rct::identity(); + for (size_t i = 0; i < a.size(); ++i) + { + rct::key term; +#if 0 + // we happen to know where A and B might fall, so don't bother checking the rest + ge_dsmp *Acache = NULL, *Bcache = NULL; + ge_dsmp Acache_custom[1], Bcache_custom[1]; + if (Gi[i] == A[i]) + Acache = Gprecomp + i; + else if (i<32 && Gi[i+32] == A[i]) + Acache = Gprecomp + i + 32; + else + { + rct::precomp(Acache_custom[0], A[i]); + Acache = Acache_custom; + } + if (i == 0 && B[i] == Hi[0]) + Bcache = Hprecomp; + else + { + rct::precomp(Bcache_custom[0], B[i]); + Bcache = Bcache_custom; + } + rct::addKeys3(term, a[i], *Acache, b[i], *Bcache); +#else + ge_dsmp Acache, Bcache; + rct::precomp(Bcache, B[i]); + rct::addKeys3(term, a[i], A[i], b[i], Bcache); +#endif + rct::addKeys(res, res, term); + } + return res; +} + +/* Given a scalar, construct a vector of powers */ +static rct::keyV vector_powers(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 two scalar arrays, construct the inner product */ +static rct::key inner_product(const rct::keyV &a, const rct::keyV &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; +} + +/* 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; +} + +/* Given two curvepoint arrays, construct the Hadamard product */ +static rct::keyV hadamard2(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) + { + rct::addKeys(res[i], a[i], b[i]); + } + return res; +} + +/* 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; +} + +/* Subtract two vectors */ +static rct::keyV vector_subtract(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_sub(res[i].bytes, a[i].bytes, b[i].bytes); + } + return res; +} + +/* Multiply a scalar and a vector */ +static rct::keyV vector_scalar(const rct::keyV &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; +} + +/* Exponentiate a curve vector by a scalar */ +static rct::keyV vector_scalar2(const rct::keyV &a, const rct::key &x) +{ + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + rct::scalarmultKey(res[i], a[i], x); + } + return res; +} + +static rct::key switch_endianness(rct::key k) +{ + std::reverse(k.bytes, k.bytes + sizeof(k)); + return k; +} + +/* Compute the inverse of a scalar, the stupid way */ +static rct::key invert(const rct::key &x) +{ + rct::key inv; + + BN_CTX *ctx = BN_CTX_new(); + BIGNUM *X = BN_new(); + BIGNUM *L = BN_new(); + BIGNUM *I = BN_new(); + + BN_bin2bn(switch_endianness(x).bytes, sizeof(rct::key), X); + BN_bin2bn(switch_endianness(rct::curveOrder()).bytes, sizeof(rct::key), L); + + CHECK_AND_ASSERT_THROW_MES(BN_mod_inverse(I, X, L, ctx), "Failed to invert"); + + const int len = BN_num_bytes(I); + CHECK_AND_ASSERT_THROW_MES((size_t)len <= sizeof(rct::key), "Invalid number length"); + inv = rct::zero(); + BN_bn2bin(I, inv.bytes); + std::reverse(inv.bytes, inv.bytes + len); + + BN_free(I); + BN_free(L); + BN_free(X); + BN_CTX_free(ctx); + +#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; +} + +/* Compute the slice of a vector */ +static rct::keyV 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"); + rct::keyV res(stop - start); + for (size_t i = start; i < stop; ++i) + { + res[i - start] = a[i]; + } + return res; +} + +/* 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) +{ + init_exponents(); + + PERF_TIMER_UNIT(PROVE, 1000000); + + constexpr size_t logN = 6; // log2(64) + constexpr size_t N = 1< 0; ) + { + if (sv[i/8] & (((uint64_t)1)<<(i%8))) + { + aL[i] = rct::identity(); + } + else + { + aL[i] = rct::zero(); + } + sc_sub(aR[i].bytes, aL[i].bytes, rct::identity().bytes); + } + PERF_TIMER_STOP(PROVE_aLaR); + + + // DEBUG: Test to ensure this recovers the value +#ifdef DEBUG_BP + uint64_t test_aL = 0, test_aR = 0; + for (size_t i = 0; i < N; ++i) + { + if (aL[i] == rct::identity()) + test_aL += ((uint64_t)1)< 1) + { + // PAPER LINE 15 + nprime /= 2; + + // PAPER LINES 16-17 + 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)); + + // PAPER LINES 18-19 + L[round] = vector_exponent_custom(slice(Gprime, nprime, Gprime.size()), slice(Hprime, 0, nprime), slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size())); + sc_mul(tmp.bytes, cL.bytes, x_ip.bytes); + rct::addKeys(L[round], L[round], rct::scalarmultBase(tmp)); + R[round] = vector_exponent_custom(slice(Gprime, 0, nprime), slice(Hprime, nprime, Hprime.size()), slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime)); + sc_mul(tmp.bytes, cR.bytes, x_ip.bytes); + rct::addKeys(R[round], R[round], rct::scalarmultBase(tmp)); + + // PAPER LINES 21-22 + hashed.clear(); + if (round == 0) + { + hashed.push_back(L[0]); + hashed.push_back(R[0]); + w[0] = rct::hash_to_scalar(hashed); + } + else + { + hashed.push_back(w[round - 1]); + hashed.push_back(L[round]); + hashed.push_back(R[round]); + w[round] = rct::hash_to_scalar(hashed); + } + + // PAPER LINES 24-25 + const rct::key winv = invert(w[round]); + Gprime = hadamard2(vector_scalar2(slice(Gprime, 0, nprime), winv), vector_scalar2(slice(Gprime, nprime, Gprime.size()), w[round])); + Hprime = hadamard2(vector_scalar2(slice(Hprime, 0, nprime), w[round]), vector_scalar2(slice(Hprime, nprime, Hprime.size()), winv)); + + // PAPER LINES 28-29 + 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])); + + ++round; + } + PERF_TIMER_STOP(PROVE_step4); + + // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20) + return Bulletproof(V, A, S, T1, T2, taux, mu, L, R, aprime[0], bprime[0], t); +} + +Bulletproof bulletproof_PROVE(uint64_t v, const rct::key &gamma) +{ + // vG + gammaH + PERF_TIMER_START_BP(PROVE_v); + rct::key sv = rct::zero(); + sv.bytes[0] = v & 255; + sv.bytes[1] = (v >> 8) & 255; + sv.bytes[2] = (v >> 16) & 255; + sv.bytes[3] = (v >> 24) & 255; + sv.bytes[4] = (v >> 32) & 255; + sv.bytes[5] = (v >> 40) & 255; + sv.bytes[6] = (v >> 48) & 255; + sv.bytes[7] = (v >> 56) & 255; + PERF_TIMER_STOP(PROVE_v); + return bulletproof_PROVE(sv, gamma); +} + +/* Given a range proof, determine if it is valid */ +bool bulletproof_VERIFY(const Bulletproof &proof) +{ + init_exponents(); + + 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"); + CHECK_AND_ASSERT_MES(proof.L.size() == 6, false, "Proof is not for 64 bits"); + + const size_t logN = proof.L.size(); + const size_t N = 1 << logN; + + // Reconstruct the challenges + PERF_TIMER_START_BP(VERIFY); + PERF_TIMER_START_BP(VERIFY_start); + rct::keyV hashed; + hashed.push_back(proof.A); + hashed.push_back(proof.S); + rct::key y = rct::hash_to_scalar(hashed); + rct::key z = rct::hash_to_scalar(y); + hashed.clear(); + hashed.push_back(z); + hashed.push_back(proof.T1); + hashed.push_back(proof.T2); + rct::key x = rct::hash_to_scalar(hashed); + PERF_TIMER_STOP(VERIFY_start); + + PERF_TIMER_START_BP(VERIFY_line_60); + // Reconstruct the challenges + hashed.clear(); + hashed.push_back(x); + hashed.push_back(proof.taux); + hashed.push_back(proof.mu); + hashed.push_back(proof.t); + rct::key x_ip = hash_to_scalar(hashed); + PERF_TIMER_STOP(VERIFY_line_60); + + PERF_TIMER_START_BP(VERIFY_line_61); + // PAPER LINE 61 + rct::key L61Left = rct::addKeys(rct::scalarmultKey(rct::H, proof.taux), rct::scalarmultBase(proof.t)); + + rct::key k = rct::zero(); + const auto yN = vector_powers(y, N); + rct::key ip1y = inner_product(oneN, yN); + rct::key zsq; + sc_mul(zsq.bytes, z.bytes, z.bytes); + rct::key tmp, tmp2; + sc_mulsub(k.bytes, zsq.bytes, ip1y.bytes, k.bytes); + rct::key zcu; + sc_mul(zcu.bytes, zsq.bytes, z.bytes); + sc_mulsub(k.bytes, zcu.bytes, ip12.bytes, k.bytes); + PERF_TIMER_STOP(VERIFY_line_61); + + PERF_TIMER_START_BP(VERIFY_line_61rl); + sc_muladd(tmp.bytes, z.bytes, ip1y.bytes, k.bytes); + rct::key L61Right = rct::scalarmultBase(tmp); + + tmp = rct::scalarmultKey(proof.V, zsq); + rct::addKeys(L61Right, L61Right, tmp); + + tmp = rct::scalarmultKey(proof.T1, x); + rct::addKeys(L61Right, L61Right, tmp); + + rct::key xsq; + sc_mul(xsq.bytes, x.bytes, x.bytes); + tmp = rct::scalarmultKey(proof.T2, xsq); + rct::addKeys(L61Right, L61Right, tmp); + PERF_TIMER_STOP(VERIFY_line_61rl); + + if (!(L61Right == L61Left)) + { + MERROR("Verification failure at step 1"); + return false; + } + + PERF_TIMER_START_BP(VERIFY_line_62); + // PAPER LINE 62 + rct::key P = rct::addKeys(proof.A, rct::scalarmultKey(proof.S, x)); + PERF_TIMER_STOP(VERIFY_line_62); + + // Compute the number of rounds for the inner product + const size_t rounds = proof.L.size(); + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + PERF_TIMER_START_BP(VERIFY_line_21_22); + // PAPER LINES 21-22 + // The inner product challenges are computed per round + rct::keyV w(rounds); + hashed.clear(); + hashed.push_back(proof.L[0]); + hashed.push_back(proof.R[0]); + w[0] = rct::hash_to_scalar(hashed); + for (size_t i = 1; i < rounds; ++i) + { + hashed.clear(); + hashed.push_back(w[i-1]); + hashed.push_back(proof.L[i]); + hashed.push_back(proof.R[i]); + w[i] = rct::hash_to_scalar(hashed); + } + PERF_TIMER_STOP(VERIFY_line_21_22); + + PERF_TIMER_START_BP(VERIFY_line_24_25); + // Basically PAPER LINES 24-25 + // Compute the curvepoints from G[i] and H[i] + rct::key inner_prod = rct::identity(); + rct::key yinvpow = rct::identity(); + rct::key ypow = rct::identity(); + + PERF_TIMER_START_BP(VERIFY_line_24_25_invert); + const rct::key yinv = invert(y); + rct::keyV winv(rounds); + for (size_t i = 0; i < rounds; ++i) + winv[i] = invert(w[i]); + PERF_TIMER_STOP(VERIFY_line_24_25_invert); + + for (size_t i = 0; i < N; ++i) + { + // Convert the index to binary IN REVERSE and construct the scalar exponent + rct::key g_scalar = proof.a; + rct::key h_scalar; + sc_mul(h_scalar.bytes, proof.b.bytes, yinvpow.bytes); + + for (size_t j = rounds; j-- > 0; ) + { + size_t J = w.size() - j - 1; + + if ((i & (((size_t)1)<(), rct::skGen()); + ASSERT_TRUE(rct::bulletproof_VERIFY(proof)); + } +} + +TEST(bulletproofs, invalid_8) +{ + rct::key invalid_amount = rct::zero(); + invalid_amount[8] = 1; + rct::Bulletproof proof = bulletproof_PROVE(invalid_amount, rct::skGen()); + ASSERT_FALSE(rct::bulletproof_VERIFY(proof)); +} + +TEST(bulletproofs, invalid_31) +{ + rct::key invalid_amount = rct::zero(); + invalid_amount[31] = 1; + rct::Bulletproof proof = bulletproof_PROVE(invalid_amount, rct::skGen()); + ASSERT_FALSE(rct::bulletproof_VERIFY(proof)); +}