From 42951015b77edb4279042e94e64fe9b46477feee Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Sun, 8 Sep 2024 23:04:51 +0000 Subject: [PATCH 01/47] temp change, must be revenrted --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a07ac77a1..cb1b8837c 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ all: release-all depends: cd contrib/depends && $(MAKE) HOST=$(target) && cd ../.. && mkdir -p build/$(target)/release - cd build/$(target)/release && cmake -DCMAKE_TOOLCHAIN_FILE=$(CURDIR)/contrib/depends/$(target)/share/toolchain.cmake ../../.. && $(MAKE) + cd build/$(target)/release && cmake -DCMAKE_TOOLCHAIN_FILE=$(CURDIR)/contrib/depends/$(target)/share/toolchain.cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 ../../.. && $(MAKE) cmake-debug: mkdir -p $(builddir)/debug From 9490aaa5a6d70c0958fbc866ab753d0d265c769d Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Sun, 8 Sep 2024 23:05:10 +0000 Subject: [PATCH 02/47] adding anonymity pool --- src/cryptonote_basic/verification_context.h | 1 + src/cryptonote_config.h | 4 + src/cryptonote_core/blockchain.cpp | 69 +++++++- src/cryptonote_core/blockchain.h | 15 +- src/cryptonote_core/cryptonote_core.cpp | 76 ++++++++- src/cryptonote_core/cryptonote_tx_utils.cpp | 167 ++++++++++++++++++++ src/cryptonote_core/cryptonote_tx_utils.h | 3 + src/cryptonote_core/tx_pool.cpp | 2 +- src/cryptonote_protocol/enums.h | 26 ++- src/ringct/rctSigs.cpp | 4 +- src/ringct/rctSigs.h | 2 +- 11 files changed, 356 insertions(+), 13 deletions(-) diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 01cbdc71e..a51c970df 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -67,6 +67,7 @@ namespace cryptonote uint64_t m_collateral; uint64_t m_slippage; bool tx_pr_height_verified = false; + anonymity_pool m_tx_anon_pool = anonymity_pool::UNSET; }; struct block_verification_context diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index a7c51448c..786ba6870 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -264,6 +264,10 @@ #define HF_VERSION_MAX_CONV_TRANSACTION_FEE 24 #define MAX_CONV_TRANSACTION_FEE ((uint64_t)10000000000000ull) +// Haven v4.2 definitions +#define HF_VERSION_SUPPLY_AUDIT 25 +#define SUPPLY_AUDIT_BLOCK_HEIGHT ((uint64_t)1656720) + #define STAGENET_VERSION 0x0e #define TESTNET_VERSION 0x1b diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 20b6e3314..b7b016e1b 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -2665,6 +2665,13 @@ crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_i return data.pubkey; } +//------------------------------------------------------------------ +void Blockchain::get_output_key(const epee::span &amounts, const std::vector &offsets, std::vector &outputs, bool allow_partial) const +{ + m_db->get_output_key(amounts, offsets, outputs, allow_partial); +} + + //------------------------------------------------------------------ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const { @@ -5344,6 +5351,66 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& goto leave; } + // Get the TX anonymity pool + anonymity_pool tx_anon_pool=anonymity_pool::UNSET; + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx.vin.size()); + for (const txin_v& txin: tx.vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + bvc.m_verifivation_failed = true; + goto leave; + } + + try{ + get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + bvc.m_verifivation_failed = true; + goto leave; + } + } + catch (...){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + bvc.m_verifivation_failed = true; + goto leave; + } + + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + MERROR_VER("Unexpected input type for transaction" << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } + } + + if (tx_ring_outputs.size()!=tx.vin.size()){ + MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } + + if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ + MERROR("Failed to get the anonymity pool for transaction " << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } + + if(tx_anon_pool==anonymity_pool::UNSET){ + MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } + + + // check for block cap limit uint64_t conversion_this_tx_xhv = 0; if (hf_version >= HF_VERSION_USE_COLLATERAL && (tx_type == tt::OFFSHORE || tx_type == tt::ONSHORE)) { @@ -5433,7 +5500,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& } // make sure proof-of-value still holds - if (!rct::verRctSemanticsSimple2(tx.rct_signatures, pr_bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage)) + if (!rct::verRctSemanticsSimple2(tx.rct_signatures, pr_bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, tx_anon_pool)) { // 2 tx that used reorged pricing record for collateral calculation. if (epee::string_tools::pod_to_hex(tx_id) != "e9c0753df108cb9de343d78c3bbdec0cebd56ee5c26c09ecf46dbf8af7838956" diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index d5fa0479c..cec827661 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -534,7 +534,20 @@ namespace cryptonote * @return the public key */ crypto::public_key get_output_key(uint64_t amount, uint64_t global_index) const; - + /** + * @brief gets outputs' data + * + * This function is a mirror of + * get_output_data(const uint64_t& amount, const uint64_t& index) + * but for a list of outputs rather than just one. + * + * @param amounts an output amount, or as many as offsets + * @param offsets a list of amount-specific output indices + * @param outputs return-by-reference a list of outputs' metadata + + @param allow_partial tbd + */ + void get_output_key(const epee::span &amounts, const std::vector &offsets, std::vector &outputs, bool allow_partial=false) const; + /** * @brief gets specific outputs to mix with * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 2ab5f4951..1398d8c25 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -32,6 +32,8 @@ #include #include +#include "cryptonote_core/cryptonote_tx_utils.h" +#include "cryptonote_protocol/enums.h" #include "string_tools.h" using namespace epee; @@ -947,6 +949,78 @@ namespace cryptonote continue; } + // Get the TX anonymity pool + anonymity_pool tx_anon_pool=anonymity_pool::UNSET; + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx_info[n].tx->vin.size()); + for (const txin_v& txin: tx_info[n].tx->vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + try{ + m_blockchain_storage.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + } + catch (...){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + MERROR_VER("Unexpected input type for transaction" << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + } + + if (tx_ring_outputs.size()!=tx_info[n].tx->vin.size()){ + MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + + if(!get_anonymity_pool(*tx_info[n].tx, tx_ring_outputs, tx_anon_pool)){ + MERROR("Failed to get the anonymity pool for transaction " << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + + if(tx_anon_pool==anonymity_pool::UNSET){ + MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + + tx_info[n].tvc.m_tx_anon_pool=tx_anon_pool; + // Get the pricing_record_height for any offshore TX if (tx_info[n].tvc.m_source_asset != tx_info[n].tvc.m_dest_asset) { // validate that tx uses a recent pr @@ -1145,7 +1219,7 @@ namespace cryptonote } } - if (!rct::verRctSemanticsSimple2(tx_info[n].tx->rct_signatures, tx_info[n].tvc.pr, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_info[n].tvc.m_type, tx_info[n].tvc.m_source_asset, tx_info[n].tvc.m_dest_asset, tx_info[n].tx->amount_burnt, tx_info[n].tx->vout, tx_info[n].tx->vin, hf_version, tx_info[n].tvc.m_collateral, tx_info[n].tvc.m_slippage)) + if (!rct::verRctSemanticsSimple2(tx_info[n].tx->rct_signatures, tx_info[n].tvc.pr, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_info[n].tvc.m_type, tx_info[n].tvc.m_source_asset, tx_info[n].tvc.m_dest_asset, tx_info[n].tx->amount_burnt, tx_info[n].tx->vout, tx_info[n].tx->vin, hf_version, tx_info[n].tvc.m_collateral, tx_info[n].tvc.m_slippage, tx_info[n].tvc.m_tx_anon_pool)) { // 2 tx that used reorged pricing reocord for callateral calculation. if (epee::string_tools::pod_to_hex(tx_info[n].tx_hash) != "e9c0753df108cb9de343d78c3bbdec0cebd56ee5c26c09ecf46dbf8af7838956" diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 013508a7f..45fefcb96 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -2113,4 +2113,171 @@ namespace cryptonote get_block_longhash(pbc, b, p, height, seed_hash, miners); return p; } + + //--------------------------------------------------------------- + //! This function tries to obtain the anonymity pool of a transaction. + //! For each input, we check the ring members and determine if from which pool they are. + //! If there are inputs with different pools, then the whole transaction has a mixed pool. + //! If errors are encountered, then the function returns false and assigns UNSET to the anon_pool. + bool get_anonymity_pool(const transaction& tx, const std::vector>& tx_ring_outputs, anonymity_pool& tx_anon_pool) + { + tx_anon_pool=anonymity_pool::UNSET; + size_t assignments=0; //additional check to ensure we update anon_pool without missing inputs + + //For each input of the transaction, we should have a vector with outputs which form its ring members + if (tx.vin.size()!=tx_ring_outputs.size()){ + tx_anon_pool=anonymity_pool::UNSET; + LOG_ERROR("The number of inputs differs from the number of groups of ring members! Rejecting.."); + return false; + } + + //A transaction cannot have 0 inputs + if (tx.vin.size()==0){ + tx_anon_pool=anonymity_pool::UNSET; + LOG_ERROR("The transaction has no inputs! Rejecting.."); + return false; + } + + for (size_t i = 0; i < tx.vin.size(); i++) { + anonymity_pool pool_current_input=anonymity_pool::UNSET; + if(!get_input_anonymity_pool(tx.vin[i], tx_ring_outputs[i], pool_current_input)){ + tx_anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Failed to get the anonymity pool of input " << i << " ! Rejecting.."); + return false; + } + + //Only Pool 1, Pool 2 and Mixed are valid values for a ring member + if (pool_current_input!=anonymity_pool::MIXED && pool_current_input!=anonymity_pool::POOL_1 && pool_current_input!=anonymity_pool::POOL_2){ + tx_anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Failed to assign a ring member to either Pool 1 or Pool 2 or Mixed! Rejecting.."); + return false; + } + + if (tx_anon_pool==anonymity_pool::UNSET){ + tx_anon_pool=pool_current_input; + assignments+=1; + } else if (tx_anon_pool!=pool_current_input){ + tx_anon_pool=anonymity_pool::MIXED; + assignments+=1; + } else if (tx_anon_pool==pool_current_input){ + assignments+=1; + } + } + + // Make sure each input has been considered when assigning the transaction anonymity pool type + if (assignments!=tx.vin.size()){ + LOG_ERROR("Not all inputs were considered when assigning a pool! Rejecting.."); + tx_anon_pool=anonymity_pool::UNSET; + return false; + } + + // If the transaction anonymity pool is Mixed, Pool 1, and Pool 2, then we can return the value + if (tx_anon_pool==anonymity_pool::MIXED || tx_anon_pool==anonymity_pool::POOL_1 || tx_anon_pool==anonymity_pool::POOL_2){ + if (tx_anon_pool==anonymity_pool::MIXED){ + LOG_PRINT_L2("Mixed anonymity pool found, this should not happen. Transaction will rejected as part of transaction validation"); + } else { + LOG_PRINT_L2("anonymity pool of the input is " << (tx_anon_pool==anonymity_pool::POOL_1 ? "Pool 1" : (tx_anon_pool==anonymity_pool::POOL_2 ? "Pool 2" : "Unknown"))); + } + return true; + } + + //If we've reached this point, then something went wrong + LOG_ERROR("Failed to assign a transaction anonymity pool! Rejecting.."); + tx_anon_pool=anonymity_pool::UNSET; + return false; + } + //--------------------------------------------------------------- + //! This function tries to obtain the anonymity pool of a transaction. + //! For each input, we check the ring members and determine from which pool they are. + //! If there are inputs with different pools, then the whole transaction has a mixed pool, which should lead to transaction rejection as part of the tx validation. + //! If errors are encountered, then the function returns false and assigns UNSET to the anon_pool. + bool get_input_anonymity_pool(const txin_v& txin, const std::vector& ring_outputs, anonymity_pool& anon_pool) + { + anon_pool=anonymity_pool::UNSET; + size_t assignments=0; //additional check to ensure we update anon_pool without missing inputs + + if (txin.type() == typeid(txin_gen)){ + if (ring_outputs.size()==0){ + anon_pool=anonymity_pool::NONE; + return true; + } else { + LOG_ERROR("Coinbase input with ring members found! Rejecting.."); + anon_pool=anonymity_pool::UNSET; + return false; + } + } + + if (txin.type() == typeid(txin_haven_key)){ + if (ring_outputs.size()==0){ + anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Non-coinbase input without ring members found! Rejecting.."); + return false; + } + + std::string source_asset_type = boost::get(txin).asset_type; + if (source_asset_type.size()==0){ + anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Failed to find input asset type! Rejecting.."); + return false; + } + + for (const auto &out: ring_outputs) { + anonymity_pool pool_current_ring_member=anonymity_pool::UNSET; + + //Rules for Pool 1 + //Any output before the supply audit cut-off is considered a member of Pool 1 + if (out.height < SUPPLY_AUDIT_BLOCK_HEIGHT && out.asset_type==source_asset_type){ + pool_current_ring_member=anonymity_pool::POOL_1; + } + //Rules for Pool 2 + if (out.height >= SUPPLY_AUDIT_BLOCK_HEIGHT && out.asset_type==source_asset_type){ + pool_current_ring_member=anonymity_pool::POOL_2; + } + + + // Please add any future logic for new anonymity pools above this point in the code + // Beyond it, the anonymity pool of the current ring member must be set + if (pool_current_ring_member==anonymity_pool::UNSET){ + anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Failed to assign a ring member to anonymity pool! Rejecting.."); + return false; + } + + //Only Pool 1 and Pool 2 are valid values for a ring member + if (pool_current_ring_member!=anonymity_pool::POOL_1 && pool_current_ring_member!=anonymity_pool::POOL_2){ + anon_pool=anonymity_pool::UNSET; + LOG_ERROR("Failed to assign a ring member to either Pool 1 or Pool 2! Rejecting.."); + return false; + } + + if (anon_pool==anonymity_pool::UNSET){ + anon_pool=pool_current_ring_member; + assignments+=1; + } else if (anon_pool!=pool_current_ring_member){ + anon_pool=anonymity_pool::MIXED; + assignments+=1; + } else if (anon_pool==pool_current_ring_member){ + assignments+=1; + } + } + + if (assignments!=ring_outputs.size()){ + LOG_ERROR("Not all ring members were considered when assigning a pool! Rejecting.."); + anon_pool=anonymity_pool::UNSET; + return false; + } + + if (anon_pool==anonymity_pool::MIXED){ + LOG_PRINT_L2("Mixed anonymity pool found, this should not happen. Transaction will rejected as part of transaction validation"); + } else { + LOG_PRINT_L2("Anonymity pool of the input is " << (anon_pool==anonymity_pool::POOL_1 ? "Pool 1" : (anon_pool==anonymity_pool::POOL_2 ? "Pool 2" : "Unknown"))); + } + return true; + } + + //If we've reached this point, then something went wrong + LOG_ERROR("Failed to assign a an input to anonymity pool! Rejecting.."); + anon_pool=anonymity_pool::UNSET; + return false; + } } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 98e52bb88..59b300a90 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -30,6 +30,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once +#include "blockchain_db/blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include #include @@ -255,6 +256,8 @@ namespace cryptonote uint64_t get_xusd_amount(const uint64_t amount, const std::string& amount_asset_type, const offshore::pricing_record& pr, const transaction_type tx_type, uint8_t hf_version); // Get onshore amount in XHV, not XUSD uint64_t get_xhv_amount(const uint64_t xusd_amount, const offshore::pricing_record& pr, const transaction_type tx_type, uint8_t hf_version); + bool get_anonymity_pool(const transaction& tx, const std::vector>& tx_ring_outputs, anonymity_pool& tx_anon_pool); + bool get_input_anonymity_pool(const txin_v& txin, const std::vector& ring_outputs, anonymity_pool& anon_pool); } BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 5) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index f55242b75..25f36ac8a 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -2622,7 +2622,7 @@ namespace cryptonote } // make sure proof-of-value still holds - if (!rct::verRctSemanticsSimple2(tx.rct_signatures, bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage)) + if (!rct::verRctSemanticsSimple2(tx.rct_signatures, bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, anonymity_pool::UNSET)) { LOG_PRINT_L2(" transaction proof-of-value is now invalid for tx " << sorted_it->second); continue; diff --git a/src/cryptonote_protocol/enums.h b/src/cryptonote_protocol/enums.h index 199073165..d8b6443e7 100644 --- a/src/cryptonote_protocol/enums.h +++ b/src/cryptonote_protocol/enums.h @@ -45,12 +45,24 @@ namespace cryptonote enum class transaction_type { UNSET = 0, - TRANSFER, - OFFSHORE, - ONSHORE, - OFFSHORE_TRANSFER, - XUSD_TO_XASSET, - XASSET_TO_XUSD, - XASSET_TRANSFER + TRANSFER, //!< Transfer of XHV + OFFSHORE, //!< Conversion of XHV to XUSD + ONSHORE, //!< Conversion of XUSD to XHV + OFFSHORE_TRANSFER, //!< Transfer of XUSD + XUSD_TO_XASSET, //!< Conversion of XUSD to non-XHV asset, such as XAU, XAG, XBTC + XASSET_TO_XUSD, //!< Conversion assets like XAU, XAG, XBTC (not XHV) to XUSD + XASSET_TRANSFER //!< Transfer of asset different from XHV or XUSD, such as XAU, XAG, XBTC + }; + //! anonymity pools of the ring signatures. + //! The supply audit requires that the ring members are split in two distinct anonymity pools, depending on + //! whether or not they preceed the block which marks the start of the supply audit. + //! Transactions should not contain ring members from different anonymity pools. + //! Different validation rules will apply, depeneding on the anonymity pool (for example transaction amount might be forced to be revealed for anonnymity pool one) + enum class anonymity_pool { + UNSET = 0, //!< anonymity pool not yet initialized + NONE, //!< Output is coinbase, so does not belong to any anonymity pool. + MIXED, //!< The ring signature contains members from multiple anonymity pools. This is not allowed and such transactions should be rejected. + POOL_1, //!< All ring members are from before the supply audit + POOL_2 //!< All ring members are from after the supply audit }; } diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index ff36172f5..286019c31 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -30,6 +30,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "cryptonote_protocol/enums.h" #include "misc_log_ex.h" #include "misc_language.h" #include "common/perf_timer.h" @@ -1658,7 +1659,8 @@ namespace rct { const std::vector &vin, const uint8_t version, const uint64_t amount_collateral, - const uint64_t amount_slippage + const uint64_t amount_slippage, + const cryptonote::anonymity_pool tx_anon_pool ){ try diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index 2e199b913..e657f1d73 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -138,7 +138,7 @@ namespace rct { rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const cryptonote::transaction_type tx_type, const std::string& in_asset_type, const std::vector &inamounts, const std::vector& inamounts_col_indices, const std::vector &outamounts, const std::map>>& outamounts_features, const xmr_amount txnFee, const xmr_amount txnOffshoreFee, const xmr_amount onshore_col_amount, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector & index, ctkeyV &outSk, uint8_t tx_version, const offshore::pricing_record& pr, const uint64_t& conversion_rate, const uint32_t hf_version, const RCTConfig &rct_config, hw::device &hwdev); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } - bool verRctSemanticsSimple2(const rctSig & rv, const offshore::pricing_record& pr, const uint64_t& conversion_rate, const uint64_t& fee_conversion_rate, const uint64_t& tx_fee_conversion_rate, const cryptonote::transaction_type& type, const std::string& strSource, const std::string& strDest, uint64_t amount_burnt, const std::vector &vout, const std::vector &vin, const uint8_t version, const uint64_t amount_collateral, const uint64_t amount_slippage); + bool verRctSemanticsSimple2(const rctSig & rv, const offshore::pricing_record& pr, const uint64_t& conversion_rate, const uint64_t& fee_conversion_rate, const uint64_t& tx_fee_conversion_rate, const cryptonote::transaction_type& type, const std::string& strSource, const std::string& strDest, uint64_t amount_burnt, const std::vector &vout, const std::vector &vin, const uint8_t version, const uint64_t amount_collateral, const uint64_t amount_slippage, const cryptonote::anonymity_pool tx_anon_pool); bool verRctSemanticsSimple(const rctSig & rv, const offshore::pricing_record& pr, const cryptonote::transaction_type& type, const std::string& strSource, const std::string& strDest); bool verRctNonSemanticsSimple(const rctSig & rv); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev); From 53e7e7c4e346532d82f04e7578bbd45c3ebd9993 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Mon, 9 Sep 2024 11:51:44 +0000 Subject: [PATCH 03/47] additional anonymity pool checks --- .gitignore | 2 + src/cryptonote_core/blockchain.cpp | 16 +++++++ src/cryptonote_core/tx_pool.cpp | 67 +++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9f62575e5..ace3b49b8 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,5 @@ nbproject __pycache__/ *.pyc *.log +compile_commands.json +.cache/ diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index b7b016e1b..0c1c0aa1f 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -38,6 +38,7 @@ #include "include_base_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "offshore/pricing_record.h" #include "tx_pool.h" #include "blockchain.h" #include "blockchain_db/blockchain_db.h" @@ -5530,6 +5531,21 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& bvc.m_verifivation_failed = true; goto leave; } + + uint64_t conversion_rate = COIN; + uint64_t fee_conversion_rate = COIN; + uint64_t tx_fee_conversion_rate = COIN; + uint64_t collateral = 0; + uint64_t slippage = 0; + offshore::pricing_record pr_empty; + if (hf_version >= HF_VERSION_HAVEN2) { + if (!rct::verRctSemanticsSimple2(tx.rct_signatures, pr_empty, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, tx_anon_pool)) + { + LOG_PRINT_L2(" transaction proof-of-value is now invalid for tx " << tx.hash); + bvc.m_verifivation_failed = true; + goto leave; + } + } } TIME_MEASURE_FINISH(cc); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 25f36ac8a..9fdbe4a42 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -2173,7 +2173,61 @@ namespace cryptonote return std::get<0>(i->second); } } - bool ret = m_blockchain.check_tx_inputs(get_tx(), max_used_block_height, max_used_block_id, tvc, kept_by_block); + + transaction tx=get_tx(); + bool ret = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, tvc, kept_by_block); + + // Get the TX anonymity pool + anonymity_pool tx_anon_pool=anonymity_pool::UNSET; + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx.vin.size()); + + for (const txin_v& txin: tx.vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + LOG_ERROR("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + ret = false; + } + try{ + m_blockchain.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + LOG_ERROR("Output does not exist! amount = " << tx_input.amount); + ret = false; + } + } + catch (...){ + LOG_ERROR("Output does not exist! amount = " << tx_input.amount); + ret = false; + } + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + LOG_ERROR("Unexpected input type for transaction" << txid); + ret=false; + } + } + + if (tx_ring_outputs.size()!=tx.vin.size()){ + LOG_ERROR("Transaction has more inputs than number of ring member groups: " << txid); + ret=false; + } + + if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ + LOG_ERROR("Failed to get the anonymity pool for transaction " << txid); + ret=false; + } + + if(tx_anon_pool==anonymity_pool::UNSET){ + LOG_ERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << txid); + ret=false; + } + + tvc.m_tx_anon_pool=tx_anon_pool; + if (!kept_by_block) m_input_cache.insert(std::make_pair(txid, std::make_tuple(ret, tvc, max_used_block_height, max_used_block_id))); return ret; @@ -2543,6 +2597,15 @@ namespace cryptonote continue; } + tx_verification_context tvc; + + + if(!check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, sorted_it->second, meta.max_used_block_height, meta.max_used_block_id, tvc, meta.kept_by_block)) + { + LOG_PRINT_L2(" transaction has invalid anonymity pool " << sorted_it->second); + continue; + } + uint64_t conversion_this_tx_xhv = 0; if (source != dest) { @@ -2622,7 +2685,7 @@ namespace cryptonote } // make sure proof-of-value still holds - if (!rct::verRctSemanticsSimple2(tx.rct_signatures, bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, anonymity_pool::UNSET)) + if (!rct::verRctSemanticsSimple2(tx.rct_signatures, bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, tvc.m_tx_anon_pool)) { LOG_PRINT_L2(" transaction proof-of-value is now invalid for tx " << sorted_it->second); continue; From 016cd4f9c4c1f50764f348f895cb2f10c09a998f Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Mon, 9 Sep 2024 16:13:43 +0000 Subject: [PATCH 04/47] anonymity pool not applicable before HF24 --- src/cryptonote_core/blockchain.cpp | 90 +++++++++++--------- src/cryptonote_core/cryptonote_core.cpp | 108 ++++++++++++------------ src/cryptonote_core/tx_pool.cpp | 84 +++++++++--------- src/cryptonote_protocol/enums.h | 1 + 4 files changed, 150 insertions(+), 133 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 0c1c0aa1f..e43ad6f14 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -36,6 +36,7 @@ #include #include +#include "cryptonote_protocol/enums.h" #include "include_base_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "offshore/pricing_record.h" @@ -5352,62 +5353,67 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& goto leave; } + // Get the TX anonymity pool anonymity_pool tx_anon_pool=anonymity_pool::UNSET; - std::vector> tx_ring_outputs; - tx_ring_outputs.reserve(tx.vin.size()); - for (const txin_v& txin: tx.vin){ - std::vector ring_outputs; - if (txin.type() == typeid(txin_haven_key)){ - txin_haven_key tx_input=boost::get(txin); - std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); - - if (absolute_offsets.size()!=tx_input.key_offsets.size()){ - MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); - bvc.m_verifivation_failed = true; - goto leave; - } - - try{ - get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); - if (absolute_offsets.size() != ring_outputs.size()){ + if (hf_version>=HF_VERSION_BURN) { + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx.vin.size()); + for (const txin_v& txin: tx.vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + bvc.m_verifivation_failed = true; + goto leave; + } + + try{ + get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + bvc.m_verifivation_failed = true; + goto leave; + } + } + catch (...){ MERROR_VER("Output does not exist! amount = " << tx_input.amount); bvc.m_verifivation_failed = true; goto leave; } - } - catch (...){ - MERROR_VER("Output does not exist! amount = " << tx_input.amount); + + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + MERROR_VER("Unexpected input type for transaction" << tx_id); bvc.m_verifivation_failed = true; goto leave; } - - tx_ring_outputs.push_back(ring_outputs); - } else if (txin.type() == typeid(txin_gen)){ - tx_ring_outputs.push_back(ring_outputs); - } else { - MERROR_VER("Unexpected input type for transaction" << tx_id); + } + + if (tx_ring_outputs.size()!=tx.vin.size()){ + MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_id); bvc.m_verifivation_failed = true; goto leave; } - } - if (tx_ring_outputs.size()!=tx.vin.size()){ - MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_id); - bvc.m_verifivation_failed = true; - goto leave; - } - - if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ - MERROR("Failed to get the anonymity pool for transaction " << tx_id); - bvc.m_verifivation_failed = true; - goto leave; - } + if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ + MERROR("Failed to get the anonymity pool for transaction " << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } - if(tx_anon_pool==anonymity_pool::UNSET){ - MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_id); - bvc.m_verifivation_failed = true; - goto leave; + if(tx_anon_pool==anonymity_pool::UNSET){ + MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_id); + bvc.m_verifivation_failed = true; + goto leave; + } + } else if (hf_version> tx_ring_outputs; - tx_ring_outputs.reserve(tx_info[n].tx->vin.size()); - for (const txin_v& txin: tx_info[n].tx->vin){ - std::vector ring_outputs; - if (txin.type() == typeid(txin_haven_key)){ - txin_haven_key tx_input=boost::get(txin); - std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); - - if (absolute_offsets.size()!=tx_input.key_offsets.size()){ - MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); - set_semantics_failed(tx_info[n].tx_hash); - tx_info[n].tvc.m_verifivation_failed = true; - tx_info[n].result = false; - continue; - } - try{ - m_blockchain_storage.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); - if (absolute_offsets.size() != ring_outputs.size()){ + if (hf_version>=HF_VERSION_BURN) { + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx_info[n].tx->vin.size()); + for (const txin_v& txin: tx_info[n].tx->vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + MERROR_VER("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + try{ + m_blockchain_storage.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + MERROR_VER("Output does not exist! amount = " << tx_input.amount); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + } + catch (...){ MERROR_VER("Output does not exist! amount = " << tx_input.amount); set_semantics_failed(tx_info[n].tx_hash); tx_info[n].tvc.m_verifivation_failed = true; tx_info[n].result = false; - continue; + continue; } - } - catch (...){ - MERROR_VER("Output does not exist! amount = " << tx_input.amount); + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + MERROR_VER("Unexpected input type for transaction" << tx_info[n].tx_hash); set_semantics_failed(tx_info[n].tx_hash); tx_info[n].tvc.m_verifivation_failed = true; tx_info[n].result = false; - continue; + continue; } - tx_ring_outputs.push_back(ring_outputs); - } else if (txin.type() == typeid(txin_gen)){ - tx_ring_outputs.push_back(ring_outputs); - } else { - MERROR_VER("Unexpected input type for transaction" << tx_info[n].tx_hash); + } + + if (tx_ring_outputs.size()!=tx_info[n].tx->vin.size()){ + MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_info[n].tx_hash); set_semantics_failed(tx_info[n].tx_hash); tx_info[n].tvc.m_verifivation_failed = true; tx_info[n].result = false; - continue; + continue; } - } - - if (tx_ring_outputs.size()!=tx_info[n].tx->vin.size()){ - MERROR_VER("Transaction has more inputs than number of ring member groups: " << tx_info[n].tx_hash); - set_semantics_failed(tx_info[n].tx_hash); - tx_info[n].tvc.m_verifivation_failed = true; - tx_info[n].result = false; - continue; - } - if(!get_anonymity_pool(*tx_info[n].tx, tx_ring_outputs, tx_anon_pool)){ - MERROR("Failed to get the anonymity pool for transaction " << tx_info[n].tx_hash); - set_semantics_failed(tx_info[n].tx_hash); - tx_info[n].tvc.m_verifivation_failed = true; - tx_info[n].result = false; - continue; - } + if(!get_anonymity_pool(*tx_info[n].tx, tx_ring_outputs, tx_anon_pool)){ + MERROR("Failed to get the anonymity pool for transaction " << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } - if(tx_anon_pool==anonymity_pool::UNSET){ - MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_info[n].tx_hash); - set_semantics_failed(tx_info[n].tx_hash); - tx_info[n].tvc.m_verifivation_failed = true; - tx_info[n].result = false; - continue; + if(tx_anon_pool==anonymity_pool::UNSET){ + MERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << tx_info[n].tx_hash); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + continue; + } + } else if (hf_version #include +#include #include #include #include "tx_pool.h" +#include "cryptonote_protocol/enums.h" #include "cryptonote_tx_utils.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_config.h" @@ -2176,56 +2178,60 @@ namespace cryptonote transaction tx=get_tx(); bool ret = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, tvc, kept_by_block); - + uint64_t hf_version=m_blockchain.get_current_hard_fork_version(); // Get the TX anonymity pool anonymity_pool tx_anon_pool=anonymity_pool::UNSET; - std::vector> tx_ring_outputs; - tx_ring_outputs.reserve(tx.vin.size()); - - for (const txin_v& txin: tx.vin){ - std::vector ring_outputs; - if (txin.type() == typeid(txin_haven_key)){ - txin_haven_key tx_input=boost::get(txin); - std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); - if (absolute_offsets.size()!=tx_input.key_offsets.size()){ - LOG_ERROR("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); - ret = false; - } - try{ - m_blockchain.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); - if (absolute_offsets.size() != ring_outputs.size()){ + + if (hf_version>=HF_VERSION_BURN) { + std::vector> tx_ring_outputs; + tx_ring_outputs.reserve(tx.vin.size()); + + for (const txin_v& txin: tx.vin){ + std::vector ring_outputs; + if (txin.type() == typeid(txin_haven_key)){ + txin_haven_key tx_input=boost::get(txin); + std::vector absolute_offsets = relative_output_offsets_to_absolute(tx_input.key_offsets); + if (absolute_offsets.size()!=tx_input.key_offsets.size()){ + LOG_ERROR("Failed to obtain absolute ring offsets! amount = " << tx_input.amount); + ret = false; + } + try{ + m_blockchain.get_output_key(epee::span(&tx_input.amount, 1), absolute_offsets, ring_outputs, true); + if (absolute_offsets.size() != ring_outputs.size()){ + LOG_ERROR("Output does not exist! amount = " << tx_input.amount); + ret = false; + } + } + catch (...){ LOG_ERROR("Output does not exist! amount = " << tx_input.amount); ret = false; } + tx_ring_outputs.push_back(ring_outputs); + } else if (txin.type() == typeid(txin_gen)){ + tx_ring_outputs.push_back(ring_outputs); + } else { + LOG_ERROR("Unexpected input type for transaction" << txid); + ret=false; } - catch (...){ - LOG_ERROR("Output does not exist! amount = " << tx_input.amount); - ret = false; - } - tx_ring_outputs.push_back(ring_outputs); - } else if (txin.type() == typeid(txin_gen)){ - tx_ring_outputs.push_back(ring_outputs); - } else { - LOG_ERROR("Unexpected input type for transaction" << txid); - ret=false; } - } - if (tx_ring_outputs.size()!=tx.vin.size()){ - LOG_ERROR("Transaction has more inputs than number of ring member groups: " << txid); - ret=false; - } + if (tx_ring_outputs.size()!=tx.vin.size()){ + LOG_ERROR("Transaction has more inputs than number of ring member groups: " << txid); + ret=false; + } - if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ - LOG_ERROR("Failed to get the anonymity pool for transaction " << txid); - ret=false; - } + if(!get_anonymity_pool(tx, tx_ring_outputs, tx_anon_pool)){ + LOG_ERROR("Failed to get the anonymity pool for transaction " << txid); + ret=false; + } - if(tx_anon_pool==anonymity_pool::UNSET){ - LOG_ERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << txid); - ret=false; + if(tx_anon_pool==anonymity_pool::UNSET){ + LOG_ERROR("Failed to get the anonymity pool (has value UNSET) for transaction " << txid); + ret=false; + } + } else if(hf_version Date: Mon, 9 Sep 2024 16:41:31 +0000 Subject: [PATCH 05/47] additional anon pool checks, error messages --- src/cryptonote_core/tx_pool.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 2846f54c4..71dd2c99b 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -2608,7 +2608,7 @@ namespace cryptonote if(!check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, sorted_it->second, meta.max_used_block_height, meta.max_used_block_id, tvc, meta.kept_by_block)) { - LOG_PRINT_L2(" transaction has invalid anonymity pool " << sorted_it->second); + LOG_PRINT_L2(" transaction has invalid inputs or anonymity pool " << sorted_it->second); continue; } @@ -2697,6 +2697,20 @@ namespace cryptonote continue; } } + } else { + // make sure proof-of-value still holds for transfers after HF_VERSION_BURN + if (hf_version>HF_VERSION_BURN){ + uint64_t slippage = 0; + uint64_t collateral = 0; + uint64_t conversion_rate = COIN; + uint64_t fee_conversion_rate = COIN; + uint64_t tx_fee_conversion_rate = COIN; + if (!rct::verRctSemanticsSimple2(tx.rct_signatures, bl.pricing_record, conversion_rate, fee_conversion_rate, tx_fee_conversion_rate, tx_type, source, dest, tx.amount_burnt, tx.vout, tx.vin, hf_version, collateral, slippage, tvc.m_tx_anon_pool)) + { + LOG_PRINT_L2(" transaction proof-of-value is now invalid for tx " << sorted_it->second); + continue; + } + } } bl.tx_hashes.push_back(sorted_it->second); From 8810408dd0ab3f54d8236b9a08a240ecb5a11669 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Tue, 10 Sep 2024 11:40:16 +0000 Subject: [PATCH 06/47] add new RCT type for the supply Audit --- src/blockchain_db/blockchain_db.cpp | 3 ++- .../cryptonote_boost_serialization.h | 6 ++--- .../cryptonote_format_utils.cpp | 5 ++-- src/cryptonote_config.h | 1 + src/cryptonote_core/blockchain.cpp | 24 +++++++++++++++---- src/cryptonote_core/cryptonote_core.cpp | 16 +++++++++++-- src/cryptonote_core/tx_verification_utils.cpp | 1 + src/device/device_ledger.cpp | 4 ++-- src/multisig/multisig_tx_builder_ringct.cpp | 2 +- src/ringct/rctSigs.cpp | 18 +++++++------- src/ringct/rctTypes.cpp | 3 +++ src/ringct/rctTypes.h | 15 ++++++------ src/wallet/wallet2.cpp | 5 ++-- 13 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 86c04780b..7a00fa5d3 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -28,6 +28,7 @@ #include +#include "ringct/rctTypes.h" #include "string_tools.h" #include "blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -260,7 +261,7 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { - if (tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus) { + if (tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus || tx.rct_signatures.type == rct::RCTTypeSupplyAudit) { amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time, tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL); } else { std::string output_asset_type; diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 5dd4b176e..41e54d1a0 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -391,7 +391,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeCLSAGN && x.type != rct::RCTTypeHaven2 && x.type != rct::RCTTypeHaven3 && x.type != rct::RCTTypeBulletproofPlus) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeCLSAGN && x.type != rct::RCTTypeHaven2 && x.type != rct::RCTTypeHaven3 && x.type != rct::RCTTypeBulletproofPlus&& x.type != rct::RCTTypeSupplyAudit) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -464,7 +464,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeCLSAGN && x.type != rct::RCTTypeHaven2 && x.type != rct::RCTTypeHaven3 && x.type != rct::RCTTypeBulletproofPlus) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeCLSAGN && x.type != rct::RCTTypeHaven2 && x.type != rct::RCTTypeHaven3 && x.type != rct::RCTTypeBulletproofPlus && x.type != rct::RCTTypeSupplyAudit) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -521,7 +521,7 @@ namespace boost a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeCLSAGN || x.type == rct::RCTTypeHaven2 || x.type == rct::RCTTypeHaven3 || x.type == rct::RCTTypeBulletproofPlus) + if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeCLSAGN || x.type == rct::RCTTypeHaven2 || x.type == rct::RCTTypeHaven3 || x.type == rct::RCTTypeBulletproofPlus || x.type == rct::RCTTypeSupplyAudit) a & x.p.pseudoOuts; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index b5af122eb..2ebf03a48 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -31,6 +31,7 @@ #include #include +#include "ringct/rctTypes.h" #include "wipeable_string.h" #include "string_tools.h" #include "string_tools_lexical.h" @@ -104,7 +105,7 @@ namespace cryptonote ge_p1p1_to_p3(&A2, &tmp3); ge_p3_tobytes(&AB, &A2); } - +//TO-DO## uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { const rct::rctSig &rv = tx.rct_signatures; @@ -485,7 +486,7 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits::max(), "get_pruned_transaction_weight does not support v1 txes"); - CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus, + CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus || tx.rct_signatures.type == rct::RCTTypeSupplyAudit, std::numeric_limits::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_haven_key), std::numeric_limits::max(), "invalid vin in get_pruned_transaction_weight"); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 786ba6870..f40ea5963 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -268,6 +268,7 @@ #define HF_VERSION_SUPPLY_AUDIT 25 #define SUPPLY_AUDIT_BLOCK_HEIGHT ((uint64_t)1656720) +#define HF_VERSION_SUPPLY_AUDIT_END 26 #define STAGENET_VERSION 0x0e #define TESTNET_VERSION 0x1b diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index e43ad6f14..27d03dd4f 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -40,6 +40,7 @@ #include "include_base_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "offshore/pricing_record.h" +#include "ringct/rctTypes.h" #include "tx_pool.h" #include "blockchain.h" #include "blockchain_db/blockchain_db.h" @@ -3944,8 +3945,20 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } } - - if (hf_version >= HF_VERSION_BULLETPROOF_PLUS) { + + if (hf_version >= HF_VERSION_SUPPLY_AUDIT_END) { + // After the supply audit, only rct::RCTTypeHaven3 txs are accepted + if (tx.rct_signatures.type != rct::RCTTypeBulletproofPlus) { + tvc.m_verifivation_failed = true; + return false; + } + } else if (hf_version >= HF_VERSION_SUPPLY_AUDIT) { + // Durring the supply Audit, both Audit transactions and BPP transactions are allowed + if (tx.rct_signatures.type != rct::RCTTypeBulletproofPlus && tx.rct_signatures.type != rct::RCTTypeSupplyAudit) { + tvc.m_verifivation_failed = true; + return false; + } + } else if (hf_version >= HF_VERSION_BULLETPROOF_PLUS) { // only accept rct::RCTTypeHaven3 txs after Haven3 fork. if (tx.rct_signatures.type != rct::RCTTypeBulletproofPlus) { tvc.m_verifivation_failed = true; @@ -4049,7 +4062,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const uint8_t hf_version, } } } - else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeCLSAGN || rv.type == rct::RCTTypeHaven2 || rv.type == rct::RCTTypeHaven3 || rv.type == rct::RCTTypeBulletproofPlus) + else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeCLSAGN || rv.type == rct::RCTTypeHaven2 || rv.type == rct::RCTTypeHaven3 || rv.type == rct::RCTTypeBulletproofPlus || rv.type == rct::RCTTypeSupplyAudit) { CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); @@ -4090,7 +4103,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const uint8_t hf_version, } } } - else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeCLSAGN || rv.type == rct::RCTTypeHaven2 || rv.type == rct::RCTTypeHaven3 || rv.type == rct::RCTTypeBulletproofPlus) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeCLSAGN || rv.type == rct::RCTTypeHaven2 || rv.type == rct::RCTTypeHaven3 || rv.type == rct::RCTTypeBulletproofPlus || rv.type == rct::RCTTypeSupplyAudit) { if (!tx.pruned) { @@ -4374,7 +4387,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, CHECK_AND_ASSERT_MES(*pmax_used_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height(), false, "Transaction spends at least one output which is too young"); } - + //TO-DO## // Warn that new RCT types are present, and thus the cache is not being used effectively static constexpr const std::uint8_t RCT_CACHE_TYPE = rct::RCTTypeBulletproofPlus; if (tx.rct_signatures.type > RCT_CACHE_TYPE) @@ -4421,6 +4434,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeHaven2: case rct::RCTTypeHaven3: case rct::RCTTypeBulletproofPlus: + case rct::RCTTypeSupplyAudit: { if (!ver_rct_non_semantics_simple_cached(tx, hf_version, pubkeys, m_rct_ver_cache, RCT_CACHE_TYPE)) { diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index dd933059b..4cab8a440 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1165,6 +1165,17 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; + case rct::RCTTypeSupplyAudit: + if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) + { + MERROR_VER("Bulletproof_plus does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; default: MERROR_VER("Unknown rct type: " << rv.type); set_semantics_failed(tx_info[n].tx_hash); @@ -1187,10 +1198,11 @@ namespace cryptonote tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAGN && tx_info[n].tx->rct_signatures.type != rct::RCTTypeHaven2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeHaven3 && - tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus) + tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus && + tx_info[n].tx->rct_signatures.type != rct::RCTTypeSupplyAudit) continue; - if (tx_info[n].tx->rct_signatures.type == rct::RCTTypeHaven2 || tx_info[n].tx->rct_signatures.type == rct::RCTTypeHaven3 || tx_info[n].tx->rct_signatures.type == rct::RCTTypeBulletproofPlus) { + if (tx_info[n].tx->rct_signatures.type == rct::RCTTypeHaven2 || tx_info[n].tx->rct_signatures.type == rct::RCTTypeHaven3 || tx_info[n].tx->rct_signatures.type == rct::RCTTypeBulletproofPlus || tx_info[n].tx->rct_signatures.type == rct::RCTTypeSupplyAudit) { // NEAC: Get conversion rates for TX and for fees uint64_t conversion_rate = COIN; diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp index ec907fda2..0217a5af1 100644 --- a/src/cryptonote_core/tx_verification_utils.cpp +++ b/src/cryptonote_core/tx_verification_utils.cpp @@ -130,6 +130,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. + //TO-DO## const bool untested_tx = tx.version > HAVEN_TYPES_TRANSACTION_VERSION || tx.rct_signatures.type > rct::RCTTypeBulletproofPlus; VER_ASSERT(!untested_tx, "Unknown TX type. Make sure RCT cache works correctly with this type and then enable it in the code here."); diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index aee56fe90..87e6e6a5d 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -1928,7 +1928,7 @@ namespace hw { // ====== Aout, Bout, AKout, C, v, k ====== kv_offset = data_offset; - if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus || type==rct::RCTTypeSupplyAudit) { C_offset = kv_offset+ (8)*outputs_size; } else { C_offset = kv_offset+ (32+32)*outputs_size; @@ -1945,7 +1945,7 @@ namespace hw { offset = set_command_header(INS_VALIDATE, 0x02, i+1); //options this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ; - this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus)?0x02:0x00; + this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus || type==rct::RCTTypeSupplyAudit)?0x02:0x00; offset += 1; //is_subaddress this->buffer_send[offset] = outKeys.is_subaddress; diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index e634b3f32..fba2ca5ac 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -621,7 +621,7 @@ static bool set_tx_rct_signatures( // rct_signatures component of tx rct::rctSig rv{}; - + //TO-DO## // set misc. fields switch (rct_config.bp_version) { diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 286019c31..d70715336 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -46,6 +46,7 @@ #include "bulletproofs.cc" #include "offshore/pricing_record.cpp" +#include "ringct/rctTypes.h" using namespace crypto; using namespace std; @@ -708,7 +709,7 @@ namespace rct { kv.push_back(p.t); } } - else if (rv.type == RCTTypeBulletproofPlus) + else if (rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit) { kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size()); for (const auto &p: rv.p.bulletproofs_plus) @@ -1143,7 +1144,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit); } //set txn fee @@ -1208,7 +1209,7 @@ namespace rct { for (size_t n = 0; n < mixRing.size(); ++n) { CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); } - + //TO-DO## rctSig rv; if (bulletproof_or_plus) { @@ -1323,6 +1324,7 @@ namespace rct { sc_add(rv.maskSums[1].bytes, rv.maskSums[1].bytes, masks[i].bytes); } + //RCTTypeAudit should not be used for conversions, only for transfers if (rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus) { // save the collateral output mask for offshore if (tx_type == tt::OFFSHORE && outamounts_features.at(i).second.first) { @@ -1675,7 +1677,7 @@ namespace rct { std::vector collateral_change_indices = {}; //size_t max_non_bp_proofs = 0, offset = 0; using tt = cryptonote::transaction_type; - CHECK_AND_ASSERT_MES(rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus, false, "verRctSemanticsSimple2 called on non-Haven2 rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit, false, "verRctSemanticsSimple2 called on non-Haven2 rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); @@ -2428,7 +2430,7 @@ namespace rct { { PERF_TIMER(verRctNonSemanticsSimple); - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit, false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); @@ -2500,7 +2502,7 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; @@ -2524,14 +2526,14 @@ namespace rct { } xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit, false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeCLSAGN || rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus|| rv.type == RCTTypeSupplyAudit); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C; diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 9ef700479..55f7e3c31 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -200,6 +200,7 @@ namespace rct { case RCTTypeHaven2: case RCTTypeHaven3: case RCTTypeBulletproofPlus: + case RCTTypeSupplyAudit: return true; default: return false; @@ -227,6 +228,7 @@ namespace rct { switch (type) { case RCTTypeBulletproofPlus: + case RCTTypeSupplyAudit: return true; default: return false; @@ -254,6 +256,7 @@ namespace rct { case RCTTypeHaven2: case RCTTypeHaven3: case RCTTypeBulletproofPlus: + case RCTTypeSupplyAudit: return true; default: return false; diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index ef7a5d229..056aa9007 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -308,6 +308,7 @@ namespace rct { RCTTypeHaven2 = 7, // Add public mask sum terms, remove extraneous fields (txnFee_usd,txnFee_xasset,txnOffshoreFee_usd,txnOffshoreFee_xasset) RCTTypeHaven3 = 8, // Add public mask sum term for collateral RCTTypeBulletproofPlus = 9, + RCTTypeSupplyAudit = 10, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -344,7 +345,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeBulletproofPlus) + if (type != RCTTypeBulletproofPlus && type != RCTTypeSupplyAudit) return serialize_rctsig_base_old(ar, inputs, outputs); VARINT_FIELD(txnFee) VARINT_FIELD(txnOffshoreFee) @@ -580,9 +581,9 @@ namespace rct { return false; if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2 && type != RCTTypeHaven3 && type != RCTTypeBulletproofPlus) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeCLSAGN && type != RCTTypeHaven2 && type != RCTTypeHaven3 && type != RCTTypeBulletproofPlus && type != RCTTypeSupplyAudit) return false; - if (type == RCTTypeBulletproofPlus) + if (type == RCTTypeBulletproofPlus || type == RCTTypeSupplyAudit) { uint32_t nbp = bulletproofs_plus.size(); VARINT_FIELD(nbp) @@ -639,7 +640,7 @@ namespace rct { ar.end_array(); } - if (type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus) + if (type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus || type == RCTTypeSupplyAudit) { ar.tag("CLSAGs"); ar.begin_array(); @@ -730,7 +731,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus || type == RCTTypeSupplyAudit) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -762,12 +763,12 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus || type == RCTTypeSupplyAudit ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeCLSAGN || type == RCTTypeHaven2 || type == RCTTypeHaven3 || type == RCTTypeBulletproofPlus || type == RCTTypeSupplyAudit ? p.pseudoOuts : pseudoOuts; } BEGIN_SERIALIZE_OBJECT() diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3db449534..baf63b6ec 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1848,6 +1848,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & case rct::RCTTypeHaven2: case rct::RCTTypeHaven3: case rct::RCTTypeBulletproofPlus: + case rct::RCTTypeSupplyAudit: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); @@ -12705,7 +12706,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::secret_key scalar1; crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus || tx.rct_signatures.type == rct::RCTTypeSupplyAudit); const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); @@ -13395,7 +13396,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::secret_key shared_secret; crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeCLSAGN || tx.rct_signatures.type == rct::RCTTypeHaven2 || tx.rct_signatures.type == rct::RCTTypeHaven3 || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus || tx.rct_signatures.type == rct::RCTTypeSupplyAudit); amount = rct::h2d(ecdh_info.amount); } total += amount; From fa458b4e923504d46a60748b4bf0d9c7a4dbe6b9 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Tue, 10 Sep 2024 15:22:37 +0000 Subject: [PATCH 07/47] tx validation checks --- src/ringct/rctSigs.cpp | 33 ++++++++++++++++++++++++++++++++- src/ringct/rctTypes.h | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index d70715336..65beeed16 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1677,6 +1677,7 @@ namespace rct { std::vector collateral_change_indices = {}; //size_t max_non_bp_proofs = 0, offset = 0; using tt = cryptonote::transaction_type; + using anon = cryptonote::anonymity_pool; CHECK_AND_ASSERT_MES(rv.type == RCTTypeHaven2 || rv.type == RCTTypeHaven3 || rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit, false, "verRctSemanticsSimple2 called on non-Haven2 rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); @@ -1694,7 +1695,37 @@ namespace rct { CHECK_AND_ASSERT_MES(rv.maskSums.size() == 2, false, "maskSums size is not 2"); CHECK_AND_ASSERT_MES(std::find(offshore::ASSET_TYPES.begin(), offshore::ASSET_TYPES.end(), strSource) != offshore::ASSET_TYPES.end(), false, "Invalid Source Asset!"); CHECK_AND_ASSERT_MES(std::find(offshore::ASSET_TYPES.begin(), offshore::ASSET_TYPES.end(), strDest) != offshore::ASSET_TYPES.end(), false, "Invalid Dest Asset!"); - CHECK_AND_ASSERT_MES(tx_type != tt::UNSET, false, "Invalid transaction type."); + CHECK_AND_ASSERT_MES(tx_type != tt::UNSET, false, "Transaction type is not set."); + //### Anonymity pool sanity checks ##### + CHECK_AND_ASSERT_MES(tx_anon_pool != anon::UNSET, false, "Transaction anonymity pool type is not set."); + CHECK_AND_ASSERT_MES(tx_anon_pool != anon::MIXED, false, "Transaction has a mixed anonymity pool which is not permited."); + CHECK_AND_ASSERT_MES(version < HF_VERSION_BURN || tx_anon_pool == anon::POOL_1 || tx_anon_pool == anon::POOL_2, false, "Transaction anonymity pool should be either Pool 1 or Pool 2 during the supply audit"); + //### Supply audit sanity checks ##### + //This check ensures old funds can't be spent after the audit is over. + const bool before_supply_audit = (version < HF_VERSION_SUPPLY_AUDIT); + const bool during_supply_audit = (version >=HF_VERSION_SUPPLY_AUDIT && version < HF_VERSION_SUPPLY_AUDIT_END); + const bool after_supply_audit = (version >= HF_VERSION_SUPPLY_AUDIT_END); + int num_epochs=(before_supply_audit? 1 : 0)+(during_supply_audit? 1 : 0)+(after_supply_audit? 1 : 0); + CHECK_AND_ASSERT_MES(num_epochs==1, false, "Failed to determine if the current block is before, during, or after the supply audit"); + if (before_supply_audit){ // Audit transactions not permited + CHECK_AND_ASSERT_MES(rv.type != RCTTypeSupplyAudit, false, "Audit transactions permited only during the audit period"); + } + if (during_supply_audit){ //Conversions disabled, Audit tx spends from Pool 1, non-Audit spends from Pool 2, burn not permited + CHECK_AND_ASSERT_MES(rv.type == RCTTypeBulletproofPlus || rv.type == RCTTypeSupplyAudit, false, "Only RCTTypeBulletproofPlus and Audit transactions permited after the supply Audit"); + if (rv.type == RCTTypeSupplyAudit) + CHECK_AND_ASSERT_MES(tx_anon_pool != anon::POOL_1, false, "Supply audit transactions should have anonymity pool 1"); + if (rv.type == RCTTypeBulletproofPlus) + CHECK_AND_ASSERT_MES(tx_anon_pool != anon::POOL_2, false, "Regular transactions after the audit start should have anonymity pool 2"); + CHECK_AND_ASSERT_MES(tx_type == tt::TRANSFER || tx_type == tt::OFFSHORE_TRANSFER || tx_type == tt::XASSET_TRANSFER, false, "Only transfers allowed during the supply audit period"); + CHECK_AND_ASSERT_MES(amount_burnt==0, false, "Only transfers allowed during the supply audit period"); + } + } + if (after_supply_audit){ // All transactions spent from Pool 2, audit tx not permited + CHECK_AND_ASSERT_MES(tx_anon_pool != anon::POOL_2, false, "Transactions after the audit end should have anonymity pool 2"); + CHECK_AND_ASSERT_MES(rv.type != RCTTypeSupplyAudit, false, "Audit transactions permited only during the audit period"); + } + + CHECK_AND_ASSERT_MES((strSource != strDest) == (tx_type == tt::ONSHORE || tx_type==tt::OFFSHORE || tx_type==tt::XASSET_TO_XUSD || tx_type == tt::XUSD_TO_XASSET), false, "Mismatch between source/dest assets and transaction type"); if (strSource != strDest) { CHECK_AND_ASSERT_MES(!pr.empty(), false, "Empty pricing record found for a conversion tx"); CHECK_AND_ASSERT_MES(amount_burnt, false, "0 amount_burnt found for a conversion tx"); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 056aa9007..edc7575b6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -280,6 +280,22 @@ namespace rct { END_SERIALIZE() }; + // Audit Amount proof + struct AmountProof { + key A; + key B; + key C; + key D; + key E; + BEGIN_SERIALIZE_OBJECT() + FIELD(A) + FIELD(B) + FIELD(C) + FIELD(D) + FIELD(E) + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector &proofs); @@ -568,6 +584,7 @@ namespace rct { std::vector MGs; // simple rct has N, full has 1 std::vector CLSAGs; keyV pseudoOuts; //C - for simple rct + std::vector amountproofs; // when changing this function, update cryptonote::get_pruned_transaction_weight template class Archive> @@ -746,6 +763,20 @@ namespace rct { } ar.end_array(); } + if (type==RCTTypeSupplyAudit){ + ar.tag("amountproofs"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(inputs, amountproofs); + if (amountproofs.size() != 1) //Only one amount proof permited + return false; + for (size_t i = 0; i < inputs; ++i) + { + FIELDS(amountproofs[i]) + if (inputs - i > 1) + ar.delimit_array(); + } + ar.end_array(); + } return ar.good(); } @@ -756,6 +787,7 @@ namespace rct { FIELD(MGs) FIELD(CLSAGs) FIELD(pseudoOuts) + FIELD(amountproofs) END_SERIALIZE() }; struct rctSig: public rctSigBase { @@ -939,6 +971,7 @@ VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag"); VARIANT_TAG(debug_archive, rct::BulletproofPlus, "rct::bulletproof_plus"); +VARIANT_TAG(debug_archive, rct::AmountProof, "rct::amountproof"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -957,6 +990,7 @@ VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(binary_archive, rct::clsag, 0x9f); VARIANT_TAG(binary_archive, rct::BulletproofPlus, 0xa0); +VARIANT_TAG(binary_archive, rct::AmountProof, 0xa1); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -975,5 +1009,6 @@ VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); VARIANT_TAG(json_archive, rct::clsag, "rct_clsag"); VARIANT_TAG(json_archive, rct::BulletproofPlus, "rct_bulletproof_plus"); +VARIANT_TAG(json_archive, rct::AmountProof, "rct_amountproof"); #endif /* RCTTYPES_H */ From 3ff2fa850f78e1ac12a1b9b4e45d1d208aa209e5 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Tue, 10 Sep 2024 23:00:44 +0000 Subject: [PATCH 08/47] added amount proof sketch --- src/cryptonote_config.h | 1 + src/ringct/rctSigs.cpp | 104 ++++++++++++++++++++++++++++++++++++++-- src/ringct/rctSigs.h | 1 + src/ringct/rctTypes.h | 25 +++++----- 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index f40ea5963..be9c6f8b6 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -347,6 +347,7 @@ namespace config const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed"; const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys"; const constexpr char HASH_KEY_TXHASH_AND_MIXRING[] = "txhash_and_mixring"; + const unsigned char HASH_KEY_AMOUNTPROOF[] = "AmountProof"; // Multisig const uint32_t MULTISIG_MAX_SIGNERS{16}; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 65beeed16..1a4d894c0 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -30,6 +30,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_protocol/enums.h" #include "misc_log_ex.h" #include "misc_language.h" @@ -46,6 +47,7 @@ #include "bulletproofs.cc" #include "offshore/pricing_record.cpp" +#include "ringct/rctOps.h" #include "ringct/rctTypes.h" using namespace crypto; @@ -1689,6 +1691,7 @@ namespace rct { CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG"); CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs"); + CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == vin.size(), false, "Mismatched sizes of rv.p.pseudoOuts and vin"); CHECK_AND_ASSERT_MES(rv.pseudoOuts.empty(), false, "rv.pseudoOuts is not empty"); CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); if (rv.type == RCTTypeHaven2) @@ -1705,7 +1708,7 @@ namespace rct { const bool before_supply_audit = (version < HF_VERSION_SUPPLY_AUDIT); const bool during_supply_audit = (version >=HF_VERSION_SUPPLY_AUDIT && version < HF_VERSION_SUPPLY_AUDIT_END); const bool after_supply_audit = (version >= HF_VERSION_SUPPLY_AUDIT_END); - int num_epochs=(before_supply_audit? 1 : 0)+(during_supply_audit? 1 : 0)+(after_supply_audit? 1 : 0); + int num_epochs=(before_supply_audit ? 1 : 0)+(during_supply_audit ? 1 : 0)+(after_supply_audit ? 1 : 0); CHECK_AND_ASSERT_MES(num_epochs==1, false, "Failed to determine if the current block is before, during, or after the supply audit"); if (before_supply_audit){ // Audit transactions not permited CHECK_AND_ASSERT_MES(rv.type != RCTTypeSupplyAudit, false, "Audit transactions permited only during the audit period"); @@ -1717,18 +1720,24 @@ namespace rct { if (rv.type == RCTTypeBulletproofPlus) CHECK_AND_ASSERT_MES(tx_anon_pool != anon::POOL_2, false, "Regular transactions after the audit start should have anonymity pool 2"); CHECK_AND_ASSERT_MES(tx_type == tt::TRANSFER || tx_type == tt::OFFSHORE_TRANSFER || tx_type == tt::XASSET_TRANSFER, false, "Only transfers allowed during the supply audit period"); - CHECK_AND_ASSERT_MES(amount_burnt==0, false, "Only transfers allowed during the supply audit period"); - } + CHECK_AND_ASSERT_MES(amount_burnt==0, false, "Burn transaction not allowed during the supply audit period"); } if (after_supply_audit){ // All transactions spent from Pool 2, audit tx not permited CHECK_AND_ASSERT_MES(tx_anon_pool != anon::POOL_2, false, "Transactions after the audit end should have anonymity pool 2"); CHECK_AND_ASSERT_MES(rv.type != RCTTypeSupplyAudit, false, "Audit transactions permited only during the audit period"); } + //Supply audit transaction should have one amount proof, and only audit transactions should have an amount proof + if (rv.type == RCTTypeSupplyAudit) + CHECK_AND_ASSERT_MES(rv.p.amountproofs.size()==1, false, "Supply audit transaction found without amount proofs"); + if (!rv.p.amountproofs.empty()) + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSupplyAudit, false, "Amount proof for non-audit transaction found"); + CHECK_AND_ASSERT_MES((strSource != strDest) == (tx_type == tt::ONSHORE || tx_type==tt::OFFSHORE || tx_type==tt::XASSET_TO_XUSD || tx_type == tt::XUSD_TO_XASSET), false, "Mismatch between source/dest assets and transaction type"); if (strSource != strDest) { CHECK_AND_ASSERT_MES(!pr.empty(), false, "Empty pricing record found for a conversion tx"); CHECK_AND_ASSERT_MES(amount_burnt, false, "0 amount_burnt found for a conversion tx"); + CHECK_AND_ASSERT_MES(rv.type != RCTTypeSupplyAudit, false, "Supply audit tx cannot be a conversion tx"); //redundant, paranoid check if (rv.type >= RCTTypeHaven3) { CHECK_AND_ASSERT_MES(rv.maskSums.size() == 3, false, "maskSums size is not correct"); if (tx_type == tt::OFFSHORE || tx_type == tt::ONSHORE) @@ -1746,6 +1755,15 @@ namespace rct { } } + if (version >= HF_VERSION_SUPPLY_AUDIT && rv.type != RCTTypeSupplyAudit){ //Another redundant paranoid check related to the pool split, but we really do not want old funds to be spendable + for (auto inp: vin) { + cryptonote::txin_haven_key inp_haven_key=boost::get(inp); + CHECK_AND_NO_ASSERT_MES(inp_haven_key.key_offsets.size()>0, false, "Input without decoys found"); + //TO-DO## Somehow get the first "new" output instead of 100 + CHECK_AND_NO_ASSERT_MES(inp_haven_key.key_offsets[0]>100, false, "Input seems too old"); + } + } + uint64_t amount_supply_burnt = 0; if ((tx_type == tt::TRANSFER || tx_type == tt::OFFSHORE_TRANSFER || tx_type == tt::XASSET_TRANSFER) && version >= HF_VERSION_BURN && amount_burnt>0){ @@ -2067,6 +2085,15 @@ namespace rct { return false; } + //Supply proof check + + if(rv.type==RCTTypeSupplyAudit){ + if(rv.p.amountproofs.empty() || ! verAmountproof(rv.p.amountproofs[0], rv.p.pseudoOuts)) { + LOG_PRINT_L1("Amount proof verified failed for an audit transaction"); + return false; + } + } + return true; } // we can get deep throws from ge_frombytes_vartime if input isn't valid @@ -2674,4 +2701,75 @@ namespace rct { // Must have succeeded return true; } + + //! This function proves that K2=r*K, where r is the random number in the commitment rG+aH + bool verAmountproof(const rct::AmountProof & amountproof, const keyV & pseudoOuts){ + + CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.G1), false, "Amount verification failed: G1 is not in the main group"); + CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.K1), false, "Amount verification failed: K1 is not in the main group"); + CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.H1), false, "Amount verification failed: H1 is not in the main group"); + CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.K2), false, "Amount verification failed: K2 is not in the main group"); + CHECK_AND_ASSERT_MES(sc_check(amountproof.sa.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); + CHECK_AND_ASSERT_MES(sc_check(amountproof.sr.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); + + key zerokey = rct::identity(); + // Sum the consumed outputs + // We do not reuse the value from VerRctSemanticsSimple in order to ensure no errors happen + key sumPseudoOuts = zerokey; + for (auto po: pseudoOuts) { + sumPseudoOuts = addKeys(sumPseudoOuts, po); + } + + keyV challenge_to_hash; + challenge_to_hash.reserve(6); + key initKey; + sc_0(initKey.bytes); + CHECK_AND_ASSERT_THROW_MES(sizeof(initKey.bytes)>=sizeof(config::HASH_KEY_AMOUNTPROOF), "Amount proof hash init string is too long"); + memcpy(initKey.bytes,config::HASH_KEY_AMOUNTPROOF,min(sizeof(config::HASH_KEY_AMOUNTPROOF)-1, sizeof(initKey.bytes)-1)); + + challenge_to_hash.push_back(initKey); + challenge_to_hash.push_back(amountproof.G1); + challenge_to_hash.push_back(amountproof.K1); + challenge_to_hash.push_back(amountproof.H1); //Aren't we more secure if we exclude H1 from the challenge? + challenge_to_hash.push_back(amountproof.K2); + challenge_to_hash.push_back(sumPseudoOuts); + + //Challenge + const key c=hash_to_scalar(challenge_to_hash); + //First check that sr*G+sa*H==G1+H1+c*C + //Assuming this holds, we can deduce the following: + //We know that G1 and H1 are in the main subgroup, and that G and H are generators. + //Therefore there exist r_r, r_a so that G1=r_r*G and H1=r_a*H + //From here we derive (using that the hardness of DLP) that: + //(1) s_r*G==r_r*G+c*r*G + //(2) s_a*H==r_a*H+c*a*H + //We can cancel G and H, and derive: + //(1) s_r==r_r+c*r + //(2) s_a==r_a+c*a + + + + + //Second, we check that s_r*K==K1+c*K2 + //Assuming this holds: + //K1 is also in the main subgroup, so there exists r_r2 so that r_r2*K=K1 + //K2 is also in the main subgroup, so there exists r2 so that r2*K=K2 + //we know from the first step that s_r==r_r+c*r, therefore + //r_r*K+c*r*K==K1+c*K2 + //r_r*K+c*r*K==r_r2*K+c*r2*K, now we cancel K + // + //r_r+c*r==r_r2+c*r2 + //c=hash(r_r, r_r2, r2, r_a, r, a) /Question - what if c=hash(r_r, r_r2, r2, r, a) -> isn't this more secure? a has limited (64 bits) degrees of freedom, while r_a has 256 bits/ + //One solution is r_r==r_r2 and r==r2 + //If there is another solution, then we must have a hash collision, which is hard. + // we can assume with negligible probability of the contrary that + // + //(3) r_r==r_r2 + //(4) r==r2 + //Therefore we have proven that: + // K2=r*K + // K1=r_r*K + + return true; + } } diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index e657f1d73..60ff68757 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -148,6 +148,7 @@ namespace rct { key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev); bool checkBurntAndMinted(const rctSig &rv, const xmr_amount amount_burnt, const xmr_amount amount_minted, const offshore::pricing_record pr, const uint64_t& conversion_rate, const std::string& source, const std::string& destination, const uint8_t version); + bool verAmountproof(const rct::AmountProof & amountproof, const keyV & pseudoOuts); } #endif /* RCTSIGS_H */ diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index edc7575b6..45b1c7e0c 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -280,19 +280,22 @@ namespace rct { END_SERIALIZE() }; - // Audit Amount proof + //! Audit Amount proof struct AmountProof { - key A; - key B; - key C; - key D; - key E; + key G1; //!< r_r*G + key K1; //!< r_r*K + key H1; //!< r_a*H + key K2; //!< r*K, where r is the r from C=r*G+a*H + key sr; //!< sr = r_r + c*r, where r is the r from C=r*G+a*H + key sa; //!< sr = r_a + c*a, where a is the r from C=r*G+a*H + BEGIN_SERIALIZE_OBJECT() - FIELD(A) - FIELD(B) - FIELD(C) - FIELD(D) - FIELD(E) + FIELD(G1) + FIELD(K1) + FIELD(H1) + FIELD(K2) + FIELD(sr) + FIELD(sa) END_SERIALIZE() }; From 7c438189cdff4291516283a58bcbf5e50d89ed20 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Tue, 10 Sep 2024 23:23:44 +0000 Subject: [PATCH 09/47] commenting --- src/ringct/rctSigs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 1a4d894c0..6c64f199d 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -2702,7 +2702,7 @@ namespace rct { return true; } - //! This function proves that K2=r*K, where r is the random number in the commitment rG+aH + //! This function proves that, for a fixed point K in the main subgroup, it holds that K2=r*K, where r is the random number in the output commitment rG+aH bool verAmountproof(const rct::AmountProof & amountproof, const keyV & pseudoOuts){ CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.G1), false, "Amount verification failed: G1 is not in the main group"); @@ -2714,7 +2714,7 @@ namespace rct { key zerokey = rct::identity(); // Sum the consumed outputs - // We do not reuse the value from VerRctSemanticsSimple in order to ensure no errors happen + // We do not reuse the value from VerRctSemanticsSimple in order to reduce chances of errors key sumPseudoOuts = zerokey; for (auto po: pseudoOuts) { sumPseudoOuts = addKeys(sumPseudoOuts, po); From a2c06211a299518f885574baff207b3b0653ccbe Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Wed, 11 Sep 2024 07:33:03 +0000 Subject: [PATCH 10/47] amount proof verification --- src/ringct/rctSigs.cpp | 43 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 6c64f199d..f93d18a60 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -2712,9 +2712,13 @@ namespace rct { CHECK_AND_ASSERT_MES(sc_check(amountproof.sa.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); CHECK_AND_ASSERT_MES(sc_check(amountproof.sr.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); - key zerokey = rct::identity(); + const key zerokey = rct::identity(); + const key init_G = scalarmultBase(d2h(1)); + const key init_H = scalarmultH(d2h(1)); + // Sum the consumed outputs // We do not reuse the value from VerRctSemanticsSimple in order to reduce chances of errors + key sumPseudoOuts = zerokey; for (auto po: pseudoOuts) { sumPseudoOuts = addKeys(sumPseudoOuts, po); @@ -2724,7 +2728,7 @@ namespace rct { challenge_to_hash.reserve(6); key initKey; sc_0(initKey.bytes); - CHECK_AND_ASSERT_THROW_MES(sizeof(initKey.bytes)>=sizeof(config::HASH_KEY_AMOUNTPROOF), "Amount proof hash init string is too long"); + CHECK_AND_ASSERT_MES(sizeof(initKey.bytes)>=sizeof(config::HASH_KEY_AMOUNTPROOF), false, "Amount proof hash init string is too long"); memcpy(initKey.bytes,config::HASH_KEY_AMOUNTPROOF,min(sizeof(config::HASH_KEY_AMOUNTPROOF)-1, sizeof(initKey.bytes)-1)); challenge_to_hash.push_back(initKey); @@ -2734,13 +2738,15 @@ namespace rct { challenge_to_hash.push_back(amountproof.K2); challenge_to_hash.push_back(sumPseudoOuts); + //Challenge const key c=hash_to_scalar(challenge_to_hash); - //First check that sr*G+sa*H==G1+H1+c*C + + //First check that sr*G+sa*H==G1+H1+c*C --> this proves nothing?!? //Assuming this holds, we can deduce the following: //We know that G1 and H1 are in the main subgroup, and that G and H are generators. //Therefore there exist r_r, r_a so that G1=r_r*G and H1=r_a*H - //From here we derive (using that the hardness of DLP) that: + //From here we derive (using that the hardness of DLP and the fact that c=hash(r_r, r_r2, r2, r_a, r, a)) that: //(1) s_r*G==r_r*G+c*r*G //(2) s_a*H==r_a*H+c*a*H //We can cancel G and H, and derive: @@ -2749,6 +2755,20 @@ namespace rct { + key saH = init_H; + key lhs = init_H; + key rhs = init_G; + key cC = init_H; + + scalarmultBase(lhs, amountproof.sr); //lhs=s_r*G + saH=scalarmultH(amountproof.sa); //saH = s_a*H + addKeys(lhs, lhs, saH); //lhr=s_r*G+s_a*H + + addKeys(rhs, amountproof.G1, amountproof.H1); //rhs=G1+H1 + cC=scalarmultKey(sumPseudoOuts, c); // cC=c*C + addKeys(rhs, rhs, cC); //rhs=G1+H1+c+H + + CHECK_AND_ASSERT_MES(equalKeys(lhs, rhs), false, "First check of amount proof verification failed"); //Second, we check that s_r*K==K1+c*K2 //Assuming this holds: @@ -2759,10 +2779,10 @@ namespace rct { //r_r*K+c*r*K==r_r2*K+c*r2*K, now we cancel K // //r_r+c*r==r_r2+c*r2 - //c=hash(r_r, r_r2, r2, r_a, r, a) /Question - what if c=hash(r_r, r_r2, r2, r, a) -> isn't this more secure? a has limited (64 bits) degrees of freedom, while r_a has 256 bits/ + //c=hash(r_r, r_r2, r2, r_a, r, a) //One solution is r_r==r_r2 and r==r2 //If there is another solution, then we must have a hash collision, which is hard. - // we can assume with negligible probability of the contrary that + // we can assume with negligible probability of the being false that // //(3) r_r==r_r2 //(4) r==r2 @@ -2770,6 +2790,17 @@ namespace rct { // K2=r*K // K1=r_r*K + lhs=init_H; + rhs=init_G; + + + key K; //TO-DO## K definition - must be a hard-coded point, similar like G + lhs=scalarmultKey(K, amountproof.sr); //lhs = s_r*K + rhs=scalarmultKey(amountproof.K2, c); //rhs = c*K2 + addKeys(rhs, rhs, amountproof.K1); //rhs = K1+c*K2 + CHECK_AND_ASSERT_MES(equalKeys(lhs, rhs), false, "Second check of amount proof verification failed"); + + return true; } } From d4c02dc22c0f05848606dee0cbc0aae56f75f98b Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Wed, 11 Sep 2024 07:38:23 +0000 Subject: [PATCH 11/47] fixed comments --- src/ringct/rctSigs.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f93d18a60..e77de8514 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -2734,15 +2734,15 @@ namespace rct { challenge_to_hash.push_back(initKey); challenge_to_hash.push_back(amountproof.G1); challenge_to_hash.push_back(amountproof.K1); - challenge_to_hash.push_back(amountproof.H1); //Aren't we more secure if we exclude H1 from the challenge? + challenge_to_hash.push_back(amountproof.H1); challenge_to_hash.push_back(amountproof.K2); challenge_to_hash.push_back(sumPseudoOuts); - //Challenge + //Challenge c=H(init, G1, K1, H1,K2, C), which is in practise c=hash(r_r, r_r2, r2, r_a, r. a), see details below on notation const key c=hash_to_scalar(challenge_to_hash); - //First check that sr*G+sa*H==G1+H1+c*C --> this proves nothing?!? + //First check that sr*G+sa*H==G1+H1+c*C //Assuming this holds, we can deduce the following: //We know that G1 and H1 are in the main subgroup, and that G and H are generators. //Therefore there exist r_r, r_a so that G1=r_r*G and H1=r_a*H From 070648f833c8e212953f837457509d4568ba5d6f Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Thu, 12 Sep 2024 10:55:47 +0000 Subject: [PATCH 12/47] added amount proof to the tx signing process --- src/multisig/multisig_tx_builder_ringct.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index fba2ca5ac..72068a5a3 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -627,7 +627,7 @@ static bool set_tx_rct_signatures( { case 0: case 7: - rv.type = rct::RCTTypeBulletproofPlus; + rv.type = rct::RCTTypeBulletproofPlus; // Can be overwritten below if we have a supply audit transaction break; case 6: rv.type = rct::RCTTypeHaven3; From 8979a3de8fe098a5cd60f216f9ab078808346fb0 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Thu, 12 Sep 2024 10:56:13 +0000 Subject: [PATCH 13/47] amount proof signing --- src/ringct/rctSigs.cpp | 88 +++++++++++++++++++++++++++++++++++++++++- src/ringct/rctTypes.h | 4 ++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index e77de8514..eefa62a1d 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1249,6 +1249,7 @@ namespace rct { using tt = cryptonote::transaction_type; bool conversion_tx = tx_type == tt::OFFSHORE || tx_type == tt::ONSHORE || tx_type == tt::XUSD_TO_XASSET || tx_type == tt::XASSET_TO_XUSD; bool use_onshore_col = tx_type == tt::ONSHORE && rv.type >= RCTTypeHaven3; + bool supply_audit_tx = rv.type == RCTTypeSupplyAudit; rv.message = message; rv.outPk.resize(destinations.size()); @@ -1488,6 +1489,15 @@ namespace rct { sc_sub(a[actual_in_amounts[i].first].bytes, sumout.bytes, sumpouts.bytes); genC(pseudoOuts[actual_in_amounts[i].first], a[actual_in_amounts[i].first], actual_in_amounts[i].second); + + //Sum of blinding factors, to be used for the supply audit + //Defining PseudooutsMaskSums as const, to ensure it is not modified + key PseudooutsMaskSumsTemp = zero(); + if (supply_audit_tx) { + sc_add(PseudooutsMaskSumsTemp.bytes, a[actual_in_amounts[i].first].bytes, sumpouts.bytes); + } + const key PseudooutsMaskSums = PseudooutsMaskSumsTemp; + // set the sum of input blinding factors if (conversion_tx) { // HERE BE DRAGONS!!! @@ -1529,6 +1539,80 @@ namespace rct { rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], index[i], hwdev); } } + + //Add amount proof in case of a supply audit tx + if (supply_audit_tx){ + //G1=r_r*G + //K1=r_r*K + //H1=r_a*H + //K2=r*K + //s_r=r_r+r*c + //s_a=r_a+a*c + //C=sum of PseudoOuts + + const key zerokey = rct::identity(); + + AmountProof amountproof; + key r_r; + key r_a; + key K; //TO-DO## K initialization + key S; //TO-DO## S initialization + + //Calculate sum of pseudoouts + key sumPseudoOuts=zerokey; + for (auto po: pseudoOuts){ + addKeys(sumPseudoOuts, sumPseudoOuts, po); + } + + skGen(r_r); //Generate random r_r + skGen(r_a); //Generate random r_a + + amountproof.G1=scalarmultBase(r_r); + amountproof.K1=scalarmultKey(K,r_r); + amountproof.H1=scalarmultH(r_a); + amountproof.K2=scalarmultKey(K,PseudooutsMaskSums); + + //Challenge c=H(init, G1, K1, H1,K2, C), where C is the sum of pseudoouts + keyV challenge_to_hash; + challenge_to_hash.reserve(6); + key initKey; + sc_0(initKey.bytes); + CHECK_AND_ASSERT_THROW_MES(sizeof(initKey.bytes)>=sizeof(config::HASH_KEY_AMOUNTPROOF), "Amount proof hash init string is too long"); + memcpy(initKey.bytes,config::HASH_KEY_AMOUNTPROOF,min(sizeof(config::HASH_KEY_AMOUNTPROOF)-1, sizeof(initKey.bytes)-1)); + + challenge_to_hash.push_back(initKey); + challenge_to_hash.push_back(amountproof.G1); + challenge_to_hash.push_back(amountproof.K1); + challenge_to_hash.push_back(amountproof.H1); + challenge_to_hash.push_back(amountproof.K2); + challenge_to_hash.push_back(sumPseudoOuts); + const key c=hash_to_scalar(challenge_to_hash); + //Calculate s_r + sc_muladd(amountproof.sr.bytes, r_r.bytes, c.bytes, PseudooutsMaskSums.bytes); + //Calculate s_a=r_a+c*a + amountproof.sa=r_a; + for (auto in_amount: inamounts){ //add (input amounts)*r + sc_muladd(amountproof.sa.bytes, amountproof.sa.bytes, c.bytes, d2h(in_amount).bytes); + } + + //Calculate encrypted amount + rv.amount_encrypted=0; + for (auto in_amount: inamounts){ //add (input amounts)*r + rv.amount_encrypted += in_amount; + CHECK_AND_ASSERT_THROW_MES(rv.amount_encrypted>=in_amount, "Overflow occured, sum of inputs exceeds the maximum xmr amount"); + } + xmr_amount encryption_key=0; + const key rS = scalarmultKey(S,PseudooutsMaskSums); + for (int i = 8; i < 16; i++){ //Use bytes 8 to 16 for the encryption + encryption_key*=256; //Shift 1 bytes + encryption_key+=rS.bytes[i]; //Add current byte + } + rv.amount_encrypted ^= encryption_key; //XOR using the encryption key + + //Post proof + rv.p.amountproofs.clear(); + rv.p.amountproofs.push_back(amountproof); + } return rv; } @@ -2710,7 +2794,7 @@ namespace rct { CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.H1), false, "Amount verification failed: H1 is not in the main group"); CHECK_AND_ASSERT_MES(isInMainSubgroup(amountproof.K2), false, "Amount verification failed: K2 is not in the main group"); CHECK_AND_ASSERT_MES(sc_check(amountproof.sa.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); - CHECK_AND_ASSERT_MES(sc_check(amountproof.sr.bytes) == 0, false, "Amount verification failed: bad scalar s_a"); + CHECK_AND_ASSERT_MES(sc_check(amountproof.sr.bytes) == 0, false, "Amount verification failed: bad scalar s_r"); const key zerokey = rct::identity(); const key init_G = scalarmultBase(d2h(1)); @@ -2732,7 +2816,7 @@ namespace rct { memcpy(initKey.bytes,config::HASH_KEY_AMOUNTPROOF,min(sizeof(config::HASH_KEY_AMOUNTPROOF)-1, sizeof(initKey.bytes)-1)); challenge_to_hash.push_back(initKey); - challenge_to_hash.push_back(amountproof.G1); + challenge_to_hash.push_back(amountproof.G1); challenge_to_hash.push_back(amountproof.K1); challenge_to_hash.push_back(amountproof.H1); challenge_to_hash.push_back(amountproof.K2); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 45b1c7e0c..872a7cfc6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -356,6 +356,7 @@ namespace rct { xmr_amount txnOffshoreFee = 0; xmr_amount txnOffshoreFee_usd = 0; xmr_amount txnOffshoreFee_xasset = 0; + xmr_amount amount_encrypted = 0; //!< Encrypted input amount. Only relevant for Audit transaction keyV maskSums; // contains 2 or 3 elements. 1. is the sum of masks of inputs. 2. is the sum of masks of change outputs. 3. mask of the col output. template class Archive> @@ -416,6 +417,9 @@ namespace rct { FIELDS(maskSums[2]) ar.end_array(); } + if(type==RCTTypeSupplyAudit){ + VARINT_FIELD(amount_encrypted) + } return ar.good(); } From 9033d98f60790a29a0fedae80f1548783280ee27 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Thu, 12 Sep 2024 11:39:03 +0000 Subject: [PATCH 14/47] added version bp_version=8 for supply audit txs --- src/multisig/multisig_tx_builder_ringct.cpp | 5 +++++ src/ringct/rctSigs.cpp | 3 +++ src/wallet/wallet2.cpp | 1 + 3 files changed, 9 insertions(+) diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index 72068a5a3..55ba91c32 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -548,6 +548,7 @@ static void make_new_range_proofs(const int bp_version, sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks)); break; case 7: + case 8: sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks)); break; } @@ -584,6 +585,7 @@ static bool try_reconstruct_range_proofs(const int bp_version, return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs); break; case 7: + case 8: if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus)) return false; return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus); @@ -625,6 +627,9 @@ static bool set_tx_rct_signatures( // set misc. fields switch (rct_config.bp_version) { + case 8: + rv.type = rct::RCTTypeSupplyAudit; + break; case 0: case 7: rv.type = rct::RCTTypeBulletproofPlus; // Can be overwritten below if we have a supply audit transaction diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index eefa62a1d..7e5964454 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1217,6 +1217,9 @@ namespace rct { { switch (rct_config.bp_version) { + case 8: + rv.type = RCTTypeSupplyAudit; + break; case 0: case 7: rv.type = RCTTypeBulletproofPlus; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index baf63b6ec..53c78ee9f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -11769,6 +11769,7 @@ std::vector wallet2::create_transactions_from( const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); + //TO-DO## const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, bulletproof_plus ? 7 : use_fork_rules(HF_VERSION_USE_COLLATERAL, 0) ? 6 : From fcaa5df71d359d7633ec02cc9cabc566e8f58f77 Mon Sep 17 00:00:00 2001 From: Tay8NWWFKpz9JT4NXU0w Date: Thu, 12 Sep 2024 19:48:28 +0000 Subject: [PATCH 15/47] supply update after audit - part 1 --- src/blockchain_db/blockchain_db.h | 7 ++ src/blockchain_db/lmdb/db_lmdb.cpp | 117 ++++++++++++++++++++++++ src/blockchain_db/lmdb/db_lmdb.h | 2 + src/cryptonote_core/cryptonote_core.cpp | 9 ++ src/cryptonote_core/cryptonote_core.h | 9 ++ src/ringct/rctSigs.cpp | 16 ++-- src/ringct/rctSigs.h | 2 +- src/ringct/rctTypes.h | 8 +- 8 files changed, 159 insertions(+), 11 deletions(-) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index ade3b80c1..c42ad1c9b 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1177,6 +1177,13 @@ class BlockchainDB */ virtual std::vector> get_circulating_supply() const = 0; + /** + * @brief Recalculate supply after the audit + * + * @param supply_audit_decryption_key Decryption key for amount_encrypted + */ + virtual void recalculate_supply_after_audit(const std::string & supply_audit_decryption_key) = 0; + /** *