diff options
author | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2019-06-09 13:02:16 +0000 |
---|---|---|
committer | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2020-08-27 12:44:04 +0000 |
commit | 82ee01699c2b910e44fd7362bd47d3a1cc9c26af (patch) | |
tree | 5dcce86d8b82e8dab9821bae6120252c27c864f1 /src | |
parent | unit_tests: add ge_triple_scalarmult_base_vartime test (diff) | |
download | monero-82ee01699c2b910e44fd7362bd47d3a1cc9c26af.tar.xz |
Integrate CLSAGs into monero
They are allowed from v12, and MLSAGs are rejected from v13.
Diffstat (limited to 'src')
-rw-r--r-- | src/cryptonote_basic/cryptonote_boost_serialization.h | 27 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_format_utils.cpp | 9 | ||||
-rw-r--r-- | src/cryptonote_config.h | 1 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 56 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 3 | ||||
-rw-r--r-- | src/device/device_ledger.cpp | 6 | ||||
-rw-r--r-- | src/device_trezor/trezor/protocol.hpp | 2 | ||||
-rw-r--r-- | src/hardforks/hardforks.cpp | 5 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 184 | ||||
-rw-r--r-- | src/ringct/rctSigs.h | 4 | ||||
-rw-r--r-- | src/ringct/rctTypes.cpp | 2 | ||||
-rw-r--r-- | src/ringct/rctTypes.h | 135 | ||||
-rw-r--r-- | src/rpc/core_rpc_server.cpp | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 94 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 2 |
16 files changed, 425 insertions, 108 deletions
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 @@ -245,6 +244,15 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, rct::clsag &x, const boost::serialization::version_type ver) + { + a & x.s; + a & x.c1; + // a & x.I; // not serialized, we can recover it from the tx vin + a & x.D; + } + + template <class Archive> inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver) { a & x.mask; @@ -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 <class Archive> @@ -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<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); - CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2, + CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin"); @@ -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<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); - extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); + if (tx.rct_signatures.type == rct::RCTTypeCLSAG) + extra = tx.vin.size() * (ring_size + 2) * 32; + else + extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); weight += extra; // calculate deterministic pseudoOuts size diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 7c240423a..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 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<txin_to_key>(tx.vin[n]).k_image); + } + } else { CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); @@ -3096,6 +3128,17 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } + else if (rv.type == rct::RCTTypeCLSAG) + { + if (!tx.pruned) + { + CHECK_AND_ASSERT_MES(rv.p.CLSAGs.size() == tx.vin.size(), false, "Bad CLSAGs size"); + for (size_t n = 0; n < tx.vin.size(); ++n) + { + rv.p.CLSAGs[n].I = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } + } + } else { CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); @@ -3377,6 +3420,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeSimple: case rct::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<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) + bool error; + if (rv.type == rct::RCTTypeCLSAG) + error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); + else + error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); + if (error) { MERROR_VER("Failed to check ringct signatures: mismatched key image"); 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_ledger.cpp b/src/device/device_ledger.cpp index 30964848d..c59b648bd 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -1857,7 +1857,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 +1874,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 +1895,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; 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/rctSigs.cpp b/src/ringct/rctSigs.cpp index cb702ed15..074812156 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -168,11 +168,14 @@ namespace rct { // Generate a CLSAG signature // See paper by Goodell et al. (https://eprint.iacr.org/2019/654) - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki) { + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout) { 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(l < n, "Signing index out of range!"); + CHECK_AND_ASSERT_THROW_MES(scalarmultBase(z) == C[l], "C does not match z!"); + 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; @@ -309,9 +312,18 @@ namespace rct { sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes); sc_mulsub(sig.s[l].bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes); + 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 unsigned int l) { + return CLSAG_Gen(message, P, p, C, z, l, NULL, NULL, NULL); + } + // Verify a CLSAG signature // See paper by Goodell et al. (https://eprint.iacr.org/2019/654) bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig) @@ -665,7 +677,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) @@ -793,6 +805,35 @@ 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; + P.reserve(pubs.size()); + C.reserve(pubs.size()); + for (const ctkey &k: pubs) + { + P.push_back(k.dest); + 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], index, kLRki, mscout, mspout); + memwipe(sk.data(), sk.size() * sizeof(key)); + return result; + } + //Ring-ct MG sigs //Prove: @@ -872,6 +913,33 @@ namespace rct { catch (...) { return false; } } + bool verRctCLSAGSimple(const key &message, const clsag &clsag, const ctkeyV & pubs, const key & C) { + try + { + PERF_TIMER(verRctCLSAGSimple); + //setup vars + const size_t cols = pubs.size(); + CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); + keyV Pi(cols), Ci(cols); + ge_p3 Cp3; + CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&Cp3, C.bytes) == 0, false, "point conv failed"); + ge_cached Ccached; + ge_p3_to_cached(&Ccached, &Cp3); + ge_p1p1 p1; + //create the matrix to mg sig + for (size_t i = 0; i < cols; i++) { + Pi[i] = pubs[i].dest; + ge_p3 p3; + CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&p3, pubs[i].mask.bytes) == 0, false, "point conv failed"); + ge_sub(&p1, &p3, &Ccached); + ge_p1p1_to_p3(&p3, &p1); + ge_p3_tobytes(Ci[i].bytes, &p3); + } + return CLSAG_Ver(message, Pi, Ci, clsag); + } + catch (...) { return false; } + } + //These functions get keys from blockchain //replace these when connecting blockchain @@ -964,7 +1032,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 @@ -1012,7 +1080,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) @@ -1102,7 +1190,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 @@ -1112,7 +1200,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++) { @@ -1126,9 +1217,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; } @@ -1233,13 +1335,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 @@ -1333,7 +1444,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 @@ -1356,14 +1467,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; } } @@ -1400,7 +1516,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; @@ -1424,13 +1540,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; @@ -1453,12 +1569,13 @@ namespace rct { return decodeRctSimple(rv, sk, i, mask, hwdev); } - bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { + bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, 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"); @@ -1468,6 +1585,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); @@ -1475,4 +1594,33 @@ namespace rct { } return true; } + + bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { + CHECK_AND_ASSERT_MES(rv.type == RCTTypeCLSAG, false, "unsupported rct type"); + CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); + CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/MGs size"); + CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); + CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs"); + CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size"); + for (size_t n = 0; n < indices.size(); ++n) { + CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range"); + } + + // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share + // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key + for (size_t n = 0; n < indices.size(); ++n) { + rct::key diff, sk; + sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes); + sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes); + sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes); + } + return true; + } + + bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { + if (rv.type == RCTTypeCLSAG) + return signMultisigCLSAG(rv, indices, k, msout, secret_key); + else + return signMultisigMLSAG(rv, indices, k, msout, secret_key); + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 87d2b994b..ed82f6bc5 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -77,9 +77,9 @@ namespace rct { 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); - clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki); + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout); + clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l); bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig); - //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); //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 b83c267d0..cb9e72d2b 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -113,9 +113,14 @@ namespace rct { struct multisig_out { std::vector<key> c; // for all inputs + std::vector<key> mu_p; // for all inputs + std::vector<key> c0; // for all inputs BEGIN_SERIALIZE_OBJECT() FIELD(c) + FIELD(mu_p) + if (!mu_p.empty() && mu_p.size() != c.size()) + return false; END_SERIALIZE() }; @@ -175,6 +180,8 @@ namespace rct { BEGIN_SERIALIZE_OBJECT() FIELD(s) FIELD(c1) + // FIELD(I) - not serialized, it can be reconstructed + FIELD(D) END_SERIALIZE() }; @@ -249,6 +256,7 @@ namespace rct { RCTTypeSimple = 2, RCTTypeBulletproof = 3, RCTTypeBulletproof2 = 4, + RCTTypeCLSAG = 5, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -277,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 @@ -306,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<W>::is_saving()) @@ -353,6 +361,7 @@ namespace rct { std::vector<rangeSig> rangeSigs; std::vector<Bulletproof> bulletproofs; std::vector<mgSig> MGs; // simple rct has N, full has 1 + std::vector<clsag> CLSAGs; keyV pseudoOuts; //C - for simple rct // when changing this function, update cryptonote::get_pruned_transaction_weight @@ -361,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) @@ -401,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(); @@ -479,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() @@ -651,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); @@ -667,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"); @@ -683,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::vector<std::pair<std::str destinations.size() + 1, extra_size, m_wallet->use_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<uint32_t>(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<cryptonote::tx_destination_entry> &dsts, const std::vector<tools::wallet2::transfer_details> &transfers, const std::vector<size_t> &selected_transfers) + { + size_t outputs = dsts.size(); + uint64_t needed_money = 0; + for (const auto& dt: dsts) + needed_money += dt.amount; + uint64_t found_money = 0; + for(size_t idx: selected_transfers) + found_money += transfers[idx].amount(); + if (found_money != needed_money) + ++outputs; // change + if (outputs < 2) + ++outputs; // extra 0 dummy output + return outputs; + } } namespace @@ -795,7 +811,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) { size_t size = 0; @@ -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<cryptonote::tx_destination_entry ptx.construction_data.extra = tx.extra; ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; - ptx.construction_data.rct_config = { tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1}; + ptx.construction_data.rct_config = { + tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, + use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 + }; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -9752,9 +9780,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_rct = 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(); @@ -9790,7 +9819,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through - const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)); + const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9808,8 +9837,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Candidate subaddress index for spending: " << i); // determine threshold for fractional amount - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof); - const size_t tx_weight_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); @@ -9906,7 +9935,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); + uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { @@ -10018,7 +10047,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << @@ -10030,7 +10059,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can partially fill that destination 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::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); } @@ -10064,7 +10093,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); + const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); uint64_t inputs = 0, outputs = needed_fee; for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); @@ -10313,10 +10343,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below // determine threshold for fractional amount 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::pending_tx> wallet2::create_transactions_from(const crypton const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); const bool use_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::pending_tx> 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::pending_tx> 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<size_t, uint64_t> 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<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); - 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(); |