diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index dd54f301f..5154f66bd 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -207,6 +207,17 @@ typedef struct yield_tx_info { crypto::public_key return_pubkey; } yield_tx_info; +typedef struct yield_tx_info_carrot { + uint8_t version; + uint64_t block_height; + crypto::hash tx_hash; + uint64_t locked_coins; + crypto::public_key return_address; + crypto::public_key return_pubkey; + carrot::view_tag_t return_view_tag; + carrot::encrypted_janus_anchor_t return_anchor_enc; +} yield_tx_info_carrot; + #define DBF_SAFE 1 #define DBF_FAST 2 #define DBF_FASTEST 4 @@ -1924,6 +1935,8 @@ public: virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi) const = 0; virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container) const = 0; + virtual int get_carrot_yield_tx_info(const uint64_t height, std::vector& yti_container) const = 0; + /** * @brief set whether or not to automatically remove logs diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index b76cf069b..17f29c512 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -298,6 +298,7 @@ const char* const LMDB_YIELD_TXS = "yield_txs"; const char* const LMDB_YIELD_BLOCKS = "yield_blocks"; const char* const LMDB_AUDIT_TXS = "audit_txs"; const char* const LMDB_AUDIT_BLOCKS = "audit_blocks"; +const char* const LMDB_CARROT_YIELD_TXS = "carrot_yield_txs"; const char zerokey[8] = {0}; const MDB_val zerokval = { sizeof(zerokey), (void *)zerokey }; @@ -947,6 +948,43 @@ int BlockchainLMDB::get_yield_tx_info(const uint64_t height, std::vector& yti_container) const { + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + // Clear the container + yti_container.clear(); + + // Query for the (presumably matured) YIELD_TX_INFO information + TXN_PREFIX_RDONLY(); + RCURSOR(carrot_yield_txs); + + MDB_val v; + MDB_val_set(k, height); + MDB_cursor_op op = MDB_SET; + while (1) + { + int ret = mdb_cursor_get(m_cur_carrot_yield_txs, &k, &v, op); + op = MDB_NEXT_DUP; + if (ret == MDB_NOTFOUND) + break; + if (ret) + throw0(DB_ERROR(lmdb_error("Failed to enumerate yield TX info: ", ret).c_str())); + + // Get the data + yield_tx_info_carrot *p = (yield_tx_info_carrot*)v.mv_data; + // Push result back into the container + yti_container.emplace_back(*p); + // Update the height retrospectively (because the DB stores the count of elements there to handle duplicates, because it's rubbish) + yti_container.back().block_height = height; + } + + TXN_POSTFIX_RDONLY(); + + // Return success to caller + return 0; +} + void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t long_term_block_weight, const difficulty_type& cumulative_difficulty, const uint64_t& coins_generated, uint64_t num_rct_outs, oracle::asset_type_counts& cum_rct_by_asset_type, const crypto::hash& blk_hash, uint64_t slippage_total, uint64_t yield_total, uint64_t audit_total, const cryptonote::network_type nettype, cryptonote::yield_block_info& ybi, cryptonote::audit_block_info& abi) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); @@ -1215,6 +1253,7 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons CURSOR(circ_supply_tally) CURSOR(yield_txs) CURSOR(audit_txs) + CURSOR(carrot_yield_txs) MDB_val_set(val_tx_id, tx_id); MDB_val_set(val_h, tx_hash); @@ -1321,7 +1360,12 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons write_circulating_supply_data(m_cur_circ_supply_tally, burn_idx, final_burn_tally); } - if (tx.type == cryptonote::transaction_type::BURN || tx.type == cryptonote::transaction_type::CONVERT || tx.type == cryptonote::transaction_type::STAKE || tx.type == cryptonote::transaction_type::AUDIT || tx.type == cryptonote::transaction_type::TRANSFER) { + if (tx.type == cryptonote::transaction_type::BURN || + tx.type == cryptonote::transaction_type::CONVERT || + tx.type == cryptonote::transaction_type::STAKE || + tx.type == cryptonote::transaction_type::AUDIT || + tx.type == cryptonote::transaction_type::TRANSFER) + { // Get the current tally value for the source currency type MDB_val_copy source_idx(cryptonote::asset_id_from_type(tx.source_asset_type)); @@ -1390,10 +1434,44 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons write_circulating_supply_data(m_cur_circ_supply_tally, burn_idx, final_burn_tally); } } - - // Is there yield_tx data to add? - if (tx.type == cryptonote::transaction_type::STAKE) { + // Is there yield_tx data to add? + if (tx.version >= TRANSACTION_VERSION_CARROT && tx.type == cryptonote::transaction_type::STAKE) + { + // Create the object we are going to write to the database + yield_tx_info_carrot yield_data; + yield_data.block_height = m_height; + yield_data.tx_hash = tx_hash; + yield_data.return_pubkey = tx.protocol_tx_data.return_pubkey; + yield_data.return_address = tx.protocol_tx_data.return_address; + yield_data.return_view_tag = tx.protocol_tx_data.return_view_tag; + yield_data.return_anchor_enc = tx.protocol_tx_data.return_anchor_enc; + yield_data.locked_coins = tx.amount_burnt; + + // Because LMDB is shockingly bad at handling duplicates, we have resorted to using a counter of elements + // in the first element of the struct. + MDB_val data; + MDB_val_set(val_height, m_height); + result = mdb_cursor_get(m_cur_carrot_yield_txs, &val_height, &data, MDB_SET); + if (!result) + { + mdb_size_t num_elems = 0; + result = mdb_cursor_count(m_cur_carrot_yield_txs, &num_elems); + if (result) + throw0(DB_ERROR(std::string("Failed to get number of yield TXs for height: ").append(mdb_strerror(result)).c_str())); + yield_data.block_height = num_elems; + } + else if (result != MDB_NOTFOUND) + throw0(DB_ERROR(lmdb_error("Failed to get output amount in db transaction: ", result).c_str())); + else + yield_data.block_height = 0; + + // Now we know how many there are, write out the data to the DB + MDB_val_set(val_yield_tx_data, yield_data); + result = mdb_cursor_put(m_cur_carrot_yield_txs, &val_height, &val_yield_tx_data, MDB_APPENDDUP); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to add tx carrot yield data to db transaction: ", result).c_str())); + } else if (tx.type == cryptonote::transaction_type::STAKE) { // Create the object we are going to write to the database yield_tx_info yield_data; yield_data.block_height = m_height; @@ -1444,7 +1522,7 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons if (result) throw0(DB_ERROR( lmdb_error("Failed to add tx yield data to db transaction: ", result).c_str() )); } - + // Is there audit_tx data to add? if (tx.type == cryptonote::transaction_type::AUDIT) { @@ -1498,7 +1576,7 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons if (result) throw0(DB_ERROR( lmdb_error("Failed to add tx audit data to db transaction: ", result).c_str() )); } - + return tx_id; } @@ -1522,6 +1600,7 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const CURSOR(circ_supply_tally) CURSOR(yield_txs) CURSOR(audit_txs) + CURSOR(carrot_yield_txs) MDB_val_set(val_h, tx_hash); @@ -1720,7 +1799,28 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const } // Is there yield_tx data to remove? - if (tx.type == cryptonote::transaction_type::STAKE) { + if (tx.version >= TRANSACTION_VERSION_CARROT && tx.type == cryptonote::transaction_type::STAKE) { + // Remove any yield_tx data for this transaction + MDB_val_set(val_height, m_height); + MDB_val v; + MDB_cursor_op op = MDB_SET; + while (1) { + result = mdb_cursor_get(m_cur_carrot_yield_txs, &val_height, &v, op); + if (result == MDB_NOTFOUND) { + throw1(DB_ERROR("Failed to locate carrot yield tx for removal from db transaction")); + } else if (result) { + throw1(DB_ERROR(lmdb_error("Failed to locate carrot yield_tx data for removal: ", result).c_str())); + } + op = MDB_NEXT_DUP; + const yield_tx_info_carrot yti = *(const yield_tx_info_carrot*)v.mv_data; + if (yti.tx_hash == tx_hash) { + result = mdb_cursor_del(m_cur_carrot_yield_txs, 0); + if (result) + throw1(DB_ERROR(lmdb_error("Failed to add removal of carrot yield_tx data to db transaction: ", result).c_str())); + break; + } + } + } else if (tx.type == cryptonote::transaction_type::STAKE) { // Remove any yield_tx data for this transaction MDB_val_set(val_height, m_height); MDB_val v; @@ -2284,10 +2384,12 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) lmdb_db_open(txn, LMDB_YIELD_TXS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_yield_txs, "Failed to open db handle for m_yield_txs"); lmdb_db_open(txn, LMDB_YIELD_BLOCKS, MDB_INTEGERKEY | MDB_CREATE, m_yield_blocks, "Failed to open db handle for m_yield_blocks"); - + lmdb_db_open(txn, LMDB_AUDIT_TXS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_audit_txs, "Failed to open db handle for m_audit_txs"); lmdb_db_open(txn, LMDB_AUDIT_BLOCKS, MDB_INTEGERKEY | MDB_CREATE, m_audit_blocks, "Failed to open db handle for m_audit_blocks"); + lmdb_db_open(txn, LMDB_CARROT_YIELD_TXS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_carrot_yield_txs, "Failed to open db handle for m_carrot_yield_txs"); + mdb_set_dupsort(txn, m_spent_keys, compare_hash32); mdb_set_dupsort(txn, m_block_heights, compare_hash32); mdb_set_dupsort(txn, m_tx_indices, compare_hash32); @@ -2312,6 +2414,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) mdb_set_dupsort(txn, m_yield_txs, compare_uint64); mdb_set_dupsort(txn, m_audit_txs, compare_uint64); + mdb_set_dupsort(txn, m_carrot_yield_txs, compare_uint64); if (!(mdb_flags & MDB_RDONLY)) { @@ -2497,6 +2600,8 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to drop m_audit_txs: ", result).c_str())); if (auto result = mdb_drop(txn, m_audit_blocks, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_audit_blocks: ", result).c_str())); + if (auto result = mdb_drop(txn, m_carrot_yield_txs, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_carrot_yield_txs: ", result).c_str())); // init with current version MDB_val_str(k, "version"); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 451c2fb60..df6910668 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -80,6 +80,7 @@ typedef struct mdb_txn_cursors MDB_cursor *m_txc_yield_blocks; MDB_cursor *m_txc_audit_txs; MDB_cursor *m_txc_audit_blocks; + MDB_cursor *m_txc_carrot_yield_txs; } mdb_txn_cursors; @@ -108,6 +109,7 @@ typedef struct mdb_txn_cursors #define m_cur_yield_blocks m_cursors->m_txc_yield_blocks #define m_cur_audit_txs m_cursors->m_txc_audit_txs #define m_cur_audit_blocks m_cursors->m_txc_audit_blocks +#define m_cur_carrot_yield_txs m_cursors->m_txc_carrot_yield_txs typedef struct mdb_rflags { @@ -137,6 +139,7 @@ typedef struct mdb_rflags bool m_rf_yield_blocks; bool m_rf_audit_txs; bool m_rf_audit_blocks; + bool m_rf_carrot_yield_txs; } mdb_rflags; typedef struct mdb_threadinfo @@ -474,6 +477,7 @@ private: virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi) const; virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container) const; + virtual int get_carrot_yield_tx_info(const uint64_t height, std::vector& yti_container) const; private: MDB_env* m_env; @@ -511,10 +515,12 @@ private: MDB_dbi m_yield_txs; MDB_dbi m_yield_blocks; - + MDB_dbi m_audit_txs; MDB_dbi m_audit_blocks; + MDB_dbi m_carrot_yield_txs; + mutable uint64_t m_cum_size; // used in batch size estimation mutable unsigned int m_cum_count; std::string m_folder; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 429e39168..d19d7ab78 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -145,6 +145,7 @@ public: virtual int get_yield_block_info(const uint64_t height, yield_block_info& ybi) const override { return 0; } virtual int get_yield_tx_info(const uint64_t height, std::vector& yti_container) const override { return 0; } + virtual int get_carrot_yield_tx_info(const uint64_t height, std::vector& yti_container) const override { return 0; } virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const override { return ""; } virtual bool for_all_txpool_txes(std::function, bool include_blob = false, relay_category category = relay_category::broadcasted) const override { return false; } diff --git a/src/carrot_core/device.h b/src/carrot_core/device.h index 04c146fea..ce6d657f5 100644 --- a/src/carrot_core/device.h +++ b/src/carrot_core/device.h @@ -134,6 +134,10 @@ struct view_incoming_key_device const crypto::public_key &onetime_address, janus_anchor_t &anchor_special_out) const = 0; + virtual void make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const = 0; + virtual ~view_incoming_key_device() = default; }; diff --git a/src/carrot_core/device_ram_borrowed.cpp b/src/carrot_core/device_ram_borrowed.cpp index 569ad7d5f..bcae45476 100644 --- a/src/carrot_core/device_ram_borrowed.cpp +++ b/src/carrot_core/device_ram_borrowed.cpp @@ -55,6 +55,13 @@ bool view_incoming_key_ram_borrowed_device::view_key_scalar_mult_x25519(const mx return make_carrot_uncontextualized_shared_key_receiver(m_k_view_incoming, D, kvD); } //------------------------------------------------------------------------------------------------------------------- +void view_incoming_key_ram_borrowed_device::make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const +{ + make_sparc_return_privkey(to_bytes(m_k_view_incoming), input_context, onetime_address, return_privkey_out); +} +//------------------------------------------------------------------------------------------------------------------- void view_incoming_key_ram_borrowed_device::make_janus_anchor_special( const mx25519_pubkey &enote_ephemeral_pubkey, const input_context_t &input_context, diff --git a/src/carrot_core/device_ram_borrowed.h b/src/carrot_core/device_ram_borrowed.h index 21fe3cae4..26b35f559 100644 --- a/src/carrot_core/device_ram_borrowed.h +++ b/src/carrot_core/device_ram_borrowed.h @@ -60,6 +60,10 @@ public: const crypto::public_key &onetime_address, janus_anchor_t &anchor_special_out) const override; + void make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const override; + protected: const crypto::secret_key &m_k_view_incoming; }; diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp index 0a6574c23..cea9e5708 100644 --- a/src/carrot_core/output_set_finalization.cpp +++ b/src/carrot_core/output_set_finalization.cpp @@ -159,6 +159,7 @@ void get_output_enote_proposals(const std::vector &norm const view_incoming_key_device *k_view_dev, const crypto::key_image &tx_first_key_image, std::vector &output_enote_proposals_out, + RCTOutputEnoteProposal &return_enote_out, encrypted_payment_id_t &encrypted_payment_id_out, cryptonote::transaction_type tx_type, size_t &change_index_out, @@ -173,7 +174,11 @@ void get_output_enote_proposals(const std::vector &norm // assert payment proposals numbers const size_t num_selfsend_proposals = selfsend_payment_proposals.size(); const size_t num_proposals = normal_payment_proposals.size() + num_selfsend_proposals; - CARROT_CHECK_AND_THROW(num_proposals >= CARROT_MIN_TX_OUTPUTS, too_few_outputs, "too few payment proposals"); + if (tx_type == cryptonote::transaction_type::STAKE || tx_type == cryptonote::transaction_type::BURN) { + CARROT_CHECK_AND_THROW(num_proposals == 1, too_few_outputs, "tx doesn't have correct number of proposals"); + } else { + CARROT_CHECK_AND_THROW(num_proposals >= CARROT_MIN_TX_OUTPUTS, too_few_outputs, "too few payment proposals"); + } CARROT_CHECK_AND_THROW(num_proposals <= CARROT_MAX_TX_OUTPUTS, too_many_outputs, "too many payment proposals"); CARROT_CHECK_AND_THROW(num_selfsend_proposals, too_few_outputs, "no selfsend payment proposal"); @@ -281,7 +286,9 @@ void get_output_enote_proposals(const std::vector &norm get_output_proposal_special_v1(selfsend_payment_proposal, *k_view_dev, tx_first_key_image, + tx_type, other_enote_ephemeral_pubkey, + return_enote_out, output_entry.first); } else // neither k_v nor s_vb device passed diff --git a/src/carrot_core/output_set_finalization.h b/src/carrot_core/output_set_finalization.h index 125e9f570..889b601e4 100644 --- a/src/carrot_core/output_set_finalization.h +++ b/src/carrot_core/output_set_finalization.h @@ -109,6 +109,7 @@ void get_output_enote_proposals(const std::vector &norm const view_incoming_key_device *k_view_dev, const crypto::key_image &tx_first_key_image, std::vector &output_enote_proposals_out, + RCTOutputEnoteProposal &return_enote_out, encrypted_payment_id_t &encrypted_payment_id_out, cryptonote::transaction_type tx_type, size_t &change_index_out, diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 0432ee884..00cfeffc5 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -357,7 +357,9 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, const crypto::key_image &tx_first_key_image, + const cryptonote::transaction_type tx_type, const std::optional &other_enote_ephemeral_pubkey, + RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out) { // 1. sanity checks @@ -424,6 +426,37 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo output_enote_out.enote.asset_type = "SAL1"; output_enote_out.enote.return_enc = crypto::rand(); output_enote_out.amount = proposal.amount; + + // 10. construct the stake return enote + if (tx_type == cryptonote::transaction_type::STAKE) { + // make k_return + crypto::secret_key k_return; + k_view_dev.make_internal_return_privkey(input_context, output_enote_out.enote.onetime_address, k_return); + + // compute K_return = k_return * G + crypto::public_key return_pub; + crypto::secret_key_to_public_key(k_return, return_pub); + + // Make a destination address for the return + CarrotDestinationV1 return_destination; + make_carrot_main_address_v1(output_enote_out.enote.onetime_address, return_pub, return_destination); + + // Create the return proposal, using the return address and the amount + const CarrotPaymentProposalV1 proposal_return = CarrotPaymentProposalV1{ + .destination = return_destination, + .amount = 0, + .randomness = gen_janus_anchor() + }; + + encrypted_payment_id_t encrypted_payment_id_return; + get_output_proposal_return_v1( + proposal_return, + tx_first_key_image, + nullptr, // s_view_balance_dev + return_enote_out, + encrypted_payment_id_return + ); + } } //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 4623bad7d..2d2f4a1f2 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -160,7 +160,9 @@ void get_output_proposal_return_v1(const CarrotPaymentProposalV1 &proposal, void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, const crypto::key_image &tx_first_key_image, + const cryptonote::transaction_type tx_type, const std::optional &other_enote_ephemeral_pubkey, + RCTOutputEnoteProposal &return_enote_out, RCTOutputEnoteProposal &output_enote_out); /** * brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) diff --git a/src/carrot_impl/account.cpp b/src/carrot_impl/account.cpp index 16964f081..69e48a38c 100644 --- a/src/carrot_impl/account.cpp +++ b/src/carrot_impl/account.cpp @@ -110,7 +110,7 @@ CarrotDestinationV1 carrot_and_legacy_account::subaddress(const subaddress_index return addr; } //---------------------------------------------------------------------------------------------------------------------- -std::unordered_map carrot_and_legacy_account::get_subaddress_map_cn() const +const std::unordered_map carrot_and_legacy_account::get_subaddress_map_cn() const { std::unordered_map res; for (const auto &p : subaddress_map) @@ -120,10 +120,15 @@ std::unordered_map carrot_and_ return res; } //---------------------------------------------------------------------------------------------------------------------- -std::unordered_map& carrot_and_legacy_account::get_subaddress_map_ref() { +const std::unordered_map& carrot_and_legacy_account::get_subaddress_map_ref() const { return subaddress_map; } //---------------------------------------------------------------------------------------------------------------------- +const std::unordered_map& +carrot_and_legacy_account::get_return_output_map_ref() const { + return return_output_map; +} +//---------------------------------------------------------------------------------------------------------------------- void carrot_and_legacy_account::opening_for_subaddress(const subaddress_index_extended &subaddress_index, crypto::secret_key &address_privkey_g_out, crypto::secret_key &address_privkey_t_out, @@ -345,7 +350,12 @@ void carrot_and_legacy_account::insert_subaddresses(const std::unordered_map& roi_map) +{ + for (const auto &p : roi_map) + return_output_map.insert({p.first, p.second}); +} //---------------------------------------------------------------------------------------------------------------------- AddressDeriveType carrot_and_legacy_account::resolve_derive_type(const AddressDeriveType derive_type) const { diff --git a/src/carrot_impl/account.h b/src/carrot_impl/account.h index 82bd5f4cc..01e280303 100644 --- a/src/carrot_impl/account.h +++ b/src/carrot_impl/account.h @@ -43,6 +43,28 @@ static constexpr std::uint32_t MAX_SUBADDRESS_MINOR_INDEX = 20; namespace carrot { + + struct return_output_info_t { + input_context_t input_context; + crypto::public_key address_spend_pubkey; + crypto::key_image key_image; + crypto::secret_key x; + crypto::secret_key y; + + return_output_info_t( + const input_context_t &input_context, + const crypto::public_key &address_spend_pubkey, + const crypto::key_image &key_image, + const crypto::secret_key &x, + const crypto::secret_key &y): + input_context(input_context), + address_spend_pubkey(address_spend_pubkey), + key_image(key_image), + x(x), + y(y) {} + }; + + class carrot_and_legacy_account : public cryptonote::account_base { public: @@ -68,9 +90,10 @@ namespace carrot CarrotDestinationV1 subaddress(const subaddress_index_extended &subaddress_index) const; - std::unordered_map get_subaddress_map_cn() const; - std::unordered_map& get_subaddress_map_ref(); - + const std::unordered_map get_subaddress_map_cn() const; + const std::unordered_map& get_subaddress_map_ref() const; + const std::unordered_map& get_return_output_map_ref() const; + // brief: opening_for_subaddress - return (k^g_a, k^t_a) for j s.t. K^j_s = (k^g_a * G + k^t_a * T) void opening_for_subaddress(const subaddress_index_extended &subaddress_index, crypto::secret_key &address_privkey_g_out, @@ -108,10 +131,15 @@ namespace carrot void set_carrot_keys(const AddressDeriveType default_derive_type = AddressDeriveType::Carrot); void insert_subaddresses(const std::unordered_map& subaddress_map); - + void insert_return_output_info( + const std::unordered_map& input_context_map + ); + AddressDeriveType resolve_derive_type(const AddressDeriveType derive_type) const; private: std::unordered_map subaddress_map; + // Kr -> return_output_info + std::unordered_map return_output_map; }; } diff --git a/src/carrot_impl/format_utils.cpp b/src/carrot_impl/format_utils.cpp index b5fe119cd..c5d342cda 100644 --- a/src/carrot_impl/format_utils.cpp +++ b/src/carrot_impl/format_utils.cpp @@ -198,7 +198,9 @@ cryptonote::transaction store_carrot_to_transaction_v1(const std::vector &sources, const rct::xmr_amount fee, const cryptonote::transaction_type tx_type, - const std::vector change_masks, + const rct::xmr_amount tx_amount_burnt, + const std::vector &change_masks, + const carrot::RCTOutputEnoteProposal &return_enote, const encrypted_payment_id_t encrypted_payment_id) { const size_t nins = key_images.size(); @@ -208,11 +210,15 @@ cryptonote::transaction store_carrot_to_transaction_v1(const std::vector &sources, const rct::xmr_amount fee, const cryptonote::transaction_type tx_type, - const std::vector change_masks, + const rct::xmr_amount tx_amount_burnt, + const std::vector &change_masks, + const RCTOutputEnoteProposal &return_enote, const encrypted_payment_id_t encrypted_payment_id); /** * brief: try_load_carrot_enote_from_transaction_v1 - load one non-coinbase Carrot enote from a cryptonote::transaction diff --git a/src/carrot_impl/tx_builder_outputs.cpp b/src/carrot_impl/tx_builder_outputs.cpp index 24f13f1cc..14c59ccc2 100644 --- a/src/carrot_impl/tx_builder_outputs.cpp +++ b/src/carrot_impl/tx_builder_outputs.cpp @@ -60,6 +60,7 @@ void get_output_enote_proposals_from_proposal_v1(const CarrotTransactionProposal // derive enote proposals size_t change_index; + RCTOutputEnoteProposal return_enote_out; std::unordered_map normal_payments_indices; get_output_enote_proposals(tx_proposal.normal_payment_proposals, selfsend_payment_proposal_cores, @@ -68,6 +69,7 @@ void get_output_enote_proposals_from_proposal_v1(const CarrotTransactionProposal k_view_dev, tx_proposal.key_images_sorted.at(0), output_enote_proposals_out, + return_enote_out, encrypted_payment_id_out, tx_proposal.tx_type, change_index, @@ -127,7 +129,9 @@ void make_pruned_transaction_from_proposal_v1(const CarrotTransactionProposalV1 tx_proposal.sources, tx_proposal.fee, cryptonote::transaction_type::TRANSFER, + 0, // tx_amount_burnt {}, // change_masks + {}, // return_enote encrypted_payment_id); // add extra payload and sort diff --git a/src/carrot_impl/tx_proposal.h b/src/carrot_impl/tx_proposal.h index 72ec2ee70..2e0325a7f 100644 --- a/src/carrot_impl/tx_proposal.h +++ b/src/carrot_impl/tx_proposal.h @@ -86,6 +86,8 @@ struct CarrotTransactionProposalV1 rct::xmr_amount fee; /// transaction type, e.g. cryptonote::transaction_type tx_type; + /// how much money tx burns + rct::xmr_amount amount_burnt; /// This field is truly "extra". It should contain only tx.extra fields that aren't present in a /// normal Carrot transaction, i.e. NOT ephemeral pubkeys nor encrypted PIDs diff --git a/src/carrot_impl/tx_proposal_utils.cpp b/src/carrot_impl/tx_proposal_utils.cpp index 884b5825e..aa9fc6f16 100644 --- a/src/carrot_impl/tx_proposal_utils.cpp +++ b/src/carrot_impl/tx_proposal_utils.cpp @@ -217,8 +217,19 @@ void make_carrot_transaction_proposal_v1(const std::vector(); + CHECK_AND_ASSERT_THROW_MES(tx_proposal_out.amount_burnt >= 0, + "make_carrot_transaction_proposal_v1: post-carved transaction burnt amount is negative: " + << tx_proposal_out.amount_burnt); + input_amount_sum -= tx_proposal_out.amount_burnt; + } // collect and sort key images tx_proposal_out.key_images_sorted.reserve(selected_inputs.size()); @@ -273,7 +284,8 @@ void make_carrot_transaction_proposal_v1_transfer( carve_fees_and_balance_func_t carve_fees_and_balance = [ &subtractable_normal_payment_proposals, - &subtractable_selfsend_payment_proposals + &subtractable_selfsend_payment_proposals, + &tx_type ] ( const boost::multiprecision::uint128_t &input_sum_amount, @@ -319,6 +331,15 @@ void make_carrot_transaction_proposal_v1_transfer( selfsend_payment_proposals.back().proposal.amount = boost::numeric_cast(implicit_change_amount); + // remove the self send payment we have made to ourself now that we have our change payment. + if (tx_type == cryptonote::transaction_type::STAKE || + tx_type == cryptonote::transaction_type::BURN) + { + selfsend_payment_proposals.back().proposal.enote_ephemeral_pubkey = + selfsend_payment_proposals.front().proposal.enote_ephemeral_pubkey; + selfsend_payment_proposals.erase(selfsend_payment_proposals.begin()); + } + // deduct an even fee amount from all subtractable outputs const size_t num_subtractble_normal = subtractable_normal_payment_proposals.size(); const size_t num_subtractable_selfsend = subtractable_selfsend_payment_proposals.size(); diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index dcf742c45..eab5fe431 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -197,6 +197,23 @@ namespace cryptonote }; + class protocol_tx_data_t { + public: + uint8_t version; + crypto::public_key return_address; + crypto::public_key return_pubkey; + carrot::view_tag_t return_view_tag; + carrot::encrypted_janus_anchor_t return_anchor_enc; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(version) + FIELD(return_address) + FIELD(return_pubkey) + FIELD(return_view_tag) + FIELD(return_anchor_enc) + END_SERIALIZE() + }; + class transaction_prefix { @@ -227,6 +244,8 @@ namespace cryptonote // Slippage limit uint64_t amount_slippage_limit; + protocol_tx_data_t protocol_tx_data; + BEGIN_SERIALIZE() VARINT_FIELD(version) if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; @@ -244,8 +263,14 @@ namespace cryptonote FIELD(return_address_list) FIELD(return_address_change_mask) } else { - FIELD(return_address) - FIELD(return_pubkey) + if (type == cryptonote::transaction_type::STAKE && + version >= TRANSACTION_VERSION_CARROT) + { + FIELD(protocol_tx_data) + } else { + FIELD(return_address) + FIELD(return_pubkey) + } } FIELD(source_asset_type) FIELD(destination_asset_type) @@ -268,6 +293,10 @@ namespace cryptonote return_address_list.clear(); return_address_change_mask.clear(); return_pubkey = crypto::null_pkey; + protocol_tx_data.return_address = crypto::null_pkey; + protocol_tx_data.return_pubkey = crypto::null_pkey; + protocol_tx_data.return_view_tag = {}; + protocol_tx_data.return_anchor_enc = {}; source_asset_type.clear(); destination_asset_type.clear(); amount_burnt = 0; @@ -754,6 +783,7 @@ VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::txout_to_carrot_v1, 0x4); +VARIANT_TAG(binary_archive, cryptonote::protocol_tx_data_t, 0x0); VARIANT_TAG(binary_archive, cryptonote::transaction, 0xcc); VARIANT_TAG(binary_archive, cryptonote::block, 0xbb); @@ -766,6 +796,7 @@ VARIANT_TAG(json_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(json_archive, cryptonote::txout_to_key, "key"); VARIANT_TAG(json_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(json_archive, cryptonote::txout_to_carrot_v1, "carrot_v1"); +VARIANT_TAG(json_archive, cryptonote::protocol_tx_data_t, "protocol_tx_data"); VARIANT_TAG(json_archive, cryptonote::transaction, "tx"); VARIANT_TAG(json_archive, cryptonote::block, "block"); @@ -778,5 +809,6 @@ VARIANT_TAG(debug_archive, cryptonote::txout_to_scripthash, "scripthash"); VARIANT_TAG(debug_archive, cryptonote::txout_to_key, "key"); VARIANT_TAG(debug_archive, cryptonote::txout_to_tagged_key, "tagged_key"); VARIANT_TAG(debug_archive, cryptonote::txout_to_carrot_v1, "carrot_v1"); +VARIANT_TAG(debug_archive, cryptonote::protocol_tx_data_t, "protocol_tx_data"); VARIANT_TAG(debug_archive, cryptonote::transaction, "tx"); VARIANT_TAG(debug_archive, cryptonote::block, "block"); diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 794ac4b97..e9b50fc83 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -166,6 +166,15 @@ namespace boost a & x.target; } + template + inline void serialize(Archive &a, cryptonote::protocol_tx_data_t &x, const boost::serialization::version_type ver) + { + a & x.version; + a & x.return_address; + a & x.return_pubkey; + a & x.return_view_tag; + a & x.return_anchor_enc; + } template inline void serialize(Archive &a, cryptonote::transaction_prefix &x, const boost::serialization::version_type ver) @@ -183,8 +192,14 @@ namespace boost a & x.return_address_list; a & x.return_address_change_mask; } else { - a & x.return_address; - a & x.return_pubkey; + if (x.type == cryptonote::transaction_type::STAKE && + x.version >= TRANSACTION_VERSION_CARROT) + { + a & x.protocol_tx_data; + } else { + a & x.return_address; + a & x.return_pubkey; + } } a & x.source_asset_type; a & x.destination_asset_type; @@ -209,8 +224,14 @@ namespace boost a & x.return_address_list; a & x.return_address_change_mask; } else { - a & x.return_address; - a & x.return_pubkey; + if (x.type == cryptonote::transaction_type::STAKE && + x.version >= TRANSACTION_VERSION_CARROT) + { + a & x.protocol_tx_data; + } else { + a & x.return_address; + a & x.return_pubkey; + } } a & x.source_asset_type; a & x.destination_asset_type; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 61734444b..e22aed5f0 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -44,9 +44,10 @@ #define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 -#define CURRENT_TRANSACTION_VERSION 3 +#define CURRENT_TRANSACTION_VERSION 4 #define TRANSACTION_VERSION_2_OUTS 2 #define TRANSACTION_VERSION_N_OUTS 3 +#define TRANSACTION_VERSION_CARROT 4 #define CURRENT_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MINOR_VERSION 1 #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 382b23877..11e5f3780 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1563,6 +1563,7 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, // Get the staking data for the block that matured this time cryptonote::yield_block_info ybi_matured; std::vector> yield_payouts; + std::vector> carrot_yield_payouts; uint64_t matured_height = height - stake_lock_period - 1; bool ok = get_ybi_entry(matured_height, ybi_matured); if (!ok) { @@ -1571,10 +1572,17 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, } else if (ybi_matured.locked_coins_this_block == 0) { LOG_PRINT_L1("Block at height: " << height << " - no yield payouts due - skipping"); } else { - // Iterate over the cached data for block yield, calculating the yield payouts due - if (!calculate_yield_payouts(matured_height, yield_payouts)) { - LOG_ERROR("Block at height: " << height << " - Failed to obtain yield payout information - aborting"); - return false; + // Iterate over the cached data for block yield, calculating the yield payouts due + if (hf_version >= HF_VERSION_CARROT) { + if (!calculate_yield_payouts(matured_height, carrot_yield_payouts)) { + LOG_ERROR("Block at height: " << height << " - Failed to obtain carrot yield payout information - aborting"); + return false; + } + } else { + if (!calculate_yield_payouts(matured_height, yield_payouts)) { + LOG_ERROR("Block at height: " << height << " - Failed to obtain yield payout information - aborting"); + return false; + } } } @@ -1607,7 +1615,36 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, } // Check we have the correct number of entries - CHECK_AND_ASSERT_MES(b.protocol_tx.vout.size() == yield_payouts.size() + audit_payouts.size(), false, "Invalid number of outputs in protocol_tx - aborting"); + CHECK_AND_ASSERT_MES( + b.protocol_tx.vout.size() == yield_payouts.size() + audit_payouts.size() + carrot_yield_payouts.size(), + false, "Invalid number of outputs in protocol_tx - aborting" + ); + + if (hf_version >= HF_VERSION_CARROT) { + // TODO: add other verifications for carrot yield payouts + size_t output_idx = 0; + for (auto it = carrot_yield_payouts.begin(); it != carrot_yield_payouts.end(); it++, output_idx++) { + // Verify the output key + crypto::public_key out_key; + cryptonote::get_output_public_key(b.protocol_tx.vout[output_idx], out_key); + CHECK_AND_ASSERT_MES(out_key == it->first.return_address, false, "Incorrect output key detected in protocol_tx"); + + // Verify the output amount + uint64_t expected_amount = it->second; + CHECK_AND_ASSERT_MES(b.protocol_tx.vout[output_idx].amount == expected_amount, false, "Incorrect output amount detected in protocol_tx. expected_amount: " << expected_amount); + + // Verify the output asset type + std::string out_asset_type; + cryptonote::get_output_asset_type(b.protocol_tx.vout[output_idx], out_asset_type); + uint8_t hf_yield = m_hardfork->get_ideal_version(it->first.block_height); + if (hf_yield >= HF_VERSION_SALVIUM_ONE_PROOFS) + CHECK_AND_ASSERT_MES(out_asset_type == "SAL1", false, "Incorrect output asset_type (!= SAL1) detected in protocol_tx"); + else + CHECK_AND_ASSERT_MES(out_asset_type == "SAL", false, "Incorrect output asset_type (!= SAL) detected in protocol_tx"); + } + + return true; + } // Merge the yield and audit payouts into an iterable vector std::vector> payouts{yield_payouts}; @@ -1917,33 +1954,64 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, // Iterate over the cached data for block yield, calculating the yield payouts due std::vector> yield_payouts; - if (!calculate_yield_payouts(start_height, yield_payouts)) { - LOG_ERROR("Failed to obtain yield payout information - aborting"); - return false; + std::vector> carrot_yield_payouts; + if (b.major_version >= HF_VERSION_CARROT) { + if (!calculate_yield_payouts(start_height, carrot_yield_payouts)) { + LOG_ERROR("Failed to obtain yield payout information - aborting"); + return false; + } + } else { + if (!calculate_yield_payouts(start_height, yield_payouts)) { + LOG_ERROR("Failed to obtain yield payout information - aborting"); + return false; + } } // Work out what the asset_type should be based on height of submission uint8_t hf_submitted = m_hardfork->get_ideal_version(start_height); // Create the protocol_metadata entries here - for (const auto& yield_entry: yield_payouts) { - cryptonote::protocol_data_entry entry; - entry.amount_burnt = yield_entry.second; - entry.amount_minted = 0; - entry.amount_slippage_limit = 0; - if (hf_submitted >= HF_VERSION_SALVIUM_ONE_PROOFS) { - entry.source_asset = "SAL1"; - entry.destination_asset = "SAL1"; - } else { - entry.source_asset = "SAL"; - entry.destination_asset = "SAL"; + if (b.major_version >= HF_VERSION_CARROT) { + for (const auto& yield_entry: carrot_yield_payouts) { + cryptonote::protocol_data_entry entry; + entry.amount_burnt = yield_entry.second; + entry.amount_minted = 0; + entry.amount_slippage_limit = 0; + if (hf_submitted >= HF_VERSION_SALVIUM_ONE_PROOFS) { + entry.source_asset = "SAL1"; + entry.destination_asset = "SAL1"; + } else { + entry.source_asset = "SAL"; + entry.destination_asset = "SAL"; + } + entry.return_address = yield_entry.first.return_address; + entry.type = cryptonote::transaction_type::STAKE; + entry.return_pubkey = yield_entry.first.return_pubkey; + entry.origin_height = start_height; + entry.return_view_tag = yield_entry.first.return_view_tag; + entry.return_anchor_enc = yield_entry.first.return_anchor_enc; + protocol_entries.push_back(entry); + } + } else { + for (const auto& yield_entry: yield_payouts) { + cryptonote::protocol_data_entry entry; + entry.amount_burnt = yield_entry.second; + entry.amount_minted = 0; + entry.amount_slippage_limit = 0; + if (hf_submitted >= HF_VERSION_SALVIUM_ONE_PROOFS) { + entry.source_asset = "SAL1"; + entry.destination_asset = "SAL1"; + } else { + entry.source_asset = "SAL"; + entry.destination_asset = "SAL"; + } + entry.return_address = yield_entry.first.return_address; + entry.type = cryptonote::transaction_type::STAKE; + entry.P_change = yield_entry.first.P_change; + entry.return_pubkey = yield_entry.first.return_pubkey; + entry.origin_height = start_height; + protocol_entries.push_back(entry); } - entry.return_address = yield_entry.first.return_address; - entry.type = cryptonote::transaction_type::STAKE; - entry.P_change = yield_entry.first.P_change; - entry.return_pubkey = yield_entry.first.return_pubkey; - entry.origin_height = start_height; - protocol_entries.push_back(entry); } } @@ -3700,7 +3768,7 @@ bool Blockchain::check_tx_type_and_version(const transaction& tx, tx_verificatio } // After v2 allow N-out TXs for TRANSFER ONLY - if (hf_version >= HF_VERSION_ENABLE_N_OUTS) { + if (hf_version >= HF_VERSION_ENABLE_N_OUTS && hf_version < HF_VERSION_CARROT) { if (tx.version >= TRANSACTION_VERSION_N_OUTS && tx.type != cryptonote::transaction_type::TRANSFER) { MERROR("N-out TXs are only permitted for TRANSFER TX type"); tvc.m_version_mismatch = true; @@ -3708,6 +3776,14 @@ bool Blockchain::check_tx_type_and_version(const transaction& tx, tx_verificatio } } + if (hf_version >= HF_VERSION_CARROT) { + if (tx.version != TRANSACTION_VERSION_CARROT) { + MERROR("TX version " + std::to_string(tx.version) + " is not supported, expected " + std::to_string(TRANSACTION_VERSION_CARROT)); + tvc.m_version_mismatch = true; + return false; + } + } + // Make sure CONVERT TXs are disabled until we are ready - belt and braces! if (hf_version < HF_VERSION_ENABLE_CONVERT) { if (tx.type == cryptonote::transaction_type::CONVERT) { @@ -3751,7 +3827,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys, const uint8_t &hf_version) { PERF_TIMER(expand_transaction_2); - CHECK_AND_ASSERT_MES(tx.version == 2 || tx.version == 3, false, "Transaction version is not 2/3"); + CHECK_AND_ASSERT_MES(tx.version == 2 || tx.version == 3 || tx.version == 4, false, "Transaction version is not 2/3/4"); rct::rctSig &rv = tx.rct_signatures; @@ -3950,14 +4026,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, return false; } - // min/max tx version based on HF, and we accept v1 txes if having a non mixable - const size_t max_tx_version = (hf_version >= HF_VERSION_ENABLE_N_OUTS) ? TRANSACTION_VERSION_N_OUTS : TRANSACTION_VERSION_2_OUTS; - if (tx.version > max_tx_version) - { - MERROR_VER("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); - tvc.m_verifivation_failed = true; - return false; - } const size_t min_tx_version = (n_unmixable > 0 ? 1 : 2); if (tx.version < min_tx_version) { @@ -4591,6 +4659,68 @@ bool Blockchain::get_abi_entry(const uint64_t height, cryptonote::audit_block_in return true; } //------------------------------------------------------------------ +bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_container) +{ + LOG_PRINT_L3("Blockchain::" << __func__); + + // Clear the yield payout amounts + yield_container.clear(); + + // Get the YIELD TX information for matured staked coins + std::vector yield_entries; + // We get the yield_tx_info from the block _before_ they started to accrue yield + int yield_tx_result = m_db->get_carrot_yield_tx_info(start_height, yield_entries); + if (!yield_entries.size()) { + + // Report error and abort + LOG_ERROR("calculate_yield_payouts() called, but no yield TXs found at height " << start_height << " - aborting"); + return false; + } + + // Build a blacklist of staking TXs _not_ to pay out for + const std::set txs_blacklist = {}; + + // Get the YBI information for the 21,600 blocks that the matured TX(s), we can calculate yield + for (const auto& entry: yield_entries) { + // Check to see if this entry is in the blacklist + if (txs_blacklist.find(epee::string_tools::pod_to_hex(entry.tx_hash)) != txs_blacklist.end()) { + LOG_ERROR("calculate_yield_payouts() found blacklisted TX at height " << start_height << " - skipping payout"); + continue; + } + yield_container.emplace_back(std::make_pair(entry, entry.locked_coins)); + } + + // Iterate over the cached yield_block_info data + uint64_t yield_lock_period = cryptonote::get_config(m_nettype).STAKE_LOCK_PERIOD; + for (uint64_t idx = start_height+1; idx <= start_height + yield_lock_period; ++idx) { + // Get the next block + if (m_yield_block_info_cache.count(idx) == 0) { + LOG_ERROR("failed to locate yield information for block height " << idx <<" - aborting"); + return false; + } + yield_block_info ybi = m_yield_block_info_cache[idx]; + if (ybi.slippage_total_this_block == 0) continue; + if (ybi.locked_coins_tally == 0) continue; + + boost::multiprecision::int128_t slippage_128 = ybi.slippage_total_this_block; + + // Get the total number of coins locked at this height + boost::multiprecision::int128_t locked_total_128 = ybi.locked_coins_tally; + + // Iterate over the yield_container, adding each proportion of the yield + for (auto& entry: yield_container) { + + boost::multiprecision::int128_t locked_coins_128 = entry.first.locked_coins; + boost::multiprecision::int128_t yield_128 = (slippage_128 * locked_coins_128) / locked_total_128; + entry.second += yield_128.convert_to(); + } + } + + // Return success to caller + return true; +} +//------------------------------------------------------------------ +//------------------------------------------------------------------ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_container) { LOG_PRINT_L3("Blockchain::" << __func__); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index a393b735b..138d8a7aa 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1175,6 +1175,13 @@ namespace cryptonote */ bool calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_payouts); + /** + * calculate the yield payouts + * + * @return TRUE if the payouts were calculated successfully, FALSE otherwise + */ + bool calculate_yield_payouts(const uint64_t start_height, std::vector>& yield_payouts); + /** * @brief get the ABI entry for a particular height from the cache * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 9830b48d6..52ed81140 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -843,7 +843,9 @@ namespace cryptonote bad_semantics_txes_lock.unlock(); uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version(); - const size_t max_tx_version = (hf_version >= HF_VERSION_ENABLE_N_OUTS) ? TRANSACTION_VERSION_N_OUTS : TRANSACTION_VERSION_2_OUTS; + const size_t max_tx_version = hf_version >= HF_VERSION_CARROT ? TRANSACTION_VERSION_CARROT : + hf_version >= HF_VERSION_ENABLE_N_OUTS ? TRANSACTION_VERSION_N_OUTS : + TRANSACTION_VERSION_2_OUTS; if (tx.version == 0 || tx.version > max_tx_version) { // v2 is the latest one we know diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index b54de211f..ed580d2db 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -362,29 +362,15 @@ namespace cryptonote // Iterate over the protocol_data we received, creating an enote for each entry for (auto const& entry: protocol_data) { - - carrot::CarrotDestinationV1 destination; - carrot::make_carrot_main_address_v1(entry.P_change, - entry.return_address, - destination); - - CHECK_AND_ASSERT_THROW_MES(!destination.is_subaddress, - "construct_protocol_tx: subaddress are not allowed in miner transactions"); - CHECK_AND_ASSERT_THROW_MES(destination.payment_id == carrot::null_payment_id, - "construct_protocol_tx: integrated addresses are not allowed in miner transactions"); - - LOG_PRINT_L2(((entry.type == cryptonote::transaction_type::STAKE) ? "Yield TX payout submitted " : "Audit TX payout submitted ") << entry.amount_burnt << entry.source_asset); - - const carrot::CarrotPaymentProposalV1 payment_proposal{ - .destination = destination, - .amount = entry.amount_burnt, - .asset_type = "SAL1", - .randomness = carrot::gen_janus_anchor() - }; - // Build the proposal carrot::CarrotCoinbaseEnoteV1 e; - get_coinbase_output_proposal_v1(payment_proposal, height, e); + e.onetime_address = entry.return_address; + e.amount = entry.amount_burnt; + e.asset_type = entry.destination_asset; + e.view_tag = entry.return_view_tag; + e.anchor_enc = entry.return_anchor_enc; + e.block_index = height; + memcpy(e.enote_ephemeral_pubkey.data, entry.return_pubkey.data, sizeof(crypto::public_key)); enotes.push_back(e); } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 02506214f..ec665bd63 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -68,6 +68,8 @@ namespace cryptonote uint8_t type; crypto::public_key P_change; crypto::public_key return_pubkey; + carrot::view_tag_t return_view_tag; + carrot::encrypted_janus_anchor_t return_anchor_enc; uint64_t origin_height; }; diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp index 15c54b08e..19d601a8e 100644 --- a/src/cryptonote_core/tx_verification_utils.cpp +++ b/src/cryptonote_core/tx_verification_utils.cpp @@ -187,7 +187,7 @@ bool ver_rct_non_semantics_simple_cached // mixring. Future versions of the protocol may differ in this regard, but if this assumptions // holds true in the future, enable the verification hash by modifying the `untested_tx` // condition below. - const bool untested_tx = tx.version > 3 || tx.rct_signatures.type > rct::RCTTypeSalviumOne; + const bool untested_tx = tx.version > 4 || tx.rct_signatures.type > rct::RCTTypeSalviumOne; VER_ASSERT(!untested_tx, "Unknown TX type. Make sure RCT cache works correctly with this type and then enable it in the code here."); // Don't cache older (or newer) rctSig types diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index 6942c0e82..cf0c444a7 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -277,8 +277,14 @@ void toJsonValue(rapidjson::Writer& dest, const cryptonote::t INSERT_INTO_JSON_OBJECT(dest, return_address_list, tx.return_address_list); INSERT_INTO_JSON_OBJECT(dest, return_address_change_mask, tx.return_address_change_mask); } else { - INSERT_INTO_JSON_OBJECT(dest, return_address, tx.return_address); - INSERT_INTO_JSON_OBJECT(dest, return_pubkey, tx.return_pubkey); + if (tx.type == cryptonote::transaction_type::STAKE && + tx.version >= TRANSACTION_VERSION_CARROT) + { + INSERT_INTO_JSON_OBJECT(dest, protocol_tx_data, tx.protocol_tx_data); + } else { + INSERT_INTO_JSON_OBJECT(dest, return_address, tx.return_address); + INSERT_INTO_JSON_OBJECT(dest, return_pubkey, tx.return_pubkey); + } } INSERT_INTO_JSON_OBJECT(dest, source_asset_type, tx.source_asset_type); INSERT_INTO_JSON_OBJECT(dest, destination_asset_type, tx.destination_asset_type); @@ -320,8 +326,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) GET_FROM_JSON_OBJECT(val, tx.return_address_list, return_address_list); GET_FROM_JSON_OBJECT(val, tx.return_address_change_mask, return_address_change_mask); } else { - GET_FROM_JSON_OBJECT(val, tx.return_address, return_address); - GET_FROM_JSON_OBJECT(val, tx.return_pubkey, return_pubkey); + if (tx.type == cryptonote::transaction_type::STAKE && + tx.version >= TRANSACTION_VERSION_CARROT) + { + GET_FROM_JSON_OBJECT(val, tx.protocol_tx_data, protocol_tx_data); + } else { + GET_FROM_JSON_OBJECT(val, tx.return_address, return_address); + GET_FROM_JSON_OBJECT(val, tx.return_pubkey, return_pubkey); + } } GET_FROM_JSON_OBJECT(val, tx.source_asset_type, source_asset_type); GET_FROM_JSON_OBJECT(val, tx.destination_asset_type, destination_asset_type); @@ -506,6 +518,33 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& } } +void toJsonValue(rapidjson::Writer& dest, const cryptonote::protocol_tx_data_t& ptd) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, version, ptd.version); + INSERT_INTO_JSON_OBJECT(dest, return_address, ptd.return_address); + INSERT_INTO_JSON_OBJECT(dest, return_pubkey, ptd.return_pubkey); + INSERT_INTO_JSON_OBJECT(dest, return_view_tag, ptd.return_view_tag); + INSERT_INTO_JSON_OBJECT(dest, return_anchor_enc, ptd.return_anchor_enc); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::protocol_tx_data_t& ptd) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, ptd.version, version); + GET_FROM_JSON_OBJECT(val, ptd.return_address, return_address); + GET_FROM_JSON_OBJECT(val, ptd.return_pubkey, return_pubkey); + GET_FROM_JSON_OBJECT(val, ptd.return_view_tag, return_view_tag); + GET_FROM_JSON_OBJECT(val, ptd.return_anchor_enc, return_anchor_enc); +} + void toJsonValue(rapidjson::Writer& dest, const cryptonote::txin_to_key& txin) { dest.StartObject(); diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index df2323289..680df3b82 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -218,6 +218,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& void toJsonValue(rapidjson::Writer& dest, const cryptonote::txin_to_key& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin); +void toJsonValue(rapidjson::Writer& dest, const cryptonote::protocol_tx_data_t& ptd); +void fromJsonValue(const rapidjson::Value& val, cryptonote::protocol_tx_data_t& ptd); + void toJsonValue(rapidjson::Writer& dest, const cryptonote::txout_target_v& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout); diff --git a/src/wallet/scanning_tools.cpp b/src/wallet/scanning_tools.cpp index 8e4ac55a7..b3aafb384 100644 --- a/src/wallet/scanning_tools.cpp +++ b/src/wallet/scanning_tools.cpp @@ -255,26 +255,123 @@ static std::optional view_incoming_scan_pre_car .amount_blinding_factor = amount_blinding_factor, .asset_type = enote.asset_type, .main_tx_pubkey_index = main_deriv_idx, - .is_carrot = false + .is_carrot = false, + .is_return = false }; } //------------------------------------------------------------------------------------------------------------------- +static bool scan_return_output( + const carrot::CarrotCoinbaseEnoteV1 &enote, + carrot::carrot_and_legacy_account &account, + crypto::public_key &address_spend_pubkey_out +) { + const auto &return_output_map = account.get_return_output_map_ref(); + CHECK_AND_ASSERT_MES(return_output_map.count(enote.onetime_address), false, "return output not found"); + const auto &return_output = return_output_map.at(enote.onetime_address); + + // 1. make k_return + crypto::secret_key k_return; + account.k_view_incoming_dev.make_internal_return_privkey(return_output.input_context, return_output.address_spend_pubkey, k_return); + + // 2. compute K_return' = k_return * G + crypto::public_key K_return; + crypto::secret_key_to_public_key(k_return, K_return); + + // 3. ssr + mx25519_pubkey shared_secret_return_unctx; + crypto::hash shared_secret_return; + carrot::make_carrot_uncontextualized_shared_key_receiver(k_return, enote.enote_ephemeral_pubkey, shared_secret_return_unctx); + carrot::make_carrot_sender_receiver_secret( + shared_secret_return_unctx.data, + enote.enote_ephemeral_pubkey, + return_output.input_context, + shared_secret_return + ); + + // 4. verify the view_tag + CHECK_AND_ASSERT_MES( + carrot::test_carrot_view_tag( + shared_secret_return_unctx.data, + return_output.input_context, + enote.onetime_address, + enote.view_tag + ), + false, + "view tag verification failed for carrot coinbase enote" + ); + + // 5. compute anchor_return + carrot::janus_anchor_t recovered_anchor_return = + carrot::decrypt_carrot_anchor(enote.anchor_enc, shared_secret_return, enote.onetime_address); + + // 6. compute d_e' + crypto::secret_key recovered_ephemeral_privkey_return; + carrot::make_carrot_enote_ephemeral_privkey( + recovered_anchor_return, + return_output.input_context, + return_output.address_spend_pubkey, + carrot::null_payment_id, + recovered_ephemeral_privkey_return + ); + + // 7. compute D_e' + mx25519_pubkey recovered_ephemeral_pubkey_return; + carrot::make_carrot_enote_ephemeral_pubkey( + recovered_ephemeral_privkey_return, + return_output.address_spend_pubkey, + false, + recovered_ephemeral_pubkey_return + ); + + // 8. verify the enote ephemeral pubkey + CHECK_AND_ASSERT_MES( + memcmp(recovered_ephemeral_pubkey_return.data, enote.enote_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0, + false, + "carrot coinbase enote protection verification failed" + ); + + address_spend_pubkey_out = return_output.address_spend_pubkey; + return true; +} //------------------------------------------------------------------------------------------------------------------- static std::optional view_incoming_scan_carrot_coinbase_enote( const carrot::CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, - const crypto::public_key &main_address_spend_pubkey) + const crypto::public_key &main_address_spend_pubkey, + carrot::carrot_and_legacy_account &account) { enote_view_incoming_scan_info_t res; + bool found_in_return = false; if (!carrot::try_scan_carrot_coinbase_enote_receiver(enote, s_sender_receiver_unctx, main_address_spend_pubkey, res.sender_extension_g, res.sender_extension_t)) - return std::nullopt; + { + // check for known return addresses + const auto &subaddress_map = account.get_subaddress_map_ref(); + if (subaddress_map.find(enote.onetime_address) == subaddress_map.end()) + return std::nullopt; - res.address_spend_pubkey = main_address_spend_pubkey; + found_in_return = true; + } + + if (found_in_return) { + // scan the return output + crypto::public_key address_spend_pubkey; + if (!scan_return_output(enote, account, address_spend_pubkey)) + return std::nullopt; + + res.address_spend_pubkey = address_spend_pubkey; + res.return_address = enote.onetime_address; + res.is_return = true; + } else { + // we received a coinbase enote + res.address_spend_pubkey = main_address_spend_pubkey; + res.is_return = false; + } + res.payment_id = crypto::null_hash; res.subaddr_index = carrot::subaddress_index_extended{{0, 0}}; res.amount = enote.amount; @@ -326,6 +423,7 @@ static std::optional view_incoming_scan_carrot_ res.main_tx_pubkey_index = 0; res.asset_type = enote.asset_type; res.is_carrot = true; + res.is_return = false; return res; } @@ -358,8 +456,8 @@ static std::optional view_incoming_scan_carrot_ res.amount, amount_blinding_factor_sk, payment_id, - dummy_enote_type)) - return std::nullopt; + dummy_enote_type)) + return std::nullopt; const auto subaddr_it = account.get_subaddress_map_ref().find(res.address_spend_pubkey); CHECK_AND_ASSERT_MES(subaddr_it != account.get_subaddress_map_ref().cend(), @@ -372,11 +470,50 @@ static std::optional view_incoming_scan_carrot_ memset(&res.payment_id, 0, sizeof(res.payment_id)); memcpy(&res.payment_id, &payment_id, sizeof(carrot::payment_id_t)); + // we received and output + // save the Kr = K_change + K_return to out subaddress map + // make k_return + crypto::secret_key k_return; + const carrot::input_context_t input_context = carrot::make_carrot_input_context(enote.tx_first_key_image); + k_view_dev.make_internal_return_privkey(input_context, enote.onetime_address, k_return); + + // compute K_return = k_return * G + crypto::public_key K_return; + crypto::secret_key_to_public_key(k_return, K_return); + + // compute K_r = K_return + K_o + crypto::public_key K_r = rct::rct2pk(rct::addKeys(rct::pk2rct(K_return), rct::pk2rct(enote.onetime_address))); + account.insert_subaddresses({{K_r, {{subaddr_index.index.major, subaddr_index.index.minor}, + carrot::AddressDeriveType::Carrot, true}}}); + + // calculate the key image for the return output + crypto::secret_key sum_g; + sc_add(to_bytes(sum_g), to_bytes(res.sender_extension_g), to_bytes(k_return)); + crypto::key_image key_image = account.derive_key_image( + account.get_keys().m_carrot_account_address.m_spend_public_key, + sum_g, + res.sender_extension_t, + K_r + ); + + crypto::secret_key x, y; + account.try_searching_for_opening_for_onetime_address( + account.get_keys().m_carrot_account_address.m_spend_public_key, + sum_g, + res.sender_extension_t, + x, + y + ); + + // save the input context & change output key + account.insert_return_output_info({{K_r, {input_context, enote.onetime_address, key_image, x, y}}}); + res.subaddr_index = subaddr_index; res.amount_blinding_factor = rct::sk2rct(amount_blinding_factor_sk); res.main_tx_pubkey_index = 0; res.asset_type = enote.asset_type; res.is_carrot = true; + res.is_return = false; return res; } @@ -516,7 +653,8 @@ std::optional view_incoming_scan_enote( return view_incoming_scan_carrot_coinbase_enote(enote, s_sender_receiver_unctx, - address.m_spend_public_key); + address.m_spend_public_key, + account); } std::optional operator()(const carrot::CarrotEnoteV1 &enote) const @@ -827,7 +965,7 @@ std::vector> view_incoming_scan_t // 3. do view-incoming scan for each output enotes hw::device &hwdev = hw::get_device("default"); carrot::carrot_and_legacy_account dummy_account; - dummy_account.get_subaddress_map_ref()[address.m_spend_public_key] = {{}}; + dummy_account.insert_subaddresses({{address.m_spend_public_key, {{0, 0}, carrot::AddressDeriveType::Carrot, false}}}); for (size_t local_output_index = 0; local_output_index < n_outputs; ++local_output_index) { auto &enote_scan_info = res[local_output_index]; @@ -860,10 +998,17 @@ bool is_long_payment_id(const crypto::hash &pid) std::optional try_derive_enote_key_image( const enote_view_incoming_scan_info_t &enote_scan_info, const carrot::carrot_and_legacy_account &acc) -{ +{ + // we skip the return output key image generation here to do it in process_new_scanned_transaction. if (!enote_scan_info.subaddr_index) return std::nullopt; + // if we have a carrot return enote, we return already derived key image + if (enote_scan_info.is_carrot && enote_scan_info.is_return) + { + return acc.get_return_output_map_ref().at(enote_scan_info.return_address).key_image; + } + // k^j_subext rct::key subaddress_extension; if (enote_scan_info.subaddr_index->index.is_subaddress()) diff --git a/src/wallet/scanning_tools.h b/src/wallet/scanning_tools.h index e1cc29e9b..5e4615188 100644 --- a/src/wallet/scanning_tools.h +++ b/src/wallet/scanning_tools.h @@ -78,6 +78,12 @@ struct enote_view_incoming_scan_info_t // whether this output is to a carrot address or not bool is_carrot; + + // whether this output is a return output + bool is_return; + + // Kr = K_return + K_o + crypto::public_key return_address; }; struct PreCarrotEnote diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index e3b9ead74..d0b1aad91 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -812,6 +812,18 @@ bool get_address_openings_x_y( crypto::secret_key &x_out, crypto::secret_key &y_out) { + // If the output is a return output, we can use the return output secret key + // to derive x and y directly. + const auto& return_output_map = w.get_account().get_return_output_map_ref(); + if (return_output_map.find(rct::rct2pk(src.outputs[src.real_output].second.dest)) != return_output_map.end()) + { + const auto &return_output = return_output_map.at(rct::rct2pk(src.outputs[src.real_output].second.dest)); + x_out = return_output.x; + y_out = return_output.y; + return true; + } + + const std::vector v_pubkeys{src.real_out_tx_key}; const std::vector v_pubkeys_empty{}; const epee::span main_tx_ephemeral_pubkeys = (src.real_out_tx_key == crypto::null_pkey) ? epee::to_span(v_pubkeys_empty) : epee::to_span(v_pubkeys); @@ -1003,6 +1015,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details( std::vector output_enote_proposals; carrot::encrypted_payment_id_t encrypted_payment_id; size_t change_index; + carrot::RCTOutputEnoteProposal return_enote_out; std::unordered_map payments_indices; carrot::get_output_enote_proposals(tx_proposal.normal_payment_proposals, selfsend_payment_proposal_cores, @@ -1011,6 +1024,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details( &addr_dev, tx_proposal.key_images_sorted.at(0), output_enote_proposals, + return_enote_out, encrypted_payment_id, tx_proposal.tx_type, change_index, @@ -1054,7 +1068,9 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details( tx_proposal.sources, tx_proposal.fee, tx_proposal.tx_type, + tx_proposal.amount_burnt, change_masks, + return_enote_out, encrypted_payment_id); // aliases @@ -1216,16 +1232,24 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details( //------------------------------------------------------------------------------------------------------------------- wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, const wallet2::transfer_container &transfers, - const crypto::secret_key &k_view, - hw::device &hwdev) + const carrot::carrot_and_legacy_account &account) { const std::size_t n_inputs = tx_proposal.key_images_sorted.size(); const std::size_t n_outputs = tx_proposal.normal_payment_proposals.size() + tx_proposal.selfsend_payment_proposals.size(); const bool shared_ephemeral_pubkey = n_outputs == 2; + CARROT_CHECK_AND_THROW( + tx_proposal.tx_type != cryptonote::transaction_type::UNSET, + carrot::missing_components, + "make_pending_carrot_tx: tx proposal has unset tx type" + ); CARROT_CHECK_AND_THROW(n_inputs >= 1, carrot::too_few_inputs, "carrot tx proposal missing inputs"); - CARROT_CHECK_AND_THROW(n_outputs >= 2, carrot::too_few_outputs, "carrot tx proposal missing outputs"); + if (tx_proposal.tx_type == cryptonote::transaction_type::STAKE || tx_proposal.tx_type == cryptonote::transaction_type::BURN) { + CARROT_CHECK_AND_THROW(n_outputs == 1, carrot::too_few_outputs, "carrot tx proposal doesn't have correct number of outputs"); + } else { + CARROT_CHECK_AND_THROW(n_outputs >= 2, carrot::too_few_outputs, "carrot tx proposal missing outputs"); + } const crypto::key_image &tx_first_key_image = tx_proposal.key_images_sorted.at(0); @@ -1249,16 +1273,13 @@ wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionPropos key_images_string << ki; } - //! @TODO: HW device - carrot::view_incoming_key_ram_borrowed_device k_view_dev(k_view); - // get order of payment proposals std::vector output_enote_proposals; carrot::encrypted_payment_id_t encrypted_payment_id; std::vector> sorted_payment_proposal_indices; carrot::get_output_enote_proposals_from_proposal_v1(tx_proposal, /*s_view_balance_dev=*/nullptr, - &k_view_dev, + &account.k_view_incoming_dev, output_enote_proposals, encrypted_payment_id, &sorted_payment_proposal_indices); @@ -1288,7 +1309,7 @@ wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionPropos if (is_selfsend) { dest = make_tx_destination_entry(tx_proposal.selfsend_payment_proposals.at(payment_idx.second), - k_view_dev); + account.k_view_incoming_dev); ephemeral_privkeys.push_back(crypto::null_skey); } else // !is_selfsend @@ -1348,11 +1369,9 @@ wallet2::pending_tx finalize_all_proofs_from_transfer_details_as_pending_tx( const wallet2::transfer_container &transfers, const wallet2 &w) { - const auto acc_keys = w.get_account().get_keys(); wallet2::pending_tx ptx = make_pending_carrot_tx(tx_proposal, transfers, - acc_keys.m_view_secret_key, - acc_keys.get_device()); + w.get_account()); ptx.tx = finalize_all_proofs_from_transfer_details( tx_proposal, diff --git a/src/wallet/tx_builder.h b/src/wallet/tx_builder.h index c2d87972a..787efd00f 100644 --- a/src/wallet/tx_builder.h +++ b/src/wallet/tx_builder.h @@ -125,19 +125,13 @@ std::vector make_carrot_transaction_proposa const std::set &subaddr_indices); wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, const wallet2::transfer_container &transfers, - const crypto::secret_key &k_view, - hw::device &hwdev); + const carrot::carrot_and_legacy_account &account); cryptonote::transaction finalize_all_proofs_from_transfer_details( const carrot::CarrotTransactionProposalV1 &tx_proposal, const cryptonote::transaction_type tx_type, const wallet2 &w); -wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionProposalV1 &tx_proposal, - const wallet2::transfer_container &transfers, - const crypto::secret_key &k_view, - hw::device &hwdev); - wallet2::pending_tx finalize_all_proofs_from_transfer_details_as_pending_tx( const carrot::CarrotTransactionProposalV1 &tx_proposal, const wallet2::transfer_container &transfers, diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 7713c3f07..f1fc99c7d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -98,6 +98,7 @@ using namespace epee; #include "carrot_impl/format_utils.h" #include "tx_builder.h" #include "scanning_tools.h" +#include "carrot_core/scan.h" extern "C" { @@ -2652,8 +2653,8 @@ void wallet2::process_new_scanned_transaction( } } - // override the key image for PROTOCOL/RETURN tx outputs. - if (td.m_td_origin_idx != std::numeric_limits::max()) + // override the key image for pre-carrot PROTOCOL/RETURN tx outputs. + if (!enote_scan_info->is_carrot && td.m_td_origin_idx != std::numeric_limits::max()) { THROW_WALLET_EXCEPTION_IF(td.m_td_origin_idx >= get_num_transfer_details(), error::wallet_internal_error, "cannot locate return_payment TX origin in m_transfers"); const transfer_details &td_origin = m_transfers[td.m_td_origin_idx]; @@ -2669,20 +2670,22 @@ void wallet2::process_new_scanned_transaction( origin_tx_data.output_index = td_origin.m_internal_output_index; origin_tx_data.tx_type = td_origin.m_tx.type; rct::salvium_input_data_t sid; - THROW_WALLET_EXCEPTION_IF(!cryptonote::generate_key_image_helper(m_account.get_keys(), - m_account.get_subaddress_map_cn(), - onetime_address, - tx_public_key, - additional_tx_public_keys, - local_output_index, - in_ephemeral, - ki, - hwdev, - true, - origin_tx_data, - sid), - error::wallet_internal_error, - "failed to obtain key image for protocol_tx output"); + THROW_WALLET_EXCEPTION_IF(!cryptonote::generate_key_image_helper( + m_account.get_keys(), + m_account.get_subaddress_map_cn(), + onetime_address, + tx_public_key, + additional_tx_public_keys, + local_output_index, + in_ephemeral, + ki, + hwdev, + true, + origin_tx_data, + sid), + error::wallet_internal_error, + "failed to obtain key image for protocol_tx output" + ); td.m_key_image_known = true; td.m_key_image = ki; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 336328020..474d134eb 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -391,6 +391,19 @@ private: return output_public_key; }; + const crypto::public_key get_eph_public_key() const { + crypto::public_key eph_public_key; + eph_public_key = cryptonote::get_tx_pub_key_from_extra(m_tx); + if (eph_public_key != crypto::null_pkey) + return eph_public_key; + + const auto additional_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(m_tx); + if (!additional_pub_keys.empty()) + return additional_pub_keys[m_internal_output_index]; + + return crypto::null_pkey; + }; + bool is_carrot() const { THROW_WALLET_EXCEPTION_IF(m_tx.vout.size() <= m_internal_output_index, diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 3756e0250..aa24c0d5b 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -323,10 +323,13 @@ TEST(carrot_core, main_address_special_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); RCTOutputEnoteProposal enote_proposal; + RCTOutputEnoteProposal return_enote; get_output_proposal_special_v1(proposal, keys.k_view_incoming_dev, tx_first_key_image, + cryptonote::transaction_type::TRANSFER, // tx_type std::nullopt, + return_enote, enote_proposal); ASSERT_EQ(proposal.amount, enote_proposal.amount); @@ -400,10 +403,13 @@ TEST(carrot_core, subaddress_special_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); RCTOutputEnoteProposal enote_proposal; + RCTOutputEnoteProposal return_enote; get_output_proposal_special_v1(proposal, keys.k_view_incoming_dev, tx_first_key_image, + cryptonote::transaction_type::TRANSFER, // tx_type std::nullopt, + return_enote, enote_proposal); ASSERT_EQ(proposal.amount, enote_proposal.amount); @@ -714,6 +720,7 @@ static void subtest_2out_transfer_get_output_enote_proposals_completeness(const std::vector enote_proposals; encrypted_payment_id_t encrypted_payment_id; size_t change_index; + RCTOutputEnoteProposal return_enote; std::unordered_map normal_payments_indices; get_output_enote_proposals({bob_payment_proposal}, {alice_payment_proposal}, @@ -722,6 +729,7 @@ static void subtest_2out_transfer_get_output_enote_proposals_completeness(const &alice.k_view_incoming_dev, tx_first_key_image, enote_proposals, + return_enote, encrypted_payment_id, cryptonote::transaction_type::TRANSFER, change_index, diff --git a/tests/unit_tests/carrot_fcmp.cpp b/tests/unit_tests/carrot_fcmp.cpp index dd92e0f72..72a9dbd28 100644 --- a/tests/unit_tests/carrot_fcmp.cpp +++ b/tests/unit_tests/carrot_fcmp.cpp @@ -417,7 +417,10 @@ TEST(carrot_fcmp, receive_scan_spend_and_verify_serialized_carrot_tx) tx_proposal.key_images_sorted, tx_proposal.sources, tx_proposal.fee, + tx_proposal.tx_type, + tx_proposal.amount_burnt, {}, // change_masks + {}, // return_enote encrypted_payment_id); ASSERT_EQ(2, tx.version); diff --git a/tests/unit_tests/carrot_impl.cpp b/tests/unit_tests/carrot_impl.cpp index 4ddd9e03d..ae2eb315d 100644 --- a/tests/unit_tests/carrot_impl.cpp +++ b/tests/unit_tests/carrot_impl.cpp @@ -206,6 +206,7 @@ static void subtest_multi_account_transfer_over_transaction(const unittest_trans std::vector rederived_output_enote_proposals; encrypted_payment_id_t rederived_encrypted_payment_id; size_t change_index; + RCTOutputEnoteProposal return_enote; std::unordered_map normal_payments_indices; get_output_enote_proposals(tx_proposal.normal_payment_proposals, modified_selfsend_payment_proposals, @@ -214,6 +215,7 @@ static void subtest_multi_account_transfer_over_transaction(const unittest_trans &ss_keys.k_view_incoming_dev, parsed_key_images.at(0), rederived_output_enote_proposals, + return_enote, rederived_encrypted_payment_id, cryptonote::transaction_type::TRANSFER, change_index, diff --git a/tests/unit_tests/carrot_legacy.cpp b/tests/unit_tests/carrot_legacy.cpp index c686e8321..96bc32c0b 100644 --- a/tests/unit_tests/carrot_legacy.cpp +++ b/tests/unit_tests/carrot_legacy.cpp @@ -188,6 +188,7 @@ static void subtest_legacy_2out_transfer_get_output_enote_proposals_completeness std::vector enote_proposals; encrypted_payment_id_t encrypted_payment_id; size_t change_index; + RCTOutputEnoteProposal return_enote; std::unordered_map normal_payments_indices; get_output_enote_proposals({bob_payment_proposal}, {alice_payment_proposal}, @@ -196,6 +197,7 @@ static void subtest_legacy_2out_transfer_get_output_enote_proposals_completeness &alive_k_v_dev, tx_first_key_image, enote_proposals, + return_enote, encrypted_payment_id, cryptonote::transaction_type::TRANSFER, change_index, diff --git a/tests/unit_tests/carrot_sparc.cpp b/tests/unit_tests/carrot_sparc.cpp index b16c6c2d9..8197b881c 100644 --- a/tests/unit_tests/carrot_sparc.cpp +++ b/tests/unit_tests/carrot_sparc.cpp @@ -226,7 +226,7 @@ std::tuple, crypto::public_key> make_return_ get_output_proposal_return_v1( proposal_return, tx_return_first_key_image, - &bob.s_view_balance_dev, + nullptr, // s_view_balance_dev enote_proposal_return, encrypted_payment_id_return );