diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/blockchain_db/blockchain_db.cpp | 2 | ||||
-rw-r--r-- | src/blockchain_db/lmdb/db_lmdb.cpp | 2 | ||||
-rw-r--r-- | src/cryptonote_config.h | 2 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.cpp | 279 | ||||
-rw-r--r-- | src/cryptonote_core/blockchain.h | 12 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_basic.h | 62 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_boost_serialization.h | 5 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_core.cpp | 35 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_format_utils.cpp | 143 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_format_utils.h | 10 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.cpp | 54 | ||||
-rw-r--r-- | src/ringct/rctSigs.cpp | 38 | ||||
-rw-r--r-- | src/rpc/core_rpc_server_commands_defs.h | 1 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 56 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 7 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 650 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 57 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 6 |
18 files changed, 1182 insertions, 239 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 68f635d18..86a117405 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -91,6 +91,8 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti for (uint64_t i = 0; i < tx.vout.size(); ++i) { amount_output_indices.push_back(add_output(tx_hash, tx.vout[i], i, tx.unlock_time)); + if (tx.version > 1 && tx.vout[i].amount == 0) + add_rct_commitment(tx.rct_signatures.outPk[i].mask); } add_tx_amount_output_indices(tx_id, amount_output_indices); } diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index ba9ee8d9e..887740e06 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -861,6 +861,8 @@ void BlockchainLMDB::remove_tx_outputs(const uint64_t tx_id, const transaction& { const tx_out tx_output = tx.vout[i-1]; remove_output(tx_output.amount, amount_output_indices[i-1]); + if (tx_output.amount == 0) + remove_rct_commitment(amount_output_indices[i-1]); } } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 1a093b501..50f61e346 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -41,7 +41,7 @@ #define CRYPTONOTE_MAX_TX_SIZE 1000000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 -#define CURRENT_TRANSACTION_VERSION 1 +#define CURRENT_TRANSACTION_VERSION 2 #define CURRENT_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MINOR_VERSION 0 #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 46ff7efbb..77794d14d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -51,6 +51,7 @@ #include "crypto/hash.h" #include "cryptonote_core/checkpoints.h" #include "cryptonote_core/cryptonote_core.h" +#include "ringct/rctSigs.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" #endif @@ -127,7 +128,7 @@ bool Blockchain::have_tx_keyimg_as_spent(const crypto::key_image &key_im) const // and collects the public key for each from the transaction it was included in // via the visitor passed to it. template <class visitor_t> -bool Blockchain::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height) const +bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -206,8 +207,21 @@ bool Blockchain::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, vi else output_index = m_db->get_output_key(tx_in_to_key.amount, i); + rct::key commitment; + if (tx_version > 1) + { + if (tx_in_to_key.amount == 0) + commitment = m_db->get_rct_commitment(i); + else + commitment = rct::zeroCommit(tx_in_to_key.amount); + } + else + { + rct::identity(commitment); + } + // call to the passed boost visitor to grab the public key for the output - if (!vis.handle_output(output_index.unlock_time, output_index.pubkey)) + if (!vis.handle_output(output_index.unlock_time, output_index.pubkey, commitment)) { LOG_PRINT_L0("Failed to handle_output for output no = " << count << ", with absolute offset " << i); return false; @@ -1086,14 +1100,24 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m { LOG_ERROR("Creating block template: error: invalid transaction size"); } - uint64_t inputs_amount; - if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) + if (cur_tx.tx.version == 1) { - LOG_ERROR("Creating block template: error: cannot get inputs amount"); + uint64_t inputs_amount; + if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) + { + LOG_ERROR("Creating block template: error: cannot get inputs amount"); + } + else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) + { + LOG_ERROR("Creating block template: error: invalid fee"); + } } - else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) + else { - LOG_ERROR("Creating block template: error: invalid fee"); + if (cur_tx.fee != cur_tx.tx.txnFee) + { + LOG_ERROR("Creating block template: error: invalid fee"); + } } } if (txs_size != real_txs_size) @@ -1599,16 +1623,25 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT //------------------------------------------------------------------ // This function adds the ringct output at index i to the list // unlocked and other such checks should be done by here. -void Blockchain::add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, size_t i) const +void Blockchain::add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, uint64_t amount, size_t i) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& oen = *outs.insert(outs.end(), COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry()); + oen.amount = amount; oen.global_amount_index = i; - output_data_t data = m_db->get_output_key(0, i); + output_data_t data = m_db->get_output_key(amount, i); oen.out_key = data.pubkey; - oen.commitment = m_db->get_rct_commitment(i); + if (amount == 0) + { + oen.commitment = m_db->get_rct_commitment(i); + } + else + { + // not a rct output, make a fake commitment with zero key + oen.commitment = rct::zeroCommit(amount); + } } //------------------------------------------------------------------ // This function takes an RPC request for mixins and creates an RPC response @@ -1648,7 +1681,7 @@ bool Blockchain::get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::r // if tx is unlocked, add output to result_outs if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) { - add_out_to_get_rct_random_outs(res.outs, i); + add_out_to_get_rct_random_outs(res.outs, 0, i); } } } @@ -1688,10 +1721,44 @@ bool Blockchain::get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::r // our list. if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) { - add_out_to_get_rct_random_outs(res.outs, i); + add_out_to_get_rct_random_outs(res.outs, 0, i); } } } + + if (res.outs.size() < req.outs_count) + return false; +#if 0 + // if we do not have enough RCT inputs, we can pick from the non RCT ones + // which will have a zero mask + if (res.outs.size() < req.outs_count) + { + LOG_PRINT_L0("Out of RCT inputs (" << res.outs.size() << "/" << req.outs_count << "), using regular ones"); + + // TODO: arbitrary selection, needs better + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req2 = AUTO_VAL_INIT(req2); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response res2 = AUTO_VAL_INIT(res2); + req2.outs_count = req.outs_count - res.outs.size(); + static const uint64_t amounts[] = {1, 10, 20, 50, 100, 200, 500, 1000, 10000}; + for (uint64_t a: amounts) + req2.amounts.push_back(a); + if (!get_random_outs_for_amounts(req2, res2)) + return false; + + // pick random ones from there + while (res.outs.size() < req.outs_count) + { + int list_idx = rand() % (sizeof(amounts)/sizeof(amounts[0])); + if (!res2.outs[list_idx].outs.empty()) + { + const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry oe = res2.outs[list_idx].outs.back(); + res2.outs[list_idx].outs.pop_back(); + add_out_to_get_rct_random_outs(res.outs, res2.outs[list_idx].amount, oe.global_amount_index); + } + } + } +#endif + return true; } //------------------------------------------------------------------ @@ -2153,9 +2220,24 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // from hard fork 2, we forbid dust and compound outputs if (m_hardfork->get_current_version() >= 2) { for (auto &o: tx.vout) { - if (!is_valid_decomposed_amount(o.amount)) { - tvc.m_invalid_output = true; - return false; + if (tx.version == 1) + { + if (!is_valid_decomposed_amount(o.amount)) { + tvc.m_invalid_output = true; + return false; + } + } + } + } + + // in a v2 tx, all outputs must have 0 amount + if (m_hardfork->get_current_version() >= 3) { + if (tx.version >= 2) { + for (auto &o: tx.vout) { + if (o.amount != 0) { + tvc.m_invalid_output = true; + return false; + } } } } @@ -2238,7 +2320,7 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context } uint64_t t_t1 = 0; - std::vector<std::vector<crypto::public_key>> pubkeys(tx.vin.size()); + std::vector<std::vector<rct::ctkey>> pubkeys(tx.vin.size()); std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); @@ -2287,28 +2369,31 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context return false; } - // basically, make sure number of inputs == number of signatures - CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); + if (tx.version == 1) + { + // basically, make sure number of inputs == number of signatures + CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); #if defined(CACHE_VIN_RESULTS) - auto itk = it->second.find(in_to_key.k_image); - if(itk != it->second.end()) - { - if(!itk->second) + auto itk = it->second.find(in_to_key.k_image); + if(itk != it->second.end()) { - LOG_PRINT_L1("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - return false; - } + if(!itk->second) + { + LOG_PRINT_L1("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); + return false; + } - // txin has been verified already, skip - sig_index++; - continue; - } + // txin has been verified already, skip + sig_index++; + continue; + } #endif + } // make sure that output being spent matches up correctly with the // signature spending it. - if (!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[sig_index], pubkeys[sig_index], pmax_used_block_height)) + if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height)) { it->second[in_to_key.k_image] = false; LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); @@ -2320,28 +2405,31 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context return false; } - if (threads > 1) + if (tx.version == 1) { - // ND: Speedup - // 1. Thread ring signature verification if possible. - ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); - } - else - { - check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]); - if (!results[sig_index]) + if (threads > 1) { - it->second[in_to_key.k_image] = false; - LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - - if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() + // ND: Speedup + // 1. Thread ring signature verification if possible. + ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); + } + else + { + check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]); + if (!results[sig_index]) { - LOG_PRINT_L1("*pmax_used_block_height: " << *pmax_used_block_height); - } + it->second[in_to_key.k_image] = false; + LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - return false; + if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() + { + LOG_PRINT_L1("*pmax_used_block_height: " << *pmax_used_block_height); + } + + return false; + } + it->second[in_to_key.k_image] = true; } - it->second[in_to_key.k_image] = true; } sig_index++; @@ -2349,30 +2437,80 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context KILL_IOSERVICE(); - if (threads > 1) + if (tx.version == 1) { - // save results to table, passed or otherwise - bool failed = false; - for (size_t i = 0; i < tx.vin.size(); i++) + if (threads > 1) { - const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]); - it->second[in_to_key.k_image] = results[i]; - if(!failed && !results[i]) - failed = true; + // save results to table, passed or otherwise + bool failed = false; + for (size_t i = 0; i < tx.vin.size(); i++) + { + const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]); + it->second[in_to_key.k_image] = results[i]; + if(!failed && !results[i]) + failed = true; + } + + if (failed) + { + LOG_PRINT_L1("Failed to check ring signatures!, t_loop: " << t_t1); + return false; + } + } + } + else + { + // from version 2, check ringct signatures + + // RCT needs the same mixin for all inputs + for (size_t n = 1; n < pubkeys.size(); ++n) + { + if (pubkeys[n].size() != pubkeys[0].size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched ring sizes"); + return false; + } + } + + bool size_matches = true; + for (size_t i = 0; i < pubkeys.size(); ++i) + size_matches &= pubkeys[i].size() == tx.rct_signatures.mixRing.size(); + for (size_t i = 0; i < tx.rct_signatures.mixRing.size(); ++i) + size_matches &= pubkeys.size() == tx.rct_signatures.mixRing[i].size(); + if (!size_matches) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + if (pubkeys[n][m].dest != rct::rct2pk(tx.rct_signatures.mixRing[m][n].dest)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(tx.rct_signatures.mixRing[m][n].mask)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } + } } - if (failed) + if (!rct::verRct(tx.rct_signatures)) { - LOG_PRINT_L1("Failed to check ring signatures!, t_loop: " << t_t1); + LOG_PRINT_L1("Failed to check ringct signatures!"); return false; } } - LOG_PRINT_L1("t_loop: " << t_t1); return true; } //------------------------------------------------------------------ -void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<crypto::public_key> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) +void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) { if (m_is_in_checkpoint_zone) { @@ -2383,7 +2521,8 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const std::vector<const crypto::public_key *> p_output_keys; for (auto &key : pubkeys) { - p_output_keys.push_back(&key); + // rct::key and crypto::public_key have the same structure, avoid object ctor/memcpy + p_output_keys.push_back(&(const crypto::public_key&)key.dest); } result = crypto::check_ring_signature(tx_prefix_hash, key_image, p_output_keys, sig.data()) ? 1 : 0; @@ -2419,7 +2558,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const // This function locates all outputs associated with a given input (mixins) // and validates that they exist and are usable. It also checks the ring // signature for each input. -bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, std::vector<crypto::public_key> &output_keys, uint64_t* pmax_related_block_height) +bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2429,13 +2568,13 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ struct outputs_visitor { - std::vector<crypto::public_key >& m_output_keys; + std::vector<rct::ctkey >& m_output_keys; const Blockchain& m_bch; - outputs_visitor(std::vector<crypto::public_key>& output_keys, const Blockchain& bch) : + outputs_visitor(std::vector<rct::ctkey>& output_keys, const Blockchain& bch) : m_output_keys(output_keys), m_bch(bch) { } - bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey) + bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey, const rct::key &commitment) { //check tx unlock time if (!m_bch.is_tx_spendtime_unlocked(unlock_time)) @@ -2449,7 +2588,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ // but only txout_to_key outputs are stored in the DB in the first place, done in // Blockchain*::add_output - m_output_keys.push_back(pubkey); + m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment})); return true; } }; @@ -2458,7 +2597,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ // collect output keys outputs_visitor vi(output_keys, *this); - if (!scan_outputkeys_for_indexes(txin, vi, tx_prefix_hash, pmax_related_block_height)) + if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height)) { LOG_PRINT_L1("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); return false; @@ -2469,7 +2608,13 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ LOG_PRINT_L1("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); return false; } - CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + if (tx_version == 1) { + CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + } + else + { + CHECK_AND_ASSERT_MES(rct_signatures.mixRing.size() == output_keys.size(), false, "internal error: tx rct signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + } return true; } //------------------------------------------------------------------ diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 701c17d9e..b22074c57 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -884,11 +884,12 @@ namespace cryptonote * @param vis an instance of the visitor to use * @param tx_prefix_hash the hash of the associated transaction_prefix * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * @param tx_version version of the tx, if > 1 we also get commitments * * @return false if any keys are not found or any inputs are not unlocked, otherwise true */ template<class visitor_t> - inline bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; + inline bool scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; /** * @brief collect output public keys of a transaction input set @@ -900,15 +901,17 @@ namespace cryptonote * If pmax_related_block_height is not NULL, its value is set to the height * of the most recent block which contains an output used in the input set * + * @param tx_version the transaction version * @param txin the transaction input * @param tx_prefix_hash the transaction prefix hash, for caching organization * @param sig the input signature * @param output_keys return-by-reference the public keys of the outputs in the input set + * @param rct_signatures the ringCT signatures, which are only valid if tx version > 1 * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set * * @return false if any output is not yet unlocked, or is missing, otherwise true */ - bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, std::vector<crypto::public_key> &output_keys, uint64_t* pmax_related_block_height); + bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height); /** * @brief validate a transaction's inputs and their keys @@ -1074,9 +1077,10 @@ namespace cryptonote * @brief adds the given output to the requested set of random ringct outputs * * @param outs return-by-reference the set the output is to be added to + * @param amount the output amount (0 for rct inputs) * @param i the rct output index */ - void add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, size_t i) const; + void add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, uint64_t amount, size_t i) const; /** * @brief checks if a transaction is unlocked (its outputs spendable) @@ -1197,7 +1201,7 @@ namespace cryptonote * @param result false if the ring signature is invalid, otherwise true */ void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, - const std::vector<crypto::public_key> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result); + const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result); /** * @brief loads block hashes from compiled-in data set diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_core/cryptonote_basic.h index 0021413f1..91bcef8c5 100644 --- a/src/cryptonote_core/cryptonote_basic.h +++ b/src/cryptonote_core/cryptonote_basic.h @@ -49,6 +49,7 @@ #include "crypto/hash.h" #include "misc_language.h" #include "tx_extra.h" +#include "ringct/rctTypes.h" namespace cryptonote { @@ -172,7 +173,7 @@ namespace cryptonote BEGIN_SERIALIZE() VARINT_FIELD(version) - if(CURRENT_TRANSACTION_VERSION < version) return false; + if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; VARINT_FIELD(unlock_time) FIELD(vin) FIELD(vout) @@ -187,6 +188,7 @@ namespace cryptonote { public: std::vector<std::vector<crypto::signature> > signatures; //count signatures always the same as inputs count + rct::rctSig rct_signatures; transaction(); virtual ~transaction(); @@ -195,34 +197,46 @@ namespace cryptonote BEGIN_SERIALIZE_OBJECT() FIELDS(*static_cast<transaction_prefix *>(this)) - ar.tag("signatures"); - ar.begin_array(); - PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); - bool signatures_not_expected = signatures.empty(); - if (!signatures_not_expected && vin.size() != signatures.size()) - return false; - - for (size_t i = 0; i < vin.size(); ++i) + if (version == 1) { - size_t signature_size = get_signature_size(vin[i]); - if (signatures_not_expected) + ar.tag("signatures"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); + bool signatures_not_expected = signatures.empty(); + if (!signatures_not_expected && vin.size() != signatures.size()) + return false; + + for (size_t i = 0; i < vin.size(); ++i) { - if (0 == signature_size) - continue; - else + size_t signature_size = get_signature_size(vin[i]); + if (signatures_not_expected) + { + if (0 == signature_size) + continue; + else + return false; + } + + PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); + if (signature_size != signatures[i].size()) return false; - } - - PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); - if (signature_size != signatures[i].size()) - return false; - FIELDS(signatures[i]); + FIELDS(signatures[i]); - if (vin.size() - i > 1) - ar.delimit_array(); + if (vin.size() - i > 1) + ar.delimit_array(); + } + ar.end_array(); + } + else + { + FIELD(rct_signatures) + for (size_t i = 0; i < rct_signatures.mixRing.size(); ++i) + { + if (rct_signatures.mixRing[i].size() != vin.size()) + return false; + } } - ar.end_array(); END_SERIALIZE() private: @@ -245,7 +259,7 @@ namespace cryptonote inline void transaction::set_null() { - version = 0; + version = 1; unlock_time = 0; vin.clear(); vout.clear(); diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_core/cryptonote_boost_serialization.h index 6a6ff5a7d..6eeb66ec8 100644 --- a/src/cryptonote_core/cryptonote_boost_serialization.h +++ b/src/cryptonote_core/cryptonote_boost_serialization.h @@ -148,7 +148,10 @@ namespace boost a & x.vin; a & x.vout; a & x.extra; - a & x.signatures; + if (x.version == 1) + a & x.signatures; + else + a & x.rct_signatures; } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index b3a7cb98d..5179cc8d2 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -43,6 +43,7 @@ using namespace epee; #include "misc_language.h" #include <csignal> #include "cryptonote_core/checkpoints.h" +#include "ringct/rctTypes.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -552,6 +553,22 @@ namespace cryptonote LOG_PRINT_RED_L1("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } + if (tx.version > 1) + { + if (tx.rct_signatures.outPk.size() != tx.vout.size()) + { + LOG_PRINT_RED_L1("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + for (size_t n = 0; n < tx.vout.size(); ++n) + { + if (tx.rct_signatures.outPk[n].dest != boost::get<txout_to_key>(tx.vout[n].target).key) + { + LOG_PRINT_RED_L1("tx ringct public key does not match output public key for tx id= " << get_transaction_hash(tx)); + return false; + } + } + } if(!check_money_overflow(tx)) { @@ -559,15 +576,19 @@ namespace cryptonote return false; } - uint64_t amount_in = 0; - get_inputs_money_amount(tx, amount_in); - uint64_t amount_out = get_outs_money_amount(tx); - - if(amount_in <= amount_out) + if (tx.version == 1) { - LOG_PRINT_RED_L1("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); - return false; + uint64_t amount_in = 0; + get_inputs_money_amount(tx, amount_in); + uint64_t amount_out = get_outs_money_amount(tx); + + if(amount_in <= amount_out) + { + LOG_PRINT_RED_L1("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + return false; + } } + // for version > 1, ringct signatures check verifies amounts match if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) { diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index ff752ae47..9f0693b30 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -37,6 +37,7 @@ using namespace epee; #include "miner.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "ringct/rctSigs.h" #define ENCRYPTED_PAYMENT_ID_TAIL 0x8d @@ -181,7 +182,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); - tx.version = CURRENT_TRANSACTION_VERSION; + tx.version = 1; //lock tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; tx.vin.push_back(in); @@ -252,6 +253,11 @@ namespace cryptonote //--------------------------------------------------------------- bool get_tx_fee(const transaction& tx, uint64_t & fee) { + if (tx.version > 1) + { + fee = tx.rct_signatures.txnFee; + return true; + } uint64_t amount_in = 0; uint64_t amount_out = 0; BOOST_FOREACH(auto& in, tx.vin) @@ -447,13 +453,14 @@ namespace cryptonote return encrypt_payment_id(payment_id, public_key, secret_key); } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) { tx.vin.clear(); tx.vout.clear(); tx.signatures.clear(); + tx.rct_signatures = rct::rctSig(); - tx.version = CURRENT_TRANSACTION_VERSION; + tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; tx.extra = extra; @@ -509,6 +516,18 @@ namespace cryptonote }; std::vector<input_generation_context_data> in_contexts; + if (tx.version > 1) + { + // ringct requires all real inputs to be at the same index for all inputs // TODO + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + if(src_entr.real_output != sources.begin()->real_output) + { + LOG_ERROR("All inputs must have the same index for ringct"); + return false; + } + } + } uint64_t summary_inputs_money = 0; //fill inputs @@ -529,7 +548,7 @@ namespace cryptonote return false; //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second) ) + if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key missmatch with output public key! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" @@ -559,7 +578,7 @@ namespace cryptonote size_t output_index = 0; BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) { - CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); + CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::key_derivation derivation; crypto::public_key out_eph_public_key; bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); @@ -586,33 +605,100 @@ namespace cryptonote } - //generate ring signatures - crypto::hash tx_prefix_hash; - get_transaction_prefix_hash(tx, tx_prefix_hash); - - std::stringstream ss_ring_s; - size_t i = 0; - BOOST_FOREACH(const tx_source_entry& src_entr, sources) + if (tx.version == 1) { - ss_ring_s << "pub_keys:" << ENDL; - std::vector<const crypto::public_key*> keys_ptrs; - BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + //generate ring signatures + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + + std::stringstream ss_ring_s; + size_t i = 0; + BOOST_FOREACH(const tx_source_entry& src_entr, sources) { - keys_ptrs.push_back(&o.second); - ss_ring_s << o.second << ENDL; + ss_ring_s << "pub_keys:" << ENDL; + std::vector<const crypto::public_key*> keys_ptrs; + std::vector<crypto::public_key> keys(src_entr.outputs.size()); + size_t ii = 0; + BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + { + keys[ii] = rct2pk(o.second.dest); + keys_ptrs.push_back(&keys[ii]); + ss_ring_s << o.second.dest << ENDL; + ++ii; + } + + tx.signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + ss_ring_s << "signatures:" << ENDL; + std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; + i++; } - tx.signatures.push_back(std::vector<crypto::signature>()); - std::vector<crypto::signature>& sigs = tx.signatures.back(); - sigs.resize(src_entr.outputs.size()); - crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); - ss_ring_s << "signatures:" << ENDL; - std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); - ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; - i++; + LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); } + else + { + // enforce same mixin for all outputs + size_t n_total_outs = sources[0].outputs.size(); + for (size_t i = 1; i < sources.size(); ++i) { + if (n_total_outs != sources[i].outputs.size()) { + LOG_ERROR("Ringct transaction has varying mixin"); + return false; + } + } - LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); + uint64_t amount_in = 0, amount_out = 0; + rct::ctkeyV inSk; + rct::ctkeyM mixRing(n_total_outs); + rct::keyV destinations; + std::vector<uint64_t> amounts; + for (size_t i = 0; i < sources.size(); ++i) + { + rct::ctkey ctkey; + amount_in += sources[i].amount; + // inSk: (secret key, mask) + ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); + ctkey.mask = sources[i].mask; + inSk.push_back(ctkey); + // inPk: (public key, commitment) + // will be done when filling in mixRing + } + for (size_t i = 0; i < tx.vout.size(); ++i) + { + destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); + amounts.push_back(tx.vout[i].amount); + amount_out += tx.vout[i].amount; + } + for (size_t i = 0; i < n_total_outs; ++i) // same index assumption + { + mixRing[i].resize(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + { + mixRing[i][n] = sources[n].outputs[i].second; + } + } + + // fee + if (amount_in > amount_out) + amounts.push_back(amount_in - amount_out); + + LOG_PRINT_L1("Signing tx: " << obj_to_json_str(tx)); + tx.rct_signatures = rct::genRct(inSk, destinations, amounts, mixRing, sources[0].real_output); // same index assumption + + // zero out all amounts to mask rct outputs, real amounts are now encrypted + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (!(sources[i].mask == rct::identity())) + boost::get<txin_to_key>(tx.vin[i]).amount = 0; + } + for (size_t i = 0; i < tx.vout.size(); ++i) + tx.vout[i].amount = 0; + + LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL, LOG_LEVEL_3); + } return true; } @@ -661,7 +747,10 @@ namespace cryptonote << out.target.type().name() << ", expected " << typeid(txout_to_key).name() << ", in transaction id=" << get_transaction_hash(tx)); - CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount ouput in transaction id=" << get_transaction_hash(tx)); + if (tx.version == 1) + { + CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output in transaction id=" << get_transaction_hash(tx)); + } if(!check_key(boost::get<txout_to_key>(out.target).key)) return false; diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 056940e98..f4fd8bd34 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -35,6 +35,7 @@ #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "ringct/rctOps.h" namespace cryptonote @@ -50,13 +51,16 @@ namespace cryptonote struct tx_source_entry { - typedef std::pair<uint64_t, crypto::public_key> output_entry; + typedef std::pair<uint64_t, rct::ctkey> output_entry; - std::vector<output_entry> outputs; //index + key + std::vector<output_entry> outputs; //index + key + optional ringct commitment size_t real_output; //index in outputs vector of real output_entry crypto::public_key real_out_tx_key; //incoming real tx public key size_t real_output_in_tx_index; //index in transaction outputs vector uint64_t amount; //money + rct::key mask; //ringct amount mask + + void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } }; struct tx_destination_entry @@ -70,7 +74,7 @@ namespace cryptonote //--------------------------------------------------------------- bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &txkey); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &txkey, bool rct = false); template<typename T> bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index bb3a6dc3e..7b7c22887 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -78,6 +78,19 @@ namespace cryptonote //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version) { + if (tx.version == 0) + { + // v0 never accepted + tvc.m_verifivation_failed = true; + return false; + } + if (tx.version > 2) // TODO: max 1/2 needs to be conditioned by a hard fork + { + // v2 is the latest one we know + tvc.m_verifivation_failed = true; + return false; + } + // we do not accept transactions that timed out before, unless they're // kept_by_block if (!kept_by_block && m_timed_out_transactions.find(id) != m_timed_out_transactions.end()) @@ -95,25 +108,34 @@ namespace cryptonote return false; } - uint64_t inputs_amount = 0; - if(!get_inputs_money_amount(tx, inputs_amount)) + // fee per kilobyte, size rounded up. + uint64_t fee; + + if (tx.version == 1) { - tvc.m_verifivation_failed = true; - return false; - } + uint64_t inputs_amount = 0; + if(!get_inputs_money_amount(tx, inputs_amount)) + { + tvc.m_verifivation_failed = true; + return false; + } - uint64_t outputs_amount = get_outs_money_amount(tx); + uint64_t outputs_amount = get_outs_money_amount(tx); + if(outputs_amount >= inputs_amount) + { + LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); + tvc.m_verifivation_failed = true; + tvc.m_overspend = true; + return false; + } - if(outputs_amount >= inputs_amount) + fee = inputs_amount - outputs_amount; + } + else { - LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); - tvc.m_verifivation_failed = true; - tvc.m_overspend = true; - return false; + fee = tx.rct_signatures.txnFee; } - // fee per kilobyte, size rounded up. - uint64_t fee = inputs_amount - outputs_amount; uint64_t needed_fee = blob_size / 1024; needed_fee += (blob_size % 1024) ? 1 : 0; needed_fee *= FEE_PER_KB; @@ -150,7 +172,7 @@ namespace cryptonote if (!m_blockchain.check_tx_outputs(tx, tvc)) { - LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid outout"); + LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output"); tvc.m_verifivation_failed = true; tvc.m_invalid_output = true; return false; @@ -170,7 +192,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; - txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; txd_p.first->second.kept_by_block = kept_by_block; @@ -193,7 +215,7 @@ namespace cryptonote txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.kept_by_block = kept_by_block; - txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = max_used_block_id; txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 7b83ca0e2..358feeb5d 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -537,22 +537,30 @@ namespace rct { bool verRct(const rctSig & rv) { CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.rangeSigs"); - size_t i = 0; - bool rvb = true; - bool tmp; - DP("range proofs verified?"); - for (i = 0; i < rv.outPk.size(); i++) { - tmp = verRange(rv.outPk[i].mask, rv.rangeSigs[i]); - DP(tmp); - rvb = (rvb && tmp); - } - //compute txn fee - key txnFeeKey = scalarmultH(d2h(rv.txnFee)); - bool mgVerd = verRctMG(rv.MG, rv.mixRing, rv.outPk, txnFeeKey); - DP("mg sig verified?"); - DP(mgVerd); + // some rct ops can throw + try + { + size_t i = 0; + bool rvb = true; + bool tmp; + DP("range proofs verified?"); + for (i = 0; i < rv.outPk.size(); i++) { + tmp = verRange(rv.outPk[i].mask, rv.rangeSigs[i]); + DP(tmp); + rvb = (rvb && tmp); + } + //compute txn fee + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + bool mgVerd = verRctMG(rv.MG, rv.mixRing, rv.outPk, txnFeeKey); + DP("mg sig verified?"); + DP(mgVerd); - return (rvb && mgVerd); + return (rvb && mgVerd); + } + catch(...) + { + return false; + } } //RingCT protocol diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index bbea145e5..5dd9de8d5 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -329,6 +329,7 @@ namespace cryptonote #pragma pack (push, 1) struct out_entry { + uint64_t amount; uint64_t global_amount_index; crypto::public_key out_key; rct::key commitment; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 1840e54c9..78d135451 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -99,6 +99,12 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) +enum TransferType { + TransferOriginal, + TransferNew, + TransferRingCT, +}; + namespace { const command_line::arg_descriptor<std::string> arg_wallet_file = {"wallet-file", sw::tr("Use wallet <arg>"), ""}; @@ -644,6 +650,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm")); + m_cmd_binder.set_handler("transfer_rct", boost::bind(&simple_wallet::transfer_rct, this, _1), tr("Same as transfer, but using RingCT transactions")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); @@ -1888,24 +1895,24 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) +void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) { message_writer(epee::log_space::console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(tx) << ", " << - tr("received ") << print_money(tx.vout[out_index].amount); + tr("received ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) +void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) { message_writer(epee::log_space::console_color_magenta, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(spend_tx) << ", " << - tr("spent ") << print_money(in_tx.vout[out_index].amount); + tr("spent ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else @@ -2052,13 +2059,15 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (!transfers_found) { - message_writer() << boost::format("%21s%8s%16s%68s") % tr("amount") % tr("spent") % tr("global index") % tr("tx id"); + message_writer() << boost::format("%21s%8s%12s%8s%16s%68s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id"); transfers_found = true; } message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << - boost::format("%21s%8s%16u%68s") % + boost::format("%21s%8s%12s%8s%16u%68s") % print_money(td.amount()) % (td.m_spent ? tr("T") : tr("F")) % + (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % + (td.is_rct() ? tr("RingCT") : tr("-")) % td.m_global_output_index % get_transaction_hash (td.m_tx); } @@ -2274,7 +2283,7 @@ bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::acc return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::string> &args_) +bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { if (!try_connect_to_daemon()) return true; @@ -2386,10 +2395,20 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str { // figure out what tx will be necessary std::vector<tools::wallet2::pending_tx> ptx_vector; - if (new_algorithm) - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); - else - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + switch (transfer_type) + { + case TransferNew: + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + break; + default: + LOG_ERROR("Unknown transfer method, using original"); + case TransferOriginal: + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + break; + case TransferRingCT: + ptx_vector = m_wallet->create_transactions_rct(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + break; + } // if more than one tx necessary, prompt user to confirm if (m_wallet->always_confirm_transfers() || ptx_vector.size() > 1) @@ -2531,14 +2550,18 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { - return transfer_main(false, args_); + return transfer_main(TransferOriginal, args_); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_new(const std::vector<std::string> &args_) { - return transfer_main(true, args_); + return transfer_main(TransferNew, args_); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::transfer_rct(const std::vector<std::string> &args_) +{ + return transfer_main(TransferRingCT, args_); } - //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { @@ -3228,9 +3251,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) m_wallet->get_unconfirmed_payments_out(upayments); for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; - uint64_t amount = 0; - cryptonote::get_inputs_money_amount(pd.m_tx, amount); - uint64_t fee = amount - get_outs_money_amount(pd.m_tx); + uint64_t amount = pd.m_amount_in; + uint64_t fee = amount - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 52a4e150f..91ebb8e6e 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -120,9 +120,10 @@ namespace cryptonote bool show_incoming_transfers(const std::vector<std::string> &args); bool show_payments(const std::vector<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); - bool transfer_main(bool new_algorithm, const std::vector<std::string> &args); + bool transfer_main(int transfer_type, const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); bool transfer_new(const std::vector<std::string> &args); + bool transfer_rct(const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); bool sweep_unmixable(const std::vector<std::string> &args); std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( @@ -171,8 +172,8 @@ namespace cryptonote //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index); - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx); + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount); + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx); virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx); //---------------------------------------------------------- diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8502353ed..767315450 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -55,6 +55,7 @@ using namespace epee; #include "rapidjson/stringbuffer.h" #include "common/json_util.h" #include "common/base58.h" +#include "ringct/rctSigs.h" extern "C" { @@ -168,7 +169,7 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const +void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const { if (o.target.type() != typeid(txout_to_key)) { @@ -176,9 +177,10 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp LOG_ERROR("wrong type id in transaction out"); return; } - if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) + received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i); + if(received) { - money_transfered = o.amount; + money_transfered = o.amount; // may be 0 for ringct outputs } else { @@ -216,6 +218,10 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ tx_pub_key = pub_key_field.pub_key; bool r = true; + std::deque<cryptonote::keypair> in_ephemeral(tx.vout.size()); + std::deque<crypto::key_image> ki(tx.vout.size()); + std::deque<uint64_t> amount(tx.vout.size()); + std::deque<rct::key> mask(tx.vout.size()); int threads = tools::get_max_concurrency(); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { @@ -224,8 +230,8 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) { uint64_t money_transfered = 0; - bool error = false; - check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, money_transfered, error); + bool error = false, received = false; + check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error); if (error) { r = false; @@ -233,9 +239,16 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ else { // this assumes that the miner tx pays a single address - if (money_transfered > 0) + if (received) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(0); + if (money_transfered == 0) + money_transfered = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[0].sec), 0, mask[0]); + amount[0] = money_transfered; tx_money_got_in_outs = money_transfered; // process the other outs from that tx @@ -250,11 +263,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); + std::deque<bool> received(tx.vout.size()); // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, - std::ref(money_transfered[i]), std::ref(error[i]))); + std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); for (size_t i = 1; i < tx.vout.size(); ++i) @@ -264,10 +278,17 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ r = false; break; } - if (money_transfered[i]) + if (received[i]) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(i); + if (money_transfered[i] == 0) + money_transfered[i] = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); tx_money_got_in_outs += money_transfered[i]; + amount[i] = money_transfered[i]; } } } @@ -286,10 +307,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); + std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, - std::ref(money_transfered[i]), std::ref(error[i]))); + std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); tx_money_got_in_outs = 0; @@ -300,16 +322,48 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ r = false; break; } - if (money_transfered[i]) + if (received[i]) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(i); + if (money_transfered[i] == 0) + money_transfered[i] = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); tx_money_got_in_outs += money_transfered[i]; + amount[i] = money_transfered[i]; } } } else { - r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); + for (size_t i = 0; i < tx.vout.size(); ++i) + { + uint64_t money_transfered = 0; + bool error = false, received = false; + check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error); + if (error) + { + r = false; + break; + } + else + { + if (received) + { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + + outs.push_back(i); + if (money_transfered == 0) + money_transfered = rct::decodeRct(tx.rct_signatures, rct::sk2rct(in_ephemeral[i].sec), i, mask[i]); + amount[i] = money_transfered; + tx_money_got_in_outs += money_transfered; + } + } + } } THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); @@ -338,13 +392,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); - crypto::key_image ki; - cryptonote::keypair in_ephemeral; - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); - THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[o].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); - - auto kit = m_key_images.find(ki); + auto kit = m_key_images.find(ki[o]); THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(), error::wallet_internal_error, std::string("Unexpected transfer index from key image: ") + "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) @@ -359,12 +407,22 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ td.m_internal_output_index = o; td.m_global_output_index = res.o_indexes[o]; td.m_tx = tx; - td.m_key_image = ki; + td.m_key_image = ki[o]; + td.m_amount = tx.vout[o].amount; + if (td.m_amount == 0) + { + td.m_mask = mask[o]; + td.m_amount = amount[o]; + } + else + { + td.m_mask = rct::identity(); + } td.m_spent = false; m_key_images[td.m_key_image] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + m_callback->on_money_received(height, td.m_tx, td.m_amount); } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) @@ -389,12 +447,22 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ td.m_internal_output_index = o; td.m_global_output_index = res.o_indexes[o]; td.m_tx = tx; - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); + td.m_amount = tx.vout[o].amount; + if (td.m_amount == 0) + { + td.m_mask = mask[o]; + td.m_amount = amount[o]; + } + else + { + td.m_mask = rct::identity(); + } + THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + m_callback->on_money_received(height, td.m_tx, td.m_amount); } } } @@ -410,12 +478,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); if(it != m_key_images.end()) { - LOG_PRINT_L0("Spent money: " << print_money(boost::get<cryptonote::txin_to_key>(in).amount) << ", with tx: " << get_transaction_hash(tx)); - tx_money_spent_in_ins += boost::get<cryptonote::txin_to_key>(in).amount; transfer_details& td = m_transfers[it->second]; + uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount; + if (amount > 0) + { + THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error, + std::string("Inconsistent amount in tx input: got ") + print_money(amount) + + std::string(", expected ") + print_money(td.amount())); + } + amount = td.amount(); + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << get_transaction_hash(tx)); + tx_money_spent_in_ins += amount; td.m_spent = true; if (0 != m_callback) - m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx); + m_callback->on_money_spent(height, td.m_tx, amount, tx); } } @@ -501,14 +577,22 @@ void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t he void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received) { crypto::hash txid = get_transaction_hash(tx); - confirmed_transfer_details &ctd = m_confirmed_txs[txid]; - // operator[] creates if not found + std::pair<std::unordered_map<crypto::hash, confirmed_transfer_details>::iterator, bool> entry = m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details())); // fill with the info we know, some info might already be there - ctd.m_amount_in = spent; - ctd.m_amount_out = get_outs_money_amount(tx); - ctd.m_change = received; - ctd.m_block_height = height; - ctd.m_timestamp = ts; + if (entry.second) + { + // this case will happen if the tx is from our outputs, but was sent by another + // wallet (eg, we're a cold wallet and the hot wallet sent it). For RCT transactions, + // we only see 0 input amounts, so have to deduce amount out from other parameters. + entry.first->second.m_amount_in = spent; + if (tx.version == 1) + entry.first->second.m_amount_out = get_outs_money_amount(tx); + else + entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee; + entry.first->second.m_change = received; + } + entry.first->second.m_block_height = height; + entry.first->second.m_timestamp = ts; } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height) @@ -2002,9 +2086,13 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un return found_money; } //---------------------------------------------------------------------------------------------------- -void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount) +void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount) { unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)]; + utd.m_amount_in = amount_in; + utd.m_amount_out = 0; + for (const auto &d: dests) + utd.m_amount_out += d.amount; utd.m_change = change_amount; utd.m_sent_time = time(NULL); utd.m_tx = tx; @@ -2190,12 +2278,15 @@ void wallet2::commit_tx(pending_tx& ptx) txid = get_transaction_hash(ptx.tx); crypto::hash payment_id = cryptonote::null_hash; std::vector<cryptonote::tx_destination_entry> dests; + uint64_t amount_in = 0; if (store_tx_info()) { payment_id = get_payment_id(ptx); dests = ptx.dests; + BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + amount_in += it->amount(); } - add_unconfirmed_tx(ptx.tx, dests, payment_id, ptx.change_dts.amount); + add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); if (store_tx_info()) m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); @@ -2546,7 +2637,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent { tx_output_entry oe; oe.first = outs[out_index][n].first; - oe.second = outs[out_index][n].second; + oe.second.dest = rct::pk2rct(outs[out_index][n].second); + oe.second.mask = rct::zeroCommit(td.amount()); + src.outputs.push_back(oe); ++i; } @@ -2561,7 +2654,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = it_to_replace - src.outputs.begin(); @@ -2621,6 +2715,219 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.dests = dsts; } +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) +{ + using namespace cryptonote; + // throw if attempting a transaction with no destinations + THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + + uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + uint64_t needed_money = fee; + LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); + + // calculate total amount being sent to all destinations + // throw if total amount overflows uint64_t + BOOST_FOREACH(auto& dt, dsts) + { + THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); + needed_money += dt.amount; + LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); + THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet); + } + + uint64_t found_money = 0; + BOOST_FOREACH(auto it, selected_transfers) + { + found_money += it->amount(); + } + + LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + + typedef cryptonote::tx_source_entry::output_entry tx_output_entry; + + std::vector<size_t> selected_transfer_to_daemon_resp; + COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request rct_req = AUTO_VAL_INIT(rct_req); + COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response rct_daemon_resp = AUTO_VAL_INIT(rct_daemon_resp); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request daemon_req = AUTO_VAL_INIT(daemon_req); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + if(fake_outputs_count) + { + rct_req.outs_count = 0; + size_t n_rct_inputs = 0; + BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + { + THROW_WALLET_EXCEPTION_IF(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, + "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + + " is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size())); + if (it->is_rct()) + ++n_rct_inputs; + else + daemon_req.amounts.push_back(it->amount()); + selected_transfer_to_daemon_resp.push_back(daemon_req.amounts.size() - 1); + } + size_t n_amounts_requested = daemon_req.amounts.size(); + + rct_req.outs_count = n_rct_inputs * (fake_outputs_count + 1); + if (rct_req.outs_count > 0) + { + LOG_PRINT_L2("We need RCT fake outs for " << n_rct_inputs << " inputs"); + rct_req.outs_count = n_rct_inputs * (fake_outputs_count + 1); // add one to make possible (if need) to skip real output key + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_rctouts.bin", rct_req, rct_daemon_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_rctouts.bin"); + THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_rctouts.bin"); + THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, rct_daemon_resp.status); + THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.outs.size() != n_rct_inputs * (fake_outputs_count + 1), error::wallet_internal_error, + "daemon returned wrong response for getrandom_rctouts.bin, wrong amounts count = " + + std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(n_rct_inputs * (fake_outputs_count + 1))); + } + if (!daemon_req.amounts.empty()) + { + LOG_PRINT_L2("We need non-RCT fake outs for " << daemon_req.amounts.size() << " inputs"); + daemon_req.outs_count = fake_outputs_count + 1; // add one to make possible (if need) to skip real output key + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", daemon_req, daemon_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); + THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); + THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); + THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != n_amounts_requested, error::wallet_internal_error, + "daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " + + std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(n_amounts_requested)); + + std::unordered_map<uint64_t, uint64_t> scanty_outs; + BOOST_FOREACH(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs, daemon_resp.outs) + { + if (amount_outs.outs.size() < fake_outputs_count) + { + scanty_outs[amount_outs.amount] = amount_outs.outs.size(); + } + } + THROW_WALLET_EXCEPTION_IF(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + } + } + + //prepare inputs + size_t i = 0; + std::vector<cryptonote::tx_source_entry> sources; + rct_daemon_resp.outs.sort([](const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& a, const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& b){return a.global_amount_index < b.global_amount_index;}); + std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>::const_iterator rctit = rct_daemon_resp.outs.begin(); + BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + { + sources.resize(sources.size()+1); + cryptonote::tx_source_entry& src = sources.back(); + transfer_details& td = *it; + src.amount = td.amount(); + //paste mixin transaction + if(it->is_rct()) + { + while (src.outputs.size() < fake_outputs_count) + { + THROW_WALLET_EXCEPTION_IF(rctit == rct_daemon_resp.outs.end(), error::wallet_internal_error, "Out of rct inputs"); + // check if we have the daemon supplied output in our real ones + bool found = false; + BOOST_FOREACH(transfer_container::iterator checkit, selected_transfers) + { + if (checkit->m_global_output_index == rctit->global_amount_index && checkit->m_tx.vout[checkit->m_internal_output_index].amount == rctit->amount) + { + found = true; + break; + } + } + if(found) + { + ++rctit; + continue; + } + tx_output_entry oe; + oe.first = rctit->global_amount_index; + oe.second.dest = rct::pk2rct(rctit->out_key); + oe.second.mask = rctit->commitment; + ++rctit; + src.outputs.push_back(oe); + } + } + else + { + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + typedef cryptonote::tx_source_entry::output_entry tx_output_entry; + + size_t idx = selected_transfer_to_daemon_resp[i]; + THROW_WALLET_EXCEPTION_IF(daemon_req.amounts.size() != daemon_resp.outs.size(), error::wallet_internal_error, "Bad amounts/out size"); + THROW_WALLET_EXCEPTION_IF(idx >= daemon_resp.outs.size(), error::wallet_internal_error, "Bad mapping to daemon_resp.outs"); + THROW_WALLET_EXCEPTION_IF(td.amount() != daemon_req.amounts[idx], error::wallet_internal_error, "Bad mapping to daemon_resp.outs/amounts"); + daemon_resp.outs[idx].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index;}); + BOOST_FOREACH(out_entry& daemon_oe, daemon_resp.outs[idx].outs) + { + if(td.m_global_output_index == daemon_oe.global_amount_index) + continue; + tx_output_entry oe; + oe.first = daemon_oe.global_amount_index; + oe.second.dest = rct::pk2rct(daemon_oe.out_key); + oe.second.mask = rct::zeroCommit(td.amount()); + src.outputs.push_back(oe); + if(src.outputs.size() >= fake_outputs_count) + break; + } + } + + //paste real transaction to the random index + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + return a.first >= td.m_global_output_index; + }); + //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; +// TODO: same index (for now) + tx_output_entry real_oe; + real_oe.first = td.m_global_output_index; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); + auto interted_it = src.outputs.insert(it_to_insert, real_oe); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + src.mask = td.m_mask; + detail::print_source_entry(src); + ++i; + } + + cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); + if (needed_money < found_money) + { + change_dts.addr = m_account.get_keys().m_account_address; + change_dts.amount = found_money - needed_money; + } + + dsts.push_back(change_dts); + + crypto::secret_key tx_key; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, dsts, extra, tx, unlock_time, tx_key, true); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet); + THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); + + std::string key_images; + bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); + key_images += boost::to_string(in.k_image) + " "; + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); + + ptx.key_images = key_images; + ptx.fee = fee; + ptx.dust = 0; + ptx.dust_added_to_fee = false; + ptx.tx = tx; + ptx.change_dts = change_dts; + ptx.selected_transfers = selected_transfers; + ptx.tx_key = tx_key; + ptx.dests = dsts; +} + // Another implementation of transaction creation that is hopefully better // While there is anything left to pay, it goes through random outputs and tries // to fill the next destination/amount. If it fully fills it, it will use the @@ -2686,7 +2993,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && is_transfer_unlocked(td)) + if (!td.m_spent && !td.is_rct() && is_transfer_unlocked(td)) { if (is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); @@ -2862,10 +3169,46 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp return ptx_vector; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) +static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) +{ + size_t size = 0; + + // tx prefix + + // first few bytes + size += 1 + 6; + + // vin + size += n_inputs * (1+6+(mixin+1)*2+32); + + // vout + size += n_outputs * (6+32); + + // extra + size += 40; + + // rct signatures + + // rangeSigs + size += (2*64*32+32+64*32) * n_outputs; + // MG + size += 32 * (mixin+1) * n_inputs + 32 + 32 * n_inputs; + // mixRing + size += 2 * 32 * (mixin+1) * n_inputs; + // ecdhInfo + size += 3 * 32 * n_outputs; + // outPk + size += 2 * 32 * n_outputs; + + LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " at mixin " << mixin << " and " << n_outputs << ": " << size); + return size; +} + +std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; + uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { std::list<transfer_container::iterator> selected_transfers; @@ -2873,19 +3216,243 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono cryptonote::transaction tx; pending_tx ptx; size_t bytes; + + void add(const account_public_address &addr, uint64_t amount) { + std::vector<cryptonote::tx_destination_entry>::iterator i; + i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); }); + if (i == dsts.end()) + dsts.push_back(tx_destination_entry(amount,addr)); + else + i->amount += amount; + } }; std::vector<TX> txes; + bool adding_fee; // true if new outputs go towards fee, rather than destinations uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); fee_multiplier = sanitize_fee_multiplier(fee_multiplier); + // throw if attempting a transaction with no destinations + THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + + // calculate total amount being sent to all destinations + // throw if total amount overflows uint64_t + needed_money = 0; + BOOST_FOREACH(auto& dt, dsts) + { + THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); + needed_money += dt.amount; + LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); + THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, 0, m_testnet); + } + + // throw if attempting a transaction with no money + THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); + // gather all our dust and non dust outputs for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; if (!td.m_spent && is_transfer_unlocked(td)) { + if (is_valid_decomposed_amount(td.amount()) || td.is_rct()) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + } + } + LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); + + // start with an empty tx + txes.push_back(TX()); + accumulated_fee = 0; + accumulated_outputs = 0; + accumulated_change = 0; + adding_fee = false; + needed_fee = 0; + + // while we have something to send + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { + TX &tx = txes.back(); + + // if we need to spend money and don't have any left, we fail + if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { + LOG_PRINT_L2("No more outputs to choose from"); + THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + } + + // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) + // This could be more clever, but maybe at the cost of making probabilistic inferences easier + size_t idx = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices); + + const transfer_details &td = m_transfers[idx]; + LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); + + // add this output to the list to spend + tx.selected_transfers.push_back(m_transfers.begin() + idx); + uint64_t available_amount = td.amount(); + accumulated_outputs += available_amount; + + if (adding_fee) + { + LOG_PRINT_L2("We need more fee, adding it to fee"); + available_for_fee += available_amount; + } + else + { + while (!dsts.empty() && dsts[0].amount <= available_amount) + { + // we can fully pay that destination + LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << + " for " << print_money(dsts[0].amount)); + tx.add(dsts[0].addr, dsts[0].amount); + available_amount -= dsts[0].amount; + dsts[0].amount = 0; + pop_index(dsts, 0); + } + + if (available_amount > 0 && !dsts.empty()) { + // we can partially fill that destination + LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << + " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); + tx.add(dsts[0].addr, available_amount); + dsts[0].amount -= available_amount; + available_amount = 0; + } + } + + // 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_size_limit); + bool try_tx; + if (adding_fee) + { + /* might not actually be enough if adding this output bumps size to next kB, but we need to try */ + try_tx = available_for_fee >= needed_fee; + } + else + { + size_t estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); + } + + if (try_tx) { + cryptonote::transaction test_tx; + pending_tx test_ptx; + + needed_fee = 0; + + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << + tx.selected_transfers.size() << " outputs"); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + auto txBlob = t_serializable_object_to_blob(test_ptx.tx); + needed_fee = calculate_fee(txBlob, fee_multiplier); + available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); + LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + print_money(needed_fee) << " needed)"); + + if (needed_fee > available_for_fee && dsts[0].amount > 0) + { + // we don't have enough for the fee, but we've only partially paid the current address, + // so we can take the fee from the paid amount, since we'll have to make another tx anyway + std::vector<cryptonote::tx_destination_entry>::iterator i; + i = std::find_if(tx.dsts.begin(), tx.dsts.end(), + [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &dsts[0].addr, sizeof(dsts[0].addr)); }); + THROW_WALLET_EXCEPTION_IF(i == tx.dsts.end(), error::wallet_internal_error, "paid address not found in outputs"); + if (i->amount > needed_fee) + { + uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee; + LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " << + print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accomodate " << + print_money(needed_fee) << " fee"); + dsts[0].amount += i->amount - new_paid_amount; + i->amount = new_paid_amount; + test_ptx.fee = needed_fee; + available_for_fee = needed_fee; + } + } + + if (needed_fee > available_for_fee) + { + LOG_PRINT_L2("We could not make a tx, switching to fee accumulation"); + + adding_fee = true; + } + else + { + LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + txBlob = t_serializable_object_to_blob(test_ptx.tx); + LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + " fee and " << print_money(test_ptx.change_dts.amount) << " change"); + + tx.tx = test_tx; + tx.ptx = test_ptx; + tx.bytes = txBlob.size(); + accumulated_fee += test_ptx.fee; + accumulated_change += test_ptx.change_dts.amount; + adding_fee = false; + if (!dsts.empty()) + { + LOG_PRINT_L2("We have more to pay, starting another tx"); + txes.push_back(TX()); + } + } + } + } + + if (adding_fee) + { + LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); + THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + } + + LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << + " total fee, " << print_money(accumulated_change) << " total change"); + + std::vector<wallet2::pending_tx> ptx_vector; + for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i) + { + TX &tx = *i; + uint64_t tx_money = 0; + for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) + tx_money += (*mi)->amount(); + LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << + ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + " outputs to " << tx.dsts.size() << " destination(s), including " << + print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); + ptx_vector.push_back(tx.ptx); + } + + // if we made it this far, we're OK to actually send the transactions + return ptx_vector; +} + +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) +{ + std::vector<size_t> unused_transfers_indices; + std::vector<size_t> unused_dust_indices; + uint64_t accumulated_fee, accumulated_outputs, accumulated_change; + struct TX { + std::list<transfer_container::iterator> selected_transfers; + std::vector<cryptonote::tx_destination_entry> dsts; + cryptonote::transaction tx; + pending_tx ptx; + size_t bytes; + }; + std::vector<TX> txes; + uint64_t needed_fee, available_for_fee = 0; + uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + + // gather all our dust and non dust outputs + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && !td.is_rct() && is_transfer_unlocked(td)) + { if (is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); else @@ -3065,7 +3632,8 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, }); tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); @@ -3213,6 +3781,8 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co } return select_available_outputs([mixable, atleast](const transfer_details &td) { + if (td.is_rct()) + return false; const uint64_t amount = td.amount(); if (atleast) { if (mixable.find(amount) != mixable.end()) @@ -3521,7 +4091,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n) { transfer_details &td = m_transfers[n]; - uint64_t amount = td.m_tx.vout[td.m_internal_output_index].amount; + uint64_t amount = td.amount(); td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT; if (td.m_spent) spent += amount; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d0c514a6d..125f8edb5 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -31,6 +31,7 @@ #pragma once #include <memory> +#include <boost/archive/binary_iarchive.hpp> #include <boost/serialization/list.hpp> #include <boost/serialization/vector.hpp> #include <atomic> @@ -46,6 +47,8 @@ #include "common/unordered_containers_boost_serialization.h" #include "crypto/chacha8.h" #include "crypto/hash.h" +#include "ringct/rctTypes.h" +#include "ringct/rctOps.h" #include "wallet_errors.h" @@ -58,8 +61,8 @@ namespace tools { public: virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) {} - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) {} + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) {} + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {} virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) {} virtual ~i_wallet2_callback() {} }; @@ -101,8 +104,11 @@ namespace tools uint64_t m_global_output_index; bool m_spent; crypto::key_image m_key_image; //TODO: key_image stored twice :( + rct::key m_mask; + uint64_t m_amount; - uint64_t amount() const { return m_tx.vout[m_internal_output_index].amount; } + bool is_rct() const { return m_tx.vout[m_internal_output_index].amount == 0; } + uint64_t amount() const { return m_amount; } }; struct payment_details @@ -117,6 +123,8 @@ namespace tools struct unconfirmed_transfer_details { cryptonote::transaction m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; uint64_t m_change; time_t m_sent_time; std::vector<cryptonote::tx_destination_entry> m_dests; @@ -137,7 +145,7 @@ namespace tools confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(cryptonote::null_hash) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_amount_out(get_outs_money_amount(utd.m_tx)), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) { get_inputs_money_amount(utd.m_tx, m_amount_in); } + m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) {} }; typedef std::vector<transfer_details> transfer_container; @@ -289,11 +297,14 @@ namespace tools template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_rct(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(bool *same_version = NULL); @@ -308,6 +319,7 @@ namespace tools uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); void rescan_blockchain(bool refresh = true); + bool is_transfer_unlocked(const transfer_details& td) const; template <class t_archive> inline void serialize(t_archive &a, const unsigned int ver) { @@ -418,7 +430,6 @@ namespace tools void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const; - bool is_transfer_unlocked(const transfer_details& td) const; bool clear(); void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<crypto::hash> &hashes); @@ -429,12 +440,12 @@ namespace tools bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); - void add_unconfirmed_tx(const cryptonote::transaction& tx, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount); + void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; + void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); @@ -479,8 +490,9 @@ namespace tools }; } BOOST_CLASS_VERSION(tools::wallet2, 13) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 1) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 3) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 4) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) namespace boost @@ -488,6 +500,17 @@ namespace boost namespace serialization { template <class Archive> + inline void initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x) + { + } + template<> + inline void initialize_transfer_details(boost::archive::binary_iarchive &a, tools::wallet2::transfer_details &x) + { + x.m_mask = rct::identity(); + x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; + } + + template <class Archive> inline void serialize(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { a & x.m_block_height; @@ -496,6 +519,14 @@ namespace boost a & x.m_tx; a & x.m_spent; a & x.m_key_image; + if (ver < 1) + { + // ensure mask and amount are set + initialize_transfer_details(a, x); + return; + } + a & x.m_mask; + a & x.m_amount; } template <class Archive> @@ -514,6 +545,10 @@ namespace boost if (ver < 3) return; a & x.m_timestamp; + if (ver < 4) + return; + a & x.m_amount_in; + a & x.m_amount_out; } template <class Archive> @@ -698,7 +733,8 @@ namespace tools continue; tx_output_entry oe; oe.first = daemon_oe.global_amount_index; - oe.second = daemon_oe.out_key; + oe.second.dest = rct::pk2rct(daemon_oe.out_key); + oe.second.mask = rct::identity(); src.outputs.push_back(oe); if(src.outputs.size() >= fake_outputs_count) break; @@ -713,7 +749,8 @@ namespace tools //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::identity(); auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index debd7056a..9bcc26077 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -961,10 +961,8 @@ namespace tools entry.payment_id = entry.payment_id.substr(0,16); entry.height = 0; entry.timestamp = pd.m_timestamp; - uint64_t amount = 0; - cryptonote::get_inputs_money_amount(pd.m_tx, amount); - entry.fee = amount - get_outs_money_amount(pd.m_tx); - entry.amount = amount - pd.m_change - entry.fee; + entry.fee = pd.m_amount_in - pd.m_amount_out; + entry.amount = pd.m_amount_in - pd.m_change - entry.fee; entry.note = m_wallet.get_tx_note(i->first); } } |