Merge pull request #6739

1660fe8a2 draft support of clsag (cslashm)
703944c4d CLSAG device support (Sarang Noether)
aff87b5f6 Added balance check to MLSAG/CLSAG performance tests (Sarang Noether)
f964a92c5 Updated MLSAG and CLSAG tests for consistency (Sarang Noether)
5aa1575e9 CLSAG verification performance test (Sarang Noether)
641b08c92 CLSAG optimizations (Sarang Noether)
82ee01699 Integrate CLSAGs into monero (moneromooo-monero)
8cd1d6df8 unit_tests: add ge_triple_scalarmult_base_vartime test (moneromooo-monero)
4b328c661 CLSAG signatures (Sarang Noether)
pull/320/head
Alexander Blair 4 years ago
commit 39a087406d
No known key found for this signature in database
GPG Key ID: C64552D877C32479

@ -1234,6 +1234,56 @@ void ge_double_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const g
} }
} }
// Computes aG + bB + cC (G is the fixed basepoint)
void ge_triple_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) {
signed char aslide[256];
signed char bslide[256];
signed char cslide[256];
ge_p1p1 t;
ge_p3 u;
int i;
slide(aslide, a);
slide(bslide, b);
slide(cslide, c);
ge_p2_0(r);
for (i = 255; i >= 0; --i) {
if (aslide[i] || bslide[i] || cslide[i]) break;
}
for (; i >= 0; --i) {
ge_p2_dbl(&t, r);
if (aslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_madd(&t, &u, &ge_Bi[aslide[i]/2]);
} else if (aslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_msub(&t, &u, &ge_Bi[(-aslide[i])/2]);
}
if (bslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Bi[bslide[i]/2]);
} else if (bslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Bi[(-bslide[i])/2]);
}
if (cslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Ci[cslide[i]/2]);
} else if (cslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Ci[(-cslide[i])/2]);
}
ge_p1p1_to_p2(r, &t);
}
}
void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A, const unsigned char *b) {
signed char aslide[256]; signed char aslide[256];
signed char bslide[256]; signed char bslide[256];
@ -2148,6 +2198,56 @@ void ge_double_scalarmult_precomp_vartime2(ge_p2 *r, const unsigned char *a, con
} }
} }
// Computes aA + bB + cC (all points require precomputation)
void ge_triple_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) {
signed char aslide[256];
signed char bslide[256];
signed char cslide[256];
ge_p1p1 t;
ge_p3 u;
int i;
slide(aslide, a);
slide(bslide, b);
slide(cslide, c);
ge_p2_0(r);
for (i = 255; i >= 0; --i) {
if (aslide[i] || bslide[i] || cslide[i]) break;
}
for (; i >= 0; --i) {
ge_p2_dbl(&t, r);
if (aslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Ai[aslide[i]/2]);
} else if (aslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Ai[(-aslide[i])/2]);
}
if (bslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Bi[bslide[i]/2]);
} else if (bslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Bi[(-bslide[i])/2]);
}
if (cslide[i] > 0) {
ge_p1p1_to_p3(&u, &t);
ge_add(&t, &u, &Ci[cslide[i]/2]);
} else if (cslide[i] < 0) {
ge_p1p1_to_p3(&u, &t);
ge_sub(&t, &u, &Ci[(-cslide[i])/2]);
}
ge_p1p1_to_p2(r, &t);
}
}
void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *r3, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) { void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *r3, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) {
signed char aslide[256]; signed char aslide[256];
signed char bslide[256]; signed char bslide[256];

@ -79,6 +79,7 @@ typedef ge_cached ge_dsmp[8];
extern const ge_precomp ge_Bi[8]; extern const ge_precomp ge_Bi[8];
void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s); void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s);
void ge_double_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *); void ge_double_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *);
void ge_triple_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const ge_p3 *, const unsigned char *); void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const ge_p3 *, const unsigned char *);
/* From ge_frombytes.c, modified */ /* From ge_frombytes.c, modified */
@ -130,6 +131,7 @@ void sc_reduce(unsigned char *);
void ge_scalarmult(ge_p2 *, const unsigned char *, const ge_p3 *); void ge_scalarmult(ge_p2 *, const unsigned char *, const ge_p3 *);
void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *); void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *);
void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp); void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp);
void ge_triple_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_mul8(ge_p1p1 *, const ge_p2 *); void ge_mul8(ge_p1p1 *, const ge_p2 *);

@ -45,7 +45,6 @@
#include "ringct/rctTypes.h" #include "ringct/rctTypes.h"
#include "ringct/rctOps.h" #include "ringct/rctOps.h"
//namespace cryptonote {
namespace boost namespace boost
{ {
namespace serialization namespace serialization
@ -244,6 +243,15 @@ namespace boost
// a & x.II; // not serialized, we can recover it from the tx vin // a & x.II; // not serialized, we can recover it from the tx vin
} }
template <class Archive>
inline void serialize(Archive &a, rct::clsag &x, const boost::serialization::version_type ver)
{
a & x.s;
a & x.c1;
// a & x.I; // not serialized, we can recover it from the tx vin
a & x.D;
}
template <class Archive> template <class Archive>
inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver) inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver)
{ {
@ -264,6 +272,9 @@ namespace boost
inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver) inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver)
{ {
a & x.c; a & x.c;
if (ver < 1)
return;
a & x.mu_p;
} }
template <class Archive> template <class Archive>
@ -294,7 +305,7 @@ namespace boost
a & x.type; a & x.type;
if (x.type == rct::RCTTypeNull) if (x.type == rct::RCTTypeNull)
return; return;
if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2) if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
// a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.message; message is not serialized, as it can be reconstructed from the tx data
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@ -312,6 +323,8 @@ namespace boost
if (x.rangeSigs.empty()) if (x.rangeSigs.empty())
a & x.bulletproofs; a & x.bulletproofs;
a & x.MGs; a & x.MGs;
if (ver >= 1u)
a & x.CLSAGs;
if (x.rangeSigs.empty()) if (x.rangeSigs.empty())
a & x.pseudoOuts; a & x.pseudoOuts;
} }
@ -322,7 +335,7 @@ namespace boost
a & x.type; a & x.type;
if (x.type == rct::RCTTypeNull) if (x.type == rct::RCTTypeNull)
return; return;
if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2) if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
// a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.message; message is not serialized, as it can be reconstructed from the tx data
// a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@ -336,7 +349,9 @@ namespace boost
if (x.p.rangeSigs.empty()) if (x.p.rangeSigs.empty())
a & x.p.bulletproofs; a & x.p.bulletproofs;
a & x.p.MGs; a & x.p.MGs;
if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2) if (ver >= 1u)
a & x.p.CLSAGs;
if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG)
a & x.p.pseudoOuts; a & x.p.pseudoOuts;
} }
@ -377,4 +392,6 @@ namespace boost
} }
} }
//} BOOST_CLASS_VERSION(rct::rctSigPrunable, 1)
BOOST_CLASS_VERSION(rct::rctSig, 1)
BOOST_CLASS_VERSION(rct::multisig_out, 1)

@ -436,7 +436,7 @@ namespace cryptonote
{ {
CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes");
CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes");
CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2, CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG,
std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types");
CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin");
CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin");
@ -458,9 +458,12 @@ namespace cryptonote
extra = 32 * (9 + 2 * nrl) + 2; extra = 32 * (9 + 2 * nrl) + 2;
weight += extra; weight += extra;
// calculate deterministic MLSAG data size // calculate deterministic CLSAG/MLSAG data size
const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size();
extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); if (tx.rct_signatures.type == rct::RCTTypeCLSAG)
extra = tx.vin.size() * (ring_size + 2) * 32;
else
extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */);
weight += extra; weight += extra;
// calculate deterministic pseudoOuts size // calculate deterministic pseudoOuts size

@ -179,6 +179,7 @@
#define HF_VERSION_ENFORCE_MIN_AGE 12 #define HF_VERSION_ENFORCE_MIN_AGE 12
#define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12 #define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12
#define HF_VERSION_EXACT_COINBASE 13 #define HF_VERSION_EXACT_COINBASE 13
#define HF_VERSION_CLSAG 13
#define PER_KB_FEE_QUANTIZATION_DECIMALS 8 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8
@ -226,6 +227,9 @@ namespace config
const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MEMORY = 'k';
const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2"; const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2";
const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round";
const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0";
const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1";
namespace testnet namespace testnet
{ {

@ -3015,6 +3015,30 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
} }
} }
// from v13, allow CLSAGs
if (hf_version < HF_VERSION_CLSAG) {
if (tx.version >= 2) {
if (tx.rct_signatures.type == rct::RCTTypeCLSAG)
{
MERROR_VER("Ringct type " << (unsigned)rct::RCTTypeCLSAG << " is not allowed before v" << HF_VERSION_CLSAG);
tvc.m_invalid_output = true;
return false;
}
}
}
// from v14, allow only CLSAGs
if (hf_version > HF_VERSION_CLSAG) {
if (tx.version >= 2) {
if (tx.rct_signatures.type <= rct::RCTTypeBulletproof2)
{
MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << (HF_VERSION_CLSAG + 1));
tvc.m_invalid_output = true;
return false;
}
}
}
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -3055,7 +3079,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
} }
} }
} }
else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2) else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG)
{ {
CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys");
rv.mixRing.resize(pubkeys.size()); rv.mixRing.resize(pubkeys.size());
@ -3068,6 +3092,14 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
} }
} }
} }
else if (rv.type == rct::RCTTypeCLSAG)
{
CHECK_AND_ASSERT_MES(rv.p.CLSAGs.size() == tx.vin.size(), false, "Bad CLSAGs size");
for (size_t n = 0; n < tx.vin.size(); ++n)
{
rv.p.CLSAGs[n].I = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
}
}
else else
{ {
CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
@ -3096,6 +3128,17 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
} }
} }
} }
else if (rv.type == rct::RCTTypeCLSAG)
{
if (!tx.pruned)
{
CHECK_AND_ASSERT_MES(rv.p.CLSAGs.size() == tx.vin.size(), false, "Bad CLSAGs size");
for (size_t n = 0; n < tx.vin.size(); ++n)
{
rv.p.CLSAGs[n].I = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
}
}
}
else else
{ {
CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
@ -3377,6 +3420,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
case rct::RCTTypeSimple: case rct::RCTTypeSimple:
case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof:
case rct::RCTTypeBulletproof2: case rct::RCTTypeBulletproof2:
case rct::RCTTypeCLSAG:
{ {
// check all this, either reconstructed (so should really pass), or not // check all this, either reconstructed (so should really pass), or not
{ {
@ -3412,14 +3456,20 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
} }
} }
if (rv.p.MGs.size() != tx.vin.size()) const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size();
if (n_sigs != tx.vin.size())
{ {
MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes");
return false; return false;
} }
for (size_t n = 0; n < tx.vin.size(); ++n) for (size_t n = 0; n < tx.vin.size(); ++n)
{ {
if (rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) bool error;
if (rv.type == rct::RCTTypeCLSAG)
error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32);
else
error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32);
if (error)
{ {
MERROR_VER("Failed to check ringct signatures: mismatched key image"); MERROR_VER("Failed to check ringct signatures: mismatched key image");
return false; return false;

@ -928,6 +928,7 @@ namespace cryptonote
break; break;
case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof:
case rct::RCTTypeBulletproof2: case rct::RCTTypeBulletproof2:
case rct::RCTTypeCLSAG:
if (!is_canonical_bulletproof_layout(rv.p.bulletproofs)) if (!is_canonical_bulletproof_layout(rv.p.bulletproofs))
{ {
MERROR_VER("Bulletproof does not have canonical form"); MERROR_VER("Bulletproof does not have canonical form");
@ -955,7 +956,7 @@ namespace cryptonote
{ {
if (!tx_info[n].result) if (!tx_info[n].result)
continue; continue;
if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2) if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG)
continue; continue;
if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures))
{ {

@ -231,6 +231,10 @@ namespace hw {
virtual bool mlsag_hash(const rct::keyV &long_message, rct::key &c) = 0; virtual bool mlsag_hash(const rct::keyV &long_message, rct::key &c) = 0;
virtual bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) = 0; virtual bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) = 0;
virtual bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) = 0;
virtual bool clsag_hash(const rct::keyV &data, rct::key &hash) = 0;
virtual bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) = 0;
virtual bool close_tx(void) = 0; virtual bool close_tx(void) = 0;
virtual bool has_ki_cold_sync(void) const { return false; } virtual bool has_ki_cold_sync(void) const { return false; }

@ -402,6 +402,29 @@ namespace hw {
return true; return true;
} }
bool device_default::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) {
rct::skpkGen(a,aG); // aG = a*G
rct::scalarmultKey(aH,H,a); // aH = a*H
rct::scalarmultKey(I,H,p); // I = p*H
rct::scalarmultKey(D,H,z); // D = z*H
return true;
}
bool device_default::clsag_hash(const rct::keyV &data, rct::key &hash) {
hash = rct::hash_to_scalar(data);
return true;
}
bool device_default::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) {
rct::key s0_p_mu_P;
sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
rct::key s0_add_z_mu_C;
sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
return true;
}
bool device_default::close_tx() { bool device_default::close_tx() {
return true; return true;
} }

@ -134,6 +134,10 @@ namespace hw {
bool mlsag_hash(const rct::keyV &long_message, rct::key &c) override; bool mlsag_hash(const rct::keyV &long_message, rct::key &c) override;
bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override; bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override;
bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) override;
bool clsag_hash(const rct::keyV &data, rct::key &hash) override;
bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) override;
bool close_tx(void) override; bool close_tx(void) override;
}; };

@ -299,6 +299,7 @@ namespace hw {
#define INS_PREFIX_HASH 0x7D #define INS_PREFIX_HASH 0x7D
#define INS_VALIDATE 0x7C #define INS_VALIDATE 0x7C
#define INS_MLSAG 0x7E #define INS_MLSAG 0x7E
#define INS_CLSAG 0x7F
#define INS_CLOSE_TX 0x80 #define INS_CLOSE_TX 0x80
#define INS_GET_TX_PROOF 0xA0 #define INS_GET_TX_PROOF 0xA0
@ -1857,7 +1858,7 @@ namespace hw {
// ====== Aout, Bout, AKout, C, v, k ====== // ====== Aout, Bout, AKout, C, v, k ======
kv_offset = data_offset; kv_offset = data_offset;
if (type==rct::RCTTypeBulletproof2) { if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
C_offset = kv_offset+ (8)*outputs_size; C_offset = kv_offset+ (8)*outputs_size;
} else { } else {
C_offset = kv_offset+ (32+32)*outputs_size; C_offset = kv_offset+ (32+32)*outputs_size;
@ -1874,7 +1875,7 @@ namespace hw {
offset = set_command_header(INS_VALIDATE, 0x02, i+1); offset = set_command_header(INS_VALIDATE, 0x02, i+1);
//options //options
this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ; this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ;
this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2)?0x02:0x00; this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG)?0x02:0x00;
offset += 1; offset += 1;
//is_subaddress //is_subaddress
this->buffer_send[offset] = outKeys.is_subaddress; this->buffer_send[offset] = outKeys.is_subaddress;
@ -1895,7 +1896,7 @@ namespace hw {
memmove(this->buffer_send+offset, data+C_offset,32); memmove(this->buffer_send+offset, data+C_offset,32);
offset += 32; offset += 32;
C_offset += 32; C_offset += 32;
if (type==rct::RCTTypeBulletproof2) { if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
//k //k
memset(this->buffer_send+offset, 0, 32); memset(this->buffer_send+offset, 0, 32);
offset += 32; offset += 32;
@ -2121,6 +2122,157 @@ namespace hw {
return true; return true;
} }
bool device_ledger::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) {
AUTO_LOCK_CMD();
#ifdef DEBUG_HWDEVICE
const rct::key p_x = hw::ledger::decrypt(p);
const rct::key z_x = hw::ledger::decrypt(z);
rct::key I_x;
rct::key D_x;
const rct::key H_x = H;
rct::key a_x;
rct::key aG_x;
rct::key aH_x;
this->controle_device->clsag_prepare(p_x, z_x, I_x, D_x, H_x, a_x, aG_x, aH_x);
#endif
/*
rct::skpkGen(a,aG); // aG = a*G
rct::scalarmultKey(aH,H,a); // aH = a*H
rct::scalarmultKey(I,H,p); // I = p*H
rct::scalarmultKey(D,H,z); // D = z*H
*/
int offset = set_command_header_noopt(INS_CLSAG, 0x01);
//p
this->send_secret(p.bytes, offset);
//z
this->send_secret(z.bytes, offset);
//H
memmove(this->buffer_send+offset, H.bytes, 32);
offset += 32;
this->buffer_send[4] = offset-5;
this->length_send = offset;
this->exchange();
offset = 0;
//a
this->receive_secret(a.bytes, offset);
//aG
memmove(aG.bytes, this->buffer_recv+offset, 32);
offset +=32;
//aH
memmove(aH.bytes, this->buffer_recv+offset, 32);
offset +=32;
//I = pH
memmove(I.bytes, this->buffer_recv+offset, 32);
offset +=32;
//D = zH
memmove(D.bytes, this->buffer_recv+offset, 32);
offset +=32;
#ifdef DEBUG_HWDEVICE
hw::ledger::check32("clsag_prepare", "I", (char*)I_x.bytes, (char*)I.bytes);
hw::ledger::check32("clsag_prepare", "D", (char*)D_x.bytes, (char*)D.bytes);
hw::ledger::check32("clsag_prepare", "a", (char*)a_x.bytes, (char*)a.bytes);
hw::ledger::check32("clsag_prepare", "aG", (char*)aG_x.bytes, (char*)aG.bytes);
hw::ledger::check32("clsag_prepare", "aH", (char*)aH_x.bytes, (char*)aH.bytes);
#endif
return true;
}
bool device_ledger::clsag_hash(const rct::keyV &data, rct::key &hash) {
AUTO_LOCK_CMD();
#ifdef DEBUG_HWDEVICE
const rct::keyV data_x = data;
rct::key hash_x;
this->controle_device->mlsag_hash(data_x, hash_x);
#endif
size_t cnt;
int offset;
cnt = data.size();
for (size_t i = 0; i<cnt; i++) {
offset = set_command_header(INS_CLSAG, 0x02, i+1);
//options
this->buffer_send[offset] = (i==(cnt-1))?0x00:0x80; //last
offset += 1;
//msg part
memmove(this->buffer_send+offset, data[i].bytes, 32);
offset += 32;
this->buffer_send[4] = offset-5;
this->length_send = offset;
this->exchange();
}
//c/hash
memmove(hash.bytes, &this->buffer_recv[0], 32);
#ifdef DEBUG_HWDEVICE
hw::ledger::check32("mlsag_hash", "hash", (char*)hash_x.bytes, (char*)hash.bytes);
#endif
return true;
}
bool device_ledger::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) {
AUTO_LOCK_CMD();
#ifdef DEBUG_HWDEVICE
const rct::key c_x = c;
const rct::key a_x = hw::ledger::decrypt(a);
const rct::key p_x = hw::ledger::decrypt(p);
const rct::key z_x = hw::ledger::decrypt(z);
const rct::key mu_P_x = mu_P;
const rct::key mu_C_x = mu_C;
rct::key s_x;
this->controle_device->clsag_sign(c_x, a_x, p_x, z_x, mu_P_x, mu_C_x, s_x);
#endif
/*
rct::key s0_p_mu_P;
sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
rct::key s0_add_z_mu_C;
sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
*/
int offset = set_command_header_noopt(INS_CLSAG, 0x03);
//c
//discard, unse internal one
//a
this->send_secret(a.bytes, offset);
//p
this->send_secret(p.bytes, offset);
//z
this->send_secret(z.bytes, offset);
//mu_P
memmove(this->buffer_send+offset, mu_P.bytes, 32);
offset += 32;
//mu_C
memmove(this->buffer_send+offset, mu_C.bytes, 32);
offset += 32;
this->buffer_send[4] = offset-5;
this->length_send = offset;
this->exchange();
offset = 0;
//s
memmove(s.bytes, this->buffer_recv+offset, 32);
#ifdef DEBUG_HWDEVICE
hw::ledger::check32("clsag_sign", "s", (char*)s_x.bytes, (char*)s.bytes);
#endif
return true;
}
bool device_ledger::close_tx() { bool device_ledger::close_tx() {
AUTO_LOCK_CMD(); AUTO_LOCK_CMD();
send_simple(INS_CLOSE_TX); send_simple(INS_CLOSE_TX);

@ -297,6 +297,11 @@ namespace hw {
bool mlsag_hash(const rct::keyV &long_message, rct::key &c) override; bool mlsag_hash(const rct::keyV &long_message, rct::key &c) override;
bool mlsag_sign( const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override; bool mlsag_sign( const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override;
bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) override;
bool clsag_hash(const rct::keyV &data, rct::key &hash) override;
bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) override;
bool close_tx(void) override; bool close_tx(void) override;
}; };

@ -309,7 +309,7 @@ namespace tx {
throw std::invalid_argument("RV not initialized"); throw std::invalid_argument("RV not initialized");
} }
auto tp = m_ct.rv->type; auto tp = m_ct.rv->type;
return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2; return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG;
} }
bool is_offloading() const { bool is_offloading() const {

@ -67,6 +67,9 @@ const hardfork_t mainnet_hard_forks[] = {
// version 12 starts from block 1978433, which is on or around the 30th of November, 2019. Fork time finalised on 2019-10-18. // version 12 starts from block 1978433, which is on or around the 30th of November, 2019. Fork time finalised on 2019-10-18.
{ 12, 1978433, 0, 1571419280 }, { 12, 1978433, 0, 1571419280 },
{ 13, 2210000, 0, 1598180817 },
{ 14, 2210720, 0, 1598180818 },
}; };
const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]);
const uint64_t mainnet_hard_fork_version_1_till = 1009826; const uint64_t mainnet_hard_fork_version_1_till = 1009826;
@ -110,5 +113,7 @@ const hardfork_t stagenet_hard_forks[] = {
{ 10, 269000, 0, 1550153694 }, { 10, 269000, 0, 1550153694 },
{ 11, 269720, 0, 1550225678 }, { 11, 269720, 0, 1550225678 },
{ 12, 454721, 0, 1571419280 }, { 12, 454721, 0, 1571419280 },
{ 13, 699045, 0, 1598180817 },
{ 14, 699765, 0, 1598180818 },
}; };
const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]);

@ -511,6 +511,23 @@ namespace rct {
ge_tobytes(aAbB.bytes, &rv); ge_tobytes(aAbB.bytes, &rv);
} }
// addKeys_aGbBcC
// computes aG + bB + cC
// G is the fixed basepoint and B,C require precomputation
void addKeys_aGbBcC(key &aGbBcC, const key &a, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C) {
ge_p2 rv;
ge_triple_scalarmult_base_vartime(&rv, a.bytes, b.bytes, B, c.bytes, C);
ge_tobytes(aGbBcC.bytes, &rv);
}
// addKeys_aAbBcC
// computes aA + bB + cC
// A,B,C require precomputation
void addKeys_aAbBcC(key &aAbBcC, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C) {
ge_p2 rv;
ge_triple_scalarmult_precomp_vartime(&rv, a.bytes, A, b.bytes, B, c.bytes, C);
ge_tobytes(aAbBcC.bytes, &rv);
}
//subtract Keys (subtracts curve points) //subtract Keys (subtracts curve points)
//AB = A - B where A, B are curve points //AB = A - B where A, B are curve points

@ -145,6 +145,10 @@ namespace rct {
//B must be input after applying "precomp" //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 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); void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B);
void addKeys_aGbBcC(key &aGbBcC, const key &a, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C);
void addKeys_aAbBcC(key &aAbBcC, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C);
//AB = A - B where A, B are curve points //AB = A - B where A, B are curve points
void subKeys(key &AB, const key &A, const key &B); void subKeys(key &AB, const key &A, const key &B);
//checks if A, B are equal as curve points //checks if A, B are equal as curve points

@ -36,6 +36,7 @@
#include "rctSigs.h" #include "rctSigs.h"
#include "bulletproofs.h" #include "bulletproofs.h"
#include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_config.h"
using namespace crypto; using namespace crypto;
using namespace std; using namespace std;
@ -165,6 +166,167 @@ namespace rct {
return verifyBorromean(bb, P1_p3, P2_p3); 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, const multisig_kLRki *kLRki, key *mscout, key *mspout, 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!");
CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present");
// 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;
// Multisig
if (kLRki)
{
sig.I = kLRki->ki;
scalarmultKey(D,H,z);
}
else
{
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;
// Multisig data is present
if (kLRki)
{
a = kLRki->k;
c_to_hash[2*n+3] = kLRki->L;
c_to_hash[2*n+4] = kLRki->R;
}
else
{
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));
if (mscout)
*mscout = c;
if (mspout)
*mspout = mu_P;
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, NULL, NULL, NULL, hw::get_device("default"));
}
// MLSAG signatures // MLSAG signatures
// See paper by Noether (https://eprint.iacr.org/2015/1098) // See paper by Noether (https://eprint.iacr.org/2015/1098)
// This generalization allows for some dimensions not to require linkability; // This generalization allows for some dimensions not to require linkability;
@ -427,7 +589,7 @@ namespace rct {
hashes.push_back(hash2rct(h)); hashes.push_back(hash2rct(h));
keyV kv; keyV kv;
if (rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2) if (rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG)
{ {
kv.reserve((6*2+9) * rv.p.bulletproofs.size()); kv.reserve((6*2+9) * rv.p.bulletproofs.size());
for (const auto &p: rv.p.bulletproofs) for (const auto &p: rv.p.bulletproofs)
@ -555,6 +717,37 @@ namespace rct {
return result; return result;
} }
clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, 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");
CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV tmp(rows + 1);
keyV sk(rows + 1);
size_t i;
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, kLRki, mscout, mspout, hwdev);
memwipe(sk.data(), sk.size() * sizeof(key));
return result;
}
//Ring-ct MG sigs //Ring-ct MG sigs
//Prove: //Prove:
@ -634,6 +827,120 @@ namespace rct {
catch (...) { return false; } 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;
geDsmp H_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 //These functions get keys from blockchain
//replace these when connecting blockchain //replace these when connecting blockchain
@ -726,7 +1033,7 @@ namespace rct {
//mask amount and mask //mask amount and mask
rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].mask = copy(outSk[i].mask);
rv.ecdhInfo[i].amount = d2h(amounts[i]); rv.ecdhInfo[i].amount = d2h(amounts[i]);
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2); hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
} }
//set txn fee //set txn fee
@ -774,7 +1081,27 @@ namespace rct {
} }
rctSig rv; rctSig rv;
rv.type = bulletproof ? (rct_config.bp_version == 0 || rct_config.bp_version >= 2 ? RCTTypeBulletproof2 : RCTTypeBulletproof) : RCTTypeSimple; if (bulletproof)
{
switch (rct_config.bp_version)
{
case 0:
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.message = message;
rv.outPk.resize(destinations.size()); rv.outPk.resize(destinations.size());
if (!bulletproof) if (!bulletproof)
@ -864,7 +1191,7 @@ namespace rct {
//mask amount and mask //mask amount and mask
rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].mask = copy(outSk[i].mask);
rv.ecdhInfo[i].amount = d2h(outamounts[i]); rv.ecdhInfo[i].amount = d2h(outamounts[i]);
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2); hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
} }
//set txn fee //set txn fee
@ -874,7 +1201,10 @@ namespace rct {
rv.mixRing = mixRing; rv.mixRing = mixRing;
keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
pseudoOuts.resize(inamounts.size()); pseudoOuts.resize(inamounts.size());
rv.p.MGs.resize(inamounts.size()); if (rv.type == RCTTypeCLSAG)
rv.p.CLSAGs.resize(inamounts.size());
else
rv.p.MGs.resize(inamounts.size());
key sumpouts = zero(); //sum pseudoOut masks key sumpouts = zero(); //sum pseudoOut masks
keyV a(inamounts.size()); keyV a(inamounts.size());
for (i = 0 ; i < inamounts.size() - 1; i++) { for (i = 0 ; i < inamounts.size() - 1; i++) {
@ -888,9 +1218,20 @@ namespace rct {
key full_message = get_pre_mlsag_hash(rv,hwdev); key full_message = get_pre_mlsag_hash(rv,hwdev);
if (msout) if (msout)
msout->c.resize(inamounts.size()); {
for (i = 0 ; i < inamounts.size(); i++) { msout->c.resize(inamounts.size());
rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev); msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0);
}
for (i = 0 ; i < inamounts.size(); i++)
{
if (rv.type == RCTTypeCLSAG)
{
rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
}
else
{
rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev);
}
} }
return rv; return rv;
} }
@ -995,13 +1336,22 @@ namespace rct {
{ {
CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL");
const rctSig &rv = *rvp; const rctSig &rv = *rvp;
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
false, "verRctSemanticsSimple called on non simple rctSig"); false, "verRctSemanticsSimple called on non simple rctSig");
const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof = is_rct_bulletproof(rv.type);
if (bulletproof) if (bulletproof)
{ {
CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.MGs"); if (rv.type == RCTTypeCLSAG)
{
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"); CHECK_AND_ASSERT_MES(rv.pseudoOuts.empty(), false, "rv.pseudoOuts is not empty");
} }
else else
@ -1095,7 +1445,7 @@ namespace rct {
{ {
PERF_TIMER(verRctNonSemanticsSimple); PERF_TIMER(verRctNonSemanticsSimple);
CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
false, "verRctNonSemanticsSimple called on non simple rctSig"); false, "verRctNonSemanticsSimple called on non simple rctSig");
const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof = is_rct_bulletproof(rv.type);
// semantics check is early, and mixRing/MGs aren't resolved yet // semantics check is early, and mixRing/MGs aren't resolved yet
@ -1118,14 +1468,19 @@ namespace rct {
results.resize(rv.mixRing.size()); results.resize(rv.mixRing.size());
for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
tpool.submit(&waiter, [&, i] { tpool.submit(&waiter, [&, i] {
results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]); if (rv.type == RCTTypeCLSAG)
{
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]);
}); });
} }
waiter.wait(&tpool); waiter.wait(&tpool);
for (size_t i = 0; i < results.size(); ++i) { for (size_t i = 0; i < results.size(); ++i) {
if (!results[i]) { if (!results[i]) {
LOG_PRINT_L1("verRctMGSimple failed for input " << i); LOG_PRINT_L1("verRctMGSimple/verRctCLSAGSimple failed for input " << i);
return false; return false;
} }
} }
@ -1162,7 +1517,7 @@ namespace rct {
//mask amount and mask //mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i]; ecdhTuple ecdh_info = rv.ecdhInfo[i];
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2); hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
mask = ecdh_info.mask; mask = ecdh_info.mask;
key amount = ecdh_info.amount; key amount = ecdh_info.amount;
key C = rv.outPk[i].mask; key C = rv.outPk[i].mask;
@ -1186,13 +1541,13 @@ namespace rct {
} }
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &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, false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig");
CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); 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"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
//mask amount and mask //mask amount and mask
ecdhTuple ecdh_info = rv.ecdhInfo[i]; ecdhTuple ecdh_info = rv.ecdhInfo[i];
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2); hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
mask = ecdh_info.mask; mask = ecdh_info.mask;
key amount = ecdh_info.amount; key amount = ecdh_info.amount;
key C = rv.outPk[i].mask; key C = rv.outPk[i].mask;
@ -1215,12 +1570,13 @@ namespace rct {
return decodeRctSimple(rv, sk, i, mask, hwdev); return decodeRctSimple(rv, sk, i, mask, hwdev);
} }
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2,
false, "unsupported rct type"); false, "unsupported rct type");
CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size");
CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs");
if (rv.type == RCTTypeFull) if (rv.type == RCTTypeFull)
{ {
CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element"); CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element");
@ -1230,6 +1586,8 @@ namespace rct {
CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line"); CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line");
} }
// MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share
// cc: msout.c[n], secret_key_share: secret_key
for (size_t n = 0; n < indices.size(); ++n) { for (size_t n = 0; n < indices.size(); ++n) {
rct::key diff; rct::key diff;
sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes); sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes);
@ -1237,4 +1595,33 @@ namespace rct {
} }
return true; return true;
} }
bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
CHECK_AND_ASSERT_MES(rv.type == RCTTypeCLSAG, false, "unsupported rct type");
CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/MGs size");
CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs");
CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size");
for (size_t n = 0; n < indices.size(); ++n) {
CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range");
}
// CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share
// cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key
for (size_t n = 0; n < indices.size(); ++n) {
rct::key diff, sk;
sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes);
sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes);
sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes);
}
return true;
}
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
if (rv.type == RCTTypeCLSAG)
return signMultisigCLSAG(rv, indices, k, msout, secret_key);
else
return signMultisigMLSAG(rv, indices, k, msout, secret_key);
}
} }

@ -76,7 +76,11 @@ namespace rct {
// Ver verifies that the MG sig was created correctly // Ver verifies that the MG sig was created correctly
mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev); mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
//mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
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, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev);
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);
clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &);
bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &);
//proveRange and verRange //proveRange and verRange
//proveRange gives C, and mask such that \sumCi = C //proveRange gives C, and mask such that \sumCi = C

@ -195,6 +195,7 @@ namespace rct {
case RCTTypeSimple: case RCTTypeSimple:
case RCTTypeBulletproof: case RCTTypeBulletproof:
case RCTTypeBulletproof2: case RCTTypeBulletproof2:
case RCTTypeCLSAG:
return true; return true;
default: default:
return false; return false;
@ -207,6 +208,7 @@ namespace rct {
{ {
case RCTTypeBulletproof: case RCTTypeBulletproof:
case RCTTypeBulletproof2: case RCTTypeBulletproof2:
case RCTTypeCLSAG:
return true; return true;
default: default:
return false; return false;

@ -113,9 +113,14 @@ namespace rct {
struct multisig_out { struct multisig_out {
std::vector<key> c; // for all inputs std::vector<key> c; // for all inputs
std::vector<key> mu_p; // for all inputs
std::vector<key> c0; // for all inputs
BEGIN_SERIALIZE_OBJECT() BEGIN_SERIALIZE_OBJECT()
FIELD(c) FIELD(c)
FIELD(mu_p)
if (!mu_p.empty() && mu_p.size() != c.size())
return false;
END_SERIALIZE() END_SERIALIZE()
}; };
@ -163,6 +168,23 @@ namespace rct {
// FIELD(II) - not serialized, it can be reconstructed // FIELD(II) - not serialized, it can be reconstructed
END_SERIALIZE() END_SERIALIZE()
}; };
// CLSAG signature
struct clsag {
keyV s; // scalars
key c1;
key I; // signing key image
key D; // commitment key image
BEGIN_SERIALIZE_OBJECT()
FIELD(s)
FIELD(c1)
// FIELD(I) - not serialized, it can be reconstructed
FIELD(D)
END_SERIALIZE()
};
//contains the data for an Borromean sig //contains the data for an Borromean sig
// also contains the "Ci" values such that // also contains the "Ci" values such that
// \sum Ci = C // \sum Ci = C
@ -234,6 +256,7 @@ namespace rct {
RCTTypeSimple = 2, RCTTypeSimple = 2,
RCTTypeBulletproof = 3, RCTTypeBulletproof = 3,
RCTTypeBulletproof2 = 4, RCTTypeBulletproof2 = 4,
RCTTypeCLSAG = 5,
}; };
enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof };
struct RCTConfig { struct RCTConfig {
@ -262,7 +285,7 @@ namespace rct {
FIELD(type) FIELD(type)
if (type == RCTTypeNull) if (type == RCTTypeNull)
return ar.stream().good(); return ar.stream().good();
if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
return false; return false;
VARINT_FIELD(txnFee) VARINT_FIELD(txnFee)
// inputs/outputs not saved, only here for serialization help // inputs/outputs not saved, only here for serialization help
@ -291,7 +314,7 @@ namespace rct {
return false; return false;
for (size_t i = 0; i < outputs; ++i) for (size_t i = 0; i < outputs; ++i)
{ {
if (type == RCTTypeBulletproof2) if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
{ {
ar.begin_object(); ar.begin_object();
if (!typename Archive<W>::is_saving()) if (!typename Archive<W>::is_saving())
@ -338,6 +361,7 @@ namespace rct {
std::vector<rangeSig> rangeSigs; std::vector<rangeSig> rangeSigs;
std::vector<Bulletproof> bulletproofs; std::vector<Bulletproof> bulletproofs;
std::vector<mgSig> MGs; // simple rct has N, full has 1 std::vector<mgSig> MGs; // simple rct has N, full has 1
std::vector<clsag> CLSAGs;
keyV pseudoOuts; //C - for simple rct keyV pseudoOuts; //C - for simple rct
// when changing this function, update cryptonote::get_pruned_transaction_weight // when changing this function, update cryptonote::get_pruned_transaction_weight
@ -346,12 +370,12 @@ namespace rct {
{ {
if (type == RCTTypeNull) if (type == RCTTypeNull)
return ar.stream().good(); return ar.stream().good();
if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2) if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
return false; return false;
if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2) if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
{ {
uint32_t nbp = bulletproofs.size(); uint32_t nbp = bulletproofs.size();
if (type == RCTTypeBulletproof2) if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
VARINT_FIELD(nbp) VARINT_FIELD(nbp)
else else
FIELD(nbp) FIELD(nbp)
@ -386,55 +410,98 @@ namespace rct {
ar.end_array(); ar.end_array();
} }
ar.tag("MGs"); if (type == RCTTypeCLSAG)
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 || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? inputs : 1;
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_elements, MGs);
if (MGs.size() != mg_elements)
return false;
for (size_t i = 0; i < mg_elements; ++i)
{ {
// we save the MGs contents directly, because we want it to save its ar.tag("CLSAGs");
// arrays and matrices without the size prefixes, and the load can't
// know what size to expect if it's not in the data
ar.begin_object();
ar.tag("ss");
ar.begin_array(); ar.begin_array();
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss); PREPARE_CUSTOM_VECTOR_SERIALIZATION(inputs, CLSAGs);
if (MGs[i].ss.size() != mixin + 1) if (CLSAGs.size() != inputs)
return false; return false;
for (size_t j = 0; j < mixin + 1; ++j) for (size_t i = 0; i < inputs; ++i)
{ {
// we save the CLSAGs contents directly, because we want it to save its
// arrays without the size prefixes, and the load can't know what size
// to expect if it's not in the data
ar.begin_object();
ar.tag("s");
ar.begin_array(); ar.begin_array();
size_t mg_ss2_elements = ((type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? 1 : inputs) + 1; PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, CLSAGs[i].s);
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_ss2_elements, MGs[i].ss[j]); if (CLSAGs[i].s.size() != mixin + 1)
if (MGs[i].ss[j].size() != mg_ss2_elements)
return false; return false;
for (size_t k = 0; k < mg_ss2_elements; ++k) for (size_t j = 0; j <= mixin; ++j)
{ {
FIELDS(MGs[i].ss[j][k]) FIELDS(CLSAGs[i].s[j])
if (mg_ss2_elements - k > 1) if (mixin + 1 - j > 1)
ar.delimit_array(); ar.delimit_array();
} }
ar.end_array(); ar.end_array();
if (mixin + 1 - j > 1) ar.tag("c1");
ar.delimit_array(); FIELDS(CLSAGs[i].c1)
// CLSAGs[i].I not saved, it can be reconstructed
ar.tag("D");
FIELDS(CLSAGs[i].D)
ar.end_object();
if (inputs - i > 1)
ar.delimit_array();
} }
ar.end_array(); ar.end_array();
}
else
{
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 || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? inputs : 1;
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_elements, MGs);
if (MGs.size() != mg_elements)
return false;
for (size_t i = 0; i < mg_elements; ++i)
{
// we save the MGs contents directly, because we want it to save its
// arrays and matrices without the size prefixes, and the load can't
// know what size to expect if it's not in the data
ar.begin_object();
ar.tag("ss");
ar.begin_array();
PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss);
if (MGs[i].ss.size() != mixin + 1)
return false;
for (size_t j = 0; j < mixin + 1; ++j)
{
ar.begin_array();
size_t mg_ss2_elements = ((type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? 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;
for (size_t k = 0; k < mg_ss2_elements; ++k)
{
FIELDS(MGs[i].ss[j][k])
if (mg_ss2_elements - k > 1)
ar.delimit_array();
}
ar.end_array();
if (mixin + 1 - j > 1)
ar.delimit_array();
}
ar.end_array();
ar.tag("cc"); ar.tag("cc");
FIELDS(MGs[i].cc) FIELDS(MGs[i].cc)
// MGs[i].II not saved, it can be reconstructed // MGs[i].II not saved, it can be reconstructed
ar.end_object(); ar.end_object();
if (mg_elements - i > 1) if (mg_elements - i > 1)
ar.delimit_array(); ar.delimit_array();
}
ar.end_array();
} }
ar.end_array(); if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2)
{ {
ar.tag("pseudoOuts"); ar.tag("pseudoOuts");
ar.begin_array(); ar.begin_array();
@ -464,12 +531,12 @@ namespace rct {
keyV& get_pseudo_outs() keyV& get_pseudo_outs()
{ {
return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 ? p.pseudoOuts : pseudoOuts; return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
} }
keyV const& get_pseudo_outs() const keyV const& get_pseudo_outs() const
{ {
return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 ? p.pseudoOuts : pseudoOuts; return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
} }
BEGIN_SERIALIZE_OBJECT() BEGIN_SERIALIZE_OBJECT()
@ -636,6 +703,7 @@ VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig");
VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof");
VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki");
VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out");
VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag");
VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key, 0x90);
VARIANT_TAG(binary_archive, rct::key64, 0x91); VARIANT_TAG(binary_archive, rct::key64, 0x91);
@ -652,6 +720,7 @@ VARIANT_TAG(binary_archive, rct::rctSig, 0x9b);
VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c);
VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d);
VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e);
VARIANT_TAG(binary_archive, rct::clsag, 0x9f);
VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key, "rct_key");
VARIANT_TAG(json_archive, rct::key64, "rct_key64"); VARIANT_TAG(json_archive, rct::key64, "rct_key64");
@ -668,5 +737,6 @@ VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig");
VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof");
VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR");
VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out");
VARIANT_TAG(json_archive, rct::clsag, "rct_clsag");
#endif /* RCTTYPES_H */ #endif /* RCTTYPES_H */

@ -1306,7 +1306,7 @@ namespace cryptonote
case 1: res.pow_algorithm = "CNv1 (Cryptonight variant 1)"; break; case 1: res.pow_algorithm = "CNv1 (Cryptonight variant 1)"; break;
case 2: case 3: res.pow_algorithm = "CNv2 (Cryptonight variant 2)"; break; case 2: case 3: res.pow_algorithm = "CNv2 (Cryptonight variant 2)"; break;
case 4: case 5: res.pow_algorithm = "CNv4 (Cryptonight variant 4)"; break; case 4: case 5: res.pow_algorithm = "CNv4 (Cryptonight variant 4)"; break;
case 6: res.pow_algorithm = "RandomX"; break; case 6: case 7: res.pow_algorithm = "RandomX"; break;
default: res.pow_algorithm = "I'm not sure actually"; break; default: res.pow_algorithm = "I'm not sure actually"; break;
} }
if (res.is_background_mining_enabled) if (res.is_background_mining_enabled)

@ -1692,6 +1692,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str
destinations.size() + 1, destinations.size() + 1,
extra_size, extra_size,
m_wallet->use_fork_rules(8, 0), m_wallet->use_fork_rules(8, 0),
m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0),
m_wallet->get_base_fee(), m_wallet->get_base_fee(),
m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))),
m_wallet->get_fee_quantization_mask()); m_wallet->get_fee_quantization_mask());

@ -243,6 +243,22 @@ namespace
add_reason(reason, "tx was not relayed"); add_reason(reason, "tx was not relayed");
return reason; return reason;
} }
size_t get_num_outputs(const std::vector<cryptonote::tx_destination_entry> &dsts, const std::vector<tools::wallet2::transfer_details> &transfers, const std::vector<size_t> &selected_transfers)
{
size_t outputs = dsts.size();
uint64_t needed_money = 0;
for (const auto& dt: dsts)
needed_money += dt.amount;
uint64_t found_money = 0;
for(size_t idx: selected_transfers)
found_money += transfers[idx].amount();
if (found_money != needed_money)
++outputs; // change
if (outputs < 2)
++outputs; // extra 0 dummy output
return outputs;
}
} }
namespace namespace
@ -795,7 +811,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_
} }
} }
size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
{ {
size_t size = 0; size_t size = 0;
@ -829,8 +845,11 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
else else
size += (2*64*32+32+64*32) * n_outputs; size += (2*64*32+32+64*32) * n_outputs;
// MGs // MGs/CLSAGs
size += n_inputs * (64 * (mixin+1) + 32); if (clsag)
size += n_inputs * (32 * (mixin+1) + 64);
else
size += n_inputs * (64 * (mixin+1) + 32);
// mixRing - not serialized, can be reconstructed // mixRing - not serialized, can be reconstructed
/* size += 2 * 32 * (mixin+1) * n_inputs; */ /* size += 2 * 32 * (mixin+1) * n_inputs; */
@ -848,17 +867,17 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
return size; return size;
} }
size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
{ {
if (use_rct) if (use_rct)
return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof); return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
else else
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
} }
uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
{ {
size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
if (use_rct && bulletproof && n_outputs > 2) if (use_rct && bulletproof && n_outputs > 2)
{ {
const uint64_t bp_base = 368; const uint64_t bp_base = 368;
@ -879,6 +898,11 @@ uint8_t get_bulletproof_fork()
return 8; return 8;
} }
uint8_t get_clsag_fork()
{
return HF_VERSION_CLSAG;
}
uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask)
{ {
if (use_per_byte_fee) if (use_per_byte_fee)
@ -1752,6 +1776,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
case rct::RCTTypeSimple: case rct::RCTTypeSimple:
case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof:
case rct::RCTTypeBulletproof2: case rct::RCTTypeBulletproof2:
case rct::RCTTypeCLSAG:
return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev);
case rct::RCTTypeFull: case rct::RCTTypeFull:
return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev);
@ -7354,16 +7379,16 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
return sign_multisig_tx_to_file(exported_txs, filename, txids); return sign_multisig_tx_to_file(exported_txs, filename, txids);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const
{ {
if (use_per_byte_fee) if (use_per_byte_fee)
{ {
const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask);
} }
else else
{ {
const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); return calculate_fee(base_fee, estimated_tx_size, fee_multiplier);
} }
} }
@ -9066,7 +9091,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.extra = tx.extra; ptx.construction_data.extra = tx.extra;
ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true; ptx.construction_data.use_rct = true;
ptx.construction_data.rct_config = { tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1}; ptx.construction_data.rct_config = {
tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof,
use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1
};
ptx.construction_data.dests = dsts; ptx.construction_data.dests = dsts;
// record which subaddress indices are being used as inputs // record which subaddress indices are being used as inputs
ptx.construction_data.subaddr_account = subaddr_account; ptx.construction_data.subaddr_account = subaddr_account;
@ -9752,9 +9780,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const bool use_rct = use_fork_rules(4, 0); const bool use_rct = use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const rct::RCTConfig rct_config { const rct::RCTConfig rct_config {
bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
}; };
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee();
@ -9790,7 +9819,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
// early out if we know we can't make it anyway // early out if we know we can't make it anyway
// we could also check for being within FEE_PER_KB, but if the fee calculation // we could also check for being within FEE_PER_KB, but if the fee calculation
// ever changes, this might be missed, so let this go through // ever changes, this might be missed, so let this go through
const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)); const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag));
uint64_t balance_subtotal = 0; uint64_t balance_subtotal = 0;
uint64_t unlocked_balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0;
for (uint32_t index_minor : subaddr_indices) for (uint32_t index_minor : subaddr_indices)
@ -9808,8 +9837,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Candidate subaddress index for spending: " << i); LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
// determine threshold for fractional amount // determine threshold for fractional amount
const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof); const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof); const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@ -9906,7 +9935,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
{ {
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
// will get us a known fee. // will get us a known fee.
uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
if (!preferred_inputs.empty()) if (!preferred_inputs.empty())
{ {
@ -10018,7 +10047,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
} }
else else
{ {
while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit))
{ {
// we can fully pay that destination // we can fully pay that destination
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
@ -10030,7 +10059,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
++original_output_index; ++original_output_index;
} }
if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
// we can partially fill that destination // we can partially fill that destination
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
@ -10054,7 +10083,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
} }
else else
{ {
const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof); const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit);
} }
@ -10064,7 +10093,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
cryptonote::transaction test_tx; cryptonote::transaction test_tx;
pending_tx test_ptx; pending_tx test_ptx;
needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
uint64_t inputs = 0, outputs = needed_fee; uint64_t inputs = 0, outputs = needed_fee;
for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
@ -10313,10 +10343,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
// determine threshold for fractional amount // determine threshold for fractional amount
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof); const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof); const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@ -10422,9 +10453,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE);
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const bool clsag = use_fork_rules(get_clsag_fork(), 0);
const rct::RCTConfig rct_config { const rct::RCTConfig rct_config {
bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
}; };
const uint64_t base_fee = get_base_fee(); const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@ -10453,7 +10485,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
uint64_t fee_dust_threshold; uint64_t fee_dust_threshold;
if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) if (use_fork_rules(HF_VERSION_PER_BYTE_FEE))
{ {
const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof); const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask);
} }
else else
@ -10484,14 +10516,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// here, check if we need to sent tx and start a new one // here, check if we need to sent tx and start a new one
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
<< upper_transaction_weight_limit); << upper_transaction_weight_limit);
const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof); const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag);
bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
if (try_tx) { if (try_tx) {
cryptonote::transaction test_tx; cryptonote::transaction test_tx;
pending_tx test_ptx; pending_tx test_ptx;
needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
// add N - 1 outputs for correct initial fee estimation // add N - 1 outputs for correct initial fee estimation
for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i)
@ -11353,7 +11386,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
crypto::secret_key scalar1; crypto::secret_key scalar1;
crypto::derivation_to_scalar(found_derivation, n, scalar1); crypto::derivation_to_scalar(found_derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2); rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
const rct::key C = tx.rct_signatures.outPk[n].mask; const rct::key C = tx.rct_signatures.outPk[n].mask;
rct::key Ctmp; rct::key Ctmp;
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
@ -11997,7 +12030,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
crypto::secret_key shared_secret; crypto::secret_key shared_secret;
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2); rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
amount = rct::h2d(ecdh_info.amount); amount = rct::h2d(ecdh_info.amount);
} }
total += amount; total += amount;
@ -14036,8 +14069,9 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i
n_outputs = 2; // extra dummy output n_outputs = 2; // extra dummy output
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); const bool clsag = use_fork_rules(get_clsag_fork(), 0);
uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
return std::make_pair(size, weight); return std::make_pair(size, weight);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------

@ -1400,7 +1400,7 @@ private:
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const;
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
uint64_t get_base_fee(); uint64_t get_base_fee();
uint64_t get_fee_quantization_mask(); uint64_t get_fee_quantization_mask();

@ -44,6 +44,7 @@ set(core_tests_sources
v2_tests.cpp v2_tests.cpp
rct.cpp rct.cpp
bulletproofs.cpp bulletproofs.cpp
rct2.cpp
wallet_tools.cpp) wallet_tools.cpp)
set(core_tests_headers set(core_tests_headers
@ -64,6 +65,7 @@ set(core_tests_headers
v2_tests.h v2_tests.h
rct.h rct.h
bulletproofs.h bulletproofs.h
rct2.h
wallet_tools.h) wallet_tools.h)
add_executable(core_tests add_executable(core_tests

@ -42,7 +42,7 @@ using namespace cryptonote;
// Tests // Tests
bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& events, bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx, const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx,
const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const
{ {
@ -157,7 +157,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
crypto::derivation_to_scalar(derivation, o, amount_key); crypto::derivation_to_scalar(derivation, o, amount_key);
rct::key rct_tx_mask; rct::key rct_tx_mask;
const uint8_t type = rct_txes.back().rct_signatures.type; const uint8_t type = rct_txes.back().rct_signatures.type;
if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2) if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
else else
rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
@ -173,7 +173,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account, CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs,
10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 10), crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 10),
false, "Failed to generate block"); false, "Failed to generate block");
if (!valid) if (!valid)
@ -205,13 +205,22 @@ bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size
return true; return true;
} }
bool gen_bp_tx_valid_1::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_valid_1_before_12::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
const size_t bp_sizes[] = {1, (size_t)-1}; const size_t bp_sizes[] = {1, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1"); }); return generate_with(events, mixin, 1, amounts_paid, true, rct_config, 11, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1_before_12"); });
}
bool gen_bp_tx_invalid_1_from_12::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
const size_t bp_sizes[] = {1, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 12, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_invalid_1_from_12"); });
} }
bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) const
@ -219,7 +228,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) cons
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL); return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
} }
bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
@ -228,7 +237,7 @@ bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const size_t bp_sizes[] = {2, (size_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); }); return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); });
} }
bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
@ -237,7 +246,7 @@ bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1}; const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1};
const size_t bp_sizes[] = {4, (size_t)-1}; const size_t bp_sizes[] = {4, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); }); return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); });
} }
bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
@ -246,7 +255,7 @@ bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1}; const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1};
const size_t bp_sizes[] = {16, (size_t)-1}; const size_t bp_sizes[] = {16, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); }); return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); });
} }
bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) const
@ -254,7 +263,7 @@ bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) co
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL); return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
} }
bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) const
@ -262,7 +271,7 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) co
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL); return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
} }
bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const
@ -271,7 +280,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) c
const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1}; const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1};
const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 }, {rct::RangeProofPaddedBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 }, {rct::RangeProofPaddedBulletproof, 0 } };
return generate_with(events, mixin, 2, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); }); return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); });
} }
bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_entry>& events) const bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_entry>& events) const
@ -279,7 +288,7 @@ bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_e
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}}; const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}};
return generate_with(events, mixin, 3, amounts_paid, false, rct_config, NULL, NULL); return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
} }
bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry>& events) const bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry>& events) const
@ -288,7 +297,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry
const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1}; const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1};
const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}}; const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}};
const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1};
return generate_with(events, mixin, 4, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); }); return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); });
} }
bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>& events) const bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>& events) const
@ -297,8 +306,8 @@ bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
tx.rct_signatures.p.bulletproofs.pop_back(); tx.rct_signatures.p.bulletproofs.pop_back();
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
@ -312,8 +321,8 @@ bool gen_bp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& eve
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1}; const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
tx.rct_signatures.p.bulletproofs.clear(); tx.rct_signatures.p.bulletproofs.clear();
return true; return true;
}); });
@ -325,8 +334,8 @@ bool gen_bp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>&
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
tx.rct_signatures.p.bulletproofs.push_back(tx.rct_signatures.p.bulletproofs.back()); tx.rct_signatures.p.bulletproofs.push_back(tx.rct_signatures.p.bulletproofs.back());
return true; return true;
@ -339,8 +348,8 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& eve
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
tx.rct_signatures.p.bulletproofs.back() = rct::bulletproof_PROVE(1000, rct::skGen()); tx.rct_signatures.p.bulletproofs.back() = rct::bulletproof_PROVE(1000, rct::skGen());
return true; return true;
@ -353,7 +362,18 @@ bool gen_bp_tx_invalid_borromean_type::generate(std::vector<test_event_entry>& e
const size_t mixin = 10; const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 0 } }; const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 0 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){ return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 11, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
return true;
});
}
bool gen_bp_tx_invalid_bulletproof2_type::generate(std::vector<test_event_entry>& events) const
{
DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_bulletproof2_type");
const size_t mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
return true; return true;
}); });
} }

@ -82,7 +82,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base
} }
bool generate_with(std::vector<test_event_entry>& events, size_t mixin, bool generate_with(std::vector<test_event_entry>& events, size_t mixin,
size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx, const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx,
const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const; const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const;
@ -95,99 +95,119 @@ private:
template<> template<>
struct get_test_options<gen_bp_tx_validation_base> { struct get_test_options<gen_bp_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(10, 73), std::make_pair(0, 0)}; const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(12, 73), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks, 0
};
};
template<uint8_t test_version = HF_VERSION_CLSAG>
struct get_bp_versioned_test_options: public get_test_options<gen_bp_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)};
const cryptonote::test_options test_options = { const cryptonote::test_options test_options = {
hard_forks, 0 hard_forks, 0
}; };
}; };
// valid // valid
struct gen_bp_tx_valid_1 : public gen_bp_tx_validation_base struct gen_bp_tx_valid_1_before_12 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_valid_1>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_valid_1_before_12>: public get_bp_versioned_test_options<11> {};
struct gen_bp_tx_invalid_1_from_12 : public gen_bp_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_bp_tx_invalid_1_from_12>: public get_bp_versioned_test_options<12> {};
struct gen_bp_tx_invalid_1_1 : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_1_1 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_1_1>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_1_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_valid_2 : public gen_bp_tx_validation_base struct gen_bp_tx_valid_2 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_valid_2>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_valid_2>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_valid_3 : public gen_bp_tx_validation_base struct gen_bp_tx_valid_3 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_valid_3>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_valid_3>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_valid_16 : public gen_bp_tx_validation_base struct gen_bp_tx_valid_16 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_valid_16>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_valid_16>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_4_2_1 : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_4_2_1 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_4_2_1>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_4_2_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_16_16 : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_16_16 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_16_16>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_16_16>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_txs_valid_2_and_2 : public gen_bp_tx_validation_base struct gen_bp_txs_valid_2_and_2 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_txs_valid_2_and_2>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_txs_valid_2_and_2>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bp_tx_validation_base struct gen_bp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_txs_invalid_2_and_8_2_and_16_16_1>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_txs_invalid_2_and_8_2_and_16_16_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_txs_valid_2_and_3_and_2_and_4 : public gen_bp_tx_validation_base struct gen_bp_txs_valid_2_and_3_and_2_and_4 : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_txs_valid_2_and_3_and_2_and_4>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_txs_valid_2_and_3_and_2_and_4>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_not_enough_proofs : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_not_enough_proofs : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_not_enough_proofs>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_not_enough_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_empty_proofs : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_empty_proofs : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_empty_proofs>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_empty_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_too_many_proofs : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_too_many_proofs : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_too_many_proofs>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_too_many_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_wrong_amount : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_wrong_amount : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_wrong_amount>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_wrong_amount>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
struct gen_bp_tx_invalid_borromean_type : public gen_bp_tx_validation_base struct gen_bp_tx_invalid_borromean_type : public gen_bp_tx_validation_base
{ {
bool generate(std::vector<test_event_entry>& events) const; bool generate(std::vector<test_event_entry>& events) const;
}; };
template<> struct get_test_options<gen_bp_tx_invalid_borromean_type>: public get_test_options<gen_bp_tx_validation_base> {}; template<> struct get_test_options<gen_bp_tx_invalid_borromean_type>: public get_bp_versioned_test_options<9> {};
struct gen_bp_tx_invalid_bulletproof2_type : public gen_bp_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_bp_tx_invalid_bulletproof2_type>: public get_bp_versioned_test_options<HF_VERSION_CLSAG + 1> {};

@ -248,7 +248,8 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_no_signers); GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_no_signers);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_23_no_threshold); GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_23_no_threshold);
GENERATE_AND_PLAY(gen_bp_tx_valid_1); GENERATE_AND_PLAY(gen_bp_tx_valid_1_before_12);
GENERATE_AND_PLAY(gen_bp_tx_invalid_1_from_12);
GENERATE_AND_PLAY(gen_bp_tx_invalid_1_1); GENERATE_AND_PLAY(gen_bp_tx_invalid_1_1);
GENERATE_AND_PLAY(gen_bp_tx_valid_2); GENERATE_AND_PLAY(gen_bp_tx_valid_2);
GENERATE_AND_PLAY(gen_bp_tx_valid_3); GENERATE_AND_PLAY(gen_bp_tx_valid_3);
@ -263,6 +264,9 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_bp_tx_invalid_too_many_proofs); GENERATE_AND_PLAY(gen_bp_tx_invalid_too_many_proofs);
GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount); GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount);
GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type); GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type);
GENERATE_AND_PLAY(gen_bp_tx_invalid_bulletproof2_type);
GENERATE_AND_PLAY(gen_rct2_tx_clsag_malleability);
GENERATE_AND_PLAY(gen_block_low_coinbase); GENERATE_AND_PLAY(gen_block_low_coinbase);

@ -43,6 +43,7 @@
#include "rct.h" #include "rct.h"
#include "multisig.h" #include "multisig.h"
#include "bulletproofs.h" #include "bulletproofs.h"
#include "rct2.h"
/************************************************************************/ /************************************************************************/
/* */ /* */
/************************************************************************/ /************************************************************************/

@ -163,9 +163,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start); MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start);
// create 8 miner accounts, and have them mine the next 8 blocks // create 16 miner accounts, and have them mine the next 16 blocks
// they will have a coinbase with a single out that's pseudo rct // they will have a coinbase with a single out that's pseudo rct
constexpr size_t n_coinbases = 8; constexpr size_t n_coinbases = 16;
cryptonote::account_base miner_accounts[n_coinbases]; cryptonote::account_base miner_accounts[n_coinbases];
const cryptonote::block *prev_block = &blk_0; const cryptonote::block *prev_block = &blk_0;
cryptonote::block blocks[n_coinbases]; cryptonote::block blocks[n_coinbases];
@ -175,7 +175,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n]; account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n];
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account, CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
4, 4, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long 10, 10, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block"); false, "Failed to generate block");
events.push_back(blocks[n]); events.push_back(blocks[n]);
@ -191,7 +191,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
cryptonote::block blk; cryptonote::block blk;
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0], CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0],
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long 10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block"); false, "Failed to generate block");
events.push_back(blk); events.push_back(blk);
@ -363,7 +363,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
#endif #endif
std::vector<crypto::secret_key> additional_tx_secret_keys; std::vector<crypto::secret_key> additional_tx_secret_keys;
auto sources_copy = sources; auto sources_copy = sources;
r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofBorromean, 0 }, msoutp); r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 2 }, msoutp);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
#ifndef NO_MULTISIG #ifndef NO_MULTISIG
@ -453,7 +453,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
crypto::secret_key scalar1; crypto::secret_key scalar1;
crypto::derivation_to_scalar(derivation, n, scalar1); crypto::derivation_to_scalar(derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2); rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
rct::key C = tx.rct_signatures.outPk[n].mask; rct::key C = tx.rct_signatures.outPk[n].mask;
rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount"); CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
@ -476,196 +476,196 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
bool gen_multisig_tx_valid_22_1_2::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_22_1_2::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_22_2_1::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_22_2_1::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL);
} }
bool gen_multisig_tx_valid_33_1_23::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_33_1_23::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL);
} }
bool gen_multisig_tx_valid_33_3_21::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_33_3_21::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL);
} }
bool gen_multisig_tx_valid_23_1_2::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_23_1_2::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_23_1_3::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_23_1_3::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL);
} }
bool gen_multisig_tx_valid_23_2_1::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_23_2_1::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL);
} }
bool gen_multisig_tx_valid_23_2_3::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_23_2_3::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL);
} }
bool gen_multisig_tx_valid_45_1_234::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_45_1_234::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL);
} }
bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL); return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL);
} }
bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL);
} }
bool gen_multisig_tx_valid_24_1_2::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_24_1_2::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_24_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_24_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); return generate_with(events, 4, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_25_1_2::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_25_1_2::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_25_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_25_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); return generate_with(events, 4, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_valid_48_1_234::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_48_1_234::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL);
} }
bool gen_multisig_tx_valid_48_1_234_many_inputs::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_valid_48_1_234_many_inputs::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); return generate_with(events, 4, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_24_1_no_signers::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_24_1_no_signers::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 4, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 2, 4, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_25_1_no_signers::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_25_1_no_signers::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 5, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 2, 5, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_48_1_no_signers::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_48_1_no_signers::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {}, NULL, NULL);
} }
bool gen_multisig_tx_invalid_48_1_23_no_threshold::generate(std::vector<test_event_entry>& events) const bool gen_multisig_tx_invalid_48_1_23_no_threshold::generate(std::vector<test_event_entry>& events) const
{ {
const size_t mixin = 4; const size_t mixin = 10;
const uint64_t amount_paid = 10000; const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL); return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL);
} }

@ -82,7 +82,7 @@ private:
template<> template<>
struct get_test_options<gen_multisig_tx_validation_base> { struct get_test_options<gen_multisig_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)}; const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(10, 1), std::make_pair(0, 0)};
const cryptonote::test_options test_options = { const cryptonote::test_options test_options = {
hard_forks, 0 hard_forks, 0
}; };

@ -133,7 +133,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry
crypto::secret_key amount_key; crypto::secret_key amount_key;
crypto::derivation_to_scalar(derivation, o, amount_key); crypto::derivation_to_scalar(derivation, o, amount_key);
const uint8_t type = rct_txes[n].rct_signatures.type; const uint8_t type = rct_txes[n].rct_signatures.type;
if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2) if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default")); rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));
else else
rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default")); rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));

@ -0,0 +1,224 @@
// Copyright (c) 2014-2019, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "ringct/rctSigs.h"
#include "ringct/bulletproofs.h"
#include "chaingen.h"
#include "rct2.h"
#include "device/device.hpp"
using namespace epee;
using namespace crypto;
using namespace cryptonote;
//----------------------------------------------------------------------------------------------------------------------
// Tests
bool gen_rct2_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx,
const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const
{
uint64_t ts_start = 1338224400;
GENERATE_ACCOUNT(miner_account);
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
// create 12 miner accounts, and have them mine the next 12 blocks
cryptonote::account_base miner_accounts[12];
const cryptonote::block *prev_block = &blk_0;
cryptonote::block blocks[12 + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW];
for (size_t n = 0; n < 12; ++n) {
miner_accounts[n].generate();
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n],
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
2, 2, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
false, "Failed to generate block");
events.push_back(blocks[n]);
prev_block = blocks + n;
LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx));
}
// rewind
cryptonote::block blk_r, blk_last;
{
blk_last = blocks[11];
for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i)
{
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[12+i], blk_last, miner_account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
2, 2, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
false, "Failed to generate block");
events.push_back(blocks[12+i]);
blk_last = blocks[12+i];
}
blk_r = blk_last;
}
// create 4 txes from these miners in another block, to generate some rct outputs
std::vector<transaction> rct_txes;
cryptonote::block blk_txes;
std::vector<crypto::hash> starting_rct_tx_hashes;
static const uint64_t input_amounts_available[] = {5000000000000, 30000000000000, 100000000000, 80000000000};
for (size_t n = 0; n < n_txes; ++n)
{
std::vector<tx_source_entry> sources;
sources.resize(1);
tx_source_entry& src = sources.back();
const uint64_t needed_amount = input_amounts_available[n];
src.amount = input_amounts_available[n];
size_t real_index_in_tx = 0;
for (size_t m = 0; m <= mixin; ++m) {
size_t index_in_tx = 0;
for (size_t i = 0; i < blocks[m].miner_tx.vout.size(); ++i)
if (blocks[m].miner_tx.vout[i].amount == needed_amount)
index_in_tx = i;
CHECK_AND_ASSERT_MES(blocks[m].miner_tx.vout[index_in_tx].amount == needed_amount, false, "Expected amount not found");
src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount);
if (m == n)
real_index_in_tx = index_in_tx;
}
src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx);
src.real_output = n;
src.real_output_in_tx_index = real_index_in_tx;
src.mask = rct::identity();
src.rct = false;
//fill outputs entry
tx_destination_entry td;
td.addr = miner_accounts[n].get_keys().m_account_address;
std::vector<tx_destination_entry> destinations;
for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
{
td.amount = amounts_paid[o];
destinations.push_back(td);
}
if (pre_tx && !pre_tx(sources, destinations, n))
{
MDEBUG("pre_tx returned failure");
return false;
}
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0};
rct_txes.resize(rct_txes.size() + 1);
bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
if (post_tx && !post_tx(rct_txes.back(), n))
{
MDEBUG("post_tx returned failure");
return false;
}
//events.push_back(rct_txes.back());
starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes.back()));
LOG_PRINT_L0("Test tx: " << obj_to_json_str(rct_txes.back()));
for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
{
crypto::key_derivation derivation;
bool r = crypto::generate_key_derivation(destinations[o].addr.m_view_public_key, tx_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
crypto::secret_key amount_key;
crypto::derivation_to_scalar(derivation, o, amount_key);
rct::key rct_tx_mask;
const uint8_t type = rct_txes.back().rct_signatures.type;
if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
else
rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
}
while (amounts_paid[0] != (size_t)-1)
++amounts_paid;
++amounts_paid;
}
if (!valid)
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(rct_txes);
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs,
hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 10),
false, "Failed to generate block");
if (!valid)
DO_CALLBACK(events, "mark_invalid_block");
events.push_back(blk_txes);
blk_last = blk_txes;
return true;
}
bool gen_rct2_tx_validation_base::check_bp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const
{
DEFINE_TESTS_ERROR_CONTEXT(context);
CHECK_TEST_CONDITION(tx.version >= 2);
CHECK_TEST_CONDITION(rct::is_rct_bulletproof(tx.rct_signatures.type));
size_t n_sizes = 0, n_amounts = 0;
for (size_t n = 0; n < tx_idx; ++n)
{
while (sizes[0] != (size_t)-1)
++sizes;
++sizes;
}
while (sizes[n_sizes] != (size_t)-1)
n_amounts += sizes[n_sizes++];
CHECK_TEST_CONDITION(tx.rct_signatures.p.bulletproofs.size() == n_sizes);
CHECK_TEST_CONDITION(rct::n_bulletproof_max_amounts(tx.rct_signatures.p.bulletproofs) == n_amounts);
for (size_t n = 0; n < n_sizes; ++n)
CHECK_TEST_CONDITION(rct::n_bulletproof_max_amounts(tx.rct_signatures.p.bulletproofs[n]) == sizes[n]);
return true;
}
bool gen_rct2_tx_clsag_malleability::generate(std::vector<test_event_entry>& events) const
{
DEFINE_TESTS_ERROR_CONTEXT("gen_rct_tx_clsag_malleability");
const int mixin = 10;
const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx) {
CHECK_TEST_CONDITION(tx.version == 2);
CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeCLSAG);
CHECK_TEST_CONDITION(!tx.rct_signatures.p.CLSAGs.empty());
rct::key x;
CHECK_TEST_CONDITION(epee::string_tools::hex_to_pod("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", x));
tx.rct_signatures.p.CLSAGs[0].D = rct::addKeys(tx.rct_signatures.p.CLSAGs[0].D, x);
return true;
});
}

@ -0,0 +1,116 @@
// Copyright (c) 2014-2019, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include "chaingen.h"
struct gen_rct2_tx_validation_base : public test_chain_unit_base
{
gen_rct2_tx_validation_base()
: m_invalid_tx_index(0)
, m_invalid_block_index(0)
{
REGISTER_CALLBACK_METHOD(gen_rct2_tx_validation_base, mark_invalid_tx);
REGISTER_CALLBACK_METHOD(gen_rct2_tx_validation_base, mark_invalid_block);
}
bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/)
{
if (m_invalid_tx_index == event_idx)
return tvc.m_verifivation_failed;
else
return !tvc.m_verifivation_failed && tx_added;
}
bool check_tx_verification_context_array(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/)
{
size_t failed = 0;
for (const cryptonote::tx_verification_context &tvc: tvcs)
if (tvc.m_verifivation_failed)
++failed;
if (m_invalid_tx_index == event_idx)
return failed > 0;
else
return failed == 0 && tx_added == tvcs.size();
}
bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/)
{
if (m_invalid_block_index == event_idx)
return bvc.m_verifivation_failed;
else
return !bvc.m_verifivation_failed;
}
bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_invalid_block_index = ev_index + 1;
return true;
}
bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_invalid_tx_index = ev_index + 1;
return true;
}
bool generate_with(std::vector<test_event_entry>& events, size_t mixin,
size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx,
const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const;
bool check_bp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const;
private:
size_t m_invalid_tx_index;
size_t m_invalid_block_index;
};
template<>
struct get_test_options<gen_rct2_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(12, 73), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks, 0
};
};
template<uint8_t test_version = 12>
struct get_rct2_versioned_test_options: public get_test_options<gen_rct2_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks, 0
};
};
struct gen_rct2_tx_clsag_malleability : public gen_rct2_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_rct2_tx_clsag_malleability>: public get_rct2_versioned_test_options<HF_VERSION_CLSAG + 1> {};

@ -135,7 +135,7 @@ class TransferTest():
assert res.fee > 0 assert res.fee > 0
fee = res.fee fee = res.fee
assert len(res.tx_blob) > 0 assert len(res.tx_blob) > 0
blob_size = len(res.tx_blob) // 2 tx_weight = res.weight
assert len(res.tx_metadata) == 0 assert len(res.tx_metadata) == 0
assert len(res.multisig_txset) == 0 assert len(res.multisig_txset) == 0
assert len(res.unsigned_txset) == 0 assert len(res.unsigned_txset) == 0
@ -144,7 +144,7 @@ class TransferTest():
res = daemon.get_fee_estimate(10) res = daemon.get_fee_estimate(10)
assert res.fee > 0 assert res.fee > 0
assert res.quantization_mask > 0 assert res.quantization_mask > 0
expected_fee = (res.fee * 1 * blob_size + res.quantization_mask - 1) // res.quantization_mask * res.quantization_mask expected_fee = (res.fee * 1 * tx_weight + res.quantization_mask - 1) // res.quantization_mask * res.quantization_mask
assert abs(1 - fee / expected_fee) < 0.01 assert abs(1 - fee / expected_fee) < 0.01
self.wallet[0].refresh() self.wallet[0].refresh()

@ -51,11 +51,15 @@ enum test_op
op_scalarmult8_p3, op_scalarmult8_p3,
op_ge_dsm_precomp, op_ge_dsm_precomp,
op_ge_double_scalarmult_base_vartime, op_ge_double_scalarmult_base_vartime,
op_ge_triple_scalarmult_base_vartime,
op_ge_double_scalarmult_precomp_vartime, op_ge_double_scalarmult_precomp_vartime,
op_ge_triple_scalarmult_precomp_vartime,
op_ge_double_scalarmult_precomp_vartime2, op_ge_double_scalarmult_precomp_vartime2,
op_addKeys2, op_addKeys2,
op_addKeys3, op_addKeys3,
op_addKeys3_2, op_addKeys3_2,
op_addKeys_aGbBcC,
op_addKeys_aAbBcC,
op_isInMainSubgroup, op_isInMainSubgroup,
op_zeroCommitUncached, op_zeroCommitUncached,
}; };
@ -70,15 +74,20 @@ public:
{ {
scalar0 = rct::skGen(); scalar0 = rct::skGen();
scalar1 = rct::skGen(); scalar1 = rct::skGen();
scalar2 = rct::skGen();
point0 = rct::scalarmultBase(rct::skGen()); point0 = rct::scalarmultBase(rct::skGen());
point1 = rct::scalarmultBase(rct::skGen()); point1 = rct::scalarmultBase(rct::skGen());
point2 = rct::scalarmultBase(rct::skGen());
if (ge_frombytes_vartime(&p3_0, point0.bytes) != 0) if (ge_frombytes_vartime(&p3_0, point0.bytes) != 0)
return false; return false;
if (ge_frombytes_vartime(&p3_1, point1.bytes) != 0) if (ge_frombytes_vartime(&p3_1, point1.bytes) != 0)
return false; return false;
if (ge_frombytes_vartime(&p3_2, point2.bytes) != 0)
return false;
ge_p3_to_cached(&cached, &p3_0); ge_p3_to_cached(&cached, &p3_0);
rct::precomp(precomp0, point0); rct::precomp(precomp0, point0);
rct::precomp(precomp1, point1); rct::precomp(precomp1, point1);
rct::precomp(precomp2, point2);
return true; return true;
} }
@ -109,11 +118,15 @@ public:
case op_scalarmult8_p3: rct::scalarmult8(p3_0,point0); break; case op_scalarmult8_p3: rct::scalarmult8(p3_0,point0); break;
case op_ge_dsm_precomp: ge_dsm_precomp(dsmp, &p3_0); break; case op_ge_dsm_precomp: ge_dsm_precomp(dsmp, &p3_0); break;
case op_ge_double_scalarmult_base_vartime: ge_double_scalarmult_base_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes); break; case op_ge_double_scalarmult_base_vartime: ge_double_scalarmult_base_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes); break;
case op_ge_triple_scalarmult_base_vartime: ge_triple_scalarmult_base_vartime(&tmp_p2, scalar0.bytes, scalar1.bytes, precomp1, scalar2.bytes, precomp2); break;
case op_ge_double_scalarmult_precomp_vartime: ge_double_scalarmult_precomp_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes, precomp0); break; case op_ge_double_scalarmult_precomp_vartime: ge_double_scalarmult_precomp_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes, precomp0); break;
case op_ge_triple_scalarmult_precomp_vartime: ge_triple_scalarmult_precomp_vartime(&tmp_p2, scalar0.bytes, precomp0, scalar1.bytes, precomp1, scalar2.bytes, precomp2); break;
case op_ge_double_scalarmult_precomp_vartime2: ge_double_scalarmult_precomp_vartime2(&tmp_p2, scalar0.bytes, precomp0, scalar1.bytes, precomp1); break; case op_ge_double_scalarmult_precomp_vartime2: ge_double_scalarmult_precomp_vartime2(&tmp_p2, scalar0.bytes, precomp0, scalar1.bytes, precomp1); break;
case op_addKeys2: rct::addKeys2(key, scalar0, scalar1, point0); break; case op_addKeys2: rct::addKeys2(key, scalar0, scalar1, point0); break;
case op_addKeys3: rct::addKeys3(key, scalar0, point0, scalar1, precomp1); break; case op_addKeys3: rct::addKeys3(key, scalar0, point0, scalar1, precomp1); break;
case op_addKeys3_2: rct::addKeys3(key, scalar0, precomp0, scalar1, precomp1); break; case op_addKeys3_2: rct::addKeys3(key, scalar0, precomp0, scalar1, precomp1); break;
case op_addKeys_aGbBcC: rct::addKeys_aGbBcC(key, scalar0, scalar1, precomp1, scalar2, precomp2); break;
case op_addKeys_aAbBcC: rct::addKeys_aAbBcC(key, scalar0, precomp0, scalar1, precomp1, scalar2, precomp2); break;
case op_isInMainSubgroup: rct::isInMainSubgroup(point0); break; case op_isInMainSubgroup: rct::isInMainSubgroup(point0); break;
case op_zeroCommitUncached: rct::zeroCommit(9001); break; case op_zeroCommitUncached: rct::zeroCommit(9001); break;
case op_zeroCommitCached: rct::zeroCommit(9000); break; case op_zeroCommitCached: rct::zeroCommit(9000); break;
@ -123,9 +136,9 @@ public:
} }
private: private:
rct::key scalar0, scalar1; rct::key scalar0, scalar1, scalar2;
rct::key point0, point1; rct::key point0, point1, point2;
ge_p3 p3_0, p3_1; ge_p3 p3_0, p3_1, p3_2;
ge_cached cached; ge_cached cached;
ge_dsmp precomp0, precomp1; ge_dsmp precomp0, precomp1, precomp2;
}; };

@ -60,6 +60,8 @@
#include "bulletproof.h" #include "bulletproof.h"
#include "crypto_ops.h" #include "crypto_ops.h"
#include "multiexp.h" #include "multiexp.h"
#include "sig_mlsag.h"
#include "sig_clsag.h"
namespace po = boost::program_options; namespace po = boost::program_options;
@ -213,6 +215,21 @@ int main(int argc, char** argv)
TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 32); TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 32);
TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384); TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 4, 2, 2); // MLSAG verification
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 8, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 16, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 32, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 64, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 128, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 256, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 4, 2, 2); // CLSAG verification
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 8, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 16, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 32, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 64, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 128, 2, 2);
TEST_PERFORMANCE3(filter, p, test_sig_clsag, 256, 2, 2);
TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false); TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true); TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
@ -257,11 +274,15 @@ int main(int argc, char** argv)
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_scalarmult8_p3); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_scalarmult8_p3);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_dsm_precomp); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_dsm_precomp);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_base_vartime); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_base_vartime);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_triple_scalarmult_base_vartime);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_triple_scalarmult_precomp_vartime);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime2); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime2);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys2); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys2);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3_2); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3_2);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys_aGbBcC);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys_aAbBcC);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_isInMainSubgroup); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_isInMainSubgroup);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitUncached); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitUncached);
TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitCached); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitCached);

@ -0,0 +1,172 @@
// Copyright (c) 2014-2020, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include "ringct/rctSigs.h"
#include "ringct/rctTypes.h"
#include "device/device.hpp"
using namespace rct;
template<size_t a_N, size_t a_T, size_t a_w>
class test_sig_clsag
{
public:
static const size_t loop_count = 1000;
static const size_t N = a_N;
static const size_t T = a_T;
static const size_t w = a_w;
bool init()
{
pubs.reserve(N);
pubs.resize(N);
r = keyV(w); // M[l[u]] = Com(0,r[u])
a = keyV(w); // P[l[u]] = Com(a[u],s[u])
s = keyV(w);
Q = keyV(T); // Q[j] = Com(b[j],t[j])
b = keyV(T);
t = keyV(T);
// Random keys
key temp;
for (size_t k = 0; k < N; k++)
{
skpkGen(temp,pubs[k].dest);
skpkGen(temp,pubs[k].mask);
}
// Signing and commitment keys (assumes fixed signing indices 0,1,...,w-1 for this test)
// TODO: random signing indices
C_offsets = keyV(w); // P[l[u]] - C_offsets[u] = Com(0,s[u]-s1[u])
s1 = keyV(w);
key a_sum = zero();
key s1_sum = zero();
messages = keyV(w);
for (size_t u = 0; u < w; u++)
{
skpkGen(r[u],pubs[u].dest); // M[u] = Com(0,r[u])
a[u] = skGen(); // P[u] = Com(a[u],s[u])
s[u] = skGen();
addKeys2(pubs[u].mask,s[u],a[u],H);
s1[u] = skGen(); // C_offsets[u] = Com(a[u],s1[u])
addKeys2(C_offsets[u],s1[u],a[u],H);
sc_add(a_sum.bytes,a_sum.bytes,a[u].bytes);
sc_add(s1_sum.bytes,s1_sum.bytes,s1[u].bytes);
messages[u] = skGen();
}
// Outputs
key b_sum = zero();
key t_sum = zero();
for (size_t j = 0; j < T-1; j++)
{
b[j] = skGen(); // Q[j] = Com(b[j],t[j])
t[j] = skGen();
addKeys2(Q[j],t[j],b[j],H);
sc_add(b_sum.bytes,b_sum.bytes,b[j].bytes);
sc_add(t_sum.bytes,t_sum.bytes,t[j].bytes);
}
// Value/mask balance for Q[T-1]
sc_sub(b[T-1].bytes,a_sum.bytes,b_sum.bytes);
sc_sub(t[T-1].bytes,s1_sum.bytes,t_sum.bytes);
addKeys2(Q[T-1],t[T-1],b[T-1],H);
// Build proofs
sigs.reserve(w);
sigs.resize(0);
ctkey sk;
for (size_t u = 0; u < w; u++)
{
sk.dest = r[u];
sk.mask = s[u];
sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,NULL,u,hw::get_device("default")));
}
return true;
}
bool test()
{
for (size_t u = 0; u < w; u++)
{
if (!verRctCLSAGSimple(messages[u],sigs[u],pubs,C_offsets[u]))
{
return false;
}
}
// Check balanace
std::vector<MultiexpData> balance;
balance.reserve(w + T);
balance.resize(0);
key ZERO = zero();
key ONE = identity();
key MINUS_ONE;
sc_sub(MINUS_ONE.bytes,ZERO.bytes,ONE.bytes);
for (size_t u = 0; u < w; u++)
{
balance.push_back({ONE,C_offsets[u]});
}
for (size_t j = 0; j < T; j++)
{
balance.push_back({MINUS_ONE,Q[j]});
}
if (!(straus(balance) == ONE)) // group identity
{
return false;
}
return true;
}
private:
ctkeyV pubs;
keyV Q;
keyV r;
keyV s;
keyV s1;
keyV t;
keyV a;
keyV b;
keyV C_offsets;
keyV messages;
std::vector<clsag> sigs;
};

@ -0,0 +1,172 @@
// Copyright (c) 2014-2020, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include "ringct/rctSigs.h"
#include "ringct/rctTypes.h"
#include "device/device.hpp"
using namespace rct;
template<size_t a_N, size_t a_T, size_t a_w>
class test_sig_mlsag
{
public:
static const size_t loop_count = 1000;
static const size_t N = a_N;
static const size_t T = a_T;
static const size_t w = a_w;
bool init()
{
pubs.reserve(N);
pubs.resize(N);
r = keyV(w); // M[l[u]] = Com(0,r[u])
a = keyV(w); // P[l[u]] = Com(a[u],s[u])
s = keyV(w);
Q = keyV(T); // Q[j] = Com(b[j],t[j])
b = keyV(T);
t = keyV(T);
// Random keys
key temp;
for (size_t k = 0; k < N; k++)
{
skpkGen(temp,pubs[k].dest);
skpkGen(temp,pubs[k].mask);
}
// Signing and commitment keys (assumes fixed signing indices 0,1,...,w-1 for this test)
// TODO: random signing indices
C_offsets = keyV(w); // P[l[u]] - C_offsets[u] = Com(0,s[u]-s1[u])
s1 = keyV(w);
key a_sum = zero();
key s1_sum = zero();
messages = keyV(w);
for (size_t u = 0; u < w; u++)
{
skpkGen(r[u],pubs[u].dest); // M[u] = Com(0,r[u])
a[u] = skGen(); // P[u] = Com(a[u],s[u])
s[u] = skGen();
addKeys2(pubs[u].mask,s[u],a[u],H);
s1[u] = skGen(); // C_offsets[u] = Com(a[u],s1[u])
addKeys2(C_offsets[u],s1[u],a[u],H);
sc_add(a_sum.bytes,a_sum.bytes,a[u].bytes);
sc_add(s1_sum.bytes,s1_sum.bytes,s1[u].bytes);
messages[u] = skGen();
}
// Outputs
key b_sum = zero();
key t_sum = zero();
for (size_t j = 0; j < T-1; j++)
{
b[j] = skGen(); // Q[j] = Com(b[j],t[j])
t[j] = skGen();
addKeys2(Q[j],t[j],b[j],H);
sc_add(b_sum.bytes,b_sum.bytes,b[j].bytes);
sc_add(t_sum.bytes,t_sum.bytes,t[j].bytes);
}
// Value/mask balance for Q[T-1]
sc_sub(b[T-1].bytes,a_sum.bytes,b_sum.bytes);
sc_sub(t[T-1].bytes,s1_sum.bytes,t_sum.bytes);
addKeys2(Q[T-1],t[T-1],b[T-1],H);
// Build proofs
sigs.reserve(w);
sigs.resize(0);
ctkey sk;
for (size_t u = 0; u < w; u++)
{
sk.dest = r[u];
sk.mask = s[u];
sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,u,hw::get_device("default")));
}
return true;
}
bool test()
{
for (size_t u = 0; u < w; u++)
{
if (!verRctMGSimple(messages[u],sigs[u],pubs,C_offsets[u]))
{
return false;
}
}
// Check balanace
std::vector<MultiexpData> balance;
balance.reserve(w + T);
balance.resize(0);
key ZERO = zero();
key ONE = identity();
key MINUS_ONE;
sc_sub(MINUS_ONE.bytes,ZERO.bytes,ONE.bytes);
for (size_t u = 0; u < w; u++)
{
balance.push_back({ONE,C_offsets[u]});
}
for (size_t j = 0; j < T; j++)
{
balance.push_back({MINUS_ONE,Q[j]});
}
if (!(straus(balance) == ONE)) // group identity
{
return false;
}
return true;
}
private:
ctkeyV pubs;
keyV Q;
keyV r;
keyV s;
keyV s1;
keyV t;
keyV a;
keyV b;
keyV C_offsets;
keyV messages;
std::vector<mgSig> sigs;
};

@ -546,7 +546,7 @@ static void expand_tsx(cryptonote::transaction &tx)
for (size_t n = 0; n < tx.vin.size(); ++n) for (size_t n = 0; n < tx.vin.size(); ++n)
rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
} }
else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2) else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG)
{ {
CHECK_AND_ASSERT_THROW_MES(rv.p.MGs.size() == tx.vin.size(), "Bad MGs size"); CHECK_AND_ASSERT_THROW_MES(rv.p.MGs.size() == tx.vin.size(), "Bad MGs size");
for (size_t n = 0; n < tx.vin.size(); ++n) for (size_t n = 0; n < tx.vin.size(); ++n)

@ -252,3 +252,65 @@ TEST(multiexp, pippenger_cached)
ASSERT_TRUE(basic(data) == pippenger(data, cache)); ASSERT_TRUE(basic(data) == pippenger(data, cache));
} }
} }
TEST(multiexp, scalarmult_triple)
{
std::vector<rct::MultiexpData> data;
ge_p2 p2;
rct::key res;
ge_p3 Gp3;
ge_frombytes_vartime(&Gp3, rct::G.bytes);
static const rct::key scalars[] = {
rct::Z,
rct::I,
rct::L,
rct::EIGHT,
rct::INV_EIGHT,
};
static const ge_p3 points[] = {
ge_p3_identity,
ge_p3_H,
Gp3,
};
ge_dsmp ppre[sizeof(points) / sizeof(points[0])];
for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); ++i)
ge_dsm_precomp(ppre[i], &points[i]);
data.resize(3);
for (const rct::key &x: scalars)
{
data[0].scalar = x;
for (const rct::key &y: scalars)
{
data[1].scalar = y;
for (const rct::key &z: scalars)
{
data[2].scalar = z;
for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); ++i)
{
data[1].point = points[i];
for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); ++j)
{
data[0].point = Gp3;
data[2].point = points[j];
ge_triple_scalarmult_base_vartime(&p2, data[0].scalar.bytes, data[1].scalar.bytes, ppre[i], data[2].scalar.bytes, ppre[j]);
ge_tobytes(res.bytes, &p2);
ASSERT_TRUE(basic(data) == res);
for (size_t k = 0; k < sizeof(points) / sizeof(points[0]); ++k)
{
data[0].point = points[k];
ge_triple_scalarmult_precomp_vartime(&p2, data[0].scalar.bytes, ppre[k], data[1].scalar.bytes, ppre[i], data[2].scalar.bytes, ppre[j]);
ge_tobytes(res.bytes, &p2);
ASSERT_TRUE(basic(data) == res);
}
}
}
}
}
}
}

@ -38,6 +38,7 @@
#include "ringct/rctSigs.h" #include "ringct/rctSigs.h"
#include "ringct/rctOps.h" #include "ringct/rctOps.h"
#include "device/device.hpp" #include "device/device.hpp"
#include "string_tools.h"
using namespace std; using namespace std;
using namespace crypto; using namespace crypto;
@ -137,6 +138,167 @@ TEST(ringct, MG_sigs)
ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R));
} }
TEST(ringct, CLSAG)
{
const size_t N = 11;
const size_t idx = 5;
ctkeyV pubs;
key p, t, t2, u;
const key message = identity();
ctkey backup;
clsag clsag;
for (size_t i = 0; i < N; ++i)
{
key sk;
ctkey tmp;
skpkGen(sk, tmp.dest);
skpkGen(sk, tmp.mask);
pubs.push_back(tmp);
}
// Set P[idx]
skpkGen(p, pubs[idx].dest);
// Set C[idx]
t = skGen();
u = skGen();
addKeys2(pubs[idx].mask,t,u,H);
// Set commitment offset
key Cout;
t2 = skGen();
addKeys2(Cout,t2,u,H);
// Prepare generation inputs
ctkey insk;
insk.dest = p;
insk.mask = t;
// bad message
clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
// bad index at creation
try
{
clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,(idx + 1) % N,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
catch (...) { /* either exception, or failure to verify above */ }
// bad z at creation
try
{
ctkey insk2;
insk2.dest = insk.dest;
insk2.mask = skGen();
clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
catch (...) { /* either exception, or failure to verify above */ }
// bad C at creation
backup = pubs[idx];
pubs[idx].mask = scalarmultBase(skGen());
try
{
clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
catch (...) { /* either exception, or failure to verify above */ }
pubs[idx] = backup;
// bad p at creation
try
{
ctkey insk2;
insk2.dest = skGen();
insk2.mask = insk.mask;
clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
catch (...) { /* either exception, or failure to verify above */ }
// bad P at creation
backup = pubs[idx];
pubs[idx].dest = scalarmultBase(skGen());
try
{
clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
catch (...) { /* either exception, or failure to verify above */ }
pubs[idx] = backup;
// Test correct signature
clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
// empty s
auto sbackup = clsag.s;
clsag.s.clear();
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.s = sbackup;
// too few s elements
key backup_key;
backup_key = clsag.s.back();
clsag.s.pop_back();
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.s.push_back(backup_key);
// too many s elements
clsag.s.push_back(skGen());
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.s.pop_back();
// bad s in clsag at verification
for (auto &s: clsag.s)
{
backup_key = s;
s = skGen();
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
s = backup_key;
}
// bad c1 in clsag at verification
backup_key = clsag.c1;
clsag.c1 = skGen();
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.c1 = backup_key;
// bad I in clsag at verification
backup_key = clsag.I;
clsag.I = scalarmultBase(skGen());
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.I = backup_key;
// bad D in clsag at verification
backup_key = clsag.D;
clsag.D = scalarmultBase(skGen());
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.D = backup_key;
// D not in main subgroup in clsag at verification
backup_key = clsag.D;
rct::key x;
ASSERT_TRUE(epee::string_tools::hex_to_pod("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", x));
clsag.D = rct::addKeys(clsag.D, x);
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
clsag.D = backup_key;
// swapped I and D in clsag at verification
std::swap(clsag.I, clsag.D);
ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
std::swap(clsag.I, clsag.D);
// check it's still good, in case we failed to restore
ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
}
TEST(ringct, range_proofs) TEST(ringct, range_proofs)
{ {
//Ring CT Stuff //Ring CT Stuff

@ -477,6 +477,7 @@ TEST(Serialization, serializes_ringct_types)
rct::ecdhTuple ecdh0, ecdh1; rct::ecdhTuple ecdh0, ecdh1;
rct::boroSig boro0, boro1; rct::boroSig boro0, boro1;
rct::mgSig mg0, mg1; rct::mgSig mg0, mg1;
rct::clsag clsag0, clsag1;
rct::Bulletproof bp0, bp1; rct::Bulletproof bp0, bp1;
rct::rctSig s0, s1; rct::rctSig s0, s1;
cryptonote::transaction tx0, tx1; cryptonote::transaction tx0, tx1;
@ -592,9 +593,11 @@ TEST(Serialization, serializes_ringct_types)
rct::skpkGen(Sk, Pk); rct::skpkGen(Sk, Pk);
destinations.push_back(Pk); destinations.push_back(Pk);
//compute rct data with mixin 3 //compute rct data with mixin 3
const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 0 }; const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 2 };
s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default")); s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default"));
ASSERT_FALSE(s0.p.MGs.empty());
ASSERT_TRUE(s0.p.CLSAGs.empty());
mg0 = s0.p.MGs[0]; mg0 = s0.p.MGs[0];
ASSERT_TRUE(serialization::dump_binary(mg0, blob)); ASSERT_TRUE(serialization::dump_binary(mg0, blob));
ASSERT_TRUE(serialization::parse_binary(blob, mg1)); ASSERT_TRUE(serialization::parse_binary(blob, mg1));
@ -614,6 +617,23 @@ TEST(Serialization, serializes_ringct_types)
ASSERT_TRUE(serialization::parse_binary(blob, bp1)); ASSERT_TRUE(serialization::parse_binary(blob, bp1));
bp1.V = bp0.V; // this is not saved, as it is reconstructed from other tx data bp1.V = bp0.V; // this is not saved, as it is reconstructed from other tx data
ASSERT_EQ(bp0, bp1); ASSERT_EQ(bp0, bp1);
const rct::RCTConfig rct_config_clsag{ rct::RangeProofPaddedBulletproof, 3 };
s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config_clsag, hw::get_device("default"));
ASSERT_FALSE(s0.p.CLSAGs.empty());
ASSERT_TRUE(s0.p.MGs.empty());
clsag0 = s0.p.CLSAGs[0];
ASSERT_TRUE(serialization::dump_binary(clsag0, blob));
ASSERT_TRUE(serialization::parse_binary(blob, clsag1));
ASSERT_TRUE(clsag0.s.size() == clsag1.s.size());
for (size_t n = 0; n < clsag0.s.size(); ++n)
{
ASSERT_TRUE(clsag0.s[n] == clsag1.s[n]);
}
ASSERT_TRUE(clsag0.c1 == clsag1.c1);
// I is not serialized, they are meant to be reconstructed
ASSERT_TRUE(clsag0.D == clsag1.D);
} }
TEST(Serialization, portability_wallet) TEST(Serialization, portability_wallet)

Loading…
Cancel
Save