diff options
Diffstat (limited to 'src')
47 files changed, 3999 insertions, 1010 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index a84a4148d..8e68abbe5 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -241,8 +241,15 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { + rct::key commitment; + if (tx.version > 1) + { + commitment = tx.rct_signatures.outPk[i].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + commitment = rct::scalarmult8(commitment); + } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, tx.unlock_time, - tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL); + tx.version > 1 ? &commitment : NULL); } } add_tx_amount_output_indices(tx_id, amount_output_indices); diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 508709280..54ca39775 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -3830,15 +3830,51 @@ int sc_isnonzero(const unsigned char *s) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } -int ge_p3_is_point_at_infinity(const ge_p3 *p) { - // X = 0 and Y == Z - int n; - for (n = 0; n < 10; ++n) +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { + // https://eprint.iacr.org/2008/522 + // X == T == 0 and Y/Z == 1 + // note: convert all pieces to canonical bytes in case rounding is required (i.e. an element is > q) + // note2: even though T = XY/Z is true for valid point representations (implying it isn't necessary to + // test T == 0), the input to this function might NOT be valid, so we must test T == 0 + char result_X_bytes[32]; + fe_tobytes((unsigned char*)&result_X_bytes, p->X); + + // X != 0 + for (int i = 0; i < 32; ++i) { - if (p->X[n] | p->T[n]) + if (result_X_bytes[i]) return 0; - if (p->Y[n] != p->Z[n]) + } + + char result_T_bytes[32]; + fe_tobytes((unsigned char*)&result_T_bytes, p->T); + + // T != 0 + for (int i = 0; i < 32; ++i) + { + if (result_T_bytes[i]) + return 0; + } + + char result_Y_bytes[32]; + char result_Z_bytes[32]; + fe_tobytes((unsigned char*)&result_Y_bytes, p->Y); + fe_tobytes((unsigned char*)&result_Z_bytes, p->Z); + + // Y != Z + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != result_Z_bytes[i]) return 0; } - return 1; + + // is Y nonzero? then Y/Z == 1 + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != 0) + return 1; + } + + // Y/Z = 0/0 + return 0; } diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 22f76974b..96da16cbd 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -162,4 +162,4 @@ void fe_add(fe h, const fe f, const fe g); void fe_tobytes(unsigned char *, const fe); void fe_invert(fe out, const fe z); -int ge_p3_is_point_at_infinity(const ge_p3 *p); +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p); diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 7ddc0150f..599ae4f91 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -64,6 +64,11 @@ namespace crypto { friend class crypto_ops; }; + POD_CLASS public_key_memsafe : epee::mlocked<tools::scrubbed<public_key>> { + public_key_memsafe() = default; + public_key_memsafe(const public_key &original) { memcpy(this->data, original.data, 32); } + }; + using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>; POD_CLASS public_keyV { @@ -100,7 +105,7 @@ namespace crypto { void random32_unbiased(unsigned char *bytes); static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && - sizeof(public_key) == 32 && sizeof(secret_key) == 32 && + sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && sizeof(signature) == 64, "Invalid structure size"); @@ -310,9 +315,13 @@ namespace crypto { const extern crypto::public_key null_pkey; const extern crypto::secret_key null_skey; + + inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; } + inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; } } CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index 801987e37..247c9032f 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -63,6 +63,7 @@ static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static randomx_dataset *rx_dataset; static int rx_dataset_nomem; +static int rx_dataset_nolp; static uint64_t rx_dataset_height; static THREADV randomx_vm *rx_vm = NULL; @@ -316,10 +317,11 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) { rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); if(rx_vm == NULL) { //large pages failed mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); + rx_dataset_nolp = 1; } } if (rx_vm == NULL) @@ -370,5 +372,6 @@ void rx_stop_mining(void) { randomx_release_dataset(rd); } rx_dataset_nomem = 0; + rx_dataset_nolp = 0; CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 36ff41684..9927351a9 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -253,11 +253,6 @@ DISABLE_VS_WARNINGS(4244 4345) return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); } //----------------------------------------------------------------- - void account_base::finalize_multisig(const crypto::public_key &spend_public_key) - { - m_keys.m_account_address.m_spend_public_key = spend_public_key; - } - //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 5288b9b04..96b024c3c 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -82,7 +82,6 @@ namespace cryptonote void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys); - void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(network_type nettype) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const; diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index c6b81b094..24d452083 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -228,6 +228,20 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, rct::BulletproofPlus &x, const boost::serialization::version_type ver) + { + a & x.V; + a & x.A; + a & x.A1; + a & x.B; + a & x.r1; + a & x.s1; + a & x.d1; + a & x.L; + a & x.R; + } + + template <class Archive> inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { a & x.s0; @@ -305,7 +319,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 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) 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 @@ -321,7 +335,11 @@ namespace boost { a & x.rangeSigs; if (x.rangeSigs.empty()) + { a & x.bulletproofs; + if (ver >= 2u) + a & x.bulletproofs_plus; + } a & x.MGs; if (ver >= 1u) a & x.CLSAGs; @@ -335,7 +353,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 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) 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 @@ -347,11 +365,15 @@ namespace boost //-------------- a & x.p.rangeSigs; if (x.p.rangeSigs.empty()) + { a & x.p.bulletproofs; + if (ver >= 2u) + a & x.p.bulletproofs_plus; + } a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG) + if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus) a & x.p.pseudoOuts; } @@ -392,6 +414,6 @@ namespace boost } } -BOOST_CLASS_VERSION(rct::rctSigPrunable, 1) -BOOST_CLASS_VERSION(rct::rctSig, 1) +BOOST_CLASS_VERSION(rct::rctSigPrunable, 2) +BOOST_CLASS_VERSION(rct::rctSig, 2) 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 17adcdc35..cedc6f546 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -105,7 +105,9 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { - const uint64_t bp_base = 368; + const rct::rctSig &rv = tx.rct_signatures; + const bool plus = rv.type == rct::RCTTypeBulletproofPlus; + const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) return 0; @@ -113,7 +115,7 @@ namespace cryptonote while ((1u << nlr) < n_padded_outputs) ++nlr; nlr += 6; - const size_t bp_size = 32 * (9 + 2 * nlr); + const size_t bp_size = 32 * ((plus ? 6 : 9) + 2 * nlr); CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); @@ -164,7 +166,32 @@ namespace cryptonote if (!base_only) { const bool bulletproof = rct::is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (bulletproof_plus) + { + if (rv.p.bulletproofs_plus.size() != 1) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus size in tx " << get_transaction_hash(tx)); + return false; + } + if (rv.p.bulletproofs_plus[0].L.size() < 6) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); + return false; + } + const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]); + if (max_outputs < tx.vout.size()) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus max outputs in tx " << get_transaction_hash(tx)); + return false; + } + const size_t n_amounts = tx.vout.size(); + CHECK_AND_ASSERT_MES(n_amounts == rv.outPk.size(), false, "Internal error filling out V"); + rv.p.bulletproofs_plus[0].V.resize(n_amounts); + for (size_t i = 0; i < n_amounts; ++i) + rv.p.bulletproofs_plus[0].V[i] = rv.outPk[i].mask; + } + else if (bulletproof) { if (rv.p.bulletproofs.size() != 1) { @@ -306,7 +333,26 @@ namespace cryptonote { // derive secret key with subaddress - step 1: original CN derivation crypto::secret_key scalar_step1; - hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b + crypto::secret_key spend_skey = crypto::null_skey; + + if (ack.m_multisig_keys.empty()) + { + // if not multisig, use normal spend skey + spend_skey = ack.m_spend_secret_key; + } + else + { + // if multisig, use sum of multisig privkeys (local account's share of aggregate spend key) + for (const auto &multisig_key : ack.m_multisig_keys) + { + sc_add((unsigned char*)spend_skey.data, + (const unsigned char*)multisig_key.data, + (const unsigned char*)spend_skey.data); + } + } + + // computes Hs(a*R || idx) + b + hwdev.derive_secret_key(recv_derivation, real_output_index, spend_skey, scalar_step1); // step 2: add Hs(a || index_major || index_minor) crypto::secret_key subaddr_sk; @@ -400,9 +446,11 @@ namespace cryptonote if (tx.version < 2) return blob_size; const rct::rctSig &rv = tx.rct_signatures; - if (!rct::is_rct_bulletproof(rv.type)) + const bool bulletproof = rct::is_rct_bulletproof(rv.type); + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (!bulletproof && !bulletproof_plus) return blob_size; - const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs); + const size_t n_padded_outputs = bulletproof_plus ? rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus) : rct::n_bulletproof_max_amounts(rv.p.bulletproofs); uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - blob_size, "Weight overflow"); return blob_size + bp_clawback; @@ -412,8 +460,8 @@ 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 || 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.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus, + std::numeric_limits<uint64_t>::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); 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"); @@ -431,12 +479,12 @@ namespace cryptonote while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) ++nrl; nrl += 6; - extra = 32 * (9 + 2 * nrl) + 2; + extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; weight += extra; // calculate deterministic CLSAG/MLSAG data size const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); - if (tx.rct_signatures.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(tx.rct_signatures.type)) extra = tx.vin.size() * (ring_size + 2) * 32; else extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 915835d1b..ff61fc036 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -182,6 +182,7 @@ #define HF_VERSION_EXACT_COINBASE 13 #define HF_VERSION_CLSAG 13 #define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13 +#define HF_VERSION_BULLETPROOF_PLUS 15 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 @@ -190,6 +191,7 @@ #define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes #define BULLETPROOF_MAX_OUTPUTS 16 +#define BULLETPROOF_PLUS_MAX_OUTPUTS 16 #define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase #define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved @@ -221,6 +223,8 @@ namespace config // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; + const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus"; + const char HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT[] = "bulletproof_plus_transcript"; const char HASH_KEY_RINGDB[] = "ringdsb"; const char HASH_KEY_SUBADDRESS[] = "SubAddr"; const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; @@ -229,6 +233,7 @@ namespace config const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg"; 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"; @@ -236,6 +241,9 @@ namespace config const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; const unsigned char HASH_KEY_MM_SLOT = 'm'; + // Multisig + const uint32_t MULTISIG_MAX_SIGNERS{16}; + namespace testnet { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 34031fb7c..cd9972d1e 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3143,6 +3143,32 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v15, allow bulletproofs plus + if (hf_version < HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); + if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) + { + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + + // from v16, forbid bulletproofs + if (hf_version > HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); + if (bulletproof) + { + MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + return true; } //------------------------------------------------------------------ @@ -3183,7 +3209,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 || rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); @@ -3224,7 +3250,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { if (!tx.pruned) { @@ -3516,6 +3542,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: { // check all this, either reconstructed (so should really pass), or not { @@ -3551,7 +3578,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? 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"); @@ -3560,7 +3587,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, for (size_t n = 0; n < tx.vin.size(); ++n) { bool error; - if (rv.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(rv.type)) 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); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 4c6536318..1da4e2d41 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -879,6 +879,16 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs) + { + if (proofs.size() != 1) + return false; + const size_t sz = proofs[0].V.size(); + if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- bool core::handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block) { bool ret = true; @@ -943,6 +953,17 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; + case rct::RCTTypeBulletproofPlus: + if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) + { + MERROR_VER("Bulletproof_plus does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; default: MERROR_VER("Unknown rct type: " << rv.type); set_semantics_failed(tx_info[n].tx_hash); @@ -960,7 +981,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 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG) + 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 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus) continue; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index f6061b803..a50ebb550 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -43,7 +43,6 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" -#include "multisig/multisig.h" using namespace crypto; diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 4aa21b149..87de3c351 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -95,55 +95,35 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str } // gather the keys - std::vector<crypto::secret_key> sk(total); - std::vector<crypto::public_key> pk(total); + std::vector<std::string> first_round_msgs; + first_round_msgs.reserve(total); for (size_t n = 0; n < total; ++n) { wallets[n]->decrypt_keys(pwd_container->password()); - if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) - { - tools::fail_msg_writer() << genms::tr("Failed to verify multisig info"); - return false; - } + + first_round_msgs.emplace_back(wallets[n]->get_multisig_first_kex_msg()); + wallets[n]->encrypt_keys(pwd_container->password()); } // make the wallets multisig - std::vector<std::string> extra_info(total); + std::vector<std::string> kex_msgs_intermediate(total); std::stringstream ss; for (size_t n = 0; n < total; ++n) { std::string name = basename + "-" + std::to_string(n + 1); - std::vector<crypto::secret_key> skn; - std::vector<crypto::public_key> pkn; - for (size_t k = 0; k < total; ++k) - { - if (k != n) - { - skn.push_back(sk[k]); - pkn.push_back(pk[k]); - } - } - extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + + kex_msgs_intermediate[n] = wallets[n]->make_multisig(pwd_container->password(), first_round_msgs, threshold); + ss << " " << name << std::endl; } //exchange keys unless exchange_multisig_keys returns no extra info - while (!extra_info[0].empty()) + while (!kex_msgs_intermediate[0].empty()) { - std::unordered_set<crypto::public_key> pkeys; - std::vector<crypto::public_key> signers(total); - for (size_t n = 0; n < total; ++n) - { - if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n])) - { - tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info"); - return false; - } - } for (size_t n = 0; n < total; ++n) { - extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers); + kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate); } } diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 9055b92e3..45db59a67 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -70,6 +70,9 @@ const hardfork_t mainnet_hard_forks[] = { { 13, 2210000, 0, 1598180817 }, { 14, 2210720, 0, 1598180818 }, + + { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules + { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules }; 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; diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index eaa2c6f71..14099e64a 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -27,12 +27,17 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(multisig_sources - multisig.cpp) + multisig.cpp + multisig_account.cpp + multisig_account_kex_impl.cpp + multisig_kex_msg.cpp) set(multisig_headers) set(multisig_private_headers - multisig.h) + multisig.h + multisig_account.h + multisig_kex_msg.h) monero_private_headers(multisig ${multisig_private_headers}) diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 272de73b2..85c45bc31 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2021, The Monero Project // // All rights reserved. // @@ -26,29 +26,34 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include <unordered_set> -#include "include_base_utils.h" #include "crypto/crypto.h" -#include "ringct/rctOps.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_format_utils.h" -#include "multisig.h" #include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "ringct/rctOps.h" + +#include <algorithm> +#include <unordered_map> +#include <unordered_set> +#include <vector> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "multisig" -using namespace std; - -namespace cryptonote +namespace multisig { - //----------------------------------------------------------------- + //---------------------------------------------------------------------------------------------------------------------- crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { + CHECK_AND_ASSERT_THROW_MES(key != crypto::null_skey, "Unexpected null secret key (danger!)."); + rct::key multisig_salt; static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size"); memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key)); + // private key = H(key, domain-sep) rct::keyV data; data.reserve(2); data.push_back(rct::sk2rct(key)); @@ -57,134 +62,79 @@ namespace cryptonote memwipe(&data[0], sizeof(rct::key)); return result; } - //----------------------------------------------------------------- - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - // the multisig spend public key is the sum of all spend public keys - multisig_keys.clear(); - const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key"); - for (const auto &k: spend_keys) - rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(spend_secret_key); - spend_skey = rct::sk2rct(spend_secret_key); - } - //----------------------------------------------------------------- - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - multisig_keys.clear(); - spend_pkey = rct::identity(); - spend_skey = rct::zero(); - - // create all our composite private keys - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: spend_keys) - { - rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk)); - memwipe(&sk, sizeof(sk)); - multisig_keys.push_back(msk); - sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data); - } - } - //----------------------------------------------------------------- - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations) - { - std::vector<crypto::public_key> multisig_keys; - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: derivations) - { - rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - multisig_keys.push_back(rct::rct2pk(d)); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys) - { - rct::key secret_key = rct::zero(); - for (const auto &k: multisig_keys) - { - sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data); - } - - return rct::rct2sk(secret_key); - } - //----------------------------------------------------------------- - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations) - { - std::vector<crypto::secret_key> multisig_keys; - multisig_keys.reserve(derivations.size()); - - for (const auto &k: derivations) - { - multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k)))); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys) - { - crypto::secret_key view_skey = get_multisig_blinded_secret_key(skey); - for (const auto &k: skeys) - sc_add((unsigned char*)&view_skey, rct::sk2rct(view_skey).bytes, rct::sk2rct(k).bytes); - return view_skey; - } - //----------------------------------------------------------------- - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys) - { - rct::key spend_public_key = rct::identity(); - for (const auto &pk: pkeys) - { - rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); - } - return rct::rct2pk(spend_public_key); - } - //----------------------------------------------------------------- - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki) { if (multisig_key_index >= keys.m_multisig_keys.size()) return false; crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); return true; } - //----------------------------------------------------------------- - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R) + //---------------------------------------------------------------------------------------------------------------------- + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R) { rct::scalarmultBase((rct::key&)L, rct::sk2rct(k)); crypto::generate_key_image(pkey, k, (crypto::key_image&)R); } - //----------------------------------------------------------------- - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki) { + // create a multisig partial key image + // KI_partial = ([view key component] + [subaddress component] + [multisig privkeys]) * Hp(output one-time address) + // - the 'multisig priv keys' here are those held by the local account + // - later, we add in the components held by other participants cryptonote::keypair in_ephemeral; if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki, keys.get_device())) return false; std::unordered_set<crypto::key_image> used; - for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) + + // create a key image component for each of the local account's multisig private keys + for (std::size_t m = 0; m < keys.m_multisig_keys.size(); ++m) { crypto::key_image pki; - bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); + // pki = keys.m_multisig_keys[m] * Hp(out_key) + // pki = key image component + // out_key = one-time address of an output owned by the multisig group + bool r = generate_multisig_key_image(keys, m, out_key, pki); if (!r) return false; + + // this KI component is 'used' because it was included in the partial key image 'ki' above used.insert(pki); } + + // add the KI components from other participants to the partial KI + // if they not included yet for (const auto &pki: pkis) { if (used.find(pki) == used.end()) { + // ignore components that have already been 'used' used.insert(pki); + + // KI_partial = KI_partial + KI_component[...] rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); } } + + // at the end, 'ki' will hold the true key image for our output if inputs were sufficient + // - if 'pkis' (the other participants' KI components) is missing some components + // then 'ki' will not be complete + return true; } - //----------------------------------------------------------------- - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold) - { - CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold"); - return participants - threshold + 1; - } -} + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index eab32187c..e041ea670 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2021, The Monero Project // // All rights reserved. // @@ -28,44 +28,42 @@ #pragma once -#include <vector> -#include <unordered_map> #include "crypto/crypto.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "ringct/rctTypes.h" -namespace cryptonote -{ - struct account_keys; +#include <unordered_map> +#include <unordered_set> +#include <vector> - crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - /** - * @brief generate_multisig_derivations performs common DH key derivation. - * Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant. - * this functions does the following: new multisig key = secret spend * public multisig key - * @param keys - current wallet's keys - * @param derivations - public multisig keys of other participants - * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants - */ - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations); - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations); - /** - * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi) - * @param derivations - others' participants public multisig keys. - * @return vector of current wallet's multisig secret keys - */ - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations); - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys); +namespace cryptonote { struct account_keys; } + +namespace multisig +{ /** - * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys - * @param pkeys unique public multisig keys - * @return multisig wallet's spend public key - */ - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys); - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki); - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold); -} + * @brief get_multisig_blinded_secret_key - converts an input private key into a blinded multisig private key + * Use 1a: converts account private spend key into multisig private key, which is used for key exchange and message signing + * Use 1b: converts account private view key into ancillary private key share, for the composite multisig private view key + * Use 2: converts DH shared secrets (curve points) into private keys, which are intermediate private keys in multisig key exchange + * @param key - private key to transform + * @return transformed private key + */ + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); + + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki); + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R); + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki); +} //namespace multisig diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp new file mode 100644 index 000000000..b7298c4b6 --- /dev/null +++ b/src/multisig/multisig_account.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2021, 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. + +#include "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstdint> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_pubkey{rct::rct2pk(rct::identity())}, + m_common_pubkey{rct::rct2pk(rct::identity())}, + m_kex_rounds_complete{0}, + m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector<crypto::public_key>{}, base_common_privkey}.get_msg()} + { + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + kex_origins_map_t kex_origins_map, + std::string next_round_kex_message) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_privkeys{std::move(multisig_privkeys)}, + m_common_privkey{common_privkey}, + m_multisig_pubkey{multisig_pubkey}, + m_common_pubkey{common_pubkey}, + m_kex_rounds_complete{kex_rounds_complete}, + m_kex_keys_to_origins_map{std::move(kex_origins_map)}, + m_next_round_kex_message{std::move(next_round_kex_message)} + { + CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized"); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + set_multisig_config(threshold, std::move(signers)); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::account_is_active() const + { + return m_kex_rounds_complete > 0; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::multisig_is_ready() const + { + if (account_is_active()) + return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete; + else + return false; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers) + { + // validate + CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold."); + CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS, + "multisig account: tried to set invalid number of signers."); + + for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it) + { + // signers should all be unique + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it, + "multisig account: tried to set signers, but found a duplicate signer unexpectedly."); + + // signer pubkeys must be in main subgroup, and not identity + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())), + "multisig account: tried to set signers, but a signer pubkey is invalid."); + } + + // own pubkey should be in signers list + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(), + "multisig account: tried to set signers, but did not find the account's base pubkey in signer list."); + + // sort signers + std::sort(signers.begin(), signers.end(), + [](const crypto::public_key &key1, const crypto::public_key &key2) -> bool + { + return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0; + } + ); + + // set + m_threshold = threshold; + m_signers = std::move(signers); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1) + { + CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized"); + + // only mutate account if update succeeds + multisig_account temp_account{*this}; + temp_account.set_multisig_config(threshold, std::move(signers)); + temp_account.kex_update_impl(expanded_msgs_rnd1); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); + CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); + + multisig_account temp_account{*this}; + temp_account.kex_update_impl(expanded_msgs); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) + { + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1"); + return num_signers - threshold + 1; + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h new file mode 100644 index 000000000..b01ae6c88 --- /dev/null +++ b/src/multisig/multisig_account.h @@ -0,0 +1,246 @@ +// Copyright (c) 2021, 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. + +#pragma once + +#include "crypto/crypto.h" +#include "multisig_kex_msg.h" + +#include <cstdint> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + + +namespace multisig +{ + /** + * multisig account: + * + * - handles account keys for an M-of-N multisig participant (M <= N; M >= 1; N >= 2) + * - encapsulates multisig account construction process (via key exchange [kex]) + * - TODO: encapsulates key preparation for aggregation-style signing + * + * :: multisig pubkey: the private key is split, M group participants are required to reassemble (e.g. to sign something) + * - in cryptonote, this is the multisig spend key + * :: multisig common pubkey: the private key is known to all participants (e.g. for authenticating as a group member) + * - in cryptonote, this is the multisig view key + * + * + * multisig key exchange: + * + * An 'M-of-N' (M <= N; M >= 1; N >= 2) multisignature key is a public key where at least 'M' out of 'N' + * possible co-signers must collaborate in order to create a signature. + * + * Constructing a multisig key involves a series of Diffie-Hellman exchanges between participants. + * At the end of key exchange (kex), each participant will hold a number of private keys. Each private + * key is shared by a group of (N - M + 1) participants. This way if (N - M) co-signers are missing, every + * private key will be held by at least one of the remaining M people. + * + * Note on MULTISIG_MAX_SIGNERS: During key exchange, participants will have up to '(N - 1) choose (N - M)' + * key shares. If N is large, then the max number of key shares (when M = (N-1)/2) can be huge. A limit of N <= 16 was + * arbitrarily chosen as a power of 2 that can accomodate the vast majority of practical use-cases. To increase the + * limit, FROST-style key aggregation should be used instead (it is more efficient than DH-based key generation + * when N - M > 1). + * + * - Further reading + * - MRL-0009: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * - MuSig2: https://eprint.iacr.org/2020/1261 + * - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3 + * - FROST: https://eprint.iacr.org/2018/417 + */ + class multisig_account final + { + public: + //member types + using kex_origins_map_t = std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>; + + //constructors + // default constructor + multisig_account() = default; + + /** + * construct from base privkeys + * + * - prepares a kex msg for the first round of multisig key construction. + * - the local account's kex msgs are signed with the base_privkey + * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey + */ + multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey); + + // reconstruct from full account details (not recommended) + multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + kex_origins_map_t kex_origins_map, + std::string next_round_kex_message); + + // copy constructor: default + + //destructor: default + ~multisig_account() = default; + + //overloaded operators: none + + //getters + // get threshold + std::uint32_t get_threshold() const { return m_threshold; } + // get signers + const std::vector<crypto::public_key>& get_signers() const { return m_signers; } + // get base privkey + const crypto::secret_key& get_base_privkey() const { return m_base_privkey; } + // get base pubkey + const crypto::public_key& get_base_pubkey() const { return m_base_pubkey; } + // get base common privkey + const crypto::secret_key& get_base_common_privkey() const { return m_base_common_privkey; } + // get multisig privkeys + const std::vector<crypto::secret_key>& get_multisig_privkeys() const { return m_multisig_privkeys; } + // get common privkey + const crypto::secret_key& get_common_privkey() const { return m_common_privkey; } + // get multisig pubkey + const crypto::public_key& get_multisig_pubkey() const { return m_multisig_pubkey; } + // get common pubkey + const crypto::public_key& get_common_pubkey() const { return m_common_pubkey; } + // get kex rounds complete + std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; } + // get kex keys to origins map + const kex_origins_map_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; } + // get the kex msg for the next round + const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; } + + //account status functions + // account has been intialized, and the account holder can use the 'common' key + bool account_is_active() const; + // account is ready to make multisig signatures + bool multisig_is_ready() const; + + //account helpers + private: + // set the threshold (M) and signers (N) + void set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers); + + //account mutators: key exchange to set up account + public: + /** + * brief: initialize_kex - initialize key exchange + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + */ + void initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1); + /** + * brief: kex_update - Complete the 'in progress' kex round and set the kex message for the next round. + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + * - The main interface for multisig key exchange, this handles all the work of processing input messages, + * creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete. + * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round + */ + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs); + + private: + // implementation of kex_update() (non-transactional) + void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs); + /** + * brief: initialize_kex_update - Helper for kex_update_impl() + * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key + * if appropriate. + * param: expanded_msgs - set of multisig kex messages to process + * param: rounds_required - number of rounds required for kex + * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + */ + void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out); + /** + * brief: finalize_kex_update - Helper for kex_update_impl() + * param: rounds_required - number of rounds required for kex + * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to + * inoutparam: temp_account_inout - account to perform last update steps on + */ + void finalize_kex_update(const std::uint32_t rounds_required, + kex_origins_map_t result_keys_to_origins_map); + + //member variables + private: + /// misc. account details + // [M] minimum number of co-signers to sign a message with the aggregate pubkey + std::uint32_t m_threshold{0}; + // [N] base keys of all participants in the multisig (used to initiate key exchange, and as participant ids for msg signing) + std::vector<crypto::public_key> m_signers; + + /// local participant's personal keys + // base keypair of the participant + // - used for signing messages, as the initial base key for key exchange, and to make DH derivations for key exchange + crypto::secret_key m_base_privkey; + crypto::public_key m_base_pubkey; + // common base privkey, used to produce the aggregate common privkey + crypto::secret_key m_base_common_privkey; + + /// core multisig account keys + // the account's private key shares of the multisig address + // TODO: also record which other signers have these privkeys, to enable aggregation signing (instead of round-robin) + std::vector<crypto::secret_key> m_multisig_privkeys; + // a privkey owned by all multisig participants (e.g. a cryptonote view key) + crypto::secret_key m_common_privkey; + // the multisig public key (e.g. a cryptonote spend key) + crypto::public_key m_multisig_pubkey; + // the common public key (e.g. a view spend key) + crypto::public_key m_common_pubkey; + + /// kex variables + // number of key exchange rounds that have been completed (all messages for the round collected and processed) + std::uint32_t m_kex_rounds_complete{0}; + // this account's pubkeys for the in-progress key exchange round + // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done) + kex_origins_map_t m_kex_keys_to_origins_map; + // the account's message for the in-progress key exchange round + std::string m_next_round_kex_message; + }; + + /** + * brief: multisig_kex_rounds_required - The number of key exchange rounds required to produce an M-of-N shared key. + * - Key exchange (kex) is a synchronous series of 'rounds'. In an 'active round', participants send messages + * to each other. + * - A participant considers a round 'complete' when they have collected sufficient messages + * from other participants, processed those messages, and updated their multisig account state. + * - Typically (as implemented in this module), completing a round coincides with making a message for the next round. + * param: num_signers - number of participants in multisig (N) + * param: threshold - threshold of multisig (M) + * return: number of kex rounds required + */ + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); +} //namespace multisig diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp new file mode 100644 index 000000000..0a0ca7bdc --- /dev/null +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -0,0 +1,726 @@ +// Copyright (c) 2021, 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. + +#include "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" + +#include <boost/math/special_functions/binomial.hpp> + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <limits> +#include <memory> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key + * Converts an input public key into a crypto private key (type cast, does not change serialization), + * then passes it to get_multisig_blinded_secret_key(). + * + * Result: + * - privkey = H(derivation) + * - pubkey = privkey * G + * param: derivation - a curve point + * outparam: derived_pubkey_out - public key of the resulting privkey + * return: multisig private key + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::secret_key calculate_multisig_keypair_from_derivation(const crypto::public_key_memsafe &derivation, + crypto::public_key &derived_pubkey_out) + { + crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(derivation))); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(blinded_skey, derived_pubkey_out), "Failed to derive public key"); + + return blinded_skey; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: make_multisig_common_privkey - Create the 'common' multisig privkey, owned by all multisig participants. + * - common privkey = H(sorted base common privkeys) + * param: participant_base_common_privkeys - Base common privkeys contributed by multisig participants. + * outparam: common_privkey_out - result + */ + //---------------------------------------------------------------------------------------------------------------------- + static void make_multisig_common_privkey(std::vector<crypto::secret_key> participant_base_common_privkeys, + crypto::secret_key &common_privkey_out) + { + // sort the privkeys for consistency + //TODO: need a constant-time operator< for sorting secret keys + std::sort(participant_base_common_privkeys.begin(), participant_base_common_privkeys.end(), + [](const crypto::secret_key &key1, const crypto::secret_key &key2) -> bool + { + return memcmp(&key1, &key2, sizeof(crypto::secret_key)) < 0; + } + ); + + // privkey = H(sorted ancillary base privkeys) + crypto::hash_to_scalar(participant_base_common_privkeys.data(), + participant_base_common_privkeys.size()*sizeof(crypto::secret_key), + common_privkey_out); + + CHECK_AND_ASSERT_THROW_MES(common_privkey_out != crypto::null_skey, "Unexpected null secret key (danger!)."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: compute_multisig_aggregation_coefficient - creates aggregation coefficient for a specific public key in a set + * of public keys + * + * WARNING: The coefficient will only be deterministic if... + * 1) input keys are pre-sorted + * - tested here + * 2) input keys are in canonical form (compressed points in the prime-order subgroup of Ed25519) + * - untested here for performance + * param: sorted_keys - set of component public keys that will be merged into a multisig public spend key + * param: aggregation_key - one of the component public keys + * return: aggregation coefficient + */ + //---------------------------------------------------------------------------------------------------------------------- + static rct::key compute_multisig_aggregation_coefficient(const std::vector<crypto::public_key> &sorted_keys, + const crypto::public_key &aggregation_key) + { + CHECK_AND_ASSERT_THROW_MES(std::is_sorted(sorted_keys.begin(), sorted_keys.end()), + "Keys for aggregation coefficient aren't sorted."); + + // aggregation key must be in sorted_keys + CHECK_AND_ASSERT_THROW_MES(std::find(sorted_keys.begin(), sorted_keys.end(), aggregation_key) != sorted_keys.end(), + "Aggregation key expected to be in input keyset."); + + // aggregation coefficient salt + rct::key salt = rct::zero(); + static_assert(sizeof(rct::key) >= sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION), "Hash domain separator is too big."); + memcpy(salt.bytes, config::HASH_KEY_MULTISIG_KEY_AGGREGATION, sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION)); + + // coeff = H(aggregation_key, sorted_keys, domain-sep) + rct::keyV data; + data.reserve(sorted_keys.size() + 2); + data.push_back(rct::pk2rct(aggregation_key)); + for (const auto &key : sorted_keys) + data.push_back(rct::pk2rct(key)); + data.push_back(salt); + + // note: coefficient is considered public knowledge, no need to memwipe data + return rct::hash_to_scalar(data); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: generate_multisig_aggregate_key - generates a multisig public spend key via key aggregation + * Key aggregation via aggregation coefficients prevents key cancellation attacks. + * See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * param: final_keys - address components (public keys) obtained from other participants (not shared with local) + * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference) + * return: final multisig public spend key for the account + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::public_key generate_multisig_aggregate_key(std::vector<crypto::public_key> final_keys, + std::vector<crypto::secret_key> &privkeys_inout) + { + // collect all public keys that will go into the spend key (these don't need to be memsafe) + final_keys.reserve(final_keys.size() + privkeys_inout.size()); + + // 1. convert local multisig private keys to pub keys + // 2. insert to final keyset if not there yet + // 3. save the corresponding index of input priv key set for later reference + std::unordered_map<crypto::public_key, std::size_t> own_keys_mapping; + + for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index) + { + crypto::public_key pubkey; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key"); + + own_keys_mapping[pubkey] = multisig_keys_index; + + final_keys.push_back(pubkey); + } + + // sort input final keys for computing aggregation coefficients (lowest to highest) + // note: input should be sanitized (no duplicates) + std::sort(final_keys.begin(), final_keys.end()); + CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(final_keys.begin(), final_keys.end()) == final_keys.end(), + "Unexpected duplicate found in input list."); + + // key aggregation + rct::key aggregate_key = rct::identity(); + + for (const crypto::public_key &key : final_keys) + { + // get aggregation coefficient + rct::key coeff = compute_multisig_aggregation_coefficient(final_keys, key); + + // convert private key if possible + // note: retain original priv key index in input list, in case order matters upstream + auto found_key = own_keys_mapping.find(key); + if (found_key != own_keys_mapping.end()) + { + // k_agg = coeff*k_base + sc_mul((unsigned char*)&(privkeys_inout[found_key->second]), + coeff.bytes, + (const unsigned char*)&(privkeys_inout[found_key->second])); + + CHECK_AND_ASSERT_THROW_MES(privkeys_inout[found_key->second] != crypto::null_skey, + "Multisig privkey with aggregation coefficient unexpectedly null."); + } + + // convert public key (pre-merge operation) + // K_agg = coeff*K_base + rct::key converted_pubkey = rct::scalarmultKey(rct::pk2rct(key), coeff); + + // build aggregate key (merge operation) + rct::addKeys(aggregate_key, aggregate_key, converted_pubkey); + } + + return rct::rct2pk(aggregate_key); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction. + * - Involves DH exchanges with pubkeys provided by other participants. + * - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you]. + * param: base_privkey - account's base private key, for performing DH exchanges and signing messages + * param: round - the round of the message that should be produced + * param: threshold - threshold for multisig (M in M-of-N) + * param: num_signers - number of participants in multisig (N) + * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of + * participants who will share each derivation with you + * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys + * - If msg is not for the last round, then these derivations are also stored in the output message + * so they can be sent to other participants, who will make more DH derivations for the next kex round. + * - If msg is for the last round, then these derivations won't be sent to other participants. + * Instead, they are converted to share secrets (i.e. s = H(derivation)) and multiplied by G. + * The keys s*G are sent to other participants in the message, so they can be used to produce the final + * multisig key via generate_multisig_spend_public_key(). + * - The values s are the local account's shares of the final multisig key's private key. The caller can + * compute those values with calculate_multisig_keypair_from_derivation() (or compute them directly). + * return: multisig kex message for the specified round + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_kex_msg multisig_kex_make_next_msg(const crypto::secret_key &base_privkey, + const std::uint32_t round, + const std::uint32_t threshold, + const std::uint32_t num_signers, + const std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &pubkey_origins_map, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &derivation_origins_map_out) + { + CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, + "Multisig threshold may not be larger than number of signers."); + CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); + CHECK_AND_ASSERT_THROW_MES(round > 1, "Round for next msg must be > 1."); + CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold), + "Trying to make key exchange message for an invalid round."); + + // make shared secrets with input pubkeys + std::vector<crypto::public_key> msg_pubkeys; + msg_pubkeys.reserve(pubkey_origins_map.size()); + derivation_origins_map_out.clear(); + + for (const auto &pubkey_and_origins : pubkey_origins_map) + { + // D = 8 * k_base * K_pubkey + // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local + // base_privkey is a multiple of 8 or not + // note2: avoid making temporaries that won't be memwiped + rct::key derivation_rct; + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(&derivation_rct, sizeof(rct::key)); + }); + + rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey)); + rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT); + + crypto::public_key_memsafe derivation{rct::rct2pk(derivation_rct)}; + + // retain mapping between pubkey's origins and the DH derivation + // note: if msg for last round, then caller must know how to handle these derivations properly + derivation_origins_map_out[derivation] = pubkey_and_origins.second; + + // if the last round, convert derivations to public keys for the output message + if (round == multisig_kex_rounds_required(num_signers, threshold)) + { + // derived_pubkey = H(derivation)*G + crypto::public_key derived_pubkey; + calculate_multisig_keypair_from_derivation(derivation, derived_pubkey); + msg_pubkeys.push_back(derived_pubkey); + } + // otherwise, put derivations in message directly, so other signers can in turn create derivations (shared secrets) + // with them for the next round + else + msg_pubkeys.push_back(derivation); + } + + return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)}; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages. + * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key, + * ignores messages signed by the local account, ignores keys found in input 'exclusion set', + * constructs map of pubkey:origins. + * - Requires that all input msgs have the same round number. + * + * origins = all the signing pubkeys that recommended a given pubkey found in input msgs + * + * - If the messages' round numbers are all '1', then only the message signing pubkey is considered + * 'recommended'. Furthermore, the 'exclusion set' is ignored. + * param: own_pubkey - local account's signing key (key used to sign multisig messages) + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - pubkeys to exclude from output set + * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins + * return: round number shared by all input msgs + */ + //---------------------------------------------------------------------------------------------------------------------- + static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &sanitized_pubkeys_out) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + + std::uint32_t round = expanded_msgs[0].get_round(); + sanitized_pubkeys_out.clear(); + + // get all pubkeys from input messages, add them to pubkey:origins map + // - origins = all the signing pubkeys that recommended a given msg pubkey + for (const auto &expanded_msg : expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number."); + + // ignore messages from self + if (expanded_msg.get_signing_pubkey() == own_pubkey) + continue; + + // in round 1, only the signing pubkey is treated as a msg pubkey + if (round == 1) + { + // note: ignores duplicates + sanitized_pubkeys_out[expanded_msg.get_signing_pubkey()].insert(expanded_msg.get_signing_pubkey()); + } + // in other rounds, only the msg pubkeys are treated as msg pubkeys + else + { + // copy all pubkeys from message into list + for (const auto &pubkey : expanded_msg.get_msg_pubkeys()) + { + // ignore own pubkey + if (pubkey == own_pubkey) + continue; + + // ignore pubkeys in 'ignore' set + if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end()) + continue; + + // note: ignores duplicates + sanitized_pubkeys_out[pubkey].insert(expanded_msg.get_signing_pubkey()); + } + } + } + + return round; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round. + * - Sanitizes input msgs. + * - Require uniqueness in: 'signers', 'exclude_pubkeys'. + * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers. + * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be + * shared by (N - M + 1) signers. + * - Requires that msgs are signed by only keys in 'signers'. + * - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations each signer recommends for round 'expected_round', excluding derivations shared + * with the local account. + * - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations the local account has corresponding to round 'expected_round'. + * param: base_privkey - multisig account's base private key + * param: expected_round - expected kex round of input messages + * param: threshold - threshold for multisig (M in M-of-N) + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round' + * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message + */ + //---------------------------------------------------------------------------------------------------------------------- + static std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluate_multisig_kex_round_msgs( + const crypto::public_key &base_pubkey, + const std::uint32_t expected_round, + const std::uint32_t threshold, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(signers.size() > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(signers.size() <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(signers.size() >= threshold, "Multisig threshold may not be larger than number of signers."); + CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); + CHECK_AND_ASSERT_THROW_MES(expected_round > 0, "Expected round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(expected_round <= multisig_kex_rounds_required(signers.size(), threshold), + "Expecting key exchange messages for an invalid round."); + + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> pubkey_origins_map; + + // leave early in the last round of 1-of-N, where all signers share a key so the local signer doesn't care about + // recommendations from other signers + if (threshold == 1 && expected_round == multisig_kex_rounds_required(signers.size(), threshold)) + return pubkey_origins_map; + + // exclude_pubkeys should all be unique + for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) + { + CHECK_AND_ASSERT_THROW_MES(std::find(exclude_pubkeys.begin(), it, *it) == it, + "Found duplicate pubkeys for exclusion unexpectedly."); + } + + // sanitize input messages + std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map); + CHECK_AND_ASSERT_THROW_MES(round == expected_round, + "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + + // evaluate pubkeys collected + std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; + + // 1. each pubkey should be recommended by a precise number of signers + for (const auto &pubkey_and_origins : pubkey_origins_map) + { + // expected amount = round_num + // With each successive round, pubkeys are shared by incrementally larger groups, + // starting at 1 in round 1 (i.e. the local multisig key to start kex with). + CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // map (sanitized) pubkeys back to origins + for (const auto &origin : pubkey_and_origins.second) + origin_pubkeys_map[origin].insert(pubkey_and_origins.first); + } + + // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer) + CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1, + "Number of unique other signers does not equal number of other signers that recommended pubkeys."); + + // 3. each origin should recommend a precise number of pubkeys + + // TODO: move to a 'math' library, with unit tests + auto n_choose_k_f = + [](const std::uint32_t n, const std::uint32_t k) -> std::uint32_t + { + static_assert(std::numeric_limits<std::int32_t>::digits <= std::numeric_limits<double>::digits, + "n_choose_k requires no rounding issues when converting between int32 <-> double."); + + if (n < k) + return 0; + + double fp_result = boost::math::binomial_coefficient<double>(n, k); + + if (fp_result < 0) + return 0; + + if (fp_result > std::numeric_limits<std::int32_t>::max()) // note: std::round() returns std::int32_t + return 0; + + return static_cast<std::uint32_t>(std::round(fp_result)); + }; + + // other signers: (N - 2) choose (msg_round_num - 1) + // - Each signer recommends keys they share with other signers. + // - In each round, a signer shares a key with 'round num - 1' other signers. + // - Since 'origins pubkey map' excludes keys shared with the local account, + // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers). + // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). + // - Each origin should have a shared key with each group of size 'round - 1'. + // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may + // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). + std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); + + // local: (N - 1) choose (msg_round_num - 1) + std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1); + + // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case + CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, + "Bad num signers or round num (possibly numerical limits exceeded)."); + + // check that local account recommends expected number of keys + CHECK_AND_ASSERT_THROW_MES(exclude_pubkeys.size() == expected_recommendations_self, + "Local account did not recommend expected number of multisig keys."); + + // check that other signers recommend expected number of keys + for (const auto &origin_and_pubkeys : origin_pubkeys_map) + { + CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // 2 (continued). only expected signers should be recommending keys + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(), + "Multisig kex message with unexpected signer encountered."); + } + + // note: above tests implicitly detect if the total number of recommended keys is correct or not + return pubkey_origins_map; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_process_round - Process kex messages for the active kex round. + * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg(). + * - In other words, evaluate the input messages and try to make a message for the next round. + * - Note: Must be called on the final round's msgs to evaluate the final key components + * recommended by other participants. + * param: base_privkey - multisig account's base private key + * param: current_round - round of kex the input messages should be designed for + * param: threshold - threshold for multisig (M in M-of-N) + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + * outparam: keys_to_origins_map_out - map between round keys and identity keys + * - If in the final round, these are key shares recommended by other signers for the final aggregate key. + * - Otherwise, these are the local account's DH derivations for the next round. + * - See multisig_kex_make_next_msg() for an explanation. + * return: multisig kex message for next round, or empty message if 'current_round' is the final round + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_kex_msg multisig_kex_process_round(const crypto::secret_key &base_privkey, + const crypto::public_key &base_pubkey, + const std::uint32_t current_round, + const std::uint32_t threshold, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &keys_to_origins_map_out) + { + // evaluate messages + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluated_pubkeys = + evaluate_multisig_kex_round_msgs(base_pubkey, current_round, threshold, signers, expanded_msgs, exclude_pubkeys); + + // produce message for next round (if there is one) + if (current_round < multisig_kex_rounds_required(signers.size(), threshold)) + { + return multisig_kex_make_next_msg(base_privkey, + current_round + 1, + threshold, + signers.size(), + evaluated_pubkeys, + keys_to_origins_map_out); + } + else + { + // no more rounds, so collect the key shares recommended by other signers for the final aggregate key + keys_to_origins_map_out.clear(); + keys_to_origins_map_out = std::move(evaluated_pubkeys); + + return multisig_kex_msg{}; + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out) + { + if (m_kex_rounds_complete == 0) + { + // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys + + // collect participants' base common privkey shares + // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers + // will be blocked by duplicate-signer errors after this function is called + std::vector<crypto::secret_key> participant_base_common_privkeys; + participant_base_common_privkeys.reserve(expanded_msgs.size() + 1); + + // add local ancillary base privkey + participant_base_common_privkeys.emplace_back(m_base_common_privkey); + + // add other signers' base common privkeys + for (const auto &expanded_msg : expanded_msgs) + { + if (expanded_msg.get_signing_pubkey() != m_base_pubkey) + { + participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey()); + } + } + + // make common privkey + make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); + + // set common pubkey + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey), + "Failed to derive public key"); + + // if N-of-N, then the base privkey will be used directly to make the account's share of the final key + if (rounds_required == 1) + { + m_multisig_privkeys.clear(); + m_multisig_privkeys.emplace_back(m_base_privkey); + } + + // exclude all keys the local account recommends + // - in the first round, only the local pubkey is recommended by the local signer + exclude_pubkeys_out.emplace_back(m_base_pubkey); + } + else + { + // in other rounds, kex msgs will contain participants' shared keys + + // ignore shared keys the account helped create for this round + for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map) + { + exclude_pubkeys_out.emplace_back(shared_key_with_origins.first); + } + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::finalize_kex_update(const std::uint32_t rounds_required, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map) + { + // prepare for next round (or complete the multisig account fully) + if (rounds_required == m_kex_rounds_complete + 1) + { + // finished (have set of msgs to complete address) + + // when 'completing the final round', result keys are other signers' shares of the final key + std::vector<crypto::public_key> result_keys; + result_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &result_key_and_origins : result_keys_to_origins_map) + { + result_keys.emplace_back(result_key_and_origins.first); + } + + // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied + m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys); + + // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys) + // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing + m_kex_keys_to_origins_map.clear(); + } + else if (rounds_required == m_kex_rounds_complete + 2) + { + // one more round (must send/receive one more set of kex msgs) + // - at this point, have local signer's pre-aggregation private key shares of the final address + + // result keys are the local signer's DH derivations for the next round + + // derivations are shared secrets between each group of N - M + 1 signers of which the local account is a member + // - convert them to private keys: multisig_key = H(derivation) + // - note: shared key = multisig_key[i]*G is recorded in the kex msg for sending to other participants + // instead of the original 'derivation' value (which MUST be kept secret!) + m_multisig_privkeys.clear(); + m_multisig_privkeys.reserve(result_keys_to_origins_map.size()); + + m_kex_keys_to_origins_map.clear(); + + for (const auto &derivation_and_origins : result_keys_to_origins_map) + { + // multisig_privkey = H(derivation) + // derived pubkey = multisig_key * G + crypto::public_key_memsafe derived_pubkey; + m_multisig_privkeys.push_back( + calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)); + + // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key] + m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second); + } + } + else + { + // next round is an 'intermediate' key exchange round, so there is nothing special to do here + + // save the account's kex keys for this round [DH derivation : other signers who will have the same derivation] + m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map); + } + + // a full set of msgs has been collected and processed, so the 'round is complete' + ++m_kex_rounds_complete; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "No key exchange messages passed in."); + + const std::uint32_t rounds_required = multisig_kex_rounds_required(m_signers.size(), m_threshold); + CHECK_AND_ASSERT_THROW_MES(rounds_required > 0, "Multisig kex rounds required unexpectedly 0."); + + // initialize account update + std::vector<crypto::public_key> exclude_pubkeys; + initialize_kex_update(expanded_msgs, rounds_required, exclude_pubkeys); + + // evaluate messages and get this account's kex msg for the next round + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map; + + m_next_round_kex_message = multisig_kex_process_round( + m_base_privkey, + m_base_pubkey, + m_kex_rounds_complete + 1, + m_threshold, + m_signers, + expanded_msgs, + exclude_pubkeys, + result_keys_to_origins_map).get_msg(); + + // finish account update + finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map)); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp new file mode 100644 index 000000000..2bbceb19d --- /dev/null +++ b/src/multisig/multisig_kex_msg.cpp @@ -0,0 +1,290 @@ +// Copyright (c) 2021, 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. + +#include "multisig_kex_msg.h" +#include "multisig_kex_msg_serialization.h" + +#include "common/base58.h" +#include "crypto/crypto.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "include_base_utils.h" +#include "ringct/rctOps.h" +#include "serialization/binary_archive.h" +#include "serialization/serialization.h" + +#include <boost/utility/string_ref.hpp> + +#include <sstream> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +const boost::string_ref MULTISIG_KEX_V1_MAGIC{"MultisigV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V1_MAGIC{"MultisigxV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_1{"MultisigxV2R1"}; //round 1 +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_N{"MultisigxV2Rn"}; //round n > 1 + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey) : + m_kex_round{round} + { + CHECK_AND_ASSERT_THROW_MES(round > 0, "Kex round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&signing_privkey) == 0 && + signing_privkey != crypto::null_skey, "Invalid msg signing key."); + + if (round == 1) + { + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&msg_privkey) == 0 && + msg_privkey != crypto::null_skey, "Invalid msg privkey."); + + m_msg_privkey = msg_privkey; + } + else + { + for (const auto &pubkey : msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey for message was invalid."); + CHECK_AND_ASSERT_THROW_MES((rct::scalarmultKey(rct::pk2rct(pubkey), rct::curveOrder()) == rct::identity()), + "Pubkey for message was not in prime subgroup."); + } + + m_msg_pubkeys = std::move(msg_pubkeys); + } + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signing_privkey, m_signing_pubkey), + "Failed to derive public key"); + + // sets message and signing pub key + construct_msg(signing_privkey); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(std::string msg) : m_msg{std::move(msg)} + { + parse_and_validate_msg(); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + crypto::hash multisig_kex_msg::get_msg_to_sign() const + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + /// + + std::string data; + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + data.reserve(MULTISIG_KEX_MSG_V2_MAGIC_1.size() + 4 + 32*(1 + (m_kex_round == 1 ? 1 : 0) + m_msg_pubkeys.size())); + + // versioning domain-sep + if (m_kex_round == 1) + data.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + else + data.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + // kex_round as little-endian bytes + for (std::size_t i{0}; i < 4; ++i) + { + data += static_cast<char>(m_kex_round >> i*8); + } + + // signing pubkey + data.append((const char *)&m_signing_pubkey, sizeof(crypto::public_key)); + + // add msg privkey if kex_round == 1 + if (m_kex_round == 1) + data.append((const char *)&m_msg_privkey, sizeof(crypto::secret_key)); + else + { + // only add pubkeys if not round 1 + + // msg pubkeys + for (const auto &key : m_msg_pubkeys) + data.append((const char *)&key, sizeof(crypto::public_key)); + } + + // message to sign + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + + return hash; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::construct_msg(const crypto::secret_key &signing_privkey) + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | serialize(msg_content | crypto_sig[signing_privkey](sign_msg)) + /// + + // sign the message + crypto::signature msg_signature; + crypto::hash msg_to_sign{get_msg_to_sign()}; + crypto::generate_signature(msg_to_sign, m_signing_pubkey, signing_privkey, msg_signature); + + // assemble the message + m_msg.clear(); + + std::stringstream serialized_msg_ss; + binary_archive<true> b_archive(serialized_msg_ss); + + if (m_kex_round == 1) + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + + multisig_kex_msg_serializable_round1 msg_serializable; + msg_serializable.msg_privkey = m_msg_privkey; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + else + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + multisig_kex_msg_serializable_general msg_serializable; + msg_serializable.kex_round = m_kex_round; + msg_serializable.msg_pubkeys = m_msg_pubkeys; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + + m_msg.append(tools::base58::encode(serialized_msg_ss.str())); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::parse_and_validate_msg() + { + // check message type + CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty."); + CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC, + "V1 multisig kex messages are deprecated (unsafe)."); + CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC, + "V1 multisig kex messages are deprecated (unsafe)."); + + // deserialize the message + std::string msg_no_magic; + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic), + "Multisig kex msg decoding error."); + binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)}; + crypto::signature msg_signature; + + if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_1.size()) == MULTISIG_KEX_MSG_V2_MAGIC_1) + { + // try round 1 message + multisig_kex_msg_serializable_round1 kex_msg_rnd1; + + if (::serialization::serialize(b_archive, kex_msg_rnd1)) + { + // in round 1 the message stores a private ancillary key component for the multisig account + // that will be shared by all participants (e.g. a shared private view key) + m_kex_round = 1; + m_msg_privkey = kex_msg_rnd1.msg_privkey; + m_signing_pubkey = kex_msg_rnd1.signing_pubkey; + msg_signature = kex_msg_rnd1.signature; + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_N.size()) == MULTISIG_KEX_MSG_V2_MAGIC_N) + { + // try general message + multisig_kex_msg_serializable_general kex_msg_general; + + if (::serialization::serialize(b_archive, kex_msg_general)) + { + m_kex_round = kex_msg_general.kex_round; + m_msg_privkey = crypto::null_skey; + m_msg_pubkeys = std::move(kex_msg_general.msg_pubkeys); + m_signing_pubkey = kex_msg_general.signing_pubkey; + msg_signature = kex_msg_general.signature; + + CHECK_AND_ASSERT_THROW_MES(m_kex_round > 1, "Invalid kex message round (must be > 1 for the general msg type)."); + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else + { + // unknown message type + CHECK_AND_ASSERT_THROW_MES(false, "Only v2 multisig kex messages are supported."); + } + + // checks + for (const auto &pubkey: m_msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey from message was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(pubkey)), + "Pubkey from message was not in prime subgroup."); + } + + CHECK_AND_ASSERT_THROW_MES(m_signing_pubkey != crypto::null_pkey && m_signing_pubkey != rct::rct2pk(rct::identity()), + "Message signing key was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(m_signing_pubkey)), + "Message signing key was not in prime subgroup."); + + // validate signature + crypto::hash signed_msg{get_msg_to_sign()}; + CHECK_AND_ASSERT_THROW_MES(crypto::check_signature(signed_msg, m_signing_pubkey, msg_signature), + "Multisig kex msg signature invalid."); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h new file mode 100644 index 000000000..23e3042f2 --- /dev/null +++ b/src/multisig/multisig_kex_msg.h @@ -0,0 +1,109 @@ +// Copyright (c) 2021, 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. + +#pragma once + +#include "crypto/crypto.h" + +#include <cstdint> +#include <vector> + + +namespace multisig +{ + //// + // multisig key exchange message + // - can parse and validate an input message + // - can construct and sign a new message + // + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // msg_to_sign = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | b58(msg_content | crypto_sig[signing_privkey](msg_to_sign)) + // + // note: round 1 messages will contain a private key (e.g. for the aggregate multisig private view key) + /// + class multisig_kex_msg final + { + //member types: none + + //constructors + public: + // default constructor + multisig_kex_msg() = default; + + // construct from info + multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey = crypto::null_skey); + + // construct from string + multisig_kex_msg(std::string msg); + + // copy constructor: default + + //destructor: default + ~multisig_kex_msg() = default; + + //overloaded operators: none + + //member functions + // get msg string + const std::string& get_msg() const { return m_msg; } + // get kex round + std::uint32_t get_round() const { return m_kex_round; } + // get msg pubkeys + const std::vector<crypto::public_key>& get_msg_pubkeys() const { return m_msg_pubkeys; } + // get msg privkey + const crypto::secret_key& get_msg_privkey() const { return m_msg_privkey; } + // get msg signing pubkey + const crypto::public_key& get_signing_pubkey() const { return m_signing_pubkey; } + + private: + // msg_to_sign = versioning-domain-sep | kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + crypto::hash get_msg_to_sign() const; + // set: msg string based on msg contents, signing pubkey based on input privkey + void construct_msg(const crypto::secret_key &signing_privkey); + // parse msg string into parts, validate contents and signature + void parse_and_validate_msg(); + + //member variables + private: + // message as string + std::string m_msg; + + // key exchange round this msg was produced for + std::uint32_t m_kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> m_msg_pubkeys; + // privkey stored in msg (if kex round 1) + crypto::secret_key m_msg_privkey; + // pubkey used to sign this msg + crypto::public_key m_signing_pubkey; + }; +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h new file mode 100644 index 000000000..9c7b993a7 --- /dev/null +++ b/src/multisig/multisig_kex_msg_serialization.h @@ -0,0 +1,78 @@ +// Copyright (c) 2021, 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. + +#pragma once + +#include "crypto/crypto.h" +#include "serialization/containers.h" +#include "serialization/crypto.h" +#include "serialization/serialization.h" + +#include <cstdint> +#include <vector> + + +namespace multisig +{ + /// round 1 kex message + struct multisig_kex_msg_serializable_round1 + { + // privkey stored in msg + crypto::secret_key msg_privkey; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; + + BEGIN_SERIALIZE() + FIELD(msg_privkey) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; + + /// general kex message (if round > 1) + struct multisig_kex_msg_serializable_general + { + // key exchange round this msg was produced for + std::uint32_t kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> msg_pubkeys; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; + + BEGIN_SERIALIZE() + VARINT_FIELD(kex_round) + FIELD(msg_pubkeys) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; +} //namespace multisig diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 86c29b4db..199601d00 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -717,7 +717,7 @@ namespace nodetool full_addrs.insert("176.9.0.187:18080"); full_addrs.insert("88.198.163.90:18080"); full_addrs.insert("95.217.25.101:18080"); - full_addrs.insert("209.250.243.248:18080"); + full_addrs.insert("136.244.105.131:18080"); full_addrs.insert("104.238.221.81:18080"); full_addrs.insert("66.85.74.134:18080"); full_addrs.insert("88.99.173.38:18080"); diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index 40b2dfd55..32da0f5f5 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -31,13 +31,15 @@ set(ringct_basic_sources rctTypes.cpp rctCryptoOps.c multiexp.cc - bulletproofs.cc) + bulletproofs.cc + bulletproofs_plus.cc) set(ringct_basic_private_headers rctOps.h rctTypes.h multiexp.h - bulletproofs.h) + bulletproofs.h + bulletproofs_plus.h) monero_private_headers(ringct_basic ${crypto_private_headers}) diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index a6e12c9b3..1689e5463 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -70,13 +70,12 @@ static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS; -static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr<straus_cached_data> straus_HiGi_cache; static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; -static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; -static const rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; -static const rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; +static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; +static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; +static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); static const rct::key ip12 = inner_product(oneN, twoN); @@ -100,8 +99,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT); - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_EXPONENT + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -121,10 +119,10 @@ static void init_exponents() data.reserve(maxN*maxM*2); for (size_t i = 0; i < maxN*maxM; ++i) { - Hi[i] = get_exponent(rct::H, i * 2); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed"); - Gi[i] = get_exponent(rct::H, i * 2 + 1); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Hi = get_exponent(rct::H, i * 2); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Gi = get_exponent(rct::H, i * 2 + 1); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed"); data.push_back({rct::zero(), Gi_p3[i]}); data.push_back({rct::zero(), Hi_p3[i]}); @@ -133,11 +131,10 @@ static void init_exponents() straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); - MINFO("Hi/Gi cache size: " << (sizeof(Hi)+sizeof(Gi))/1024 << " kB"); MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB"); MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB"); MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB"); - size_t cache_size = (sizeof(Hi)+sizeof(Hi_p3))*2 + straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); + size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Total cache size: " << cache_size/1024 << "kB"); init_done = true; } @@ -895,7 +892,8 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); - const std::vector<rct::key> inverses = invert(to_invert); + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert); // setup weighted aggregates diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc new file mode 100644 index 000000000..3d27849c1 --- /dev/null +++ b/src/ringct/bulletproofs_plus.cc @@ -0,0 +1,1121 @@ +// Copyright (c) 2017-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. + +// Implements the Bulletproofs+ prover and verifier algorithms +// +// Preprint: https://eprint.iacr.org/2020/735, version 17 Jun 2020 +// +// NOTE ON NOTATION: +// In the signature constructions used in Monero, commitments to zero are treated as +// public keys against the curve group generator `G`. This means that amount +// commitments must use another generator `H` for values in order to show balance. +// The result is that the roles of `g` and `h` in the preprint are effectively swapped +// in this code, taking on the roles of `H` and `G`, respectively. Read carefully! + +#include <stdlib.h> +#include <boost/thread/mutex.hpp> +#include <boost/thread/lock_guard.hpp> +#include "misc_log_ex.h" +#include "span.h" +#include "cryptonote_config.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "rctOps.h" +#include "multiexp.h" +#include "bulletproofs_plus.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bulletproof_plus" + +#define STRAUS_SIZE_LIMIT 232 +#define PIPPENGER_SIZE_LIMIT 0 + +namespace rct +{ + // Vector functions + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n); + + // Proof bounds + static constexpr size_t maxN = 64; // maximum number of bits in range + static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + + // Cached public generators + static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; + static std::shared_ptr<straus_cached_data> straus_HiGi_cache; + static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; + + // Useful scalar constants + static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 + static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 + static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 + static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 + static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) + static rct::key TWO_SIXTY_FOUR_MINUS_ONE; // 2**64 - 1 + + // Initial transcript hash + static rct::key initial_transcript; + + static boost::mutex init_mutex; + + // Use the generator caches to compute a multiscalar multiplication + static inline rct::key multiexp(const std::vector<MultiexpData> &data, size_t HiGi_size) + { + if (HiGi_size > 0) + { + static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT"); + return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size())); + } + else + { + return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size())); + } + } + + // Confirm that a scalar is properly reduced + static inline bool is_reduced(const rct::key &scalar) + { + return sc_check(scalar.bytes) == 0; + } + + // Use hashed values to produce indexed public generators + static ge_p3 get_exponent(const rct::key &base, size_t idx) + { + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + rct::key generator; + ge_p3 generator_p3; + rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); + ge_p3_tobytes(generator.bytes, &generator_p3); + CHECK_AND_ASSERT_THROW_MES(!(generator == rct::identity()), "Exponent is point at infinity"); + return generator_p3; + } + + // Construct public generators + static void init_exponents() + { + boost::lock_guard<boost::mutex> lock(init_mutex); + + // Only needs to be done once + static bool init_done = false; + if (init_done) + return; + + std::vector<MultiexpData> data; + data.reserve(maxN*maxM*2); + for (size_t i = 0; i < maxN*maxM; ++i) + { + Hi_p3[i] = get_exponent(rct::H, i * 2); + Gi_p3[i] = get_exponent(rct::H, i * 2 + 1); + + data.push_back({rct::zero(), Gi_p3[i]}); + data.push_back({rct::zero(), Hi_p3[i]}); + } + + straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); + pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); + + // Compute 2**64 - 1 for later use in simplifying verification + TWO_SIXTY_FOUR_MINUS_ONE = TWO; + for (size_t i = 0; i < 6; i++) + { + sc_mul(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes); + } + sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); + + // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs + const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + ge_p3 initial_transcript_p3; + rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); + ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); + + init_done = true; + } + + // Given two scalar arrays, construct a vector pre-commitment: + // + // a = (a_0, ..., a_{n-1}) + // b = (b_0, ..., b_{n-1}) + // + // Outputs a_0*Gi_0 + ... + a_{n-1}*Gi_{n-1} + + // b_0*Hi_0 + ... + b_{n-1}*Hi_{n-1} + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(a.size()*2); + for (size_t i = 0; i < a.size(); ++i) + { + multiexp_data.emplace_back(a[i], Gi_p3[i]); + multiexp_data.emplace_back(b[i], Hi_p3[i]); + } + return multiexp(multiexp_data, 2 * a.size()); + } + + // Helper function used to compute the L and R terms used in the inner-product round function + static rct::key compute_LR(size_t size, const rct::key &y, const std::vector<ge_p3> &G, size_t G0, const std::vector<ge_p3> &H, size_t H0, const rct::keyV &a, size_t a0, const rct::keyV &b, size_t b0, const rct::key &c, const rct::key &d) + { + CHECK_AND_ASSERT_THROW_MES(size + G0 <= G.size(), "Incompatible size for G"); + CHECK_AND_ASSERT_THROW_MES(size + H0 <= H.size(), "Incompatible size for H"); + CHECK_AND_ASSERT_THROW_MES(size + a0 <= a.size(), "Incompatible size for a"); + CHECK_AND_ASSERT_THROW_MES(size + b0 <= b.size(), "Incompatible size for b"); + CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.resize(size*2 + 2); + rct::key temp; + for (size_t i = 0; i < size; ++i) + { + sc_mul(temp.bytes, a[a0+i].bytes, y.bytes); + sc_mul(multiexp_data[i*2].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + multiexp_data[i*2].point = G[G0+i]; + + sc_mul(multiexp_data[i*2+1].scalar.bytes, b[b0+i].bytes, INV_EIGHT.bytes); + multiexp_data[i*2+1].point = H[H0+i]; + } + + sc_mul(multiexp_data[2*size].scalar.bytes, c.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + multiexp_data[2*size].point = H_p3; + + sc_mul(multiexp_data[2*size+1].scalar.bytes, d.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + multiexp_data[2*size+1].point = G_p3; + + return multiexp(multiexp_data, 0); + } + + // Given a scalar, construct a vector of its powers: + // + // Output (1,x,x**2,...,x**{n-1}) + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::keyV res(n); + res[0] = rct::identity(); + if (n == 1) + return res; + res[1] = x; + for (size_t i = 2; i < n; ++i) + { + sc_mul(res[i].bytes, res[i-1].bytes, x.bytes); + } + return res; + } + + // Given a scalar, construct the sum of its powers from 2 to n (where n is a power of 2): + // + // Output x**2 + x**4 + x**6 + ... + x**n + static rct::key sum_of_even_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES((n & (n - 1)) == 0, "Need n to be a power of 2"); + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key x1 = copy(x); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + + rct::key res = copy(x1); + while (n > 2) + { + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + n /= 2; + } + + return res; + } + + // Given a scalar, return the sum of its powers from 1 to n + // + // Output x**1 + x**2 + x**3 + ... + x**n + static rct::key sum_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key res = ONE; + if (n == 1) + return x; + + n += 1; + rct::key x1 = copy(x); + + const bool is_power_of_2 = (n & (n - 1)) == 0; + if (is_power_of_2) + { + sc_add(res.bytes, res.bytes, x1.bytes); + while (n > 2) + { + sc_mul(x1.bytes, x1.bytes, x1.bytes); + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + n /= 2; + } + } + else + { + rct::key prev = x1; + for (size_t i = 1; i < n; ++i) + { + if (i > 1) + sc_mul(prev.bytes, prev.bytes, x1.bytes); + sc_add(res.bytes, res.bytes, prev.bytes); + } + } + sc_sub(res.bytes, res.bytes, ONE.bytes); + + return res; + } + + // Given two scalar arrays, construct the weighted inner product against another scalar + // + // Output a_0*b_0*y**1 + a_1*b_1*y**2 + ... + a_{n-1}*b_{n-1}*y**n + static rct::key weighted_inner_product(const epee::span<const rct::key> &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::key res = rct::zero(); + rct::key y_power = ONE; + rct::key temp; + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(temp.bytes, a[i].bytes, b[i].bytes); + sc_mul(y_power.bytes, y_power.bytes, y.bytes); + sc_muladd(res.bytes, temp.bytes, y_power.bytes, res.bytes); + } + return res; + } + + static rct::key weighted_inner_product(const rct::keyV &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + return weighted_inner_product(epee::to_span(a), b, y); + } + + // Fold inner-product point vectors + static void hadamard_fold(std::vector<ge_p3> &v, const rct::key &a, const rct::key &b) + { + CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even"); + const size_t sz = v.size() / 2; + for (size_t n = 0; n < sz; ++n) + { + ge_dsmp c[2]; + ge_dsm_precomp(c[0], &v[n]); + ge_dsm_precomp(c[1], &v[sz + n]); + ge_double_scalarmult_precomp_vartime2_p3(&v[n], a.bytes, c[0], b.bytes, c[1]); + } + v.resize(sz); + } + + // Add vectors componentwise + static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b[i].bytes); + } + return res; + } + + // Add a scalar to all elements of a vector + static rct::keyV vector_add(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Subtract a scalar from all elements of a vector + static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_sub(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Multiply a scalar by all elements of a vector + static rct::keyV vector_scalar(const epee::span<const rct::key> &a, const rct::key &x) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(res[i].bytes, a[i].bytes, x.bytes); + } + return res; + } + + // Inversion helper function + static rct::key sm(rct::key y, int n, const rct::key &x) + { + while (n--) + sc_mul(y.bytes, y.bytes, y.bytes); + sc_mul(y.bytes, y.bytes, x.bytes); + return y; + } + + // Compute the inverse of a nonzero + static rct::key invert(const rct::key &x) + { + CHECK_AND_ASSERT_THROW_MES(!(x == ZERO), "Cannot invert zero!"); + rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111; + + _1 = x; + sc_mul(_10.bytes, _1.bytes, _1.bytes); + sc_mul(_100.bytes, _10.bytes, _10.bytes); + sc_mul(_11.bytes, _10.bytes, _1.bytes); + sc_mul(_101.bytes, _10.bytes, _11.bytes); + sc_mul(_111.bytes, _10.bytes, _101.bytes); + sc_mul(_1001.bytes, _10.bytes, _111.bytes); + sc_mul(_1011.bytes, _10.bytes, _1001.bytes); + sc_mul(_1111.bytes, _100.bytes, _1011.bytes); + + rct::key inv; + sc_mul(inv.bytes, _1111.bytes, _1.bytes); + + inv = sm(inv, 123 + 3, _101); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 4, _1001); + inv = sm(inv, 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 3, _101); + inv = sm(inv, 3 + 3, _101); + inv = sm(inv, 3, _111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 2 + 3, _111); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 2 + 4, _1011); + inv = sm(inv, 6 + 4, _1001); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 1 + 4, _1001); + inv = sm(inv, 1 + 3, _111); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 3, _101); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 3, _101); + inv = sm(inv, 1 + 2, _11); + + return inv; + } + + // Invert a batch of scalars, all of which _must_ be nonzero + static rct::keyV invert(rct::keyV x) + { + rct::keyV scratch; + scratch.reserve(x.size()); + + rct::key acc = rct::identity(); + for (size_t n = 0; n < x.size(); ++n) + { + CHECK_AND_ASSERT_THROW_MES(!(x[n] == ZERO), "Cannot invert zero!"); + scratch.push_back(acc); + if (n == 0) + acc = x[0]; + else + sc_mul(acc.bytes, acc.bytes, x[n].bytes); + } + + acc = invert(acc); + + rct::key tmp; + for (int i = x.size(); i-- > 0; ) + { + sc_mul(tmp.bytes, acc.bytes, x[i].bytes); + sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes); + acc = tmp; + } + + return x; + } + + // Compute the slice of a vector + static epee::span<const rct::key> slice(const rct::keyV &a, size_t start, size_t stop) + { + CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index"); + CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index"); + CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices"); + return epee::span<const rct::key>(&a[start], stop - start); + } + + // Update the transcript + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0) + { + rct::key data[2]; + data[0] = transcript; + data[1] = update_0; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0, const rct::key &update_1) + { + rct::key data[3]; + data[0] = transcript; + data[1] = update_0; + data[2] = update_1; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + // Given a value v [0..2**N) and a mask gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma) + { + return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); + } + + BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma) + { + return bulletproof_plus_PROVE(std::vector<uint64_t>(1, v), rct::keyV(1, gamma)); + } + + // Given a set of values v [0..2**N) and masks gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma) + { + // Sanity check on inputs + CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma"); + CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty"); + for (const rct::key &sve: sv) + CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input"); + for (const rct::key &g: gamma) + CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input"); + + init_exponents(); + + // Useful proof bounds + // + // N: number of bits in each range (here, 64) + // logN: base-2 logarithm + // M: first power of 2 greater than or equal to the number of range proofs to aggregate + // logM: base-2 logarithm + constexpr size_t logN = 6; // log2(64) + constexpr size_t N = 1<<logN; + size_t M, logM; + for (logM = 0; (M = 1<<logM) <= maxM && M < sv.size(); ++logM); + CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv/gamma are too large"); + const size_t logMN = logM + logN; + const size_t MN = M * N; + + rct::keyV V(sv.size()); + rct::keyV aL(MN), aR(MN); + rct::keyV aL8(MN), aR8(MN); + rct::key temp; + rct::key temp2; + + // Prepare output commitments and offset by a factor of 8**(-1) + // + // This offset is applied to other group elements as well; + // it allows us to apply a multiply-by-8 operation in the verifier efficiently + // to ensure that the resulting group elements are in the prime-order point subgroup + // and avoid much more constly multiply-by-group-order operations. + for (size_t i = 0; i < sv.size(); ++i) + { + rct::key gamma8, sv8; + sc_mul(gamma8.bytes, gamma[i].bytes, INV_EIGHT.bytes); + sc_mul(sv8.bytes, sv[i].bytes, INV_EIGHT.bytes); + rct::addKeys2(V[i], gamma8, sv8, rct::H); + } + + // Decompose values + // + // Note that this effectively pads the set to a power of 2, which is required for the inner-product argument later. + for (size_t j = 0; j < M; ++j) + { + for (size_t i = N; i-- > 0; ) + { + if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8)))) + { + aL[j*N+i] = rct::identity(); + aL8[j*N+i] = INV_EIGHT; + aR[j*N+i] = aR8[j*N+i] = rct::zero(); + } + else + { + aL[j*N+i] = aL8[j*N+i] = rct::zero(); + aR[j*N+i] = MINUS_ONE; + aR8[j*N+i] = MINUS_INV_EIGHT; + } + } + } + +try_again: + // This is a Fiat-Shamir transcript + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(V)); + + // A + rct::key alpha = rct::skGen(); + rct::key pre_A = vector_exponent(aL8, aR8); + rct::key A; + sc_mul(temp.bytes, alpha.bytes, INV_EIGHT.bytes); + rct::addKeys(A, pre_A, rct::scalarmultBase(temp)); + + // Challenges + rct::key y = transcript_update(transcript, A); + if (y == rct::zero()) + { + MINFO("y is 0, trying again"); + goto try_again; + } + rct::key z = transcript = rct::hash_to_scalar(y); + if (z == rct::zero()) + { + MINFO("z is 0, trying again"); + goto try_again; + } + rct::key z_squared; + sc_mul(z_squared.bytes, z.bytes, z.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + // + // We compute this iteratively in order to reduce scalar operations. + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_mul(d[i].bytes, d[i-1].bytes, TWO.bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + rct::keyV y_powers = vector_of_scalar_powers(y, MN+2); + + // Prepare inner product terms + rct::keyV aL1 = vector_subtract(aL, z); + + rct::keyV aR1 = vector_add(aR, z); + rct::keyV d_y(MN); + for (size_t i = 0; i < MN; i++) + { + sc_mul(d_y[i].bytes, d[i].bytes, y_powers[MN-i].bytes); + } + aR1 = vector_add(aR1, d_y); + + rct::key alpha1 = alpha; + temp = ONE; + for (size_t j = 0; j < sv.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes); + sc_mul(temp2.bytes, temp2.bytes, gamma[j].bytes); + sc_add(alpha1.bytes, alpha1.bytes, temp2.bytes); + } + + // These are used in the inner product rounds + size_t nprime = MN; + std::vector<ge_p3> Gprime(MN); + std::vector<ge_p3> Hprime(MN); + rct::keyV aprime(MN); + rct::keyV bprime(MN); + + const rct::key yinv = invert(y); + rct::keyV yinvpow(MN); + yinvpow[0] = ONE; + for (size_t i = 0; i < MN; ++i) + { + Gprime[i] = Gi_p3[i]; + Hprime[i] = Hi_p3[i]; + if (i > 0) + { + sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes); + } + aprime[i] = aL1[i]; + bprime[i] = aR1[i]; + } + rct::keyV L(logMN); + rct::keyV R(logMN); + int round = 0; + + // Inner-product rounds + while (nprime > 1) + { + nprime /= 2; + + rct::key cL = weighted_inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()), y); + rct::key cR = weighted_inner_product(vector_scalar(slice(aprime, nprime, aprime.size()), y_powers[nprime]), slice(bprime, 0, nprime), y); + + rct::key dL = rct::skGen(); + rct::key dR = rct::skGen(); + + L[round] = compute_LR(nprime, yinvpow[nprime], Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, cL, dL); + R[round] = compute_LR(nprime, y_powers[nprime], Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, cR, dR); + + const rct::key challenge = transcript_update(transcript, L[round], R[round]); + if (challenge == rct::zero()) + { + MINFO("challenge is 0, trying again"); + goto try_again; + } + + const rct::key challenge_inv = invert(challenge); + + sc_mul(temp.bytes, yinvpow[nprime].bytes, challenge.bytes); + hadamard_fold(Gprime, challenge_inv, temp); + hadamard_fold(Hprime, challenge, challenge_inv); + + sc_mul(temp.bytes, challenge_inv.bytes, y_powers[nprime].bytes); + aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), challenge), vector_scalar(slice(aprime, nprime, aprime.size()), temp)); + bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), challenge_inv), vector_scalar(slice(bprime, nprime, bprime.size()), challenge)); + + rct::key challenge_squared; + sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes); + rct::key challenge_squared_inv = invert(challenge_squared); + sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes); + sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes); + + ++round; + } + + // Final round computations + rct::key r = rct::skGen(); + rct::key s = rct::skGen(); + rct::key d_ = rct::skGen(); + rct::key eta = rct::skGen(); + + std::vector<MultiexpData> A1_data; + A1_data.reserve(4); + A1_data.resize(4); + + sc_mul(A1_data[0].scalar.bytes, r.bytes, INV_EIGHT.bytes); + A1_data[0].point = Gprime[0]; + + sc_mul(A1_data[1].scalar.bytes, s.bytes, INV_EIGHT.bytes); + A1_data[1].point = Hprime[0]; + + sc_mul(A1_data[2].scalar.bytes, d_.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + A1_data[2].point = G_p3; + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, bprime[0].bytes); + sc_mul(temp2.bytes, s.bytes, y.bytes); + sc_mul(temp2.bytes, temp2.bytes, aprime[0].bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(A1_data[3].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + A1_data[3].point = H_p3; + + rct::key A1 = multiexp(A1_data, 0); + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, s.bytes); + sc_mul(temp.bytes, temp.bytes, INV_EIGHT.bytes); + sc_mul(temp2.bytes, eta.bytes, INV_EIGHT.bytes); + rct::key B; + rct::addKeys2(B, temp2, temp, rct::H); + + rct::key e = transcript_update(transcript, A1, B); + if (e == rct::zero()) + { + MINFO("e is 0, trying again"); + goto try_again; + } + rct::key e_squared; + sc_mul(e_squared.bytes, e.bytes, e.bytes); + + rct::key r1; + sc_muladd(r1.bytes, aprime[0].bytes, e.bytes, r.bytes); + + rct::key s1; + sc_muladd(s1.bytes, bprime[0].bytes, e.bytes, s.bytes); + + rct::key d1; + sc_muladd(d1.bytes, d_.bytes, e.bytes, eta.bytes); + sc_muladd(d1.bytes, alpha1.bytes, e_squared.bytes, d1.bytes); + + return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R)); + } + + BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma) + { + CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma"); + + // vG + gammaH + rct::keyV sv(v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + sv[i] = rct::d2h(v[i]); + } + return bulletproof_plus_PROVE(sv, gamma); + } + + struct bp_plus_proof_data_t + { + rct::key y, z, e; + std::vector<rct::key> challenges; + size_t logM, inv_offset; + }; + + // Given a batch of range proofs, determine if they are all valid + bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs) + { + init_exponents(); + + const size_t logN = 6; + const size_t N = 1 << logN; + + // Set up + size_t max_length = 0; // size of each of the longest proof's inner-product vectors + size_t nV = 0; // number of output commitments across all proofs + size_t inv_offset = 0; + size_t max_logM = 0; + + std::vector<bp_plus_proof_data_t> proof_data; + proof_data.reserve(proofs.size()); + + // We'll perform only a single batch inversion across all proofs in the batch, + // since batch inversion requires only one scalar inversion operation. + std::vector<rct::key> to_invert; + to_invert.reserve(11 * proofs.size()); // maximal size, given the aggregation limit + + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + + // Sanity checks + CHECK_AND_ASSERT_MES(is_reduced(proof.r1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.s1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.d1), false, "Input scalar not in range"); + + CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element"); + CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes"); + CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof"); + + max_length = std::max(max_length, proof.L.size()); + nV += proof.V.size(); + + proof_data.push_back({}); + bp_plus_proof_data_t &pd = proof_data.back(); + + // Reconstruct the challenges + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(proof.V)); + pd.y = transcript_update(transcript, proof.A); + CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0"); + pd.z = transcript = rct::hash_to_scalar(pd.y); + CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0"); + + // Determine the number of inner-product rounds based on proof size + size_t M; + for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < proof.V.size(); ++pd.logM); + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + max_logM = std::max(pd.logM, max_logM); + + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + // The inner-product challenges are computed per round + pd.challenges.resize(rounds); + for (size_t j = 0; j < rounds; ++j) + { + pd.challenges[j] = transcript_update(transcript, proof.L[j], proof.R[j]); + CHECK_AND_ASSERT_MES(!(pd.challenges[j] == rct::zero()), false, "challenges[j] == 0"); + } + + // Final challenge + pd.e = transcript_update(transcript,proof.A1,proof.B); + CHECK_AND_ASSERT_MES(!(pd.e == rct::zero()), false, "e == 0"); + + // Batch scalar inversions + pd.inv_offset = inv_offset; + for (size_t j = 0; j < rounds; ++j) + to_invert.push_back(pd.challenges[j]); + to_invert.push_back(pd.y); + inv_offset += rounds + 1; + } + CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large"); + size_t maxMN = 1u << max_length; + + rct::key temp; + rct::key temp2; + + // Final batch proof data + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN); + multiexp_data.resize(2 * maxMN); + + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); + + // Weights and aggregates + // + // The idea is to take the single multiscalar multiplication used in the verification + // of each proof in the batch and weight it using a random weighting factor, resulting + // in just one multiscalar multiplication check to zero for the entire batch. + // We can further simplify the verifier complexity by including common group elements + // only once in this single multiscalar multiplication. + // Common group elements' weighted scalar sums are tracked across proofs for this reason. + // + // To build a multiscalar multiplication for each proof, we use the method described in + // Section 6.1 of the preprint. Note that the result given there does not account for + // the construction of the inner-product inputs that are produced in the range proof + // verifier algorithm; we have done so here. + rct::key G_scalar = rct::zero(); + rct::key H_scalar = rct::zero(); + rct::keyV Gi_scalars(maxMN, rct::zero()); + rct::keyV Hi_scalars(maxMN, rct::zero()); + + int proof_data_index = 0; + rct::keyV challenges_cache; + std::vector<ge_p3> proof8_V, proof8_L, proof8_R; + + // Process each proof and add to the weighted batch + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + const bp_plus_proof_data_t &pd = proof_data[proof_data_index++]; + + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + const size_t M = 1 << pd.logM; + const size_t MN = M*N; + + // Random weighting factor must be nonzero, which is exceptionally unlikely! + rct::key weight = ZERO; + while (weight == ZERO) + { + weight = rct::skGen(); + } + + // Rescale previously offset proof elements + // + // This ensures that all such group elements are in the prime-order subgroup. + proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]); + proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]); + proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]); + ge_p3 proof8_A1; + ge_p3 proof8_B; + ge_p3 proof8_A; + rct::scalarmult8(proof8_A1, proof.A1); + rct::scalarmult8(proof8_B, proof.B); + rct::scalarmult8(proof8_A, proof.A); + + // Compute necessary powers of the y-challenge + rct::key y_MN = copy(pd.y); + rct::key y_MN_1; + size_t temp_MN = MN; + while (temp_MN > 1) + { + sc_mul(y_MN.bytes, y_MN.bytes, y_MN.bytes); + temp_MN /= 2; + } + sc_mul(y_MN_1.bytes, y_MN.bytes, pd.y.bytes); + + // V_j: -e**2 * z**(2*j+1) * y**(MN+1) * weight + rct::key e_squared; + sc_mul(e_squared.bytes, pd.e.bytes, pd.e.bytes); + + rct::key z_squared; + sc_mul(z_squared.bytes, pd.z.bytes, pd.z.bytes); + + sc_sub(temp.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(temp.bytes, temp.bytes, y_MN_1.bytes); + sc_mul(temp.bytes, temp.bytes, weight.bytes); + for (size_t j = 0; j < proof8_V.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + multiexp_data.emplace_back(temp, proof8_V[j]); + } + + // B: -weight + sc_mul(temp.bytes, MINUS_ONE.bytes, weight.bytes); + multiexp_data.emplace_back(temp, proof8_B); + + // A1: -weight*e + sc_mul(temp.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(temp, proof8_A1); + + // A: -weight*e*e + rct::key minus_weight_e_squared; + sc_mul(minus_weight_e_squared.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(minus_weight_e_squared, proof8_A); + + // G: weight*d1 + sc_muladd(G_scalar.bytes, weight.bytes, proof.d1.bytes, G_scalar.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_add(d[i].bytes, d[i-1].bytes, d[i-1].bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + // More efficient computation of sum(d) + rct::key sum_d; + sc_mul(sum_d.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, sum_of_even_powers(pd.z, 2*M).bytes); + + // H: weight*( r1*y*s1 + e**2*( y**(MN+1)*z*sum(d) + (z**2-z)*sum(y) ) ) + rct::key sum_y = sum_of_scalar_powers(pd.y, MN); + sc_sub(temp.bytes, z_squared.bytes, pd.z.bytes); + sc_mul(temp.bytes, temp.bytes, sum_y.bytes); + + sc_mul(temp2.bytes, y_MN_1.bytes, pd.z.bytes); + sc_mul(temp2.bytes, temp2.bytes, sum_d.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(temp.bytes, temp.bytes, e_squared.bytes); + sc_mul(temp2.bytes, proof.r1.bytes, pd.y.bytes); + sc_mul(temp2.bytes, temp2.bytes, proof.s1.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_muladd(H_scalar.bytes, temp.bytes, weight.bytes, H_scalar.bytes); + + // Compute the number of rounds for the inner-product argument + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + const rct::key *challenges_inv = &inverses[pd.inv_offset]; + const rct::key yinv = inverses[pd.inv_offset + rounds]; + + // Compute challenge products + challenges_cache.resize(1<<rounds); + challenges_cache[0] = challenges_inv[0]; + challenges_cache[1] = pd.challenges[0]; + for (size_t j = 1; j < rounds; ++j) + { + const size_t slots = 1<<(j+1); + for (size_t s = slots; s-- > 0; --s) + { + sc_mul(challenges_cache[s].bytes, challenges_cache[s/2].bytes, pd.challenges[j].bytes); + sc_mul(challenges_cache[s-1].bytes, challenges_cache[s/2].bytes, challenges_inv[j].bytes); + } + } + + // Gi and Hi + rct::key e_r1_w_y; + sc_mul(e_r1_w_y.bytes, pd.e.bytes, proof.r1.bytes); + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, weight.bytes); + rct::key e_s1_w; + sc_mul(e_s1_w.bytes, pd.e.bytes, proof.s1.bytes); + sc_mul(e_s1_w.bytes, e_s1_w.bytes, weight.bytes); + rct::key e_squared_z_w; + sc_mul(e_squared_z_w.bytes, e_squared.bytes, pd.z.bytes); + sc_mul(e_squared_z_w.bytes, e_squared_z_w.bytes, weight.bytes); + rct::key minus_e_squared_z_w; + sc_sub(minus_e_squared_z_w.bytes, ZERO.bytes, e_squared_z_w.bytes); + rct::key minus_e_squared_w_y; + sc_sub(minus_e_squared_w_y.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, weight.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, y_MN.bytes); + for (size_t i = 0; i < MN; ++i) + { + rct::key g_scalar = copy(e_r1_w_y); + rct::key h_scalar; + + // Use the binary decomposition of the index + sc_muladd(g_scalar.bytes, g_scalar.bytes, challenges_cache[i].bytes, e_squared_z_w.bytes); + sc_muladd(h_scalar.bytes, e_s1_w.bytes, challenges_cache[(~i) & (MN-1)].bytes, minus_e_squared_z_w.bytes); + + // Complete the scalar derivation + sc_add(Gi_scalars[i].bytes, Gi_scalars[i].bytes, g_scalar.bytes); + sc_muladd(h_scalar.bytes, minus_e_squared_w_y.bytes, d[i].bytes, h_scalar.bytes); + sc_add(Hi_scalars[i].bytes, Hi_scalars[i].bytes, h_scalar.bytes); + + // Update iterated values + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, yinv.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, yinv.bytes); + } + + // L_j: -weight*e*e*challenges[j]**2 + // R_j: -weight*e*e*challenges[j]**(-2) + for (size_t j = 0; j < rounds; ++j) + { + sc_mul(temp.bytes, pd.challenges[j].bytes, pd.challenges[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_L[j]); + + sc_mul(temp.bytes, challenges_inv[j].bytes, challenges_inv[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_R[j]); + } + } + + // Verify all proofs in the weighted batch + multiexp_data.emplace_back(G_scalar, rct::G); + multiexp_data.emplace_back(H_scalar, rct::H); + for (size_t i = 0; i < maxMN; ++i) + { + multiexp_data[i * 2] = {Gi_scalars[i], Gi_p3[i]}; + multiexp_data[i * 2 + 1] = {Hi_scalars[i], Hi_p3[i]}; + } + if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity())) + { + MERROR("Verification failure"); + return false; + } + + return true; + } + + bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs) + { + std::vector<const BulletproofPlus*> proof_pointers; + proof_pointers.reserve(proofs.size()); + for (const BulletproofPlus &proof: proofs) + proof_pointers.push_back(&proof); + return bulletproof_plus_VERIFY(proof_pointers); + } + + bool bulletproof_plus_VERIFY(const BulletproofPlus &proof) + { + std::vector<const BulletproofPlus*> proofs; + proofs.push_back(&proof); + return bulletproof_plus_VERIFY(proofs); + } +} diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h new file mode 100644 index 000000000..d9084075a --- /dev/null +++ b/src/ringct/bulletproofs_plus.h @@ -0,0 +1,49 @@ +// Copyright (c) 2017-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. + +#pragma once + +#ifndef BULLETPROOFS_PLUS_H +#define BULLETPROOFS_PLUS_H + +#include "rctTypes.h" + +namespace rct +{ + +BulletproofPlus bulletproof_plus_PROVE(const rct::key &v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &v, const rct::keyV &gamma); +BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma); +bool bulletproof_plus_VERIFY(const BulletproofPlus &proof); +bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs); +bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs); + +} + +#endif diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index 784c90a4e..f256325a1 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -235,7 +235,7 @@ rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data) heap.reserve(points); for (size_t n = 0; n < points; ++n) { - if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity(&data[n].point)) + if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity_vartime(&data[n].point)) heap.push_back(n); } points = heap.size(); @@ -457,7 +457,7 @@ rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<str MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000)); std::vector<uint8_t> skip(data.size()); for (size_t i = 0; i < data.size(); ++i) - skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity(&data[i].point); + skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity_vartime(&data[i].point); MULTIEXP_PERF(PERF_TIMER_STOP(skip)); #endif diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f5950c53c..d7883baac 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -35,6 +35,7 @@ #include "common/util.h" #include "rctSigs.h" #include "bulletproofs.h" +#include "bulletproofs_plus.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_config.h" @@ -78,6 +79,36 @@ namespace return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I}; } + rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks) + { + const size_t n_outs = outamounts.size(); + const rct::key I = rct::identity(); + size_t nrl = 0; + while ((1u << nrl) < n_outs) + ++nrl; + nrl += 6; + + C.resize(n_outs); + masks.resize(n_outs); + for (size_t i = 0; i < n_outs; ++i) + { + masks[i] = I; + rct::key sv8, sv; + sv = rct::zero(); + sv.bytes[0] = outamounts[i] & 255; + sv.bytes[1] = (outamounts[i] >> 8) & 255; + sv.bytes[2] = (outamounts[i] >> 16) & 255; + sv.bytes[3] = (outamounts[i] >> 24) & 255; + sv.bytes[4] = (outamounts[i] >> 32) & 255; + sv.bytes[5] = (outamounts[i] >> 40) & 255; + sv.bytes[6] = (outamounts[i] >> 48) & 255; + sv.bytes[7] = (outamounts[i] >> 56) & 255; + sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); + rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); + } + + return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)}; + } } namespace rct { @@ -107,6 +138,32 @@ namespace rct { catch (...) { return false; } } + BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev) + { + CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + masks.resize(amounts.size()); + for (size_t i = 0; i < masks.size(); ++i) + masks[i] = hwdev.genCommitmentMask(sk[i]); + BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks); + CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); + C = proof.V; + return proof; + } + + bool verBulletproofPlus(const BulletproofPlus &proof) + { + try { return bulletproof_plus_VERIFY(proof); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + + bool verBulletproofPlus(const std::vector<const BulletproofPlus*> &proofs) + { + try { return bulletproof_plus_VERIFY(proofs); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + //Borromean (c.f. gmax/andytoshi's paper) boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { key64 L[2], alpha; @@ -611,6 +668,25 @@ namespace rct { kv.push_back(p.t); } } + else if (rv.type == RCTTypeBulletproofPlus) + { + kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size()); + for (const auto &p: rv.p.bulletproofs_plus) + { + // V are not hashed as they're expanded from outPk.mask + // (and thus hashed as part of rctSigBase above) + kv.push_back(p.A); + kv.push_back(p.A1); + kv.push_back(p.B); + kv.push_back(p.r1); + kv.push_back(p.s1); + kv.push_back(p.d1); + for (size_t n = 0; n < p.L.size(); ++n) + kv.push_back(p.L[n]); + for (size_t n = 0; n < p.R.size(); ++n) + kv.push_back(p.R[n]); + } + } else { kv.reserve((64*3+1) * rv.p.rangeSigs.size()); @@ -1031,7 +1107,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 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1063,7 +1139,7 @@ namespace rct { //RCT simple //for post-rct only rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { - const bool bulletproof = rct_config.range_proof_type != RangeProofBorromean; + const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -1079,11 +1155,14 @@ namespace rct { } rctSig rv; - if (bulletproof) + if (bulletproof_or_plus) { switch (rct_config.bp_version) { case 0: + case 4: + rv.type = RCTTypeBulletproofPlus; + break; case 3: rv.type = RCTTypeCLSAG; break; @@ -1102,7 +1181,7 @@ namespace rct { rv.message = message; rv.outPk.resize(destinations.size()); - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); @@ -1114,17 +1193,19 @@ namespace rct { //add destination to sig rv.outPk[i].dest = copy(destinations[i]); //compute range proof - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG - if (!bulletproof) + if (!bulletproof_or_plus) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif } rv.p.bulletproofs.clear(); - if (bulletproof) + rv.p.bulletproofs_plus.clear(); + if (bulletproof_or_plus) { + const bool plus = is_rct_bulletproof_plus(rv.type); size_t n_amounts = outamounts.size(); size_t amounts_proved = 0; if (rct_config.range_proof_type == RangeProofPaddedBulletproof) @@ -1133,19 +1214,31 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[0], amount_keys.size()}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < outamounts.size(); ++i) { - rv.outPk[i].mask = rct::scalarmult8(C[i]); + if (plus) + rv.outPk[i].mask = C[i]; + else + rv.outPk[i].mask = rct::scalarmult8(C[i]); outSk[i].mask = masks[i]; } } @@ -1153,7 +1246,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofMultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? BULLETPROOF_PLUS_MAX_OUTPUTS : BULLETPROOF_MAX_OUTPUTS)) batch_size *= 2; rct::keyV C, masks; std::vector<uint64_t> batch_amounts(batch_size); @@ -1162,19 +1255,31 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < batch_size; ++i) { - rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); + if (plus) + rv.outPk[i + amounts_proved].mask = C[i]; + else + rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); outSk[i + amounts_proved].mask = masks[i]; } amounts_proved += batch_size; @@ -1189,7 +1294,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 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1197,9 +1302,9 @@ namespace rct { // TODO: unused ?? // key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts; pseudoOuts.resize(inamounts.size()); - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) rv.p.CLSAGs.resize(inamounts.size()); else rv.p.MGs.resize(inamounts.size()); @@ -1218,11 +1323,11 @@ namespace rct { if (msout) { msout->c.resize(inamounts.size()); - msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0); + msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0); } for (i = 0 ; i < inamounts.size(); i++) { - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) { 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); } @@ -1328,20 +1433,25 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results; - std::vector<const Bulletproof*> proofs; + std::vector<const Bulletproof*> bp_proofs; + std::vector<const BulletproofPlus*> bpp_proofs; size_t max_non_bp_proofs = 0, offset = 0; for (const rctSig *rvp: rvv) { CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); const rctSig &rv = *rvp; - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + if (bulletproof || bulletproof_plus) { - CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); - if (rv.type == RCTTypeCLSAG) + if (bulletproof_plus) + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus"); + else + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); + if (is_rct_clsag(rv.type)) { CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG"); CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs"); @@ -1361,7 +1471,7 @@ namespace rct { } CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - if (!bulletproof) + if (!bulletproof && !bulletproof_plus) max_non_bp_proofs += rv.p.rangeSigs.size(); } @@ -1371,11 +1481,15 @@ namespace rct { const rctSig &rv = *rvp; const bool bulletproof = is_rct_bulletproof(rv.type); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { - masks[i] = rv.outPk[i].mask; + if (bulletproof_plus) + masks[i] = rct::scalarmult8(rv.outPk[i].mask); + else + masks[i] = rv.outPk[i].mask; } key sumOutpks = addKeys(masks); DP(sumOutpks); @@ -1391,10 +1505,15 @@ namespace rct { return false; } - if (bulletproof) + if (bulletproof_plus) + { + for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++) + bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]); + } + else if (bulletproof) { for (size_t i = 0; i < rv.p.bulletproofs.size(); i++) - proofs.push_back(&rv.p.bulletproofs[i]); + bp_proofs.push_back(&rv.p.bulletproofs[i]); } else { @@ -1403,9 +1522,18 @@ namespace rct { offset += rv.p.rangeSigs.size(); } } - if (!proofs.empty() && !verBulletproof(proofs)) + if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) + { + LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; + return false; + } + if (!bp_proofs.empty() && !verBulletproof(bp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; return false; } @@ -1445,11 +1573,12 @@ namespace rct { { PERF_TIMER(verRctNonSemanticsSimple); - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet - if (bulletproof) + if (bulletproof || bulletproof_plus) CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing"); else CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); @@ -1460,7 +1589,7 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; const key message = get_pre_mlsag_hash(rv, hw::get_device("default")); @@ -1468,10 +1597,8 @@ namespace rct { results.resize(rv.mixRing.size()); for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { tpool.submit(&waiter, [&, i] { - if (rv.type == RCTTypeCLSAG) - { + if (is_rct_clsag(rv.type)) results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]); - } else results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]); }); @@ -1518,10 +1645,12 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1542,16 +1671,19 @@ 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 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, + false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1574,6 +1706,7 @@ namespace rct { 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(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function"); 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"); @@ -1598,7 +1731,7 @@ namespace rct { } 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(is_rct_clsag(rv.type), 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/CLSAGs size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); @@ -1620,7 +1753,7 @@ namespace rct { } 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) + if (is_rct_clsag(rv.type)) return signMultisigCLSAG(rv, indices, k, msout, secret_key); else return signMultisigMLSAG(rv, indices, k, msout, secret_key); diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 1f674056d..c22b0524f 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -196,6 +196,7 @@ namespace rct { case RCTTypeBulletproof: case RCTTypeBulletproof2: case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: return true; default: return false; @@ -215,6 +216,17 @@ namespace rct { } } + bool is_rct_bulletproof_plus(int type) + { + switch (type) + { + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + bool is_rct_borromean(int type) { switch (type) @@ -227,19 +239,34 @@ namespace rct { } } - size_t n_bulletproof_amounts(const Bulletproof &proof) + bool is_rct_clsag(int type) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + switch (type) + { + case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + + static size_t n_bulletproof_amounts_base(const size_t L_size, const size_t R_size, const size_t V_size, const size_t max_outputs) + { + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() > 0, 0, "Empty bulletproof"); - return proof.V.size(); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(V_size <= (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size * 2 > (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size > 0, 0, "Empty bulletproof"); + return V_size; } + size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs) { size_t n = 0; @@ -254,15 +281,31 @@ namespace rct { return n; } - size_t n_bulletproof_max_amounts(const Bulletproof &proof) + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + + static size_t n_bulletproof_max_amounts_base(size_t L_size, size_t R_size, size_t max_outputs) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - return 1 << (proof.L.size() - 6); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + return 1 << (L_size - 6); } + size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs) { @@ -278,4 +321,18 @@ namespace rct { return n; } + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_max_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 278ff4164..59ed4d6a6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -238,11 +238,48 @@ namespace rct { END_SERIALIZE() }; + struct BulletproofPlus + { + rct::keyV V; + rct::key A, A1, B; + rct::key r1, s1, d1; + rct::keyV L, R; + + BulletproofPlus() {} + BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + + bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; } + + BEGIN_SERIALIZE_OBJECT() + // Commitments aren't saved, they're restored via outPk + // FIELD(V) + FIELD(A) + FIELD(A1) + FIELD(B) + FIELD(r1) + FIELD(s1) + FIELD(d1) + FIELD(L) + FIELD(R) + + if (L.empty() || L.size() != R.size()) + return false; + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs); size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs); + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs); + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs); + //A container to hold all signatures necessary for RingCT // rangeSigs holds all the rangeproof data of a transaction // MG holds the MLSAG signature of a transaction @@ -257,6 +294,7 @@ namespace rct { RCTTypeBulletproof = 3, RCTTypeBulletproof2 = 4, RCTTypeCLSAG = 5, + RCTTypeBulletproofPlus = 6, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -285,7 +323,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; VARINT_FIELD(txnFee) // inputs/outputs not saved, only here for serialization help @@ -314,7 +352,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.begin_object(); if (!typename Archive<W>::is_saving()) @@ -360,6 +398,7 @@ namespace rct { struct rctSigPrunable { std::vector<rangeSig> rangeSigs; std::vector<Bulletproof> bulletproofs; + std::vector<BulletproofPlus> bulletproofs_plus; std::vector<mgSig> MGs; // simple rct has N, full has 1 std::vector<clsag> CLSAGs; keyV pseudoOuts; //C - for simple rct @@ -376,9 +415,28 @@ namespace rct { return false; if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproofPlus) + { + uint32_t nbp = bulletproofs_plus.size(); + VARINT_FIELD(nbp) + ar.tag("bpp"); + ar.begin_array(); + if (nbp > outputs) + return false; + PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + for (size_t i = 0; i < nbp; ++i) + { + FIELDS(bulletproofs_plus[i]) + if (nbp - i > 1) + ar.delimit_array(); + } + if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) + return false; + ar.end_array(); + } + else if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { uint32_t nbp = bulletproofs.size(); if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) @@ -416,7 +474,7 @@ namespace rct { ar.end_array(); } - if (type == RCTTypeCLSAG) + if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("CLSAGs"); ar.begin_array(); @@ -507,7 +565,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -528,6 +586,7 @@ namespace rct { BEGIN_SERIALIZE_OBJECT() FIELD(rangeSigs) FIELD(bulletproofs) + FIELD(bulletproofs_plus) FIELD(MGs) FIELD(CLSAGs) FIELD(pseudoOuts) @@ -538,12 +597,12 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } BEGIN_SERIALIZE_OBJECT() @@ -655,7 +714,9 @@ namespace rct { bool is_rct_simple(int type); bool is_rct_bulletproof(int type); + bool is_rct_bulletproof_plus(int type); bool is_rct_borromean(int type); + bool is_rct_clsag(int type); static inline const rct::key &pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; } static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; } @@ -711,6 +772,7 @@ 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(debug_archive, rct::BulletproofPlus, "rct::bulletproof_plus"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -728,6 +790,7 @@ 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(binary_archive, rct::BulletproofPlus, 0xa0); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -745,5 +808,6 @@ 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"); +VARIANT_TAG(json_archive, rct::BulletproofPlus, "rct_bulletproof_plus"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e114ea7c6..40b1b1000 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -478,6 +478,7 @@ namespace cryptonote } CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; @@ -534,6 +535,7 @@ namespace cryptonote res.version = restricted ? "" : MONERO_VERSION_FULL; res.synchronized = check_core_ready(); res.busy_syncing = m_p2p.get_payload_object().is_busy_syncing(); + res.restricted = restricted; res.status = CORE_RPC_STATUS_OK; return true; @@ -582,6 +584,7 @@ namespace cryptonote } CHECK_PAYMENT(req, res, 1); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); // quick check for noop if (!req.block_ids.empty()) @@ -592,7 +595,7 @@ namespace cryptonote if (last_block_hash == req.block_ids.front()) { res.start_height = 0; - res.current_height = m_core.get_current_blockchain_height(); + res.current_height = last_block_height + 1; res.status = CORE_RPC_STATUS_OK; return true; } @@ -713,6 +716,7 @@ namespace cryptonote res.blocks.clear(); res.blocks.reserve(req.heights.size()); CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); for (uint64_t height : req.heights) { block blk; @@ -1574,6 +1578,7 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, 1); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; @@ -1598,6 +1603,7 @@ namespace cryptonote return r; CHECK_PAYMENT(req, res, 1); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; @@ -1700,11 +1706,14 @@ namespace cryptonote error_resp.message = "Wrong parameters, expected height"; return false; } + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); uint64_t h = req[0]; - if(m_core.get_current_blockchain_height() <= h) + uint64_t blockchain_height = m_core.get_current_blockchain_height(); + if(blockchain_height <= h) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); + error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(blockchain_height - 1); + return false; } res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h)); return true; @@ -1854,6 +1863,7 @@ namespace cryptonote return false; } } + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); crypto::hash seed_hash, next_seed_hash; if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp)) return false; @@ -2327,6 +2337,7 @@ namespace cryptonote CHECK_CORE_READY(); CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); uint64_t last_block_height; crypto::hash last_block_hash; m_core.get_blockchain_top(last_block_height, last_block_hash); @@ -2367,6 +2378,8 @@ namespace cryptonote return false; } + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); + auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool { crypto::hash block_hash; bool hash_parsed = parse_hash256(hash, block_hash); @@ -2426,13 +2439,6 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r)) return r; - const uint64_t bc_height = m_core.get_current_blockchain_height(); - if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) - { - error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = "Invalid start/end heights."; - return false; - } const bool restricted = m_restricted && ctx; if (restricted && req.end_height - req.start_height > RESTRICTED_BLOCK_HEADER_RANGE) { @@ -2442,6 +2448,16 @@ namespace cryptonote } CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); + + const uint64_t bc_height = m_core.get_current_blockchain_height(); + if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) + { + error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; + error_resp.message = "Invalid start/end heights."; + return false; + } + for (uint64_t h = req.start_height; h <= req.end_height; ++h) { crypto::hash block_hash = m_core.get_block_id_by_height(h); @@ -2486,10 +2502,12 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r)) return r; - if(m_core.get_current_blockchain_height() <= req.height) + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); + uint64_t blockchain_height = m_core.get_current_blockchain_height(); + if(blockchain_height <= req.height) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); + error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1); return false; } CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); @@ -2522,6 +2540,7 @@ namespace cryptonote return r; CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); crypto::hash block_hash; if (!req.hash.empty()) @@ -2536,10 +2555,11 @@ namespace cryptonote } else { - if(m_core.get_current_blockchain_height() <= req.height) + uint64_t blockchain_height = m_core.get_current_blockchain_height(); + if(blockchain_height <= req.height) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; - error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); + error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1); return false; } block_hash = m_core.get_block_id_by_height(req.height); @@ -2847,6 +2867,7 @@ namespace cryptonote bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(get_coinbase_tx_sum); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); const uint64_t bc_height = m_core.get_current_blockchain_height(); if (req.height >= bc_height || req.count > bc_height) { @@ -2878,6 +2899,7 @@ namespace cryptonote bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(get_alternate_chains); + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); try { std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); @@ -3180,6 +3202,7 @@ namespace cryptonote bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r)) return r; + db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db()); size_t n_txes = m_core.get_pool_transactions_count(); CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 166fb39ea..d25196841 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 9 +#define CORE_RPC_VERSION_MINOR 10 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -689,6 +689,7 @@ namespace cryptonote bool busy_syncing; std::string version; bool synchronized; + bool restricted; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -730,6 +731,7 @@ namespace cryptonote KV_SERIALIZE(busy_syncing) KV_SERIALIZE(version) KV_SERIALIZE(synchronized) + KV_SERIALIZE(restricted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 0966fb6d2..b2d019ae7 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -247,12 +247,6 @@ namespace cryptonote auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins); if (!access_control_origins_input.empty()) { - if (!config.login) - { - LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RPC server password --") << arg.rpc_login.name << tr(" cannot be empty")); - return boost::none; - } - std::vector<std::string> access_control_origins; boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(",")); std::for_each(access_control_origins.begin(), access_control_origins.end(), std::bind(&boost::trim<std::string>, std::placeholders::_1, std::locale::classic())); diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index b03da1edc..bd715dcfd 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -300,7 +300,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) } const auto& rsig = tx.rct_signatures; - if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) + if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.bulletproofs_plus.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) tx.pruned = true; } @@ -1100,13 +1100,14 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::rctSig& } // prunable - if (!sig.p.bulletproofs.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) + if (!sig.p.bulletproofs.empty() || !sig.p.bulletproofs_plus.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) { dest.Key("prunable"); dest.StartObject(); INSERT_INTO_JSON_OBJECT(dest, range_proofs, sig.p.rangeSigs); INSERT_INTO_JSON_OBJECT(dest, bulletproofs, sig.p.bulletproofs); + INSERT_INTO_JSON_OBJECT(dest, bulletproofs_plus, sig.p.bulletproofs_plus); INSERT_INTO_JSON_OBJECT(dest, mlsags, sig.p.MGs); INSERT_INTO_JSON_OBJECT(dest, pseudo_outs, sig.get_pseudo_outs()); @@ -1141,6 +1142,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) GET_FROM_JSON_OBJECT(prunable->value, sig.p.rangeSigs, range_proofs); GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs, bulletproofs); + GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs_plus, bulletproofs_plus); GET_FROM_JSON_OBJECT(prunable->value, sig.p.MGs, mlsags); GET_FROM_JSON_OBJECT(prunable->value, pseudo_outs, pseudo_outs); @@ -1150,6 +1152,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) { sig.p.rangeSigs.clear(); sig.p.bulletproofs.clear(); + sig.p.bulletproofs_plus.clear(); sig.p.MGs.clear(); sig.get_pseudo_outs().clear(); } @@ -1258,6 +1261,41 @@ void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p) GET_FROM_JSON_OBJECT(val, p.t, t); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, V, p.V); + INSERT_INTO_JSON_OBJECT(dest, A, p.A); + INSERT_INTO_JSON_OBJECT(dest, A1, p.A1); + INSERT_INTO_JSON_OBJECT(dest, B, p.B); + INSERT_INTO_JSON_OBJECT(dest, r1, p.r1); + INSERT_INTO_JSON_OBJECT(dest, s1, p.s1); + INSERT_INTO_JSON_OBJECT(dest, d1, p.d1); + INSERT_INTO_JSON_OBJECT(dest, L, p.L); + INSERT_INTO_JSON_OBJECT(dest, R, p.R); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, p.V, V); + GET_FROM_JSON_OBJECT(val, p.A, A); + GET_FROM_JSON_OBJECT(val, p.A1, A1); + GET_FROM_JSON_OBJECT(val, p.B, B); + GET_FROM_JSON_OBJECT(val, p.r1, r1); + GET_FROM_JSON_OBJECT(val, p.s1, s1); + GET_FROM_JSON_OBJECT(val, p.d1, d1); + GET_FROM_JSON_OBJECT(val, p.L, L); + GET_FROM_JSON_OBJECT(val, p.R, R); +} + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig) { dest.StartObject(); diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index c858faf5a..4514ad568 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -292,6 +292,9 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::Bulletproof& p); void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p); +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p); + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 4afca7ea2..752adc640 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -235,7 +235,6 @@ namespace const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); - const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]"); const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); @@ -1021,7 +1020,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); - std::string multisig_info = m_wallet->get_multisig_info(); + std::string multisig_info = m_wallet->get_multisig_first_kex_msg(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); @@ -1122,58 +1121,6 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo return true; } -bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) -{ - bool ready; - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } - - const auto pwd_container = get_and_verify_password(); - if(pwd_container == boost::none) - { - fail_msg_writer() << tr("Your original password was incorrect."); - return true; - } - - if (!m_wallet->multisig(&ready)) - { - fail_msg_writer() << tr("This wallet is not multisig"); - return true; - } - if (ready) - { - fail_msg_writer() << tr("This wallet is already finalized"); - return true; - } - - LOCK_IDLE_SCOPE(); - - if (args.size() < 2) - { - PRINT_USAGE(USAGE_FINALIZE_MULTISIG); - return true; - } - - try - { - if (!m_wallet->finalize_multisig(pwd_container->password(), args)) - { - fail_msg_writer() << tr("Failed to finalize multisig"); - return true; - } - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); - return true; - } - - return true; -} - bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { exchange_multisig_keys_main(args, false); @@ -3041,6 +2988,19 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->show_wallet_name_when_locked(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::set_inactivity_lock_timeout(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { #ifdef _WIN32 @@ -3484,6 +3444,8 @@ simple_wallet::simple_wallet() " Whether to automatically start mining for RPC payment if the daemon requires it.\n" "credits-target <unsigned int>\n" " The RPC payment credits balance to target (0 for default).\n " + "show-wallet-name-when-locked <1|0>\n " + " Set this if you would like to display the wallet name when locked.\n " "inactivity-lock-timeout <unsigned int>\n " " How many seconds to wait before locking the wallet (0 to disable).")); m_cmd_binder.set_handler("encrypted_seed", @@ -3627,10 +3589,6 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1), tr(USAGE_MAKE_MULTISIG), tr("Turn this wallet into a multisig wallet")); - m_cmd_binder.set_handler("finalize_multisig", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1), - tr(USAGE_FINALIZE_MULTISIG), - tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("exchange_multisig_keys", boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1), tr(USAGE_EXCHANGE_MULTISIG_KEYS), @@ -3893,6 +3851,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; success_msg_writer() << "device-name = " << m_wallet->device_name(); success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); + success_msg_writer() << "show-wallet-name-when-locked = " << m_wallet->show_wallet_name_when_locked(); success_msg_writer() << "inactivity-lock-timeout = " << m_wallet->inactivity_lock_timeout() #ifdef _WIN32 << " (disabled on Windows)" @@ -3960,6 +3919,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); @@ -6520,6 +6480,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) { const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); + + const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); + if (show_wallet_name) + { + tools::msg_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + tools::msg_writer() << tr("Network type: ") << ( + m_wallet->nettype() == cryptonote::TESTNET ? tr("Testnet") : + m_wallet->nettype() == cryptonote::STAGENET ? tr("Stagenet") : tr("Mainnet") + ); + } try { if (get_and_verify_password()) @@ -9625,8 +9595,8 @@ void simple_wallet::print_accounts(const std::string& tag) total_balance += m_wallet->balance(account_index, false); total_unlocked_balance += m_wallet->unlocked_balance(account_index, false); } - success_msg_writer() << tr("----------------------------------------------------------------------------------"); - success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); + success_msg_writer() << tr("------------------------------------------------------------------------------------"); + success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) @@ -11005,8 +10975,8 @@ void simple_wallet::mms_init(const std::vector<std::string> &args) std::vector<std::string> numbers; boost::split(numbers, mn, boost::is_any_of("/")); bool mn_ok = (numbers.size() == 2) - && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100) - && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers); + && get_number_from_arg(numbers[1], num_authorized_signers, 2, config::MULTISIG_MAX_SIGNERS) + && get_number_from_arg(numbers[0], num_required_signers, 1, num_authorized_signers); if (!mn_ok) { fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers"); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 8780bee1d..643270735 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -148,6 +148,7 @@ namespace cryptonote bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>()); bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); @@ -233,7 +234,6 @@ namespace cryptonote bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool make_multisig(const std::vector<std::string>& args); bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); - bool finalize_multisig(const std::vector<std::string> &args); bool exchange_multisig_keys(const std::vector<std::string> &args); bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 989061250..87242b79c 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1332,7 +1332,7 @@ MultisigState WalletImpl::multisig() const { string WalletImpl::getMultisigInfo() const { try { clearStatus(); - return m_wallet->get_multisig_info(); + return m_wallet->get_multisig_first_kex_msg(); } catch (const exception& e) { LOG_ERROR("Error on generating multisig info: " << e.what()); setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); @@ -1341,7 +1341,7 @@ string WalletImpl::getMultisigInfo() const { return string(); } -string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { +string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) { try { clearStatus(); @@ -1366,30 +1366,12 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info); } catch (const exception& e) { LOG_ERROR("Error on exchanging multisig keys: " << e.what()); - setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what()); } return string(); } -bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { - try { - clearStatus(); - checkMultisigWalletNotReady(m_wallet); - - if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { - return true; - } - - setStatusError(tr("Failed to finalize multisig wallet creation")); - } catch (const exception& e) { - LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what()); - setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); - } - - return false; -} - bool WalletImpl::exportMultisigImages(string& images) { try { clearStatus(); @@ -1760,6 +1742,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str extra_size, m_wallet->use_fork_rules(8, 0), m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), + m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 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/api/wallet.h b/src/wallet/api/wallet.h index 67fc2c08a..7e1e62081 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -147,7 +147,6 @@ public: std::string getMultisigInfo() const override; std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; std::string exchangeMultisigKeys(const std::vector<std::string> &info) override; - bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index f9c421a93..6da547054 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -790,7 +790,7 @@ struct Wallet /** * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call - * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @param threshold - number of required signers to make valid transaction. Must be <= number of participants * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info */ virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; @@ -801,12 +801,6 @@ struct Wallet */ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0; /** - * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation - * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call - * @return true if success - */ - virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; - /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images * @return true if success diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f36fa73e0..ee56aa61a 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <algorithm> #include <numeric> #include <tuple> #include <queue> @@ -59,6 +60,8 @@ using namespace epee; #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "multisig/multisig.h" +#include "multisig/multisig_account.h" +#include "multisig/multisig_kex_msg.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -149,7 +152,6 @@ using namespace cryptonote; #define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2) static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; -static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; @@ -167,42 +169,6 @@ namespace return dir.string(); } - std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key) - { - std::string data; - crypto::public_key signer; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key"); - data += std::string((const char *)&signer, sizeof(crypto::public_key)); - - for (const auto &key: keys) - { - data += std::string((const char *)&key, sizeof(crypto::public_key)); - } - - data.resize(data.size() + sizeof(crypto::signature)); - - crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, signer, signer_secret_key, signature); - - return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data); - } - - std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys) - { - std::vector<crypto::public_key> public_keys; - public_keys.reserve(keys.size()); - - std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key { - crypto::public_key p; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key"); - return p; - }); - - return public_keys; - } - bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2) { if (s1.empty() || s2.empty()) @@ -815,7 +781,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, bool clsag) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { size_t size = 0; @@ -839,12 +805,12 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra size += 1; // rangeSigs - if (bulletproof) + if (bulletproof || bulletproof_plus) { size_t log_padded_outputs = 0; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; - size += (2 * (6 + log_padded_outputs) + 4 + 5) * 32 + 3; + size += (2 * (6 + log_padded_outputs) + (bulletproof_plus ? 6 : (4 + 5))) * 32 + 3; } else size += (2*64*32+32+64*32) * n_outputs; @@ -867,29 +833,29 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra // txnFee size += 4; - LOG_PRINT_L2("estimated " << (bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + LOG_PRINT_L2("estimated " << (bulletproof_plus ? "bulletproof plus" : bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); return size; } -size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); + return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); 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, bool clsag) +uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { - size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); - if (use_rct && bulletproof && n_outputs > 2) + size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + if (use_rct && (bulletproof || bulletproof_plus) && n_outputs > 2) { - const uint64_t bp_base = 368; + const uint64_t bp_base = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) size_t log_padded_outputs = 2; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; uint64_t nlr = 2 * (6 + log_padded_outputs); - const uint64_t bp_size = 32 * (9 + nlr); + const uint64_t bp_size = 32 * ((bulletproof_plus ? 6 : 9) + nlr); const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5; MDEBUG("clawback on size " << size << ": " << bp_clawback); size += bp_clawback; @@ -902,6 +868,11 @@ uint8_t get_bulletproof_fork() return 8; } +uint8_t get_bulletproof_plus_fork() +{ + return HF_VERSION_BULLETPROOF_PLUS; +} + uint8_t get_clsag_fork() { return HF_VERSION_CLSAG; @@ -1202,6 +1173,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ignore_outputs_above(MONEY_SUPPLY), m_ignore_outputs_below(0), m_track_uses(false), + m_show_wallet_name_when_locked(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), m_persistent_rpc_client_id(false), @@ -1850,6 +1822,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); @@ -4000,6 +3973,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); + json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); + value2.SetInt(m_inactivity_lock_timeout); json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator()); @@ -4184,6 +4160,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_above = MONEY_SUPPLY; m_ignore_outputs_below = 0; m_track_uses = false; + m_show_wallet_name_when_locked = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; m_setup_background_mining = BackgroundMiningMaybe; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; @@ -4358,6 +4335,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_below = field_ignore_outputs_below; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, show_wallet_name_when_locked, int, Int, false, false); + m_show_wallet_name_when_locked = field_show_wallet_name_when_locked; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT); m_inactivity_lock_timeout = field_inactivity_lock_timeout; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); @@ -4763,7 +4742,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); - m_account.finalize_multisig(spend_public_key); // Not possible to restore a multisig wallet that is able to activate the MMS // (because the original keys are not (yet) part of the restore info), so @@ -4978,24 +4956,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p store(); } } - +//---------------------------------------------------------------------------------------------------- std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold) + const std::vector<std::string> &initial_kex_msgs, + const std::uint32_t threshold) { - CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); - CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); - CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - - std::string extra_multisig_info; - std::vector<crypto::secret_key> multisig_keys; - rct::key spend_pkey = rct::identity(); - rct::key spend_skey; - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(&spend_skey, sizeof(spend_skey));}); - std::vector<crypto::public_key> multisig_signers; - - // decrypt keys + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5003,104 +4969,89 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() + { + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); + } + ); } - // In common multisig scheme there are 4 types of key exchange rounds: - // 1. First round is exchange of view secret keys and public spend keys. - // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key, - // M - public multisig key (in first round it equals to public spend key), K - new public multisig key. - // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key, - // k - secret multisig key used to sign transactions. k and M are sets of keys, of course. - // And secret spend key as the sum of all participant's secret multisig keys - // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys - // and calculate common spend public key as sum of all unique participants' public multisig keys. - // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds. + // create multisig account + multisig::multisig_account multisig_account{ + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G! - // Wallet's public spend key is the sum of unique public multisig keys of all participants. - // secret_spend_key * G = public signer key + // open initial kex messages, validate them, extract signers + std::vector<multisig::multisig_kex_msg> expanded_msgs; + std::vector<crypto::public_key> signers; + expanded_msgs.reserve(initial_kex_msgs.size()); + signers.reserve(initial_kex_msgs.size() + 1); - if (threshold == spend_keys.size() + 1) + for (const auto &msg : initial_kex_msgs) { - // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key - MINFO("Creating spend key..."); + expanded_msgs.emplace_back(msg); - // Calculates all multisig keys and spend key - cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); + // validate each message + // 1. must be 'round 1' + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1, + "Trying to make multisig with message that has invalid multisig kex round (should be '1')."); - // Our signer key is b * G, where b is secret spend key. - multisig_signers = spend_keys; - multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key)); - } - else - { - // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi). - // note that derivations are public keys as DH exchange suppose it to be - auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys); + // 2. duplicate signers not allowed + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(), + "Duplicate signers not allowed when converting a wallet to multisig."); - spend_pkey = rct::identity(); - multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey); - - if (threshold == spend_keys.size()) - { - // N - 1 / N case - - // We need an extra step, so we package all the composite public keys - // we know about, and make a signed string out of them - MINFO("Creating spend key..."); + // add signer (skip self for now) + if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey()) + signers.push_back(expanded_msgs.back().get_signing_pubkey()); + } - // Calculating set of our secret multisig keys as follows: mi = H(Mi), - // where mi - secret multisig key, Mi - others' participants public multisig key - multisig_keys = cryptonote::calculate_multisig_keys(derivations); + // add self to signers + signers.push_back(multisig_account.get_base_pubkey()); - // calculating current participant's spend secret key as sum of all secret multisig keys for current participant. - // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend! - // Entire wallet's secret spend is sum of all unique secret multisig keys - // among all of participants and is not held by anyone! - spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys)); + // intialize key exchange + multisig_account.initialize_kex(threshold, signers, expanded_msgs); + CHECK_AND_ASSERT_THROW_MES(multisig_account.account_is_active(), "Failed to activate multisig account."); - // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey)); - } - else - { - // M / N case - MINFO("Preparing keys for next exchange round..."); - - // Preparing data for middle round - packing new public multisig keys to exchage with others. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key); - spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key); - - // Need to store middle keys to be able to proceed in case of wallet shutdown. - m_multisig_derivations = derivations; - } - } - + // update wallet state if (!m_original_keys_available) { // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages // (making a wallet multisig overwrites those keys, see account_base::make_multisig) - m_original_address = m_account.get_keys().m_account_address; - m_original_view_secret_key = m_account.get_keys().m_view_secret_key; + m_original_address = get_account().get_keys().m_account_address; + m_original_view_secret_key = get_account().get_keys().m_view_secret_key; m_original_keys_available = true; } clear(); - MINFO("Creating view key..."); - crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); + // account base MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), - "Failed to create multisig wallet due to bad keys"); - memwipe(&spend_skey, sizeof(rct::key)); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to create multisig wallet account due to bad keys"); init_type(hw::device::device_type::SOFTWARE); m_original_keys_available = true; m_multisig = true; m_multisig_threshold = threshold; - m_multisig_signers = multisig_signers; - ++m_multisig_rounds_passed; + m_multisig_signers = signers; + m_multisig_rounds_passed = 1; + + // derivations stored (should be empty in last round) + // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5113,42 +5064,18 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, if (!m_wallet_file.empty()) store(); - return extra_multisig_info; + return multisig_account.get_next_kex_round_msg(); } - -std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info) -{ - THROW_WALLET_EXCEPTION_IF(info.empty(), - error::wallet_internal_error, "Empty multisig info"); - - if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - THROW_WALLET_EXCEPTION_IF(false, - error::wallet_internal_error, "Unsupported info string"); - } - - std::vector<crypto::public_key> signers; - std::unordered_set<crypto::public_key> pkeys; - - THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys), - error::wallet_internal_error, "Bad extra multisig info"); - - return exchange_multisig_keys(password, pkeys, signers); -} - +//---------------------------------------------------------------------------------------------------- std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> derivations, - std::vector<crypto::public_key> signers) + const std::vector<std::string> &kex_messages) { - CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys"); - CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers"); - - bool ready = false; + bool ready{false}; CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished"); + CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); - // keys are decrypted + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5156,37 +5083,72 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); - } - - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1) - { - // the last round is passed and we have to calculate spend public key - // add ours if not included - crypto::public_key local_signer = get_multisig_signer_public_key(); - - if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) - { - signers.push_back(local_signer); - for (const auto &msk: get_account().get_multisig_keys()) + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() { - derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); } - } + ); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const auto &msg : kex_messages) + expanded_msgs.emplace_back(msg); + + // reconstruct multisig account + crypto::public_key dummy; + multisig::multisig_account::kex_origins_map_t kex_origins_map; + + for (const auto &derivation : m_multisig_derivations) + kex_origins_map[derivation]; + + multisig::multisig_account multisig_account{ + m_multisig_threshold, + m_multisig_signers, + get_account().get_keys().m_spend_secret_key, + crypto::null_skey, //base common privkey: not used + get_account().get_keys().m_multisig_keys, + get_account().get_keys().m_view_secret_key, + m_account_public_address.m_spend_public_key, + dummy, //common pubkey: not used + m_multisig_rounds_passed, + std::move(kex_origins_map), + "" + }; - CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + // update multisig kex + multisig_account.kex_update(expanded_msgs); - // Summing all of unique public multisig keys to calculate common public spend key - crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - m_account_public_address.m_spend_public_key = spend_public_key; - m_account.finalize_multisig(spend_public_key); + // update wallet state - m_multisig_signers = signers; - std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)) < 0; }); + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); - ++m_multisig_rounds_passed; - m_multisig_derivations.clear(); + // account base + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to update multisig wallet account due to bad keys"); + + // derivations stored (should be empty in last round) + // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + + // rounds passed + m_multisig_rounds_passed = multisig_account.get_kex_rounds_complete(); + // why is this necessary? who knows... + if (multisig_account.multisig_is_ready()) + { // keys are encrypted again keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5208,270 +5170,28 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor if (!m_wallet_file.empty()) store(); - - return {}; } - // Below are either middle or secret spend key establishment rounds - - for (const auto& key: m_multisig_derivations) - derivations.erase(key); - - // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys. - auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - - std::string extra_multisig_info; - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last - { - // Next round is last therefore we are performing secret spend establishment round as described above. - MINFO("Creating spend key..."); - - // Calculating our secret multisig keys by hashing our public multisig keys. - auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end())); - // And summing it to get personal secret spend key - crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys); - - m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys); - - // Packing public multisig keys to exchange with others and calculate common public spend key in the last round - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey); - } - else - { - // This is just middle round - MINFO("Preparing keys for next exchange round..."); - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key); - m_multisig_derivations = new_derivations; - } - - ++m_multisig_rounds_passed; - + // wallet/file relationship if (!m_wallet_file.empty()) create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); - return extra_multisig_info; -} - -void wallet2::unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const -{ - // parse all multisig info - public_keys.resize(info.size()); - secret_keys.resize(info.size()); - for (size_t i = 0; i < info.size(); ++i) - { - THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), - error::wallet_internal_error, "Bad multisig info: " + info[i]); - } - - // remove duplicates - for (size_t i = 0; i < secret_keys.size(); ++i) - { - for (size_t j = i + 1; j < secret_keys.size(); ++j) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) - { - MDEBUG("Duplicate key found, ignoring"); - secret_keys[j] = secret_keys.back(); - public_keys[j] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --j; - } - } - } - - // people may include their own, weed it out - const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (secret_keys[i] == local_skey) - { - MDEBUG("Local key is present, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; - } - else - { - THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, - "Found local spend public key, but not local view secret key - something very weird"); - } - } -} - -std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold) -{ - std::vector<crypto::secret_key> secret_keys(info.size()); - std::vector<crypto::public_key> public_keys(info.size()); - unpack_multisig_info(info, public_keys, secret_keys); - return make_multisig(password, secret_keys, public_keys, threshold); -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers) -{ - bool ready; - uint32_t threshold, total; - if (!multisig(&ready, &threshold, &total)) - { - MERROR("This is not a multisig wallet"); - return false; - } - if (ready) - { - MERROR("This multisig wallet is already finalized"); - return false; - } - if (threshold + 1 != total) - { - MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead"); - return false; - } - exchange_multisig_keys(password, pkeys, signers); - return true; -} - -bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const -{ - // parse all multisig info - signers.resize(info.size(), crypto::null_pkey); - for (size_t i = 0; i < info.size(); ++i) - { - if (!verify_extra_multisig_info(info[i], pkeys, signers[i])) - { - return false; - } - } - - return true; -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info) -{ - std::unordered_set<crypto::public_key> public_keys; - std::vector<crypto::public_key> signers; - if (!unpack_extra_multisig_info(info, signers, public_keys)) - { - MERROR("Bad multisig info"); - return false; - } - - return finalize_multisig(password, public_keys, signers); -} - -std::string wallet2::get_multisig_info() const -{ - // It's a signed package of private view key and public spend key - const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - crypto::hash hash; - - std::string data; - data += std::string((const char *)&skey, sizeof(crypto::secret_key)); - data += std::string((const char *)&pkey, sizeof(crypto::public_key)); - - data.resize(data.size() + sizeof(crypto::signature)); - crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); - - return std::string("MultisigV1") + tools::base58::encode(data); + return multisig_account.get_next_kex_round_msg(); } - -bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) -{ - const size_t header_len = strlen("MultisigV1"); - if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(header_len), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - size_t offset = 0; - skey = *(const crypto::secret_key*)(decoded.data() + offset); - offset += sizeof(skey); - pkey = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(pkey); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, pkey, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - return true; -} - -bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer) +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_multisig_first_kex_msg() const { - if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); - size_t offset = 0; - signer = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(signer); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, signer, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - for (size_t n = 0; n < n_keys; ++n) - { - crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); - pkeys.insert(mspk); - offset += sizeof(mspk); - } + // create multisig account + multisig::multisig_account multisig_account{ + // k_base = H(normal private spend key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + // k_view = H(normal private view key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - return true; + return multisig_account.get_next_kex_round_msg(); } - +//---------------------------------------------------------------------------------------------------- bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const { if (!m_multisig) @@ -5484,7 +5204,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); return true; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_multisig_partial_key_images() const { if (!m_multisig) @@ -5494,7 +5214,7 @@ bool wallet2::has_multisig_partial_key_images() const return true; return false; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_unknown_key_images() const { for (const auto &td: m_transfers) @@ -6126,6 +5846,19 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo amount_per_subaddr[0] = utx.second.m_change; else found->second += utx.second.m_change; + + // add transfers to same wallet + for (const auto &dest: utx.second.m_dests) { + auto index = get_subaddress_index(dest.addr); + if (index && (*index).major == index_major) + { + auto found = amount_per_subaddr.find((*index).minor); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[(*index).minor] = dest.amount; + else + found->second += dest.amount; + } + } } } @@ -6722,7 +6455,7 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device())); } - txs.transfers = export_outputs(); + txs.new_transfers = export_outputs(); // save as binary std::ostringstream oss; binary_archive<true> ar(oss); @@ -6863,7 +6596,10 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { - import_outputs(exported_txs.transfers); + if (!exported_txs.new_transfers.second.empty()) + import_outputs(exported_txs.new_transfers); + else + import_outputs(exported_txs.transfers); // sign the transactions for (size_t n = 0; n < exported_txs.txes.size(); ++n) @@ -7496,16 +7232,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, bool clsag, 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, bool bulletproof_plus, 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, clsag); + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); 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, clsag); + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); } } @@ -9226,8 +8962,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 + rct::RangeProofPaddedBulletproof, + use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : 3 }; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs @@ -9922,10 +9658,11 @@ 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 bulletproof_plus = use_fork_rules(get_bulletproof_plus_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_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; const uint64_t base_fee = get_base_fee(); @@ -9961,7 +9698,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, clsag)); + const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9979,8 +9716,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, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); 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); @@ -10077,7 +9814,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, clsag, 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, bulletproof_plus, 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()) { @@ -10190,7 +9927,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, clsag) < 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, bulletproof_plus) < 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) << @@ -10207,7 +9944,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (!out_slots_exhausted && 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)) { + if (!out_slots_exhausted && 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, bulletproof_plus) < 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)); @@ -10245,7 +9983,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, clsag); + 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, bulletproof_plus); 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); } @@ -10256,7 +9994,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp pending_tx test_ptx; 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); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee) { @@ -10516,11 +10254,12 @@ 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 bulletproof_plus = use_fork_rules(get_bulletproof_plus_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, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); 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); @@ -10626,10 +10365,11 @@ 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 bulletproof_plus = use_fork_rules(get_bulletproof_plus_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_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -10658,7 +10398,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, clsag); + 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, bulletproof_plus); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); } else @@ -10689,7 +10429,7 @@ 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, clsag); + 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, bulletproof_plus); 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) { @@ -10697,7 +10437,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton pending_tx test_ptx; 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); + needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, 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) @@ -11559,8 +11299,10 @@ 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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); - const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + rct::key C = tx.rct_signatures.outPk[n].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + C = rct::scalarmult8(C); rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount"); @@ -12212,7 +11954,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 || tx.rct_signatures.type == rct::RCTTypeCLSAG); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); amount = rct::h2d(ecdh_info.amount); } total += amount; @@ -13133,10 +12875,10 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const +std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const { PERF_TIMER(export_outputs); - std::vector<tools::wallet2::transfer_details> outs; + std::vector<tools::wallet2::exported_transfer_details> outs; size_t offset = 0; if (!all) @@ -13148,7 +12890,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::expo { const transfer_details &td = m_transfers[n]; - outs.push_back(td); + exported_transfer_details etd; + etd.m_pubkey = td.get_public_key(); + etd.m_tx_pubkey = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index); + etd.m_internal_output_index = td.m_internal_output_index; + etd.m_global_output_index = td.m_global_output_index; + etd.m_flags.flags = 0; + etd.m_flags.m_spent = td.m_spent; + etd.m_flags.m_frozen = td.m_frozen; + etd.m_flags.m_rct = td.m_rct; + etd.m_flags.m_key_image_known = td.m_key_image_known; + etd.m_flags.m_key_image_request = td.m_key_image_request; + etd.m_flags.m_key_image_partial = td.m_key_image_partial; + etd.m_amount = td.m_amount; + etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + + outs.push_back(etd); } return std::make_pair(offset, outs); @@ -13241,6 +12998,93 @@ process: return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) +{ + PERF_TIMER(import_outputs); + + THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + "Imported outputs omit more outputs that we know of. Try using export_outputs all."); + + const size_t offset = outputs.first; + const size_t original_size = m_transfers.size(); + m_transfers.resize(offset + outputs.second.size()); + for (size_t i = 0; i < offset; ++i) + m_transfers[i].m_key_image_request = false; + for (size_t i = 0; i < outputs.second.size(); ++i) + { + exported_transfer_details etd = outputs.second[i]; + transfer_details &td = m_transfers[i + offset]; + + // setup td with "cheao" loaded data + td.m_block_height = 0; + td.m_txid = crypto::null_hash; + td.m_global_output_index = etd.m_global_output_index; + td.m_spent = etd.m_flags.m_spent; + td.m_frozen = etd.m_flags.m_frozen; + td.m_spent_height = 0; + td.m_mask = rct::identity(); + td.m_amount = etd.m_amount; + td.m_rct = etd.m_flags.m_rct; + td.m_key_image_known = etd.m_flags.m_key_image_known; + td.m_key_image_request = etd.m_flags.m_key_image_request; + td.m_key_image_partial = false; + + // skip those we've already imported, or which have different data + if (i + offset < original_size) + { + bool needs_processing = false; + if (!td.m_key_image_known) + needs_processing = true; + else if (!(etd.m_internal_output_index == td.m_internal_output_index)) + needs_processing = true; + else if (!(etd.m_pubkey == td.get_public_key())) + needs_processing = true; + + if (!needs_processing) + continue; + } + + // construct a synthetix tx prefix that has the info we'll need: the output with its pubkey, the tx pubkey in extra + td.m_tx = {}; + + THROW_WALLET_EXCEPTION_IF(etd.m_internal_output_index >= 65536, error::wallet_internal_error, "internal output index seems outrageously high, rejecting"); + td.m_internal_output_index = etd.m_internal_output_index; + cryptonote::txout_to_key tk; + tk.key = etd.m_pubkey; + cryptonote::tx_out out; + out.amount = etd.m_amount; + out.target = tk; + td.m_tx.vout.resize(etd.m_internal_output_index); + td.m_tx.vout.push_back(out); + + td.m_pk_index = 0; + add_tx_pub_key_to_extra(td.m_tx, etd.m_tx_pubkey); + if (!etd.m_additional_tx_keys.empty()) + add_additional_tx_pub_keys_to_extra(td.m_tx.extra, etd.m_additional_tx_keys); + + // the hot wallet wouldn't have known about key images (except if we already exported them) + cryptonote::keypair in_ephemeral; + + const crypto::public_key &tx_pub_key = etd.m_tx_pubkey; + const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys; + const crypto::public_key& out_key = etd.m_pubkey; + bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + if (should_expand(td.m_subaddr_index)) + expand_subaddresses(td.m_subaddr_index); + td.m_key_image_known = true; + td.m_key_image_request = true; + td.m_key_image_partial = false; + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i + offset)); + + m_key_images[td.m_key_image] = i + offset; + m_pub_keys[td.get_public_key()] = i + offset; + } + + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { PERF_TIMER(import_outputs_from_str); @@ -13279,10 +13123,23 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) try { std::string body(data, headerlen); - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + + std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; + if (::serialization::serialize(ar, new_outputs)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } + catch (...) {} + if (!loaded) + new_outputs.second.clear(); + + std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + if (!loaded) try + { + binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; if (::serialization::serialize(ar, outputs)) if (::serialization::check_stream_state(ar)) loaded = true; @@ -13308,7 +13165,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) outputs.second = {}; } - imported_outputs = import_outputs(outputs); + imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs); } catch (const std::exception &e) { @@ -13318,13 +13175,6 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) return imported_outputs; } //---------------------------------------------------------------------------------------------------- -crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const -{ - crypto::public_key pkey; - crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey); - return pkey; -} -//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signer_public_key() const { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); @@ -13368,7 +13218,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); rct::multisig_kLRki kLRki; kLRki.k = k; - cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); + multisig::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image); return kLRki; } @@ -13415,7 +13265,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); + bool r = multisig::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -13438,7 +13288,7 @@ cryptonote::blobdata wallet2::export_multisig() for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); + bool r = multisig::generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } @@ -14353,9 +14203,10 @@ 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); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); 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); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); return std::make_pair(size, weight); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7648becc8..ee0974fdc 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -373,6 +373,40 @@ private: END_SERIALIZE() }; + struct exported_transfer_details + { + crypto::public_key m_pubkey; + uint64_t m_internal_output_index; + uint64_t m_global_output_index; + crypto::public_key m_tx_pubkey; + union + { + struct + { + uint8_t m_spent: 1; + uint8_t m_frozen: 1; + uint8_t m_rct: 1; + uint8_t m_key_image_known: 1; + uint8_t m_key_image_request: 1; // view wallets: we want to request it; cold wallets: it was requested + uint8_t m_key_image_partial: 1; + }; + uint8_t flags; + } m_flags; + uint64_t m_amount; + std::vector<crypto::public_key> m_additional_tx_keys; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(m_pubkey) + VARINT_FIELD(m_internal_output_index) + VARINT_FIELD(m_global_output_index) + FIELD(m_tx_pubkey) + FIELD(m_flags.flags) + VARINT_FIELD(m_amount) + FIELD(m_additional_tx_keys) + END_SERIALIZE() + }; + typedef std::vector<uint64_t> amounts_container; struct payment_details { @@ -575,11 +609,15 @@ private: { std::vector<tx_construction_data> txes; std::pair<size_t, wallet2::transfer_container> transfers; + std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(txes) - FIELD(transfers) + if (version >= 1) + FIELD(new_transfers) + else + FIELD(transfers) END_SERIALIZE() }; @@ -757,45 +795,20 @@ private: * to other participants */ std::string make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold); + const std::vector<std::string> &kex_messages, + const std::uint32_t threshold); /*! - * \brief Creates a multisig wallet + * \brief Increment the multisig key exchange round * \return empty if done, non empty if we need to send another string * to other participants */ - std::string make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold); std::string exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info); - /*! - * \brief Any but first round of keys exchange - */ - std::string exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> pkeys, - std::vector<crypto::public_key> signers); - /*! - * \brief Finalizes creation of a multisig wallet - */ - bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info); - /*! - * \brief Finalizes creation of a multisig wallet - */ - bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers); - /*! - * Get a packaged multisig information string - */ - std::string get_multisig_info() const; - /*! - * Verifies and extracts keys from a packaged multisig information string - */ - static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); + const std::vector<std::string> &kex_messages); /*! - * Verifies and extracts keys from a packaged multisig information string + * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called) + * \return string to send to other participants */ - static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer); + std::string get_multisig_first_kex_msg() const; /*! * Export multisig info * This will generate and remember new k values @@ -1229,6 +1242,8 @@ private: void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } + void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; } @@ -1372,8 +1387,9 @@ private: bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> export_outputs(bool all = false) const; + std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const; std::string export_outputs_to_str(bool all = false) const; + size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; @@ -1411,7 +1427,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, bool clsag, 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, bool bulletproof_plus, 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(); @@ -1477,7 +1493,6 @@ private: void set_attribute(const std::string &key, const std::string &value); bool get_attribute(const std::string &key, std::string &value) const; - crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const; crypto::public_key get_multisig_signer_public_key() const; crypto::public_key get_multisig_signing_public_key(size_t idx) const; crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; @@ -1641,12 +1656,6 @@ private: bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); uint64_t get_segregation_fork_height() const; - void unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const; - bool unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const; void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const; @@ -1749,6 +1758,7 @@ private: uint64_t m_ignore_outputs_above; uint64_t m_ignore_outputs_below; bool m_track_uses; + bool m_show_wallet_name_when_locked; uint32_t m_inactivity_lock_timeout; BackgroundMiningSetupType m_setup_background_mining; bool m_persistent_rpc_client_id; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 4655e24cd..a173b5a50 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3938,7 +3938,7 @@ namespace tools return false; } - res.multisig_info = m_wallet->get_multisig_info(); + res.multisig_info = m_wallet->get_multisig_first_kex_msg(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -4069,7 +4069,7 @@ namespace tools catch (const std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling import_multisig"; + er.message = std::string{"Error calling import_multisig: "} + e.what(); return false; } @@ -4094,53 +4094,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { - if (!m_wallet) return not_open(er); - if (m_restricted) - { - er.code = WALLET_RPC_ERROR_CODE_DENIED; - er.message = "Command unavailable in restricted mode."; - return false; - } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) - { - er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; - er.message = "This wallet is not multisig"; - return false; - } - if (ready) - { - er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; - er.message = "This wallet is multisig, and already finalized"; - return false; - } - - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) - { - er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; - er.message = "Needs multisig info from more participants"; - return false; - } - - try - { - if (!m_wallet->finalize_multisig(req.password, req.multisig_info)) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling finalize_multisig"; - return false; - } - } - catch (const std::exception &e) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = std::string("Error calling finalize_multisig: ") + e.what(); - return false; - } - res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - - return true; + return false; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx) @@ -4168,7 +4122,7 @@ namespace tools return false; } - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) + if (req.multisig_info.size() + 1 < total) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Needs multisig info from more participants"; @@ -4426,7 +4380,11 @@ namespace tools return false; } - if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) + boost::optional<epee::net_utils::http::login> daemon_login{}; + if (!req.username.empty() || !req.password.empty()) + daemon_login.emplace(req.username, req.password); + + if (!m_wallet->set_daemon(req.address, daemon_login, req.trusted, std::move(ssl_options))) { er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; er.message = std::string("Unable to set daemon"); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 248d31aa4..867ea54bd 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 23 +#define WALLET_RPC_VERSION_MINOR 24 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -2504,24 +2504,17 @@ namespace wallet_rpc struct COMMAND_RPC_FINALIZE_MULTISIG { + // NOP struct request_t { - std::string password; - std::vector<std::string> multisig_info; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(password) - KV_SERIALIZE(multisig_info) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; struct response_t { - std::string address; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2664,6 +2657,8 @@ namespace wallet_rpc struct request_t { std::string address; + std::string username; + std::string password; bool trusted; std::string ssl_support; // disabled, enabled, autodetect std::string ssl_private_key_path; @@ -2674,6 +2669,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) + KV_SERIALIZE(username) + KV_SERIALIZE(password) KV_SERIALIZE_OPT(trusted, false) KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect") KV_SERIALIZE(ssl_private_key_path) |