aboutsummaryrefslogtreecommitdiff
path: root/src/ringct
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2017-12-08 23:30:51 +0200
committerRiccardo Spagni <ric@spagni.net>2017-12-08 23:30:51 +0200
commit782a84f7b490baa10afef94dd6954d6ab50d020b (patch)
tree099cf5e07386236924d312606a2e4569658a1fb8 /src/ringct
parentMerge pull request #2845 (diff)
parentadd bulletproofs from v7 on testnet (diff)
downloadmonero-782a84f7b490baa10afef94dd6954d6ab50d020b.tar.xz
Merge pull request #2883
c83d0b3e add bulletproofs from v7 on testnet (moneromooo-monero) 8620ef0a bulletproofs: switch H/G in Pedersen commitments to match rct (moneromooo-monero) d58835b2 integrate bulletproofs into monero (moneromooo-monero) 90b8d9f2 add bulletproofs to the build, with basic unit tests (moneromooo-monero) fe120264 perf_timer: add non scoped start/stop timer defines (moneromooo-monero) ada42914 add a version of ge_double_scalarmult_precomp_vartime with A precomp (moneromooo-monero) d43eef6d ringct: add a version of addKeys which returns the result (moneromooo-monero) 7ff07928 sc_mul and sc_muladd (luigi1111) 3d0b54bd epee: add do while(0) around brace statement in a macro (moneromooo-monero)
Diffstat (limited to 'src/ringct')
-rw-r--r--src/ringct/CMakeLists.txt7
-rw-r--r--src/ringct/bulletproofs.cc761
-rw-r--r--src/ringct/bulletproofs.h47
-rw-r--r--src/ringct/rctOps.cpp14
-rw-r--r--src/ringct/rctOps.h2
-rw-r--r--src/ringct/rctSigs.cpp147
-rw-r--r--src/ringct/rctSigs.h4
-rw-r--r--src/ringct/rctTypes.h87
8 files changed, 1017 insertions, 52 deletions
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..51cf9e3be
--- /dev/null
+++ b/src/ringct/bulletproofs.cc
@@ -0,0 +1,761 @@
+// 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 <stdlib.h>
+#include <openssl/ssl.h>
+#include <boost/thread/mutex.hpp>
+#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<boost::mutex> 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<<logN;
+
+ rct::key V;
+ rct::keyV aL(N), aR(N);
+
+ PERF_TIMER_START_BP(PROVE_v);
+ rct::addKeys2(V, gamma, sv, rct::H);
+ PERF_TIMER_STOP(PROVE_v);
+
+ PERF_TIMER_START_BP(PROVE_aLaR);
+ for (size_t i = N; i-- > 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)<<i;
+ if (aR[i] == rct::zero())
+ test_aR += ((uint64_t)1)<<i;
+ }
+ uint64_t v_test = 0;
+ for (int n = 0; n < 8; ++n) v_test |= (((uint64_t)sv[n]) << (8*n));
+ CHECK_AND_ASSERT_THROW_MES(test_aL == v_test, "test_aL failed");
+ CHECK_AND_ASSERT_THROW_MES(test_aR == v_test, "test_aR failed");
+#endif
+
+ PERF_TIMER_START_BP(PROVE_step1);
+ // PAPER LINES 38-39
+ rct::key alpha = rct::skGen();
+ rct::key ve = vector_exponent(aL, aR);
+ rct::key A;
+ rct::addKeys(A, ve, rct::scalarmultBase(alpha));
+
+ // PAPER LINES 40-42
+ rct::keyV sL = rct::skvGen(N), sR = rct::skvGen(N);
+ rct::key rho = rct::skGen();
+ ve = vector_exponent(sL, sR);
+ rct::key S;
+ rct::addKeys(S, ve, rct::scalarmultBase(rho));
+
+ // PAPER LINES 43-45
+ rct::keyV hashed;
+ hashed.push_back(A);
+ hashed.push_back(S);
+ rct::key y = rct::hash_to_scalar(hashed);
+ rct::key z = rct::hash_to_scalar(y);
+
+ // Polynomial construction before PAPER LINE 46
+ rct::key t0 = rct::zero();
+ rct::key t1 = rct::zero();
+ rct::key t2 = rct::zero();
+
+ const auto yN = vector_powers(y, N);
+
+ rct::key ip1y = inner_product(oneN, yN);
+ rct::key tmp;
+ sc_muladd(t0.bytes, z.bytes, ip1y.bytes, t0.bytes);
+
+ rct::key zsq;
+ sc_mul(zsq.bytes, z.bytes, z.bytes);
+ sc_muladd(t0.bytes, zsq.bytes, sv.bytes, t0.bytes);
+
+ rct::key k = rct::zero();
+ 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);
+ sc_add(t0.bytes, t0.bytes, k.bytes);
+
+ // DEBUG: Test the value of t0 has the correct form
+#ifdef DEBUG_BP
+ rct::key test_t0 = rct::zero();
+ rct::key iph = inner_product(aL, hadamard(aR, yN));
+ sc_add(test_t0.bytes, test_t0.bytes, iph.bytes);
+ rct::key ips = inner_product(vector_subtract(aL, aR), yN);
+ sc_muladd(test_t0.bytes, z.bytes, ips.bytes, test_t0.bytes);
+ rct::key ipt = inner_product(twoN, aL);
+ sc_muladd(test_t0.bytes, zsq.bytes, ipt.bytes, test_t0.bytes);
+ sc_add(test_t0.bytes, test_t0.bytes, k.bytes);
+ CHECK_AND_ASSERT_THROW_MES(t0 == test_t0, "t0 check failed");
+#endif
+ PERF_TIMER_STOP(PROVE_step1);
+
+ PERF_TIMER_START_BP(PROVE_step2);
+ const auto HyNsR = hadamard(yN, sR);
+ const auto vpIz = vector_scalar(oneN, z);
+ const auto vp2zsq = vector_scalar(twoN, zsq);
+ const auto aL_vpIz = vector_subtract(aL, vpIz);
+ const auto aR_vpIz = vector_add(aR, vpIz);
+
+ rct::key ip1 = inner_product(aL_vpIz, HyNsR);
+ sc_add(t1.bytes, t1.bytes, ip1.bytes);
+
+ rct::key ip2 = inner_product(sL, vector_add(hadamard(yN, aR_vpIz), vp2zsq));
+ sc_add(t1.bytes, t1.bytes, ip2.bytes);
+
+ rct::key ip3 = inner_product(sL, HyNsR);
+ sc_add(t2.bytes, t2.bytes, ip3.bytes);
+
+ // PAPER LINES 47-48
+ rct::key tau1 = rct::skGen(), tau2 = rct::skGen();
+
+ rct::key T1 = rct::addKeys(rct::scalarmultKey(rct::H, t1), rct::scalarmultBase(tau1));
+ rct::key T2 = rct::addKeys(rct::scalarmultKey(rct::H, t2), rct::scalarmultBase(tau2));
+
+ // PAPER LINES 49-51
+ hashed.clear();
+ hashed.push_back(z);
+ hashed.push_back(T1);
+ hashed.push_back(T2);
+ rct::key x = rct::hash_to_scalar(hashed);
+
+ // PAPER LINES 52-53
+ rct::key taux = rct::zero();
+ sc_mul(taux.bytes, tau1.bytes, x.bytes);
+ rct::key xsq;
+ sc_mul(xsq.bytes, x.bytes, x.bytes);
+ sc_muladd(taux.bytes, tau2.bytes, xsq.bytes, taux.bytes);
+ sc_muladd(taux.bytes, gamma.bytes, zsq.bytes, taux.bytes);
+ rct::key mu;
+ sc_muladd(mu.bytes, x.bytes, rho.bytes, alpha.bytes);
+
+ // PAPER LINES 54-57
+ rct::keyV l = vector_add(aL_vpIz, vector_scalar(sL, x));
+ rct::keyV r = vector_add(hadamard(yN, vector_add(aR_vpIz, vector_scalar(sR, x))), vp2zsq);
+ PERF_TIMER_STOP(PROVE_step2);
+
+ PERF_TIMER_START_BP(PROVE_step3);
+ rct::key t = inner_product(l, r);
+
+ // DEBUG: Test if the l and r vectors match the polynomial forms
+#ifdef DEBUG_BP
+ rct::key test_t;
+ sc_muladd(test_t.bytes, t1.bytes, x.bytes, t0.bytes);
+ sc_muladd(test_t.bytes, t2.bytes, xsq.bytes, test_t.bytes);
+ CHECK_AND_ASSERT_THROW_MES(test_t == t, "test_t check failed");
+#endif
+
+ // PAPER LINES 32-33
+ hashed.clear();
+ hashed.push_back(x);
+ hashed.push_back(taux);
+ hashed.push_back(mu);
+ hashed.push_back(t);
+ rct::key x_ip = rct::hash_to_scalar(hashed);
+
+ // These are used in the inner product rounds
+ size_t nprime = N;
+ rct::keyV Gprime(N);
+ rct::keyV Hprime(N);
+ rct::keyV aprime(N);
+ rct::keyV bprime(N);
+ const rct::key yinv = invert(y);
+ rct::key yinvpow = rct::identity();
+ for (size_t i = 0; i < N; ++i)
+ {
+ Gprime[i] = Gi[i];
+ Hprime[i] = scalarmultKey(Hi[i], yinvpow);
+ sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes);
+ aprime[i] = l[i];
+ bprime[i] = r[i];
+ }
+ rct::keyV L(logN);
+ rct::keyV R(logN);
+ int round = 0;
+ rct::keyV w(logN); // this is the challenge x in the inner product protocol
+ PERF_TIMER_STOP(PROVE_step3);
+
+ PERF_TIMER_START_BP(PROVE_step4);
+ // PAPER LINE 13
+ while (nprime > 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::scalarmultKey(rct::H, 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::scalarmultKey(rct::H, 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::scalarmultBase(proof.taux), rct::scalarmultKey(rct::H, 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::scalarmultKey(rct::H, tmp);
+
+ CHECK_AND_ASSERT_MES(proof.V.size() == 1, false, "proof.V does not have exactly one element");
+ tmp = rct::scalarmultKey(proof.V[0], 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)<<j)) == 0)
+ {
+ sc_mul(g_scalar.bytes, g_scalar.bytes, winv[J].bytes);
+ sc_mul(h_scalar.bytes, h_scalar.bytes, w[J].bytes);
+ }
+ else
+ {
+ sc_mul(g_scalar.bytes, g_scalar.bytes, w[J].bytes);
+ sc_mul(h_scalar.bytes, h_scalar.bytes, winv[J].bytes);
+ }
+ }
+
+ // Adjust the scalars using the exponents from PAPER LINE 62
+ sc_add(g_scalar.bytes, g_scalar.bytes, z.bytes);
+ sc_mul(tmp.bytes, zsq.bytes, twoN[i].bytes);
+ sc_muladd(tmp.bytes, z.bytes, ypow.bytes, tmp.bytes);
+ sc_mulsub(h_scalar.bytes, tmp.bytes, yinvpow.bytes, h_scalar.bytes);
+
+ // Now compute the basepoint's scalar multiplication
+ // Each of these could be written as a multiexp operation instead
+ rct::addKeys3(tmp, g_scalar, Gprecomp[i], h_scalar, Hprecomp[i]);
+ rct::addKeys(inner_prod, inner_prod, tmp);
+
+ if (i != N-1)
+ {
+ sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes);
+ sc_mul(ypow.bytes, ypow.bytes, y.bytes);
+ }
+ }
+ PERF_TIMER_STOP(VERIFY_line_24_25);
+
+ PERF_TIMER_START_BP(VERIFY_line_26);
+ // PAPER LINE 26
+ rct::key pprime;
+ sc_sub(tmp.bytes, rct::zero().bytes, proof.mu.bytes);
+ rct::addKeys(pprime, P, rct::scalarmultBase(tmp));
+
+ for (size_t i = 0; i < rounds; ++i)
+ {
+ sc_mul(tmp.bytes, w[i].bytes, w[i].bytes);
+ sc_mul(tmp2.bytes, winv[i].bytes, winv[i].bytes);
+#if 1
+ ge_dsmp cacheL, cacheR;
+ rct::precomp(cacheL, proof.L[i]);
+ rct::precomp(cacheR, proof.R[i]);
+ rct::addKeys3(tmp, tmp, cacheL, tmp2, cacheR);
+ rct::addKeys(pprime, pprime, tmp);
+#else
+ rct::addKeys(pprime, pprime, rct::scalarmultKey(proof.L[i], tmp));
+ rct::addKeys(pprime, pprime, rct::scalarmultKey(proof.R[i], tmp2));
+#endif
+ }
+ sc_mul(tmp.bytes, proof.t.bytes, x_ip.bytes);
+ rct::addKeys(pprime, pprime, rct::scalarmultKey(rct::H, tmp));
+ PERF_TIMER_STOP(VERIFY_line_26);
+
+ PERF_TIMER_START_BP(VERIFY_step2_check);
+ sc_mul(tmp.bytes, proof.a.bytes, proof.b.bytes);
+ sc_mul(tmp.bytes, tmp.bytes, x_ip.bytes);
+ tmp = rct::scalarmultKey(rct::H, tmp);
+ rct::addKeys(tmp, tmp, inner_prod);
+ PERF_TIMER_STOP(VERIFY_step2_check);
+ if (!(pprime == tmp))
+ {
+ MERROR("Verification failure at step 2");
+ return false;
+ }
+
+ PERF_TIMER_STOP(VERIFY);
+ return true;
+}
+
+}
diff --git a/src/ringct/bulletproofs.h b/src/ringct/bulletproofs.h
new file mode 100644
index 000000000..aca470f47
--- /dev/null
+++ b/src/ringct/bulletproofs.h
@@ -0,0 +1,47 @@
+// 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
+
+#pragma once
+
+#ifndef BULLETPROOFS_H
+#define BULLETPROOFS_H
+
+#include "rctTypes.h"
+
+namespace rct
+{
+
+Bulletproof bulletproof_PROVE(const rct::key &v, const rct::key &gamma);
+Bulletproof bulletproof_PROVE(uint64_t v, const rct::key &gamma);
+bool bulletproof_VERIFY(const Bulletproof &proof);
+
+}
+
+#endif
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index d0e0964b6..8e94b52b3 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -220,6 +220,11 @@ namespace rct {
ge_p3_tobytes(AB.bytes, &A2);
}
+ rct::key addKeys(const key &A, const key &B) {
+ key k;
+ addKeys(k, A, B);
+ return k;
+ }
//addKeys1
//aGB = aG + B where a is a scalar, G is the basepoint, and B is a point
@@ -257,6 +262,15 @@ namespace rct {
ge_tobytes(aAbB.bytes, &rv);
}
+ //addKeys3
+ //aAbB = a*A + b*B where a, b are scalars, A, B are curve points
+ //A and B must be input after applying "precomp"
+ void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B) {
+ ge_p2 rv;
+ ge_double_scalarmult_precomp_vartime2(&rv, a.bytes, A, b.bytes, B);
+ ge_tobytes(aAbB.bytes, &rv);
+ }
+
//subtract Keys (subtracts curve points)
//AB = A - B where A, B are curve points
diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h
index 412450c18..3f8f6955c 100644
--- a/src/ringct/rctOps.h
+++ b/src/ringct/rctOps.h
@@ -123,6 +123,7 @@ namespace rct {
//for curve points: AB = A + B
void addKeys(key &AB, const key &A, const key &B);
+ rct::key addKeys(const key &A, const key &B);
//aGB = aG + B where a is a scalar, G is the basepoint, and B is a point
void addKeys1(key &aGB, const key &a, const key & B);
//aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point
@@ -133,6 +134,7 @@ namespace rct {
//aAbB = a*A + b*B where a, b are scalars, A, B are curve points
//B must be input after applying "precomp"
void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B);
+ void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B);
//AB = A - B where A, B are curve points
void subKeys(key &AB, const key &A, const key &B);
//checks if A, B are equal as curve points
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 946325367..cfb4aaf97 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -33,6 +33,7 @@
#include "common/threadpool.h"
#include "common/util.h"
#include "rctSigs.h"
+#include "bulletproofs.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
using namespace crypto;
@@ -42,6 +43,15 @@ using namespace std;
#define MONERO_DEFAULT_LOG_CATEGORY "ringct"
namespace rct {
+ Bulletproof proveRangeBulletproof(key &C, key &mask, uint64_t amount)
+ {
+ mask = rct::skGen();
+ Bulletproof proof = bulletproof_PROVE(amount, mask);
+ CHECK_AND_ASSERT_THROW_MES(proof.V.size() == 1, "V has not exactly one element");
+ C = proof.V[0];
+ return proof;
+ }
+
//Borromean (c.f. gmax/andytoshi's paper)
boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) {
key64 L[2], alpha;
@@ -335,16 +345,41 @@ namespace rct {
hashes.push_back(hash2rct(h));
keyV kv;
- kv.reserve((64*3+1) * rv.p.rangeSigs.size());
- for (auto r: rv.p.rangeSigs)
+ if (rv.type == RCTTypeSimpleBulletproof || rv.type == RCTTypeFullBulletproof)
+ {
+ kv.reserve((6*2+10) * rv.p.bulletproofs.size());
+ for (const auto &p: rv.p.bulletproofs)
+ {
+ for (size_t n = 0; n < p.V.size(); ++n)
+ kv.push_back(p.V[n]);
+ kv.push_back(p.A);
+ kv.push_back(p.S);
+ kv.push_back(p.T1);
+ kv.push_back(p.T2);
+ kv.push_back(p.taux);
+ kv.push_back(p.mu);
+ for (size_t n = 0; n < p.L.size(); ++n)
+ kv.push_back(p.L[n]);
+ for (size_t n = 0; n < p.R.size(); ++n)
+ kv.push_back(p.R[n]);
+ kv.push_back(p.a);
+ kv.push_back(p.b);
+ kv.push_back(p.t);
+ }
+ }
+ else
{
- for (size_t n = 0; n < 64; ++n)
- kv.push_back(r.asig.s0[n]);
- for (size_t n = 0; n < 64; ++n)
- kv.push_back(r.asig.s1[n]);
- kv.push_back(r.asig.ee);
- for (size_t n = 0; n < 64; ++n)
- kv.push_back(r.Ci[n]);
+ kv.reserve((64*3+1) * rv.p.rangeSigs.size());
+ for (const auto &r: rv.p.rangeSigs)
+ {
+ for (size_t n = 0; n < 64; ++n)
+ kv.push_back(r.asig.s0[n]);
+ for (size_t n = 0; n < 64; ++n)
+ kv.push_back(r.asig.s1[n]);
+ kv.push_back(r.asig.ee);
+ for (size_t n = 0; n < 64; ++n)
+ kv.push_back(r.Ci[n]);
+ }
}
hashes.push_back(cn_fast_hash(kv));
return cn_fast_hash(hashes);
@@ -563,7 +598,7 @@ namespace rct {
// must know the destination private key to find the correct amount, else will return a random number
// Note: For txn fees, the last index in the amounts vector should contain that
// Thus the amounts vector will be "one" longer than the destinations vectort
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk) {
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof) {
CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations");
CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations");
CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing");
@@ -572,10 +607,13 @@ namespace rct {
}
rctSig rv;
- rv.type = RCTTypeFull;
+ rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull;
rv.message = message;
rv.outPk.resize(destinations.size());
- rv.p.rangeSigs.resize(destinations.size());
+ if (bulletproof)
+ rv.p.bulletproofs.resize(destinations.size());
+ else
+ rv.p.rangeSigs.resize(destinations.size());
rv.ecdhInfo.resize(destinations.size());
size_t i = 0;
@@ -585,8 +623,14 @@ namespace rct {
//add destination to sig
rv.outPk[i].dest = copy(destinations[i]);
//compute range proof
- rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]);
+ if (bulletproof)
+ rv.p.bulletproofs[i] = proveRangeBulletproof(rv.outPk[i].mask, outSk[i].mask, amounts[i]);
+ else
+ rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]);
#ifdef DBG
+ if (bulletproof)
+ CHECK_AND_ASSERT_THROW_MES(bulletproof_VERIFY(rv.p.bulletproofs[i]), "bulletproof_VERIFY failed on newly created proof");
+ else
CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof");
#endif
@@ -618,12 +662,12 @@ namespace rct {
ctkeyM mixRing;
ctkeyV outSk;
tie(mixRing, index) = populateFromBlockchain(inPk, mixin);
- return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk);
+ return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, false);
}
//RCT simple
//for post-rct only
- rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk) {
+ rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof) {
CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts");
CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk");
CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations");
@@ -635,10 +679,13 @@ namespace rct {
}
rctSig rv;
- rv.type = RCTTypeSimple;
+ rv.type = bulletproof ? RCTTypeSimpleBulletproof : RCTTypeSimple;
rv.message = message;
rv.outPk.resize(destinations.size());
- rv.p.rangeSigs.resize(destinations.size());
+ if (bulletproof)
+ rv.p.bulletproofs.resize(destinations.size());
+ else
+ rv.p.rangeSigs.resize(destinations.size());
rv.ecdhInfo.resize(destinations.size());
size_t i;
@@ -650,10 +697,16 @@ namespace rct {
//add destination to sig
rv.outPk[i].dest = copy(destinations[i]);
//compute range proof
- rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]);
- #ifdef DBG
- verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
- #endif
+ if (bulletproof)
+ rv.p.bulletproofs[i] = proveRangeBulletproof(rv.outPk[i].mask, outSk[i].mask, outamounts[i]);
+ else
+ rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]);
+ #ifdef DBG
+ if (bulletproof)
+ CHECK_AND_ASSERT_THROW_MES(bulletproof_VERIFY(rv.p.bulletproofs[i]), "bulletproof_VERIFY failed on newly created proof");
+ else
+ CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof");
+ #endif
sc_add(sumout.bytes, outSk[i].mask.bytes, sumout.bytes);
@@ -699,7 +752,7 @@ namespace rct {
mixRing[i].resize(mixin+1);
index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin);
}
- return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk);
+ return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, false);
}
//RingCT protocol
@@ -714,10 +767,13 @@ namespace rct {
// must know the destination private key to find the correct amount, else will return a random number
bool verRct(const rctSig & rv, bool semantics) {
PERF_TIMER(verRct);
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "verRct called on non-full rctSig");
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof, false, "verRct called on non-full rctSig");
if (semantics)
{
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
+ if (rv.type == RCTTypeFullBulletproof)
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.bulletproofs.size(), false, "Mismatched sizes of outPk and rv.p.bulletproofs");
+ else
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG");
}
@@ -736,7 +792,10 @@ namespace rct {
DP("range proofs verified?");
for (size_t i = 0; i < rv.outPk.size(); i++) {
tpool.submit(&waiter, [&, i] {
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
+ if (rv.p.rangeSigs.empty())
+ results[i] = bulletproof_VERIFY(rv.p.bulletproofs[i]);
+ else
+ results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
});
}
waiter.wait();
@@ -776,10 +835,13 @@ namespace rct {
{
PERF_TIMER(verRctSimple);
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "verRctSimple called on non simple rctSig");
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeSimpleBulletproof, false, "verRctSimple called on non simple rctSig");
if (semantics)
{
- CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
+ if (rv.type == RCTTypeSimpleBulletproof)
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.bulletproofs.size(), false, "Mismatched sizes of outPk and rv.p.bulletproofs");
+ else
+ CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs");
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs");
}
@@ -820,7 +882,10 @@ namespace rct {
results.resize(rv.outPk.size());
for (size_t i = 0; i < rv.outPk.size(); i++) {
tpool.submit(&waiter, [&, i] {
- results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
+ if (rv.p.rangeSigs.empty())
+ results[i] = bulletproof_VERIFY(rv.p.bulletproofs[i]);
+ else
+ results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]);
});
}
waiter.wait();
@@ -869,9 +934,17 @@ namespace rct {
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask) {
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "decodeRct called on non-full rctSig");
- CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof, false, "decodeRct called on non-full rctSig");
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
+ if (rv.type == RCTTypeFullBulletproof)
+ {
+ CHECK_AND_ASSERT_THROW_MES(rv.p.bulletproofs.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.p.bulletproofs and rv.ecdhInfo");
+ CHECK_AND_ASSERT_THROW_MES(rv.p.bulletproofs[i].V.size() == 1, "Unexpected sizes of rv.p.bulletproofs[i].V");
+ }
+ else
+ {
+ CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
+ }
//mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i];
@@ -897,16 +970,24 @@ namespace rct {
}
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask) {
- CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "decodeRct called on non simple rctSig");
- CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
+ CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeSimpleBulletproof, false, "decodeRct called on non simple rctSig");
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
+ if (rv.type == RCTTypeSimpleBulletproof)
+ {
+ CHECK_AND_ASSERT_THROW_MES(rv.p.bulletproofs.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.p.bulletproofs and rv.ecdhInfo");
+ CHECK_AND_ASSERT_THROW_MES(rv.p.bulletproofs[i].V.size() == 1, "Unexpected sizes of rv.p.bulletproofs[i].V");
+ }
+ else
+ {
+ CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
+ }
//mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i];
ecdhDecode(ecdh_info, sk);
mask = ecdh_info.mask;
key amount = ecdh_info.amount;
- key C = rv.outPk[i].mask;
+ key C = (rv.type == RCTTypeSimpleBulletproof) ? rv.p.bulletproofs[i].V.front() : rv.outPk[i].mask;
DP("C");
DP(C);
key Ctmp;
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index d158f06f0..46c9cb2df 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -118,10 +118,10 @@ namespace rct {
//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number
- rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk);
+ rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof);
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin);
- rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk);
+ rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof);
bool verRct(const rctSig & rv, bool semantics);
static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); }
bool verRctSimple(const rctSig & rv, bool semantics);
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index 8147cb602..50dfdb432 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -161,6 +161,39 @@ namespace rct {
FIELD(Ci)
END_SERIALIZE()
};
+
+ struct Bulletproof
+ {
+ rct::keyV V;
+ rct::key A, S, T1, T2;
+ rct::key taux, mu;
+ rct::keyV L, R;
+ rct::key a, b, t;
+
+ Bulletproof() {}
+ Bulletproof(const rct::key &V, const rct::key &A, const rct::key &S, const rct::key &T1, const rct::key &T2, const rct::key &taux, const rct::key &mu, const rct::keyV &L, const rct::keyV &R, const rct::key &a, const rct::key &b, const rct::key &t):
+ V({V}), A(A), S(S), T1(T1), T2(T2), taux(taux), mu(mu), L(L), R(R), a(a), b(b), t(t) {}
+
+ BEGIN_SERIALIZE_OBJECT()
+ // Commitments aren't saved, they're restored via outPk
+ // FIELD(V)
+ FIELD(A)
+ FIELD(S)
+ FIELD(T1)
+ FIELD(T2)
+ FIELD(taux)
+ FIELD(mu)
+ FIELD(L)
+ FIELD(R)
+ FIELD(a)
+ FIELD(b)
+ FIELD(t)
+
+ if (L.empty() || L.size() != R.size())
+ return false;
+ END_SERIALIZE()
+ };
+
//A container to hold all signatures necessary for RingCT
// rangeSigs holds all the rangeproof data of a transaction
// MG holds the MLSAG signature of a transaction
@@ -172,6 +205,8 @@ namespace rct {
RCTTypeNull = 0,
RCTTypeFull = 1,
RCTTypeSimple = 2,
+ RCTTypeFullBulletproof = 3,
+ RCTTypeSimpleBulletproof = 4,
};
struct rctSigBase {
uint8_t type;
@@ -189,13 +224,13 @@ namespace rct {
FIELD(type)
if (type == RCTTypeNull)
return true;
- if (type != RCTTypeFull && type != RCTTypeSimple)
+ if (type != RCTTypeFull && type != RCTTypeFullBulletproof && type != RCTTypeSimple && type != RCTTypeSimpleBulletproof)
return false;
VARINT_FIELD(txnFee)
// inputs/outputs not saved, only here for serialization help
// FIELD(message) - not serialized, it can be reconstructed
// FIELD(mixRing) - not serialized, it can be reconstructed
- if (type == RCTTypeSimple)
+ if (type == RCTTypeSimple || type == RCTTypeSimpleBulletproof)
{
ar.tag("pseudoOuts");
ar.begin_array();
@@ -241,6 +276,7 @@ namespace rct {
};
struct rctSigPrunable {
std::vector<rangeSig> rangeSigs;
+ std::vector<Bulletproof> bulletproofs;
std::vector<mgSig> MGs; // simple rct has N, full has 1
template<bool W, template <bool> class Archive>
@@ -248,26 +284,44 @@ namespace rct {
{
if (type == RCTTypeNull)
return true;
- if (type != RCTTypeFull && type != RCTTypeSimple)
+ if (type != RCTTypeFull && type != RCTTypeFullBulletproof && type != RCTTypeSimple && type != RCTTypeSimpleBulletproof)
return false;
- ar.tag("rangeSigs");
- ar.begin_array();
- PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, rangeSigs);
- if (rangeSigs.size() != outputs)
- return false;
- for (size_t i = 0; i < outputs; ++i)
+ if (type == RCTTypeSimpleBulletproof || type == RCTTypeFullBulletproof)
{
- FIELDS(rangeSigs[i])
- if (outputs - i > 1)
- ar.delimit_array();
+ ar.tag("bp");
+ ar.begin_array();
+ PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, bulletproofs);
+ if (bulletproofs.size() != outputs)
+ return false;
+ for (size_t i = 0; i < outputs; ++i)
+ {
+ FIELDS(bulletproofs[i])
+ if (outputs - i > 1)
+ ar.delimit_array();
+ }
+ ar.end_array();
+ }
+ else
+ {
+ ar.tag("rangeSigs");
+ ar.begin_array();
+ PREPARE_CUSTOM_VECTOR_SERIALIZATION(outputs, rangeSigs);
+ if (rangeSigs.size() != outputs)
+ return false;
+ for (size_t i = 0; i < outputs; ++i)
+ {
+ FIELDS(rangeSigs[i])
+ if (outputs - i > 1)
+ ar.delimit_array();
+ }
+ ar.end_array();
}
- ar.end_array();
ar.tag("MGs");
ar.begin_array();
// we keep a byte for size of MGs, because we don't know whether this is
// a simple or full rct signature, and it's starting to annoy the hell out of me
- size_t mg_elements = type == RCTTypeSimple ? inputs : 1;
+ size_t mg_elements = (type == RCTTypeSimple || type == RCTTypeSimpleBulletproof) ? inputs : 1;
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_elements, MGs);
if (MGs.size() != mg_elements)
return false;
@@ -285,7 +339,7 @@ namespace rct {
for (size_t j = 0; j < mixin + 1; ++j)
{
ar.begin_array();
- size_t mg_ss2_elements = (type == RCTTypeSimple ? 1 : inputs) + 1;
+ size_t mg_ss2_elements = ((type == RCTTypeSimple || type == RCTTypeSimpleBulletproof) ? 1 : inputs) + 1;
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_ss2_elements, MGs[i].ss[j]);
if (MGs[i].ss[j].size() != mg_ss2_elements)
return false;
@@ -464,6 +518,7 @@ VARIANT_TAG(debug_archive, rct::mgSig, "rct::mgSig");
VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig");
VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig");
VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig");
+VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof");
VARIANT_TAG(binary_archive, rct::key, 0x90);
VARIANT_TAG(binary_archive, rct::key64, 0x91);
@@ -477,6 +532,7 @@ VARIANT_TAG(binary_archive, rct::mgSig, 0x98);
VARIANT_TAG(binary_archive, rct::rangeSig, 0x99);
VARIANT_TAG(binary_archive, rct::boroSig, 0x9a);
VARIANT_TAG(binary_archive, rct::rctSig, 0x9b);
+VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c);
VARIANT_TAG(json_archive, rct::key, "rct_key");
VARIANT_TAG(json_archive, rct::key64, "rct_key64");
@@ -490,5 +546,6 @@ VARIANT_TAG(json_archive, rct::mgSig, "rct_mgSig");
VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig");
VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig");
VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig");
+VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof");
#endif /* RCTTYPES_H */