diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 3110d3ce7..508709280 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -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) { signed char aslide[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) { signed char aslide[256]; signed char bslide[256]; diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index eeb94669b..22f76974b 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -79,6 +79,7 @@ typedef ge_cached ge_dsmp[8]; extern const ge_precomp ge_Bi[8]; 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_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 *); /* 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_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_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_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); void ge_mul8(ge_p1p1 *, const ge_p2 *); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index b3d39a616..c6b81b094 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -45,7 +45,6 @@ #include "ringct/rctTypes.h" #include "ringct/rctOps.h" -//namespace cryptonote { namespace boost { namespace serialization @@ -244,6 +243,15 @@ namespace boost // a & x.II; // not serialized, we can recover it from the tx vin } + template + 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 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) { a & x.c; + if (ver < 1) + return; + a & x.mu_p; } template @@ -294,7 +305,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) 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"); // 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 @@ -312,6 +323,8 @@ namespace boost if (x.rangeSigs.empty()) a & x.bulletproofs; a & x.MGs; + if (ver >= 1u) + a & x.CLSAGs; if (x.rangeSigs.empty()) a & x.pseudoOuts; } @@ -322,7 +335,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) 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"); // 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 @@ -336,7 +349,9 @@ namespace boost if (x.p.rangeSigs.empty()) a & x.p.bulletproofs; 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; } @@ -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) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index d808a9c1d..fcc96883b 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -436,7 +436,7 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits::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::max(), "get_pruned_transaction_weight does not support older range proof types"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits::max(), "empty vin"); @@ -458,9 +458,12 @@ namespace cryptonote extra = 32 * (9 + 2 * nrl) + 2; weight += extra; - // calculate deterministic MLSAG data size + // calculate deterministic CLSAG/MLSAG data size const size_t ring_size = boost::get(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; // calculate deterministic pseudoOuts size diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 8051ee9fa..f4709dc01 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -179,6 +179,7 @@ #define HF_VERSION_ENFORCE_MIN_AGE 12 #define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12 #define HF_VERSION_EXACT_COINBASE 13 +#define HF_VERSION_CLSAG 13 #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_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_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 { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 20dc7f9fb..9d4c5a66c 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -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; } //------------------------------------------------------------------ @@ -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"); 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(tx.vin[n]).k_image); + } + } else { CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast(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(tx.vin[n]).k_image); + } + } + } else { CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast(rv.type)); @@ -3377,6 +3420,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeSimple: case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: + case rct::RCTTypeCLSAG: { // 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"); return false; } for (size_t n = 0; n < tx.vin.size(); ++n) { - if (rv.p.MGs[n].II.empty() || memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) + bool error; + if (rv.type == rct::RCTTypeCLSAG) + error = memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); + else + error = rv.p.MGs[n].II.empty() || memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); + if (error) { MERROR_VER("Failed to check ringct signatures: mismatched key image"); return false; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 9a1439c4a..474362ed0 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -928,6 +928,7 @@ namespace cryptonote break; case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: + case rct::RCTTypeCLSAG: if (!is_canonical_bulletproof_layout(rv.p.bulletproofs)) { MERROR_VER("Bulletproof does not have canonical form"); @@ -955,7 +956,7 @@ namespace cryptonote { if (!tx_info[n].result) 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; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) { diff --git a/src/device/device.hpp b/src/device/device.hpp index ef973c9f4..582eb2242 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -231,6 +231,10 @@ namespace hw { 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 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 has_ki_cold_sync(void) const { return false; } diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index 096cb35ba..145197212 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -402,6 +402,29 @@ namespace hw { 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() { return true; } diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index bdd99f89c..2493bd67d 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -134,6 +134,10 @@ namespace hw { 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 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; }; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 30964848d..4e89f835d 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -299,6 +299,7 @@ namespace hw { #define INS_PREFIX_HASH 0x7D #define INS_VALIDATE 0x7C #define INS_MLSAG 0x7E + #define INS_CLSAG 0x7F #define INS_CLOSE_TX 0x80 #define INS_GET_TX_PROOF 0xA0 @@ -1857,7 +1858,7 @@ namespace hw { // ====== Aout, Bout, AKout, C, v, k ====== kv_offset = data_offset; - if (type==rct::RCTTypeBulletproof2) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { C_offset = kv_offset+ (8)*outputs_size; } else { C_offset = kv_offset+ (32+32)*outputs_size; @@ -1874,7 +1875,7 @@ namespace hw { offset = set_command_header(INS_VALIDATE, 0x02, i+1); //options 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; //is_subaddress this->buffer_send[offset] = outKeys.is_subaddress; @@ -1895,7 +1896,7 @@ namespace hw { memmove(this->buffer_send+offset, data+C_offset,32); offset += 32; C_offset += 32; - if (type==rct::RCTTypeBulletproof2) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { //k memset(this->buffer_send+offset, 0, 32); offset += 32; @@ -2121,6 +2122,157 @@ namespace hw { 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; ibuffer_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() { AUTO_LOCK_CMD(); send_simple(INS_CLOSE_TX); diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 4036035c8..d3ec08288 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -297,6 +297,11 @@ namespace hw { 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 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; }; diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index 35e7d9605..fa824ec3b 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -309,7 +309,7 @@ namespace tx { throw std::invalid_argument("RV not initialized"); } 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 { diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index f4de0ddcf..c94884fd8 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -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. { 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 uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -110,5 +113,7 @@ const hardfork_t stagenet_hard_forks[] = { { 10, 269000, 0, 1550153694 }, { 11, 269720, 0, 1550225678 }, { 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]); diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index b2dd32ada..245a3f477 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -511,6 +511,23 @@ namespace rct { 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) //AB = A - B where A, B are curve points diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 74e0ad833..679ed1441 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -145,6 +145,10 @@ namespace rct { //B must be input after applying "precomp" void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B); void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B); + + 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 void subKeys(key &AB, const key &A, const key &B); //checks if A, B are equal as curve points diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 2e3e7007e..2a7b36b66 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -36,6 +36,7 @@ #include "rctSigs.h" #include "bulletproofs.h" #include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_config.h" using namespace crypto; using namespace std; @@ -165,6 +166,167 @@ namespace rct { 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 // See paper by Noether (https://eprint.iacr.org/2015/1098) // This generalization allows for some dimensions not to require linkability; @@ -427,7 +589,7 @@ namespace rct { hashes.push_back(hash2rct(h)); 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()); for (const auto &p: rv.p.bulletproofs) @@ -555,6 +717,37 @@ namespace rct { 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 //Prove: @@ -634,6 +827,120 @@ namespace rct { 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 //replace these when connecting blockchain @@ -726,7 +1033,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); } //set txn fee @@ -774,7 +1081,27 @@ namespace rct { } 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.outPk.resize(destinations.size()); if (!bulletproof) @@ -864,7 +1191,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(outamounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); } //set txn fee @@ -874,7 +1201,10 @@ namespace rct { rv.mixRing = mixRing; keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; 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 keyV a(inamounts.size()); for (i = 0 ; i < inamounts.size() - 1; i++) { @@ -888,9 +1218,20 @@ namespace rct { key full_message = get_pre_mlsag_hash(rv,hwdev); if (msout) - msout->c.resize(inamounts.size()); - for (i = 0 ; i < inamounts.size(); i++) { - 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->c.resize(inamounts.size()); + 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; } @@ -995,13 +1336,22 @@ namespace rct { { CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); const rctSig &rv = *rvp; - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); 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.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"); } else @@ -1095,7 +1445,7 @@ namespace rct { { 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"); const bool bulletproof = is_rct_bulletproof(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet @@ -1118,14 +1468,19 @@ namespace rct { results.resize(rv.mixRing.size()); for (size_t i = 0 ; i < rv.mixRing.size() ; 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); for (size_t i = 0; i < results.size(); ++i) { if (!results[i]) { - LOG_PRINT_L1("verRctMGSimple failed for input " << i); + LOG_PRINT_L1("verRctMGSimple/verRctCLSAGSimple failed for input " << i); return false; } } @@ -1162,7 +1517,7 @@ namespace rct { //mask amount and mask 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; key amount = ecdh_info.amount; 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) { - 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(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; @@ -1215,12 +1570,13 @@ namespace rct { return decodeRctSimple(rv, sk, i, mask, hwdev); } - bool signMultisig(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { + bool signMultisigMLSAG(rctSig &rv, const std::vector &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, 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.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(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs"); if (rv.type == RCTTypeFull) { 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"); } + // 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) { rct::key diff; sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes); @@ -1237,4 +1595,33 @@ namespace rct { } return true; } + + bool signMultisigCLSAG(rctSig &rv, const std::vector &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 &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); + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 9227eab1e..a0346b34e 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -76,7 +76,11 @@ namespace rct { // 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); 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 gives C, and mask such that \sumCi = C diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 1763542db..1f674056d 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -195,6 +195,7 @@ namespace rct { case RCTTypeSimple: case RCTTypeBulletproof: case RCTTypeBulletproof2: + case RCTTypeCLSAG: return true; default: return false; @@ -207,6 +208,7 @@ namespace rct { { case RCTTypeBulletproof: case RCTTypeBulletproof2: + case RCTTypeCLSAG: return true; default: return false; diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index ce11981ad..cb9e72d2b 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -113,9 +113,14 @@ namespace rct { struct multisig_out { std::vector c; // for all inputs + std::vector mu_p; // for all inputs + std::vector c0; // for all inputs BEGIN_SERIALIZE_OBJECT() FIELD(c) + FIELD(mu_p) + if (!mu_p.empty() && mu_p.size() != c.size()) + return false; END_SERIALIZE() }; @@ -163,6 +168,23 @@ namespace rct { // FIELD(II) - not serialized, it can be reconstructed 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 // also contains the "Ci" values such that // \sum Ci = C @@ -234,6 +256,7 @@ namespace rct { RCTTypeSimple = 2, RCTTypeBulletproof = 3, RCTTypeBulletproof2 = 4, + RCTTypeCLSAG = 5, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -262,7 +285,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) 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; VARINT_FIELD(txnFee) // inputs/outputs not saved, only here for serialization help @@ -291,7 +314,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { ar.begin_object(); if (!typename Archive::is_saving()) @@ -338,6 +361,7 @@ namespace rct { std::vector rangeSigs; std::vector bulletproofs; std::vector MGs; // simple rct has N, full has 1 + std::vector CLSAGs; keyV pseudoOuts; //C - for simple rct // when changing this function, update cryptonote::get_pruned_transaction_weight @@ -346,12 +370,12 @@ namespace rct { { if (type == RCTTypeNull) 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; - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { uint32_t nbp = bulletproofs.size(); - if (type == RCTTypeBulletproof2) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) VARINT_FIELD(nbp) else FIELD(nbp) @@ -386,55 +410,98 @@ namespace rct { ar.end_array(); } - ar.tag("MGs"); - ar.begin_array(); - // we keep a byte for size of MGs, because we don't know whether this is - // a simple or full rct signature, and it's starting to annoy the hell out of me - size_t mg_elements = (type == RCTTypeSimple || 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) + if (type == RCTTypeCLSAG) { - // 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.tag("CLSAGs"); ar.begin_array(); - PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss); - if (MGs[i].ss.size() != mixin + 1) + PREPARE_CUSTOM_VECTOR_SERIALIZATION(inputs, CLSAGs); + if (CLSAGs.size() != inputs) 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(); - 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) + PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, CLSAGs[i].s); + if (CLSAGs[i].s.size() != mixin + 1) 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]) - if (mg_ss2_elements - k > 1) + FIELDS(CLSAGs[i].s[j]) + if (mixin + 1 - j > 1) ar.delimit_array(); } ar.end_array(); - if (mixin + 1 - j > 1) - ar.delimit_array(); + ar.tag("c1"); + 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(); + } + 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"); - FIELDS(MGs[i].cc) - // MGs[i].II not saved, it can be reconstructed - ar.end_object(); + ar.tag("cc"); + FIELDS(MGs[i].cc) + // MGs[i].II not saved, it can be reconstructed + ar.end_object(); - if (mg_elements - i > 1) - ar.delimit_array(); + if (mg_elements - i > 1) + ar.delimit_array(); + } + ar.end_array(); } - ar.end_array(); - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -464,12 +531,12 @@ namespace rct { 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 { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; } 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::multisig_kLRki, "rct::multisig_kLRki"); 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::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::multisig_kLRki, 0x9d); 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::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::multisig_kLRki, "rct_multisig_kLR"); VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); +VARIANT_TAG(json_archive, rct::clsag, "rct_clsag"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9e17bed63..36e86ae5d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1306,7 +1306,7 @@ namespace cryptonote case 1: res.pow_algorithm = "CNv1 (Cryptonight variant 1)"; 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 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; } if (res.is_background_mining_enabled) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 73ab88a9f..e00f9d2e9 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1692,6 +1692,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vectoruse_fork_rules(8, 0), + m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), m_wallet->get_base_fee(), m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast(priority))), m_wallet->get_fee_quantization_mask()); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5d78bb7b0..31aaaddea 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -243,6 +243,22 @@ namespace add_reason(reason, "tx was not relayed"); return reason; } + + size_t get_num_outputs(const std::vector &dsts, const std::vector &transfers, const std::vector &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 @@ -795,7 +811,7 @@ void drop_from_short_history(std::list &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; @@ -829,8 +845,11 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra else size += (2*64*32+32+64*32) * n_outputs; - // MGs - size += n_inputs * (64 * (mixin+1) + 32); + // MGs/CLSAGs + if (clsag) + size += n_inputs * (32 * (mixin+1) + 64); + else + size += n_inputs * (64 * (mixin+1) + 32); // mixRing - not serialized, can be reconstructed /* 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; } -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) - 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 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) { const uint64_t bp_base = 368; @@ -879,6 +898,11 @@ uint8_t get_bulletproof_fork() 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) { 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::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: + case rct::RCTTypeCLSAG: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: 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); } //---------------------------------------------------------------------------------------------------- -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) { - 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); } 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); } } @@ -9066,7 +9091,10 @@ void wallet2::transfer_selected_rct(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector 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 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::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector 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 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)); @@ -10054,7 +10083,7 @@ std::vector wallet2::create_transactions_2(std::vector= 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); } @@ -10064,7 +10093,8 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_all(uint64_t below // determine threshold for fractional amount 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 clsag = use_fork_rules(get_clsag_fork(), 0); const uint64_t base_fee = get_base_fee(); 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_two_rings = estimate_tx_weight(use_rct, 2, 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, 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!"); 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); @@ -10422,9 +10453,10 @@ std::vector wallet2::create_transactions_from(const crypton 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 bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { 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 fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -10453,7 +10485,7 @@ std::vector wallet2::create_transactions_from(const crypton uint64_t fee_dust_threshold; 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); } else @@ -10484,14 +10516,15 @@ std::vector wallet2::create_transactions_from(const crypton // 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 " << 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)); if (try_tx) { cryptonote::transaction test_tx; 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 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::derivation_to_scalar(found_derivation, n, scalar1); 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; rct::key Ctmp; 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::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); 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); } total += amount; @@ -14036,8 +14069,9 @@ std::pair wallet2::estimate_tx_size_and_weight(bool use_rct, i n_outputs = 2; // extra dummy output 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); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); + 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); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f283a873e..117e4e2f2 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1400,7 +1400,7 @@ private: std::vector> estimate_backlog(const std::vector> &fee_levels); std::vector> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector &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_base_fee(); uint64_t get_fee_quantization_mask(); diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index ca9a09d82..654233d03 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -44,6 +44,7 @@ set(core_tests_sources v2_tests.cpp rct.cpp bulletproofs.cpp + rct2.cpp wallet_tools.cpp) set(core_tests_headers @@ -64,6 +65,7 @@ set(core_tests_headers v2_tests.h rct.h bulletproofs.h + rct2.h wallet_tools.h) add_executable(core_tests diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp index 04eeb9e01..44adc42e7 100644 --- a/tests/core_tests/bulletproofs.cpp +++ b/tests/core_tests/bulletproofs.cpp @@ -42,7 +42,7 @@ using namespace cryptonote; // Tests bool gen_bp_tx_validation_base::generate_with(std::vector& 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 &sources, std::vector &destinations, size_t tx_idx)> &pre_tx, const std::function &post_tx) const { @@ -157,7 +157,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve 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) + 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")); @@ -173,7 +173,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve 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, - 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), false, "Failed to generate block"); if (!valid) @@ -205,13 +205,22 @@ bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size return true; } -bool gen_bp_tx_valid_1::generate(std::vector& events) const +bool gen_bp_tx_valid_1_before_12::generate(std::vector& 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, 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_1"); }); + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } }; + 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& 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& events) const @@ -219,7 +228,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector& events) cons const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; 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& events) const @@ -228,7 +237,7 @@ bool gen_bp_tx_valid_2::generate(std::vector& events) const const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1}; 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& events) const @@ -237,7 +246,7 @@ bool gen_bp_tx_valid_3::generate(std::vector& events) const const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1}; const size_t bp_sizes[] = {4, (size_t)-1}; 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& events) const @@ -246,7 +255,7 @@ bool gen_bp_tx_valid_16::generate(std::vector& 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 size_t bp_sizes[] = {16, (size_t)-1}; 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& events) const @@ -254,7 +263,7 @@ bool gen_bp_tx_invalid_4_2_1::generate(std::vector& events) co const size_t mixin = 10; const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; 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& events) const @@ -262,7 +271,7 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector& events) co 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 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& events) const @@ -271,7 +280,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector& events) c 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 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& events) const @@ -279,7 +288,7 @@ bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector& events) const @@ -288,7 +297,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector& events) const @@ -297,8 +306,8 @@ bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; 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){ - CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); + 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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); tx.rct_signatures.p.bulletproofs.pop_back(); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); @@ -312,8 +321,8 @@ bool gen_bp_tx_invalid_empty_proofs::generate(std::vector& eve const size_t mixin = 10; const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1}; 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){ - CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); + 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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); tx.rct_signatures.p.bulletproofs.clear(); return true; }); @@ -325,8 +334,8 @@ bool gen_bp_tx_invalid_too_many_proofs::generate(std::vector& const size_t mixin = 10; const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; 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){ - CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); + 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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); tx.rct_signatures.p.bulletproofs.push_back(tx.rct_signatures.p.bulletproofs.back()); return true; @@ -339,8 +348,8 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector& eve const size_t mixin = 10; const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; 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){ - CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2); + 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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); tx.rct_signatures.p.bulletproofs.back() = rct::bulletproof_PROVE(1000, rct::skGen()); return true; @@ -353,7 +362,18 @@ bool gen_bp_tx_invalid_borromean_type::generate(std::vector& e const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; 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& 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; }); } diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h index 93fe2947f..b30d82e68 100644 --- a/tests/core_tests/bulletproofs.h +++ b/tests/core_tests/bulletproofs.h @@ -82,7 +82,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base } bool generate_with(std::vector& 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 &sources, std::vector &destinations, size_t)> &pre_tx, const std::function &post_tx) const; @@ -95,99 +95,119 @@ private: template<> struct get_test_options { - const std::pair 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 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 +struct get_bp_versioned_test_options: public get_test_options { + const std::pair 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 }; }; // 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& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: 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& events) const; +}; +template<> struct get_test_options: public get_bp_versioned_test_options<12> {}; struct gen_bp_tx_invalid_1_1 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_valid_2 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_valid_3 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_valid_16 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_4_2_1 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_16_16 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_txs_valid_2_and_2 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_txs_valid_2_and_3_and_2_and_4 : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_not_enough_proofs : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_empty_proofs : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_too_many_proofs : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_wrong_amount : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; struct gen_bp_tx_invalid_borromean_type : public gen_bp_tx_validation_base { bool generate(std::vector& events) const; }; -template<> struct get_test_options: public get_test_options {}; +template<> struct get_test_options: public get_bp_versioned_test_options<9> {}; + +struct gen_bp_tx_invalid_bulletproof2_type : public gen_bp_tx_validation_base +{ + bool generate(std::vector& events) const; +}; +template<> struct get_test_options: public get_bp_versioned_test_options {}; diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 9895d4814..c55154917 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -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_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_valid_2); 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_wrong_amount); 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); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index 94eb23ce9..db78c3e41 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -43,6 +43,7 @@ #include "rct.h" #include "multisig.h" #include "bulletproofs.h" +#include "rct2.h" /************************************************************************/ /* */ /************************************************************************/ diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index 28d43e815..f098e1bce 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -163,9 +163,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vectortimestamp + 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(), 0, 1, 4), false, "Failed to generate block"); events.push_back(blocks[n]); @@ -191,7 +191,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector(), 0, 1, 4), false, "Failed to generate block"); events.push_back(blk); @@ -363,7 +363,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector additional_tx_secret_keys; auto sources_copy = sources; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector(), 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(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 2 }, msoutp); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); #ifndef NO_MULTISIG @@ -453,7 +453,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; 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); } bool gen_multisig_tx_valid_24_1_2::generate(std::vector& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; 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& events) const { - const size_t mixin = 4; + const size_t mixin = 10; const uint64_t amount_paid = 10000; return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL); } diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index 462c74f46..333c4fe38 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -82,7 +82,7 @@ private: template<> struct get_test_options { - const std::pair hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)}; + const std::pair hard_forks[3] = {std::make_pair(1, 0), std::make_pair(10, 1), std::make_pair(0, 0)}; const cryptonote::test_options test_options = { hard_forks, 0 }; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 6bf708855..6ce99e76e 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -133,7 +133,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector& 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 &sources, std::vector &destinations, size_t tx_idx)> &pre_tx, + const std::function &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(), 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(), 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 rct_txes; + cryptonote::block blk_txes; + std::vector 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 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(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 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 additional_tx_keys; + std::unordered_map 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(), 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& 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; + }); +} diff --git a/tests/core_tests/rct2.h b/tests/core_tests/rct2.h new file mode 100644 index 000000000..2fe9d6113 --- /dev/null +++ b/tests/core_tests/rct2.h @@ -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& tvcs, size_t tx_added, size_t event_idx, const std::vector& /*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& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + + bool generate_with(std::vector& 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 &sources, std::vector &destinations, size_t)> &pre_tx, + const std::function &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 { + const std::pair 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 +struct get_rct2_versioned_test_options: public get_test_options { + const std::pair 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& events) const; +}; +template<> struct get_test_options: public get_rct2_versioned_test_options {}; diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index 132758f50..fca9ce91c 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -135,7 +135,7 @@ class TransferTest(): assert res.fee > 0 fee = res.fee 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.multisig_txset) == 0 assert len(res.unsigned_txset) == 0 @@ -144,7 +144,7 @@ class TransferTest(): res = daemon.get_fee_estimate(10) assert res.fee > 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 self.wallet[0].refresh() diff --git a/tests/performance_tests/crypto_ops.h b/tests/performance_tests/crypto_ops.h index ae00bb517..9db2e413a 100644 --- a/tests/performance_tests/crypto_ops.h +++ b/tests/performance_tests/crypto_ops.h @@ -51,11 +51,15 @@ enum test_op op_scalarmult8_p3, op_ge_dsm_precomp, op_ge_double_scalarmult_base_vartime, + op_ge_triple_scalarmult_base_vartime, op_ge_double_scalarmult_precomp_vartime, + op_ge_triple_scalarmult_precomp_vartime, op_ge_double_scalarmult_precomp_vartime2, op_addKeys2, op_addKeys3, op_addKeys3_2, + op_addKeys_aGbBcC, + op_addKeys_aAbBcC, op_isInMainSubgroup, op_zeroCommitUncached, }; @@ -70,15 +74,20 @@ public: { scalar0 = rct::skGen(); scalar1 = rct::skGen(); + scalar2 = rct::skGen(); point0 = rct::scalarmultBase(rct::skGen()); point1 = rct::scalarmultBase(rct::skGen()); + point2 = rct::scalarmultBase(rct::skGen()); if (ge_frombytes_vartime(&p3_0, point0.bytes) != 0) return false; if (ge_frombytes_vartime(&p3_1, point1.bytes) != 0) return false; + if (ge_frombytes_vartime(&p3_2, point2.bytes) != 0) + return false; ge_p3_to_cached(&cached, &p3_0); rct::precomp(precomp0, point0); rct::precomp(precomp1, point1); + rct::precomp(precomp2, point2); return true; } @@ -109,11 +118,15 @@ public: 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_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_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_addKeys2: rct::addKeys2(key, scalar0, scalar1, point0); 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_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_zeroCommitUncached: rct::zeroCommit(9001); break; case op_zeroCommitCached: rct::zeroCommit(9000); break; @@ -123,9 +136,9 @@ public: } private: - rct::key scalar0, scalar1; - rct::key point0, point1; - ge_p3 p3_0, p3_1; + rct::key scalar0, scalar1, scalar2; + rct::key point0, point1, point2; + ge_p3 p3_0, p3_1, p3_2; ge_cached cached; - ge_dsmp precomp0, precomp1; + ge_dsmp precomp0, precomp1, precomp2; }; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index ca0528e16..e59bb52fd 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -60,6 +60,8 @@ #include "bulletproof.h" #include "crypto_ops.h" #include "multiexp.h" +#include "sig_mlsag.h" +#include "sig_clsag.h" 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, 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, 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_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_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_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_addKeys2); 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_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_zeroCommitUncached); TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitCached); diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h new file mode 100644 index 000000000..c59e1e869 --- /dev/null +++ b/tests/performance_tests/sig_clsag.h @@ -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 +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 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 sigs; +}; diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h new file mode 100644 index 000000000..89645e155 --- /dev/null +++ b/tests/performance_tests/sig_mlsag.h @@ -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 +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 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 sigs; +}; diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index f5867f5e7..6a92868cf 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -546,7 +546,7 @@ static void expand_tsx(cryptonote::transaction &tx) for (size_t n = 0; n < tx.vin.size(); ++n) rv.p.MGs[0].II[n] = rct::ki2rct(boost::get(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"); for (size_t n = 0; n < tx.vin.size(); ++n) diff --git a/tests/unit_tests/multiexp.cpp b/tests/unit_tests/multiexp.cpp index f12dd6b49..722c568da 100644 --- a/tests/unit_tests/multiexp.cpp +++ b/tests/unit_tests/multiexp.cpp @@ -252,3 +252,65 @@ TEST(multiexp, pippenger_cached) ASSERT_TRUE(basic(data) == pippenger(data, cache)); } } + +TEST(multiexp, scalarmult_triple) +{ + std::vector 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); + } + } + } + } + } + } +} diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index 807bab64a..2388d647b 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -38,6 +38,7 @@ #include "ringct/rctSigs.h" #include "ringct/rctOps.h" #include "device/device.hpp" +#include "string_tools.h" using namespace std; using namespace crypto; @@ -137,6 +138,167 @@ TEST(ringct, MG_sigs) 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) { //Ring CT Stuff diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index e730f6867..7b8a291d0 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -477,6 +477,7 @@ TEST(Serialization, serializes_ringct_types) rct::ecdhTuple ecdh0, ecdh1; rct::boroSig boro0, boro1; rct::mgSig mg0, mg1; + rct::clsag clsag0, clsag1; rct::Bulletproof bp0, bp1; rct::rctSig s0, s1; cryptonote::transaction tx0, tx1; @@ -592,9 +593,11 @@ TEST(Serialization, serializes_ringct_types) rct::skpkGen(Sk, Pk); destinations.push_back(Pk); //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")); + ASSERT_FALSE(s0.p.MGs.empty()); + ASSERT_TRUE(s0.p.CLSAGs.empty()); mg0 = s0.p.MGs[0]; ASSERT_TRUE(serialization::dump_binary(mg0, blob)); ASSERT_TRUE(serialization::parse_binary(blob, mg1)); @@ -614,6 +617,23 @@ TEST(Serialization, serializes_ringct_types) ASSERT_TRUE(serialization::parse_binary(blob, bp1)); bp1.V = bp0.V; // this is not saved, as it is reconstructed from other tx data 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)