mirror of
https://git.wownero.com/wownero/wownero.git
synced 2025-01-03 17:28:51 +00:00
1652 lines
68 KiB
C++
1652 lines
68 KiB
C++
// Copyright (c) 2016, Monero Research Labs
|
|
//
|
|
// Author: Shen Noether <shen.noether@gmx.com>
|
|
//
|
|
// 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.
|
|
|
|
#include "misc_log_ex.h"
|
|
#include "misc_language.h"
|
|
#include "common/perf_timer.h"
|
|
#include "common/threadpool.h"
|
|
#include "common/util.h"
|
|
#include "rctSigs.h"
|
|
#include "bulletproofs.h"
|
|
#include "bulletproofs_plus.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
#include "cryptonote_config.h"
|
|
|
|
using namespace crypto;
|
|
using namespace std;
|
|
|
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
|
#define MONERO_DEFAULT_LOG_CATEGORY "ringct"
|
|
|
|
#define CHECK_AND_ASSERT_MES_L1(expr, ret, message) {if(!(expr)) {MCERROR("verify", message); return ret;}}
|
|
|
|
namespace
|
|
{
|
|
rct::Bulletproof make_dummy_bulletproof(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks)
|
|
{
|
|
const size_t n_outs = outamounts.size();
|
|
const rct::key I = rct::identity();
|
|
size_t nrl = 0;
|
|
while ((1u << nrl) < n_outs)
|
|
++nrl;
|
|
nrl += 6;
|
|
|
|
C.resize(n_outs);
|
|
masks.resize(n_outs);
|
|
for (size_t i = 0; i < n_outs; ++i)
|
|
{
|
|
masks[i] = I;
|
|
rct::key sv8, sv;
|
|
sv = rct::zero();
|
|
sv.bytes[0] = outamounts[i] & 255;
|
|
sv.bytes[1] = (outamounts[i] >> 8) & 255;
|
|
sv.bytes[2] = (outamounts[i] >> 16) & 255;
|
|
sv.bytes[3] = (outamounts[i] >> 24) & 255;
|
|
sv.bytes[4] = (outamounts[i] >> 32) & 255;
|
|
sv.bytes[5] = (outamounts[i] >> 40) & 255;
|
|
sv.bytes[6] = (outamounts[i] >> 48) & 255;
|
|
sv.bytes[7] = (outamounts[i] >> 56) & 255;
|
|
sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes);
|
|
rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H);
|
|
}
|
|
|
|
return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I};
|
|
}
|
|
|
|
rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks)
|
|
{
|
|
const size_t n_outs = outamounts.size();
|
|
const rct::key I = rct::identity();
|
|
size_t nrl = 0;
|
|
while ((1u << nrl) < n_outs)
|
|
++nrl;
|
|
nrl += 6;
|
|
|
|
C.resize(n_outs);
|
|
masks.resize(n_outs);
|
|
for (size_t i = 0; i < n_outs; ++i)
|
|
{
|
|
masks[i] = I;
|
|
rct::key sv8, sv;
|
|
sv = rct::zero();
|
|
sv.bytes[0] = outamounts[i] & 255;
|
|
sv.bytes[1] = (outamounts[i] >> 8) & 255;
|
|
sv.bytes[2] = (outamounts[i] >> 16) & 255;
|
|
sv.bytes[3] = (outamounts[i] >> 24) & 255;
|
|
sv.bytes[4] = (outamounts[i] >> 32) & 255;
|
|
sv.bytes[5] = (outamounts[i] >> 40) & 255;
|
|
sv.bytes[6] = (outamounts[i] >> 48) & 255;
|
|
sv.bytes[7] = (outamounts[i] >> 56) & 255;
|
|
sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes);
|
|
rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H);
|
|
}
|
|
|
|
return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)};
|
|
}
|
|
|
|
rct::clsag make_dummy_clsag(size_t ring_size)
|
|
{
|
|
const rct::key I = rct::identity();
|
|
const size_t n_scalars = ring_size;
|
|
return rct::clsag{rct::keyV(n_scalars, I), I, I, I};
|
|
}
|
|
}
|
|
|
|
namespace rct {
|
|
Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev)
|
|
{
|
|
CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes");
|
|
masks.resize(amounts.size());
|
|
for (size_t i = 0; i < masks.size(); ++i)
|
|
masks[i] = hwdev.genCommitmentMask(sk[i]);
|
|
Bulletproof proof = bulletproof_PROVE(amounts, masks);
|
|
CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size");
|
|
C = proof.V;
|
|
return proof;
|
|
}
|
|
|
|
bool verBulletproof(const Bulletproof &proof)
|
|
{
|
|
try { return bulletproof_VERIFY(proof); }
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
bool verBulletproof(const std::vector<const Bulletproof*> &proofs)
|
|
{
|
|
try { return bulletproof_VERIFY(proofs); }
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev)
|
|
{
|
|
CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes");
|
|
masks.resize(amounts.size());
|
|
for (size_t i = 0; i < masks.size(); ++i)
|
|
masks[i] = hwdev.genCommitmentMask(sk[i]);
|
|
BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks);
|
|
CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size");
|
|
C = proof.V;
|
|
return proof;
|
|
}
|
|
|
|
bool verBulletproofPlus(const BulletproofPlus &proof)
|
|
{
|
|
try { return bulletproof_plus_VERIFY(proof); }
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
bool verBulletproofPlus(const std::vector<const BulletproofPlus*> &proofs)
|
|
{
|
|
try { return bulletproof_plus_VERIFY(proofs); }
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
//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;
|
|
auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(alpha, sizeof(alpha));});
|
|
key c;
|
|
int naught = 0, prime = 0, ii = 0, jj=0;
|
|
boroSig bb;
|
|
for (ii = 0 ; ii < 64 ; ii++) {
|
|
naught = indices[ii]; prime = (indices[ii] + 1) % 2;
|
|
skGen(alpha[ii]);
|
|
scalarmultBase(L[naught][ii], alpha[ii]);
|
|
if (naught == 0) {
|
|
skGen(bb.s1[ii]);
|
|
c = hash_to_scalar(L[naught][ii]);
|
|
addKeys2(L[prime][ii], bb.s1[ii], c, P2[ii]);
|
|
}
|
|
}
|
|
bb.ee = hash_to_scalar(L[1]); //or L[1]..
|
|
key LL, cc;
|
|
for (jj = 0 ; jj < 64 ; jj++) {
|
|
if (!indices[jj]) {
|
|
sc_mulsub(bb.s0[jj].bytes, x[jj].bytes, bb.ee.bytes, alpha[jj].bytes);
|
|
} else {
|
|
skGen(bb.s0[jj]);
|
|
addKeys2(LL, bb.s0[jj], bb.ee, P1[jj]); //different L0
|
|
cc = hash_to_scalar(LL);
|
|
sc_mulsub(bb.s1[jj].bytes, x[jj].bytes, cc.bytes, alpha[jj].bytes);
|
|
}
|
|
}
|
|
return bb;
|
|
}
|
|
|
|
//see above.
|
|
bool verifyBorromean(const boroSig &bb, const ge_p3 P1[64], const ge_p3 P2[64]) {
|
|
key64 Lv1; key chash, LL;
|
|
int ii = 0;
|
|
ge_p2 p2;
|
|
for (ii = 0 ; ii < 64 ; ii++) {
|
|
// equivalent of: addKeys2(LL, bb.s0[ii], bb.ee, P1[ii]);
|
|
ge_double_scalarmult_base_vartime(&p2, bb.ee.bytes, &P1[ii], bb.s0[ii].bytes);
|
|
ge_tobytes(LL.bytes, &p2);
|
|
chash = hash_to_scalar(LL);
|
|
// equivalent of: addKeys2(Lv1[ii], bb.s1[ii], chash, P2[ii]);
|
|
ge_double_scalarmult_base_vartime(&p2, chash.bytes, &P2[ii], bb.s1[ii].bytes);
|
|
ge_tobytes(Lv1[ii].bytes, &p2);
|
|
}
|
|
key eeComputed = hash_to_scalar(Lv1); //hash function fine
|
|
return equalKeys(eeComputed, bb.ee);
|
|
}
|
|
|
|
bool verifyBorromean(const boroSig &bb, const key64 P1, const key64 P2) {
|
|
ge_p3 P1_p3[64], P2_p3[64];
|
|
for (size_t i = 0 ; i < 64 ; ++i) {
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&P1_p3[i], P1[i].bytes) == 0, false, "point conv failed");
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&P2_p3[i], P2[i].bytes) == 0, false, "point conv failed");
|
|
}
|
|
return verifyBorromean(bb, P1_p3, P2_p3);
|
|
}
|
|
|
|
// Generate a CLSAG signature
|
|
// See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
|
|
//
|
|
// The keys are set as follows:
|
|
// P[l] == p*G
|
|
// C[l] == z*G
|
|
// C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i
|
|
clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, hw::device &hwdev) {
|
|
clsag sig;
|
|
size_t n = P.size(); // ring size
|
|
CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
|
|
CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!");
|
|
CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!");
|
|
|
|
// Key images
|
|
ge_p3 H_p3;
|
|
hash_to_p3(H_p3,P[l]);
|
|
key H;
|
|
ge_p3_tobytes(H.bytes,&H_p3);
|
|
|
|
key D;
|
|
|
|
// Initial values
|
|
key a;
|
|
key aG;
|
|
key aH;
|
|
|
|
hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH);
|
|
|
|
geDsmp I_precomp;
|
|
geDsmp D_precomp;
|
|
precomp(I_precomp.k,sig.I);
|
|
precomp(D_precomp.k,D);
|
|
|
|
// Offset key image
|
|
scalarmultKey(sig.D,D,INV_EIGHT);
|
|
|
|
// Aggregation hashes
|
|
keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset
|
|
keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset
|
|
sc_0(mu_P_to_hash[0].bytes);
|
|
memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
|
|
sc_0(mu_C_to_hash[0].bytes);
|
|
memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
|
|
for (size_t i = 1; i < n+1; ++i) {
|
|
mu_P_to_hash[i] = P[i-1];
|
|
mu_C_to_hash[i] = P[i-1];
|
|
}
|
|
for (size_t i = n+1; i < 2*n+1; ++i) {
|
|
mu_P_to_hash[i] = C_nonzero[i-n-1];
|
|
mu_C_to_hash[i] = C_nonzero[i-n-1];
|
|
}
|
|
mu_P_to_hash[2*n+1] = sig.I;
|
|
mu_P_to_hash[2*n+2] = sig.D;
|
|
mu_P_to_hash[2*n+3] = C_offset;
|
|
mu_C_to_hash[2*n+1] = sig.I;
|
|
mu_C_to_hash[2*n+2] = sig.D;
|
|
mu_C_to_hash[2*n+3] = C_offset;
|
|
key mu_P, mu_C;
|
|
mu_P = hash_to_scalar(mu_P_to_hash);
|
|
mu_C = hash_to_scalar(mu_C_to_hash);
|
|
|
|
// Initial commitment
|
|
keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, aG, aH
|
|
key c;
|
|
sc_0(c_to_hash[0].bytes);
|
|
memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
|
|
for (size_t i = 1; i < n+1; ++i)
|
|
{
|
|
c_to_hash[i] = P[i-1];
|
|
c_to_hash[i+n] = C_nonzero[i-1];
|
|
}
|
|
c_to_hash[2*n+1] = C_offset;
|
|
c_to_hash[2*n+2] = message;
|
|
|
|
c_to_hash[2*n+3] = aG;
|
|
c_to_hash[2*n+4] = aH;
|
|
|
|
hwdev.clsag_hash(c_to_hash,c);
|
|
|
|
size_t i;
|
|
i = (l + 1) % n;
|
|
if (i == 0)
|
|
copy(sig.c1, c);
|
|
|
|
// Decoy indices
|
|
sig.s = keyV(n);
|
|
key c_new;
|
|
key L;
|
|
key R;
|
|
key c_p; // = c[i]*mu_P
|
|
key c_c; // = c[i]*mu_C
|
|
geDsmp P_precomp;
|
|
geDsmp C_precomp;
|
|
geDsmp H_precomp;
|
|
ge_p3 Hi_p3;
|
|
|
|
while (i != l) {
|
|
sig.s[i] = skGen();
|
|
sc_0(c_new.bytes);
|
|
sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
|
|
sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
|
|
|
|
// Precompute points
|
|
precomp(P_precomp.k,P[i]);
|
|
precomp(C_precomp.k,C[i]);
|
|
|
|
// Compute L
|
|
addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
|
|
|
|
// Compute R
|
|
hash_to_p3(Hi_p3,P[i]);
|
|
ge_dsm_precomp(H_precomp.k, &Hi_p3);
|
|
addKeys_aAbBcC(R,sig.s[i],H_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
|
|
|
|
c_to_hash[2*n+3] = L;
|
|
c_to_hash[2*n+4] = R;
|
|
hwdev.clsag_hash(c_to_hash,c_new);
|
|
copy(c,c_new);
|
|
|
|
i = (i + 1) % n;
|
|
if (i == 0)
|
|
copy(sig.c1,c);
|
|
}
|
|
|
|
// Compute final scalar
|
|
hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]);
|
|
memwipe(&a, sizeof(key));
|
|
|
|
return sig;
|
|
}
|
|
|
|
clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) {
|
|
return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, hw::get_device("default"));
|
|
}
|
|
|
|
// MLSAG signatures
|
|
// See paper by Noether (https://eprint.iacr.org/2015/1098)
|
|
// This generalization allows for some dimensions not to require linkability;
|
|
// this is used in practice for commitment data within signatures
|
|
// Note that using more than one linkable dimension is not recommended.
|
|
mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows, hw::device &hwdev) {
|
|
mgSig rv;
|
|
size_t cols = pk.size();
|
|
CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!");
|
|
CHECK_AND_ASSERT_THROW_MES(index < cols, "Index out of range");
|
|
size_t rows = pk[0].size();
|
|
CHECK_AND_ASSERT_THROW_MES(rows >= 1, "Empty pk");
|
|
for (size_t i = 1; i < cols; ++i) {
|
|
CHECK_AND_ASSERT_THROW_MES(pk[i].size() == rows, "pk is not rectangular");
|
|
}
|
|
CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size");
|
|
CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size");
|
|
|
|
size_t i = 0, j = 0, ii = 0;
|
|
key c, c_old, L, R, Hi;
|
|
ge_p3 Hi_p3;
|
|
sc_0(c_old.bytes);
|
|
vector<geDsmp> Ip(dsRows);
|
|
rv.II = keyV(dsRows);
|
|
keyV alpha(rows);
|
|
auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(alpha.data(), alpha.size() * sizeof(alpha[0]));});
|
|
keyV aG(rows);
|
|
rv.ss = keyM(cols, aG);
|
|
keyV aHP(dsRows);
|
|
keyV toHash(1 + 3 * dsRows + 2 * (rows - dsRows));
|
|
toHash[0] = message;
|
|
DP("here1");
|
|
for (i = 0; i < dsRows; i++) {
|
|
toHash[3 * i + 1] = pk[index][i];
|
|
hash_to_p3(Hi_p3, pk[index][i]);
|
|
ge_p3_tobytes(Hi.bytes, &Hi_p3);
|
|
hwdev.mlsag_prepare(Hi, xx[i], alpha[i] , aG[i] , aHP[i] , rv.II[i]);
|
|
toHash[3 * i + 2] = aG[i];
|
|
toHash[3 * i + 3] = aHP[i];
|
|
precomp(Ip[i].k, rv.II[i]);
|
|
}
|
|
size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper)
|
|
for (i = dsRows, ii = 0 ; i < rows ; i++, ii++) {
|
|
skpkGen(alpha[i], aG[i]); //need to save alphas for later..
|
|
toHash[ndsRows + 2 * ii + 1] = pk[index][i];
|
|
toHash[ndsRows + 2 * ii + 2] = aG[i];
|
|
}
|
|
|
|
hwdev.mlsag_hash(toHash, c_old);
|
|
|
|
|
|
i = (index + 1) % cols;
|
|
if (i == 0) {
|
|
copy(rv.cc, c_old);
|
|
}
|
|
while (i != index) {
|
|
|
|
rv.ss[i] = skvGen(rows);
|
|
sc_0(c.bytes);
|
|
for (j = 0; j < dsRows; j++) {
|
|
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
|
|
hash_to_p3(Hi_p3, pk[i][j]);
|
|
ge_p3_tobytes(Hi.bytes, &Hi_p3);
|
|
addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j].k);
|
|
toHash[3 * j + 1] = pk[i][j];
|
|
toHash[3 * j + 2] = L;
|
|
toHash[3 * j + 3] = R;
|
|
}
|
|
for (j = dsRows, ii = 0; j < rows; j++, ii++) {
|
|
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
|
|
toHash[ndsRows + 2 * ii + 1] = pk[i][j];
|
|
toHash[ndsRows + 2 * ii + 2] = L;
|
|
}
|
|
hwdev.mlsag_hash(toHash, c);
|
|
copy(c_old, c);
|
|
i = (i + 1) % cols;
|
|
|
|
if (i == 0) {
|
|
copy(rv.cc, c_old);
|
|
}
|
|
}
|
|
hwdev.mlsag_sign(c, xx, alpha, rows, dsRows, rv.ss[index]);
|
|
return rv;
|
|
}
|
|
|
|
// MLSAG signatures
|
|
// See paper by Noether (https://eprint.iacr.org/2015/1098)
|
|
// This generalization allows for some dimensions not to require linkability;
|
|
// this is used in practice for commitment data within signatures
|
|
// Note that using more than one linkable dimension is not recommended.
|
|
bool MLSAG_Ver(const key &message, const keyM & pk, const mgSig & rv, size_t dsRows) {
|
|
size_t cols = pk.size();
|
|
CHECK_AND_ASSERT_MES(cols >= 2, false, "Signature must contain more than one public key");
|
|
size_t rows = pk[0].size();
|
|
CHECK_AND_ASSERT_MES(rows >= 1, false, "Bad total row number");
|
|
for (size_t i = 1; i < cols; ++i) {
|
|
CHECK_AND_ASSERT_MES(pk[i].size() == rows, false, "Bad public key matrix dimensions");
|
|
}
|
|
CHECK_AND_ASSERT_MES(rv.II.size() == dsRows, false, "Wrong number of key images present");
|
|
CHECK_AND_ASSERT_MES(rv.ss.size() == cols, false, "Bad scalar matrix dimensions");
|
|
for (size_t i = 0; i < cols; ++i) {
|
|
CHECK_AND_ASSERT_MES(rv.ss[i].size() == rows, false, "Bad scalar matrix dimensions");
|
|
}
|
|
CHECK_AND_ASSERT_MES(dsRows <= rows, false, "Non-double-spend rows cannot exceed total rows");
|
|
|
|
for (size_t i = 0; i < rv.ss.size(); ++i) {
|
|
for (size_t j = 0; j < rv.ss[i].size(); ++j) {
|
|
CHECK_AND_ASSERT_MES(sc_check(rv.ss[i][j].bytes) == 0, false, "Bad signature scalar");
|
|
}
|
|
}
|
|
CHECK_AND_ASSERT_MES(sc_check(rv.cc.bytes) == 0, false, "Bad initial signature hash");
|
|
|
|
size_t i = 0, j = 0, ii = 0;
|
|
key c, L, R;
|
|
key c_old = copy(rv.cc);
|
|
vector<geDsmp> Ip(dsRows);
|
|
for (i = 0 ; i < dsRows ; i++) {
|
|
CHECK_AND_ASSERT_MES(!(rv.II[i] == rct::identity()), false, "Bad key image");
|
|
precomp(Ip[i].k, rv.II[i]);
|
|
}
|
|
size_t ndsRows = 3 * dsRows; // number of dimensions not requiring linkability
|
|
keyV toHash(1 + 3 * dsRows + 2 * (rows - dsRows));
|
|
toHash[0] = message;
|
|
i = 0;
|
|
while (i < cols) {
|
|
sc_0(c.bytes);
|
|
for (j = 0; j < dsRows; j++) {
|
|
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
|
|
|
|
// Compute R directly
|
|
ge_p3 hash8_p3;
|
|
hash_to_p3(hash8_p3, pk[i][j]);
|
|
ge_p2 R_p2;
|
|
ge_double_scalarmult_precomp_vartime(&R_p2, rv.ss[i][j].bytes, &hash8_p3, c_old.bytes, Ip[j].k);
|
|
ge_tobytes(R.bytes, &R_p2);
|
|
|
|
toHash[3 * j + 1] = pk[i][j];
|
|
toHash[3 * j + 2] = L;
|
|
toHash[3 * j + 3] = R;
|
|
}
|
|
for (j = dsRows, ii = 0 ; j < rows ; j++, ii++) {
|
|
addKeys2(L, rv.ss[i][j], c_old, pk[i][j]);
|
|
toHash[ndsRows + 2 * ii + 1] = pk[i][j];
|
|
toHash[ndsRows + 2 * ii + 2] = L;
|
|
}
|
|
c = hash_to_scalar(toHash);
|
|
CHECK_AND_ASSERT_MES(!(c == rct::zero()), false, "Bad signature hash");
|
|
copy(c_old, c);
|
|
i = (i + 1);
|
|
}
|
|
sc_sub(c.bytes, c_old.bytes, rv.cc.bytes);
|
|
return sc_isnonzero(c.bytes) == 0;
|
|
}
|
|
|
|
|
|
|
|
//proveRange and verRange
|
|
//proveRange gives C, and mask such that \sumCi = C
|
|
// c.f. https://eprint.iacr.org/2015/1098 section 5.1
|
|
// and Ci is a commitment to either 0 or 2^i, i=0,...,63
|
|
// thus this proves that "amount" is in [0, 2^64]
|
|
// mask is a such that C = aG + bH, and b = amount
|
|
//verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i
|
|
rangeSig proveRange(key & C, key & mask, const xmr_amount & amount) {
|
|
sc_0(mask.bytes);
|
|
identity(C);
|
|
bits b;
|
|
d2b(b, amount);
|
|
rangeSig sig;
|
|
key64 ai;
|
|
key64 CiH;
|
|
int i = 0;
|
|
for (i = 0; i < ATOMS; i++) {
|
|
skGen(ai[i]);
|
|
if (b[i] == 0) {
|
|
scalarmultBase(sig.Ci[i], ai[i]);
|
|
}
|
|
if (b[i] == 1) {
|
|
addKeys1(sig.Ci[i], ai[i], H2[i]);
|
|
}
|
|
subKeys(CiH[i], sig.Ci[i], H2[i]);
|
|
sc_add(mask.bytes, mask.bytes, ai[i].bytes);
|
|
addKeys(C, C, sig.Ci[i]);
|
|
}
|
|
sig.asig = genBorromean(ai, sig.Ci, CiH, b);
|
|
return sig;
|
|
}
|
|
|
|
//proveRange and verRange
|
|
//proveRange gives C, and mask such that \sumCi = C
|
|
// c.f. https://eprint.iacr.org/2015/1098 section 5.1
|
|
// and Ci is a commitment to either 0 or 2^i, i=0,...,63
|
|
// thus this proves that "amount" is in [0, 2^64]
|
|
// mask is a such that C = aG + bH, and b = amount
|
|
//verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i
|
|
bool verRange(const key & C, const rangeSig & as) {
|
|
try
|
|
{
|
|
PERF_TIMER(verRange);
|
|
ge_p3 CiH[64], asCi[64];
|
|
int i = 0;
|
|
ge_p3 Ctmp_p3 = ge_p3_identity;
|
|
for (i = 0; i < 64; i++) {
|
|
// faster equivalent of:
|
|
// subKeys(CiH[i], as.Ci[i], H2[i]);
|
|
// addKeys(Ctmp, Ctmp, as.Ci[i]);
|
|
ge_cached cached;
|
|
ge_p3 p3;
|
|
ge_p1p1 p1;
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&p3, H2[i].bytes) == 0, false, "point conv failed");
|
|
ge_p3_to_cached(&cached, &p3);
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&asCi[i], as.Ci[i].bytes) == 0, false, "point conv failed");
|
|
ge_sub(&p1, &asCi[i], &cached);
|
|
ge_p3_to_cached(&cached, &asCi[i]);
|
|
ge_p1p1_to_p3(&CiH[i], &p1);
|
|
ge_add(&p1, &Ctmp_p3, &cached);
|
|
ge_p1p1_to_p3(&Ctmp_p3, &p1);
|
|
}
|
|
key Ctmp;
|
|
ge_p3_tobytes(Ctmp.bytes, &Ctmp_p3);
|
|
if (!equalKeys(C, Ctmp))
|
|
return false;
|
|
if (!verifyBorromean(as.asig, asCi, CiH))
|
|
return false;
|
|
return true;
|
|
}
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev)
|
|
{
|
|
keyV hashes;
|
|
hashes.reserve(3);
|
|
hashes.push_back(rv.message);
|
|
crypto::hash h;
|
|
|
|
std::stringstream ss;
|
|
binary_archive<true> ba(ss);
|
|
CHECK_AND_ASSERT_THROW_MES(!rv.mixRing.empty(), "Empty mixRing");
|
|
const size_t inputs = is_rct_simple(rv.type) ? rv.mixRing.size() : rv.mixRing[0].size();
|
|
const size_t outputs = rv.ecdhInfo.size();
|
|
key prehash;
|
|
CHECK_AND_ASSERT_THROW_MES(const_cast<rctSig&>(rv).serialize_rctsig_base(ba, inputs, outputs),
|
|
"Failed to serialize rctSigBase");
|
|
cryptonote::get_blob_hash(ss.str(), h);
|
|
hashes.push_back(hash2rct(h));
|
|
|
|
keyV kv;
|
|
if (rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG)
|
|
{
|
|
kv.reserve((6*2+9) * rv.p.bulletproofs.size());
|
|
for (const auto &p: rv.p.bulletproofs)
|
|
{
|
|
// V are not hashed as they're expanded from outPk.mask
|
|
// (and thus hashed as part of rctSigBase above)
|
|
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 if (rv.type == RCTTypeBulletproofPlus)
|
|
{
|
|
kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size());
|
|
for (const auto &p: rv.p.bulletproofs_plus)
|
|
{
|
|
// V are not hashed as they're expanded from outPk.mask
|
|
// (and thus hashed as part of rctSigBase above)
|
|
kv.push_back(p.A);
|
|
kv.push_back(p.A1);
|
|
kv.push_back(p.B);
|
|
kv.push_back(p.r1);
|
|
kv.push_back(p.s1);
|
|
kv.push_back(p.d1);
|
|
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]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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));
|
|
hwdev.mlsag_prehash(ss.str(), inputs, outputs, hashes, rv.outPk, prehash);
|
|
return prehash;
|
|
}
|
|
|
|
//Ring-ct MG sigs
|
|
//Prove:
|
|
// c.f. https://eprint.iacr.org/2015/1098 section 4. definition 10.
|
|
// This does the MG sig on the "dest" part of the given key matrix, and
|
|
// the last row is the sum of input commitments from that column - sum output commitments
|
|
// this shows that sum inputs = sum outputs
|
|
//Ver:
|
|
// verifies the above sig is created corretly
|
|
mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, const key &txnFeeKey, hw::device &hwdev) {
|
|
//setup vars
|
|
size_t cols = pubs.size();
|
|
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
|
|
size_t rows = pubs[0].size();
|
|
CHECK_AND_ASSERT_THROW_MES(rows >= 1, "Empty pubs");
|
|
for (size_t i = 1; i < cols; ++i) {
|
|
CHECK_AND_ASSERT_THROW_MES(pubs[i].size() == rows, "pubs is not rectangular");
|
|
}
|
|
CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size");
|
|
CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size");
|
|
|
|
keyV sk(rows + 1);
|
|
keyV tmp(rows + 1);
|
|
size_t i = 0, j = 0;
|
|
for (i = 0; i < rows + 1; i++) {
|
|
sc_0(sk[i].bytes);
|
|
identity(tmp[i]);
|
|
}
|
|
keyM M(cols, tmp);
|
|
//create the matrix to mg sig
|
|
for (i = 0; i < cols; i++) {
|
|
M[i][rows] = identity();
|
|
for (j = 0; j < rows; j++) {
|
|
M[i][j] = pubs[i][j].dest;
|
|
addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); //add input commitments in last row
|
|
}
|
|
}
|
|
sc_0(sk[rows].bytes);
|
|
for (j = 0; j < rows; j++) {
|
|
sk[j] = copy(inSk[j].dest);
|
|
sc_add(sk[rows].bytes, sk[rows].bytes, inSk[j].mask.bytes); //add masks in last row
|
|
}
|
|
for (i = 0; i < cols; i++) {
|
|
for (size_t j = 0; j < outPk.size(); j++) {
|
|
subKeys(M[i][rows], M[i][rows], outPk[j].mask); //subtract output Ci's in last row
|
|
}
|
|
//subtract txn fee output in last row
|
|
subKeys(M[i][rows], M[i][rows], txnFeeKey);
|
|
}
|
|
for (size_t j = 0; j < outPk.size(); j++) {
|
|
sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row..
|
|
}
|
|
mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev);
|
|
memwipe(sk.data(), sk.size() * sizeof(key));
|
|
return result;
|
|
}
|
|
|
|
|
|
//Ring-ct MG sigs Simple
|
|
// Simple version for when we assume only
|
|
// post rct inputs
|
|
// here pubs is a vector of (P, C) length mixin
|
|
// inSk is x, a_in corresponding to signing index
|
|
// a_out, Cout is for the output commitment
|
|
// index is the signing index..
|
|
mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index, hw::device &hwdev) {
|
|
//setup vars
|
|
size_t rows = 1;
|
|
size_t cols = pubs.size();
|
|
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
|
|
keyV tmp(rows + 1);
|
|
keyV sk(rows + 1);
|
|
size_t i;
|
|
keyM M(cols, tmp);
|
|
|
|
sk[0] = copy(inSk.dest);
|
|
sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
|
|
for (i = 0; i < cols; i++) {
|
|
M[i][0] = pubs[i].dest;
|
|
subKeys(M[i][1], pubs[i].mask, Cout);
|
|
}
|
|
mgSig result = MLSAG_Gen(message, M, sk, index, rows, hwdev);
|
|
memwipe(sk.data(), sk.size() * sizeof(key));
|
|
return result;
|
|
}
|
|
|
|
clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, unsigned int index, hw::device &hwdev) {
|
|
//setup vars
|
|
size_t rows = 1;
|
|
size_t cols = pubs.size();
|
|
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
|
|
keyV tmp(rows + 1);
|
|
keyV sk(rows + 1);
|
|
keyM M(cols, tmp);
|
|
|
|
keyV P, C, C_nonzero;
|
|
P.reserve(pubs.size());
|
|
C.reserve(pubs.size());
|
|
C_nonzero.reserve(pubs.size());
|
|
for (const ctkey &k: pubs)
|
|
{
|
|
P.push_back(k.dest);
|
|
C_nonzero.push_back(k.mask);
|
|
rct::key tmp;
|
|
subKeys(tmp, k.mask, Cout);
|
|
C.push_back(tmp);
|
|
}
|
|
|
|
sk[0] = copy(inSk.dest);
|
|
sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
|
|
clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, hwdev);
|
|
memwipe(sk.data(), sk.size() * sizeof(key));
|
|
return result;
|
|
}
|
|
|
|
|
|
//Ring-ct MG sigs
|
|
//Prove:
|
|
// c.f. https://eprint.iacr.org/2015/1098 section 4. definition 10.
|
|
// This does the MG sig on the "dest" part of the given key matrix, and
|
|
// the last row is the sum of input commitments from that column - sum output commitments
|
|
// this shows that sum inputs = sum outputs
|
|
//Ver:
|
|
// verifies the above sig is created corretly
|
|
bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, const key &txnFeeKey, const key &message) {
|
|
PERF_TIMER(verRctMG);
|
|
//setup vars
|
|
size_t cols = pubs.size();
|
|
CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs");
|
|
size_t rows = pubs[0].size();
|
|
CHECK_AND_ASSERT_MES(rows >= 1, false, "Empty pubs");
|
|
for (size_t i = 1; i < cols; ++i) {
|
|
CHECK_AND_ASSERT_MES(pubs[i].size() == rows, false, "pubs is not rectangular");
|
|
}
|
|
|
|
keyV tmp(rows + 1);
|
|
size_t i = 0, j = 0;
|
|
for (i = 0; i < rows + 1; i++) {
|
|
identity(tmp[i]);
|
|
}
|
|
keyM M(cols, tmp);
|
|
|
|
//create the matrix to mg sig
|
|
for (j = 0; j < rows; j++) {
|
|
for (i = 0; i < cols; i++) {
|
|
M[i][j] = pubs[i][j].dest;
|
|
addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); //add Ci in last row
|
|
}
|
|
}
|
|
for (i = 0; i < cols; i++) {
|
|
for (j = 0; j < outPk.size(); j++) {
|
|
subKeys(M[i][rows], M[i][rows], outPk[j].mask); //subtract output Ci's in last row
|
|
}
|
|
//subtract txn fee output in last row
|
|
subKeys(M[i][rows], M[i][rows], txnFeeKey);
|
|
}
|
|
return MLSAG_Ver(message, M, mg, rows);
|
|
}
|
|
|
|
//Ring-ct Simple MG sigs
|
|
//Ver:
|
|
//This does a simplified version, assuming only post Rct
|
|
//inputs
|
|
bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C) {
|
|
try
|
|
{
|
|
PERF_TIMER(verRctMGSimple);
|
|
//setup vars
|
|
size_t rows = 1;
|
|
size_t cols = pubs.size();
|
|
CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs");
|
|
keyV tmp(rows + 1);
|
|
size_t i;
|
|
keyM M(cols, tmp);
|
|
ge_p3 Cp3;
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&Cp3, C.bytes) == 0, false, "point conv failed");
|
|
ge_cached Ccached;
|
|
ge_p3_to_cached(&Ccached, &Cp3);
|
|
ge_p1p1 p1;
|
|
//create the matrix to mg sig
|
|
for (i = 0; i < cols; i++) {
|
|
M[i][0] = pubs[i].dest;
|
|
ge_p3 p3;
|
|
CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&p3, pubs[i].mask.bytes) == 0, false, "point conv failed");
|
|
ge_sub(&p1, &p3, &Ccached);
|
|
ge_p1p1_to_p3(&p3, &p1);
|
|
ge_p3_tobytes(M[i][1].bytes, &p3);
|
|
}
|
|
//DP(C);
|
|
return MLSAG_Ver(message, M, mg, rows);
|
|
}
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
bool verRctCLSAGSimple(const key &message, const clsag &sig, const ctkeyV & pubs, const key & C_offset) {
|
|
try
|
|
{
|
|
PERF_TIMER(verRctCLSAGSimple);
|
|
const size_t n = pubs.size();
|
|
|
|
// Check data
|
|
CHECK_AND_ASSERT_MES(n >= 1, false, "Empty pubs");
|
|
CHECK_AND_ASSERT_MES(n == sig.s.size(), false, "Signature scalar vector is the wrong size!");
|
|
for (size_t i = 0; i < n; ++i)
|
|
CHECK_AND_ASSERT_MES(sc_check(sig.s[i].bytes) == 0, false, "Bad signature scalar!");
|
|
CHECK_AND_ASSERT_MES(sc_check(sig.c1.bytes) == 0, false, "Bad signature commitment!");
|
|
CHECK_AND_ASSERT_MES(!(sig.I == rct::identity()), false, "Bad key image!");
|
|
|
|
// Cache commitment offset for efficient subtraction later
|
|
ge_p3 C_offset_p3;
|
|
CHECK_AND_ASSERT_MES(ge_frombytes_vartime(&C_offset_p3, C_offset.bytes) == 0, false, "point conv failed");
|
|
ge_cached C_offset_cached;
|
|
ge_p3_to_cached(&C_offset_cached, &C_offset_p3);
|
|
|
|
// Prepare key images
|
|
key c = copy(sig.c1);
|
|
key D_8 = scalarmult8(sig.D);
|
|
CHECK_AND_ASSERT_MES(!(D_8 == rct::identity()), false, "Bad auxiliary key image!");
|
|
geDsmp I_precomp;
|
|
geDsmp D_precomp;
|
|
precomp(I_precomp.k,sig.I);
|
|
precomp(D_precomp.k,D_8);
|
|
|
|
// Aggregation hashes
|
|
keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset
|
|
keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset
|
|
sc_0(mu_P_to_hash[0].bytes);
|
|
memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
|
|
sc_0(mu_C_to_hash[0].bytes);
|
|
memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
|
|
for (size_t i = 1; i < n+1; ++i) {
|
|
mu_P_to_hash[i] = pubs[i-1].dest;
|
|
mu_C_to_hash[i] = pubs[i-1].dest;
|
|
}
|
|
for (size_t i = n+1; i < 2*n+1; ++i) {
|
|
mu_P_to_hash[i] = pubs[i-n-1].mask;
|
|
mu_C_to_hash[i] = pubs[i-n-1].mask;
|
|
}
|
|
mu_P_to_hash[2*n+1] = sig.I;
|
|
mu_P_to_hash[2*n+2] = sig.D;
|
|
mu_P_to_hash[2*n+3] = C_offset;
|
|
mu_C_to_hash[2*n+1] = sig.I;
|
|
mu_C_to_hash[2*n+2] = sig.D;
|
|
mu_C_to_hash[2*n+3] = C_offset;
|
|
key mu_P, mu_C;
|
|
mu_P = hash_to_scalar(mu_P_to_hash);
|
|
mu_C = hash_to_scalar(mu_C_to_hash);
|
|
|
|
// Set up round hash
|
|
keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, L, R
|
|
sc_0(c_to_hash[0].bytes);
|
|
memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
|
|
for (size_t i = 1; i < n+1; ++i)
|
|
{
|
|
c_to_hash[i] = pubs[i-1].dest;
|
|
c_to_hash[i+n] = pubs[i-1].mask;
|
|
}
|
|
c_to_hash[2*n+1] = C_offset;
|
|
c_to_hash[2*n+2] = message;
|
|
key c_p; // = c[i]*mu_P
|
|
key c_c; // = c[i]*mu_C
|
|
key c_new;
|
|
key L;
|
|
key R;
|
|
geDsmp P_precomp;
|
|
geDsmp C_precomp;
|
|
size_t i = 0;
|
|
ge_p3 hash8_p3;
|
|
geDsmp hash_precomp;
|
|
ge_p3 temp_p3;
|
|
ge_p1p1 temp_p1;
|
|
|
|
while (i < n) {
|
|
sc_0(c_new.bytes);
|
|
sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
|
|
sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
|
|
|
|
// Precompute points for L/R
|
|
precomp(P_precomp.k,pubs[i].dest);
|
|
|
|
CHECK_AND_ASSERT_MES(ge_frombytes_vartime(&temp_p3, pubs[i].mask.bytes) == 0, false, "point conv failed");
|
|
ge_sub(&temp_p1,&temp_p3,&C_offset_cached);
|
|
ge_p1p1_to_p3(&temp_p3,&temp_p1);
|
|
ge_dsm_precomp(C_precomp.k,&temp_p3);
|
|
|
|
// Compute L
|
|
addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
|
|
|
|
// Compute R
|
|
hash_to_p3(hash8_p3,pubs[i].dest);
|
|
ge_dsm_precomp(hash_precomp.k, &hash8_p3);
|
|
addKeys_aAbBcC(R,sig.s[i],hash_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
|
|
|
|
c_to_hash[2*n+3] = L;
|
|
c_to_hash[2*n+4] = R;
|
|
c_new = hash_to_scalar(c_to_hash);
|
|
CHECK_AND_ASSERT_MES(!(c_new == rct::zero()), false, "Bad signature hash");
|
|
copy(c,c_new);
|
|
|
|
i = i + 1;
|
|
}
|
|
sc_sub(c_new.bytes,c.bytes,sig.c1.bytes);
|
|
return sc_isnonzero(c_new.bytes) == 0;
|
|
}
|
|
catch (...) { return false; }
|
|
}
|
|
|
|
|
|
//These functions get keys from blockchain
|
|
//replace these when connecting blockchain
|
|
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
|
|
//populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk
|
|
// the return value are the key matrix, and the index where inPk was put (random).
|
|
void getKeyFromBlockchain(ctkey & a, size_t reference_index) {
|
|
a.mask = pkGen();
|
|
a.dest = pkGen();
|
|
}
|
|
|
|
//These functions get keys from blockchain
|
|
//replace these when connecting blockchain
|
|
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
|
|
//populateFromBlockchain creates a keymatrix with "mixin" + 1 columns and one of the columns is inPk
|
|
// the return value are the key matrix, and the index where inPk was put (random).
|
|
tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin) {
|
|
int rows = inPk.size();
|
|
ctkeyM rv(mixin + 1, inPk);
|
|
int index = randXmrAmount(mixin);
|
|
int i = 0, j = 0;
|
|
for (i = 0; i <= mixin; i++) {
|
|
if (i != index) {
|
|
for (j = 0; j < rows; j++) {
|
|
getKeyFromBlockchain(rv[i][j], (size_t)randXmrAmount);
|
|
}
|
|
}
|
|
}
|
|
return make_tuple(rv, index);
|
|
}
|
|
|
|
//These functions get keys from blockchain
|
|
//replace these when connecting blockchain
|
|
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
|
|
//populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk
|
|
// the return value are the key matrix, and the index where inPk was put (random).
|
|
xmr_amount populateFromBlockchainSimple(ctkeyV & mixRing, const ctkey & inPk, int mixin) {
|
|
int index = randXmrAmount(mixin);
|
|
int i = 0;
|
|
for (i = 0; i <= mixin; i++) {
|
|
if (i != index) {
|
|
getKeyFromBlockchain(mixRing[i], (size_t)randXmrAmount(1000));
|
|
} else {
|
|
mixRing[i] = inPk;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
//RingCT protocol
|
|
//genRct:
|
|
// creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the
|
|
// columns that are claimed as inputs, and that the sum of inputs = sum of outputs.
|
|
// Also contains masked "amount" and "mask" so the receiver can see how much they received
|
|
//verRct:
|
|
// verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct
|
|
//decodeRct: (c.f. https://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
|
|
// 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, const RCTConfig &rct_config, hw::device &hwdev) {
|
|
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");
|
|
for (size_t n = 0; n < mixRing.size(); ++n) {
|
|
CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size");
|
|
}
|
|
CHECK_AND_ASSERT_THROW_MES(inSk.size() < 2, "genRct is not suitable for 2+ rings");
|
|
|
|
rctSig rv;
|
|
rv.type = RCTTypeFull;
|
|
rv.message = message;
|
|
rv.outPk.resize(destinations.size());
|
|
rv.p.rangeSigs.resize(destinations.size());
|
|
rv.ecdhInfo.resize(destinations.size());
|
|
|
|
size_t i = 0;
|
|
keyV masks(destinations.size()); //sk mask..
|
|
outSk.resize(destinations.size());
|
|
for (i = 0; i < destinations.size(); i++) {
|
|
//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]);
|
|
#ifdef DBG
|
|
CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof");
|
|
#endif
|
|
//mask amount and mask
|
|
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
|
rv.ecdhInfo[i].amount = d2h(amounts[i]);
|
|
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
|
|
}
|
|
|
|
//set txn fee
|
|
if (amounts.size() > destinations.size())
|
|
{
|
|
rv.txnFee = amounts[destinations.size()];
|
|
}
|
|
else
|
|
{
|
|
rv.txnFee = 0;
|
|
}
|
|
key txnFeeKey = scalarmultH(d2h(rv.txnFee));
|
|
|
|
rv.mixRing = mixRing;
|
|
rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv, hwdev), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey,hwdev));
|
|
return rv;
|
|
}
|
|
|
|
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
|
|
unsigned int index;
|
|
ctkeyM mixRing;
|
|
ctkeyV outSk;
|
|
tie(mixRing, index) = populateFromBlockchain(inPk, mixin);
|
|
return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, rct_config, hwdev);
|
|
}
|
|
|
|
//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, const RCTConfig &rct_config, hw::device &hwdev) {
|
|
const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean;
|
|
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");
|
|
CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations");
|
|
CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk");
|
|
CHECK_AND_ASSERT_THROW_MES(mixRing.size() == inSk.size(), "Different number of mixRing/inSk");
|
|
for (size_t n = 0; n < mixRing.size(); ++n) {
|
|
CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing");
|
|
}
|
|
|
|
rctSig rv;
|
|
if (bulletproof_or_plus)
|
|
{
|
|
switch (rct_config.bp_version)
|
|
{
|
|
case 0:
|
|
case 4:
|
|
rv.type = RCTTypeBulletproofPlus;
|
|
break;
|
|
case 3:
|
|
rv.type = RCTTypeCLSAG;
|
|
break;
|
|
case 2:
|
|
rv.type = RCTTypeBulletproof2;
|
|
break;
|
|
case 1:
|
|
rv.type = RCTTypeBulletproof;
|
|
break;
|
|
default:
|
|
ASSERT_MES_AND_THROW("Unsupported BP version: " << rct_config.bp_version);
|
|
}
|
|
}
|
|
else
|
|
rv.type = RCTTypeSimple;
|
|
|
|
rv.message = message;
|
|
rv.outPk.resize(destinations.size());
|
|
if (!bulletproof_or_plus)
|
|
rv.p.rangeSigs.resize(destinations.size());
|
|
rv.ecdhInfo.resize(destinations.size());
|
|
|
|
size_t i;
|
|
keyV masks(destinations.size()); //sk mask..
|
|
outSk.resize(destinations.size());
|
|
for (i = 0; i < destinations.size(); i++) {
|
|
|
|
//add destination to sig
|
|
rv.outPk[i].dest = copy(destinations[i]);
|
|
//compute range proof
|
|
if (!bulletproof_or_plus)
|
|
rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]);
|
|
#ifdef DBG
|
|
if (!bulletproof_or_plus)
|
|
CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof");
|
|
#endif
|
|
}
|
|
|
|
rv.p.bulletproofs.clear();
|
|
rv.p.bulletproofs_plus.clear();
|
|
if (bulletproof_or_plus)
|
|
{
|
|
const bool plus = is_rct_bulletproof_plus(rv.type);
|
|
size_t n_amounts = outamounts.size();
|
|
size_t amounts_proved = 0;
|
|
if (rct_config.range_proof_type == RangeProofPaddedBulletproof)
|
|
{
|
|
rct::keyV C, masks;
|
|
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
|
|
{
|
|
// use a fake bulletproof for speed
|
|
if (plus)
|
|
rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks));
|
|
else
|
|
rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks));
|
|
}
|
|
else
|
|
{
|
|
const epee::span<const key> keys{&amount_keys[0], amount_keys.size()};
|
|
if (plus)
|
|
rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev));
|
|
else
|
|
rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev));
|
|
#ifdef DBG
|
|
if (plus)
|
|
CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof");
|
|
else
|
|
CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
|
|
#endif
|
|
}
|
|
for (i = 0; i < outamounts.size(); ++i)
|
|
{
|
|
rv.outPk[i].mask = rct::scalarmult8(C[i]);
|
|
outSk[i].mask = masks[i];
|
|
}
|
|
}
|
|
else while (amounts_proved < n_amounts)
|
|
{
|
|
size_t batch_size = 1;
|
|
if (rct_config.range_proof_type == RangeProofMultiOutputBulletproof)
|
|
while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? BULLETPROOF_PLUS_MAX_OUTPUTS : BULLETPROOF_MAX_OUTPUTS))
|
|
batch_size *= 2;
|
|
rct::keyV C, masks;
|
|
std::vector<uint64_t> batch_amounts(batch_size);
|
|
for (i = 0; i < batch_size; ++i)
|
|
batch_amounts[i] = outamounts[i + amounts_proved];
|
|
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
|
|
{
|
|
// use a fake bulletproof for speed
|
|
if (plus)
|
|
rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks));
|
|
else
|
|
rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks));
|
|
}
|
|
else
|
|
{
|
|
const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size};
|
|
if (plus)
|
|
rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev));
|
|
else
|
|
rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev));
|
|
#ifdef DBG
|
|
if (plus)
|
|
CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof");
|
|
else
|
|
CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
|
|
#endif
|
|
}
|
|
for (i = 0; i < batch_size; ++i)
|
|
{
|
|
rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
|
|
outSk[i + amounts_proved].mask = masks[i];
|
|
}
|
|
amounts_proved += batch_size;
|
|
}
|
|
}
|
|
|
|
key sumout = zero();
|
|
for (i = 0; i < outSk.size(); ++i)
|
|
{
|
|
sc_add(sumout.bytes, outSk[i].mask.bytes, sumout.bytes);
|
|
|
|
//mask amount and mask
|
|
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
|
rv.ecdhInfo[i].amount = d2h(outamounts[i]);
|
|
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
|
|
}
|
|
|
|
//set txn fee
|
|
rv.txnFee = txnFee;
|
|
// TODO: unused ??
|
|
// key txnFeeKey = scalarmultH(d2h(rv.txnFee));
|
|
rv.mixRing = mixRing;
|
|
keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
|
|
pseudoOuts.resize(inamounts.size());
|
|
if (is_rct_clsag(rv.type))
|
|
rv.p.CLSAGs.resize(inamounts.size());
|
|
else
|
|
rv.p.MGs.resize(inamounts.size());
|
|
key sumpouts = zero(); //sum pseudoOut masks
|
|
keyV a(inamounts.size());
|
|
for (i = 0 ; i < inamounts.size() - 1; i++) {
|
|
skGen(a[i]);
|
|
sc_add(sumpouts.bytes, a[i].bytes, sumpouts.bytes);
|
|
genC(pseudoOuts[i], a[i], inamounts[i]);
|
|
}
|
|
sc_sub(a[i].bytes, sumout.bytes, sumpouts.bytes);
|
|
genC(pseudoOuts[i], a[i], inamounts[i]);
|
|
DP(pseudoOuts[i]);
|
|
|
|
key full_message = get_pre_mlsag_hash(rv,hwdev);
|
|
|
|
for (i = 0 ; i < inamounts.size(); i++)
|
|
{
|
|
if (is_rct_clsag(rv.type))
|
|
{
|
|
if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
|
|
rv.p.CLSAGs[i] = make_dummy_clsag(rv.mixRing[i].size());
|
|
else
|
|
rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev);
|
|
}
|
|
else
|
|
{
|
|
rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin, const RCTConfig &rct_config, hw::device &hwdev) {
|
|
std::vector<unsigned int> index;
|
|
index.resize(inPk.size());
|
|
ctkeyM mixRing;
|
|
ctkeyV outSk;
|
|
mixRing.resize(inPk.size());
|
|
for (size_t i = 0; i < inPk.size(); ++i) {
|
|
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, rct_config, hwdev);
|
|
}
|
|
|
|
//RingCT protocol
|
|
//genRct:
|
|
// creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the
|
|
// columns that are claimed as inputs, and that the sum of inputs = sum of outputs.
|
|
// Also contains masked "amount" and "mask" so the receiver can see how much they received
|
|
//verRct:
|
|
// verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct
|
|
//decodeRct: (c.f. https://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
|
|
bool verRct(const rctSig & rv, bool semantics) {
|
|
PERF_TIMER(verRct);
|
|
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, 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");
|
|
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");
|
|
}
|
|
else
|
|
{
|
|
// semantics check is early, we don't have the MGs resolved yet
|
|
}
|
|
|
|
// some rct ops can throw
|
|
try
|
|
{
|
|
if (semantics) {
|
|
tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
|
|
tools::threadpool::waiter waiter(tpool);
|
|
std::deque<bool> results(rv.outPk.size(), false);
|
|
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 (!waiter.wait())
|
|
return false;
|
|
|
|
for (size_t i = 0; i < results.size(); ++i) {
|
|
if (!results[i]) {
|
|
LOG_PRINT_L1("Range proof verified failed for proof " << i);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!semantics) {
|
|
//compute txn fee
|
|
key txnFeeKey = scalarmultH(d2h(rv.txnFee));
|
|
bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv, hw::get_device("default")));
|
|
DP("mg sig verified?");
|
|
DP(mgVerd);
|
|
if (!mgVerd) {
|
|
LOG_PRINT_L1("MG signature verification failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
LOG_PRINT_L1("Error in verRct: " << e.what());
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_PRINT_L1("Error in verRct, but not an actual exception");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//ver RingCT simple
|
|
//assumes only post-rct style inputs (at least for max anonymity)
|
|
bool verRctSemanticsSimple(const std::vector<const rctSig*> & rvv) {
|
|
try
|
|
{
|
|
PERF_TIMER(verRctSemanticsSimple);
|
|
|
|
tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
|
|
tools::threadpool::waiter waiter(tpool);
|
|
std::deque<bool> results;
|
|
std::vector<const Bulletproof*> bp_proofs;
|
|
std::vector<const BulletproofPlus*> bpp_proofs;
|
|
size_t max_non_bp_proofs = 0, offset = 0;
|
|
|
|
for (const rctSig *rvp: rvv)
|
|
{
|
|
CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL");
|
|
const rctSig &rv = *rvp;
|
|
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
|
|
false, "verRctSemanticsSimple called on non simple rctSig");
|
|
const bool bulletproof = is_rct_bulletproof(rv.type);
|
|
const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
|
|
if (bulletproof || bulletproof_plus)
|
|
{
|
|
if (bulletproof_plus)
|
|
CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus");
|
|
else
|
|
CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
|
|
if (is_rct_clsag(rv.type))
|
|
{
|
|
CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG");
|
|
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs");
|
|
}
|
|
else
|
|
{
|
|
CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs are not empty for MLSAG");
|
|
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.MGs");
|
|
}
|
|
CHECK_AND_ASSERT_MES(rv.pseudoOuts.empty(), false, "rv.pseudoOuts is not empty");
|
|
}
|
|
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.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs");
|
|
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.empty(), false, "rv.p.pseudoOuts is not empty");
|
|
}
|
|
CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo");
|
|
|
|
if (!bulletproof && !bulletproof_plus)
|
|
max_non_bp_proofs += rv.p.rangeSigs.size();
|
|
}
|
|
|
|
results.resize(max_non_bp_proofs);
|
|
for (const rctSig *rvp: rvv)
|
|
{
|
|
const rctSig &rv = *rvp;
|
|
|
|
const bool bulletproof = is_rct_bulletproof(rv.type);
|
|
const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
|
|
const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
|
|
|
|
rct::keyV masks(rv.outPk.size());
|
|
for (size_t i = 0; i < rv.outPk.size(); i++) {
|
|
masks[i] = rv.outPk[i].mask;
|
|
}
|
|
key sumOutpks = addKeys(masks);
|
|
DP(sumOutpks);
|
|
const key txnFeeKey = scalarmultH(d2h(rv.txnFee));
|
|
addKeys(sumOutpks, txnFeeKey, sumOutpks);
|
|
|
|
key sumPseudoOuts = addKeys(pseudoOuts);
|
|
DP(sumPseudoOuts);
|
|
|
|
//check pseudoOuts vs Outs..
|
|
if (!equalKeys(sumPseudoOuts, sumOutpks)) {
|
|
LOG_PRINT_L1("Sum check failed");
|
|
return false;
|
|
}
|
|
|
|
if (bulletproof_plus)
|
|
{
|
|
for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++)
|
|
bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]);
|
|
}
|
|
else if (bulletproof)
|
|
{
|
|
for (size_t i = 0; i < rv.p.bulletproofs.size(); i++)
|
|
bp_proofs.push_back(&rv.p.bulletproofs[i]);
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < rv.p.rangeSigs.size(); i++)
|
|
tpool.submit(&waiter, [&, i, offset] { results[i+offset] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); });
|
|
offset += rv.p.rangeSigs.size();
|
|
}
|
|
}
|
|
if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs))
|
|
{
|
|
LOG_PRINT_L1("Aggregate range proof verified failed");
|
|
if (!waiter.wait())
|
|
return false;
|
|
return false;
|
|
}
|
|
if (!bp_proofs.empty() && !verBulletproof(bp_proofs))
|
|
{
|
|
LOG_PRINT_L1("Aggregate range proof verified failed");
|
|
if (!waiter.wait())
|
|
return false;
|
|
return false;
|
|
}
|
|
|
|
if (!waiter.wait())
|
|
return false;
|
|
for (size_t i = 0; i < results.size(); ++i) {
|
|
if (!results[i]) {
|
|
LOG_PRINT_L1("Range proof verified failed for proof " << i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (const std::exception &e)
|
|
{
|
|
LOG_PRINT_L1("Error in verRctSemanticsSimple: " << e.what());
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_PRINT_L1("Error in verRctSemanticsSimple, but not an actual exception");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool verRctSemanticsSimple(const rctSig & rv)
|
|
{
|
|
return verRctSemanticsSimple(std::vector<const rctSig*>(1, &rv));
|
|
}
|
|
|
|
//ver RingCT simple
|
|
//assumes only post-rct style inputs (at least for max anonymity)
|
|
bool verRctNonSemanticsSimple(const rctSig & rv) {
|
|
try
|
|
{
|
|
PERF_TIMER(verRctNonSemanticsSimple);
|
|
|
|
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
|
|
false, "verRctNonSemanticsSimple called on non simple rctSig");
|
|
const bool bulletproof = is_rct_bulletproof(rv.type);
|
|
const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type);
|
|
// semantics check is early, and mixRing/MGs aren't resolved yet
|
|
if (bulletproof || bulletproof_plus)
|
|
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing");
|
|
else
|
|
CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing");
|
|
|
|
const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size());
|
|
|
|
std::deque<bool> results(threads);
|
|
tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
|
|
tools::threadpool::waiter waiter(tpool);
|
|
|
|
const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts;
|
|
|
|
const key message = get_pre_mlsag_hash(rv, hw::get_device("default"));
|
|
|
|
results.clear();
|
|
results.resize(rv.mixRing.size());
|
|
for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
|
|
tpool.submit(&waiter, [&, i] {
|
|
if (is_rct_clsag(rv.type))
|
|
results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]);
|
|
else
|
|
results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]);
|
|
});
|
|
}
|
|
if (!waiter.wait())
|
|
return false;
|
|
|
|
for (size_t i = 0; i < results.size(); ++i) {
|
|
if (!results[i]) {
|
|
LOG_PRINT_L1("verRctMGSimple/verRctCLSAGSimple failed for input " << i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// we can get deep throws from ge_frombytes_vartime if input isn't valid
|
|
catch (const std::exception &e)
|
|
{
|
|
LOG_PRINT_L1("Error in verRctNonSemanticsSimple: " << e.what());
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_PRINT_L1("Error in verRctNonSemanticsSimple, but not an actual exception");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//RingCT protocol
|
|
//genRct:
|
|
// creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the
|
|
// columns that are claimed as inputs, and that the sum of inputs = sum of outputs.
|
|
// Also contains masked "amount" and "mask" so the receiver can see how much they received
|
|
//verRct:
|
|
// verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct
|
|
//decodeRct: (c.f. https://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
|
|
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev) {
|
|
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "decodeRct called on non-full rctSig");
|
|
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
|
|
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];
|
|
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
|
|
mask = ecdh_info.mask;
|
|
key amount = ecdh_info.amount;
|
|
key C = rv.outPk[i].mask;
|
|
DP("C");
|
|
DP(C);
|
|
key Ctmp;
|
|
CHECK_AND_ASSERT_THROW_MES(sc_check(mask.bytes) == 0, "warning, bad ECDH mask");
|
|
CHECK_AND_ASSERT_THROW_MES(sc_check(amount.bytes) == 0, "warning, bad ECDH amount");
|
|
addKeys2(Ctmp, mask, amount, H);
|
|
DP("Ctmp");
|
|
DP(Ctmp);
|
|
if (equalKeys(C, Ctmp) == false) {
|
|
CHECK_AND_ASSERT_THROW_MES(false, "warning, amount decoded incorrectly, will be unable to spend");
|
|
}
|
|
return h2d(amount);
|
|
}
|
|
|
|
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev) {
|
|
key mask;
|
|
return decodeRct(rv, sk, i, mask, hwdev);
|
|
}
|
|
|
|
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) {
|
|
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus,
|
|
false, "decodeRct called on non simple rctSig");
|
|
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
|
|
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];
|
|
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus);
|
|
mask = ecdh_info.mask;
|
|
key amount = ecdh_info.amount;
|
|
key C = rv.outPk[i].mask;
|
|
DP("C");
|
|
DP(C);
|
|
key Ctmp;
|
|
CHECK_AND_ASSERT_THROW_MES(sc_check(mask.bytes) == 0, "warning, bad ECDH mask");
|
|
CHECK_AND_ASSERT_THROW_MES(sc_check(amount.bytes) == 0, "warning, bad ECDH amount");
|
|
addKeys2(Ctmp, mask, amount, H);
|
|
DP("Ctmp");
|
|
DP(Ctmp);
|
|
if (equalKeys(C, Ctmp) == false) {
|
|
CHECK_AND_ASSERT_THROW_MES(false, "warning, amount decoded incorrectly, will be unable to spend");
|
|
}
|
|
return h2d(amount);
|
|
}
|
|
|
|
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev) {
|
|
key mask;
|
|
return decodeRctSimple(rv, sk, i, mask, hwdev);
|
|
}
|
|
}
|