diff --git a/README.md b/README.md index 1ffc38c8a..226ea453a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Salvium Zero v0.7.0-rc2 +# Salvium Zero v0.7.0-rc3 Copyright (c) 2023-2024, Salvium Portions Copyright (c) 2014-2023, The Monero Project diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 07f58295d..0be78f752 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3671,6 +3671,15 @@ bool Blockchain::check_tx_type_and_version(const transaction& tx, tx_verificatio return false; } } + + // Make sure CONVERT TXs are disabled until we are ready - belt and braces! + if (hf_version < HF_VERSION_ENABLE_CONVERT) { + if (tx.type == cryptonote::transaction_type::CONVERT) { + MERROR("CONVERT TXs are not permitted prior to v" + std::to_string(HF_VERSION_ENABLE_CONVERT)); + tvc.m_version_mismatch = true; + return false; + } + } return true; } diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 3d6112e54..d4021332b 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -522,33 +522,6 @@ namespace rct { return sc_isnonzero(c.bytes) == 0; } - - // Optimized function to hash a vector of keys into a scalar - rct::key my_hash_to_scalar(std::vector& keys) { - - // Create a fixed-size buffer large enough to hold all keys and a domain separator - size_t total_size = keys.size() * sizeof(rct::key) + sizeof("ZKP") - 1; - std::vector data(total_size); - - // Copy the keys into the buffer - size_t offset = 0; - for (const auto& key : keys) { - std::memcpy(data.data() + offset, key.bytes, sizeof(rct::key)); - offset += sizeof(rct::key); - } - - // Add the domain separator "ZKP" at the end of the buffer - const char* domain_separator = "ZKP"; - std::memcpy(data.data() + offset, domain_separator, sizeof("ZKP") - 1); - - // Hash the concatenated data into a fixed-size hash - rct::key hash_output; - keccak((const uint8_t *)data.data(), total_size, hash_output.bytes, sizeof(rct::key)); - sc_reduce32(hash_output.bytes); // Reduce to valid scalar - - return hash_output; - } - zk_proof PRProof_Gen(const rct::key &difference) { zk_proof proof; @@ -565,7 +538,6 @@ namespace rct { // Calculate challenge c = H_p(R) std::vector keys{proof.R, comm_diff}; rct::key c = rct::hash_to_scalar(keys); - sc_reduce32(c.bytes); // Calculate response z = r + c * difference sc_muladd(proof.z1.bytes, difference.bytes, c.bytes, r.bytes); @@ -1100,27 +1072,22 @@ namespace rct { zk_proof SAProof_Gen(const key &P, const key &x_change, const key &key_yF) { - // Declare a return structure - zk_proof proof{}; - proof.z2 = rct::zero(); - - // Sanity checks + // Sanity checks for inputs CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(P, rct::zero()), "SAProof_Gen() failed - invalid public key provided"); CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(x_change, rct::zero()), "SAProof_Gen() failed - invalid x_change key provided"); CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(key_yF, rct::zero()), "SAProof_Gen() failed - invalid shared secret key provided"); + // Declare a return structure + zk_proof proof{}; + proof.z2 = rct::zero(); + // Calculate a random r value and calculate a commitment R for it rct::key r = rct::skGen(); proof.R = rct::scalarmultBase(r); - // Calculate the challenge hash from the commitments plus the pubkeys - keyV challenge_keys; - challenge_keys.reserve(3); - challenge_keys.push_back(proof.R); - challenge_keys.push_back(P); - challenge_keys.push_back(key_yF); + // Calculate the challenge hash from the commitment plus the pubkey plus the shared secret + keyV challenge_keys{proof.R, P, key_yF}; rct::key c = rct::hash_to_scalar(challenge_keys); - sc_reduce32(c.bytes); rct::key z_x; sc_muladd(z_x.bytes, x_change.bytes, c.bytes, r.bytes); @@ -1132,18 +1099,13 @@ namespace rct { bool SAProof_Ver(const zk_proof &proof, const key &P, const key &key_yF) { - // Sanity checks - CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(P, rct::zero()), "SAProof_Gen() failed - invalid public key provided"); - CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(key_yF, rct::zero()), "SAProof_Gen() failed - invalid shared secret key provided"); + // Sanity checks for inputs + CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(P, rct::zero()), "SAProof_Ver() failed - invalid public key provided"); + CHECK_AND_ASSERT_THROW_MES(!rct::equalKeys(key_yF, rct::zero()), "SAProof_Ver() failed - invalid shared secret key provided"); // Recompute the challenge hash - keyV challenge_keys; - challenge_keys.reserve(3); - challenge_keys.push_back(proof.R); - challenge_keys.push_back(P); - challenge_keys.push_back(key_yF); + keyV challenge_keys{proof.R, P, key_yF}; rct::key c = rct::hash_to_scalar(challenge_keys); - sc_reduce32(c.bytes); // Recalculate the expected commitment using the formula: z_x * G = R + c * P rct::key expected_commitment = rct::addKeys(proof.R, rct::scalarmultKey(P, c)); diff --git a/src/version.cpp.in b/src/version.cpp.in index 649417768..41201dff1 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@" -#define DEF_SALVIUM_VERSION "0.7.0-rc2" +#define DEF_SALVIUM_VERSION "0.7.0-rc3" #define DEF_MONERO_VERSION_TAG "release" #define DEF_MONERO_VERSION "0.18.3.3" #define DEF_MONERO_RELEASE_NAME "Zero" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 419ab5cfb..313dd7cde 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2242,7 +2242,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons */ // Populate the unlock_time THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_unlock_time(tx.vout[i], tx_scan_info.unlock_time), error::wallet_internal_error, "failed to get output unlock_time"); - + outs.push_back(i); THROW_WALLET_EXCEPTION_IF(tx_money_got_in_outs[tx_scan_info.received->index][tx_scan_info.asset_type] >= std::numeric_limits::max() - tx_scan_info.money_transfered, error::wallet_internal_error, "Overflow in received amounts"); @@ -2481,6 +2481,77 @@ bool wallet2::get_yield_summary_info(uint64_t &total_burnt, return true; } //---------------------------------------------------------------------------------------------------- +bool wallet2::verify_spend_authority_proof(const cryptonote::transaction &tx, const size_t i, const tx_scan_info_t &tx_scan_info) +{ + // Sanity checks + if (tx.type != cryptonote::transaction_type::TRANSFER) return true; + if (tx.version < TRANSACTION_VERSION_N_OUTS) return true; + if (tx.rct_signatures.type != rct::RCTTypeFullProofs) return true; + + // To verify the spend authority proof, we need to know the y value to process the F value + ec_scalar y; + + // Get P_change from the TX + crypto::public_key P_change = crypto::null_pkey; + + // Calculate z_i (the shared secret between sender and ourselves for the original TX) + crypto::public_key txkey_pub = null_pkey; // R + const std::vector in_additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + if (in_additional_tx_pub_keys.size() != 0) { + THROW_WALLET_EXCEPTION_IF(in_additional_tx_pub_keys.size() != tx.vout.size(), + error::wallet_internal_error, + tr("at verify_spend_authority_proof(): incorrect number of additional TX pubkeys in TX")); + txkey_pub = in_additional_tx_pub_keys[i]; + } else { + txkey_pub = get_tx_pub_key_from_extra(tx); + } + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + THROW_WALLET_EXCEPTION_IF(!generate_key_derivation(txkey_pub, m_account.get_keys().m_view_secret_key, derivation), + error::wallet_internal_error, + tr("at verify_spend_authority_proof(): failed to generate_key_derivation")); + crypto::secret_key z_i; + derivation_to_scalar(derivation, i, z_i); + + // Calculate the y value for return_payment support + struct { + char domain_separator[8]; + rct::key amount_key; + } buf; + std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); + std::strncpy(buf.domain_separator, "RETURN", 7); + buf.amount_key = rct::sk2rct(z_i); + crypto::hash_to_scalar(&buf, sizeof(buf), y); + + // The change_index needs decoding too + uint8_t eci_data = tx.return_address_change_mask[i]; + + // Calculate the encrypted_change_index data for this output + std::memset(buf.domain_separator, 0x0, sizeof(buf.domain_separator)); + std::strncpy(buf.domain_separator, "CHG_IDX", 8); + crypto::secret_key eci_out; + keccak((uint8_t *)&buf, sizeof(buf), (uint8_t*)&eci_out, sizeof(eci_out)); + uint8_t change_index = eci_data ^ eci_out.data[0]; + THROW_WALLET_EXCEPTION_IF(change_index >= tx.vout.size(), error::wallet_internal_error, tr("at verify_spend_authority_proof(): invalid change_index calculated")); + + // Now we know the index, we can get P_change + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_output_public_key(tx.vout[change_index], P_change), error::wallet_internal_error, tr("at verify_spend_authority_proof(): failed to get P_change")); + rct::key key_P_change = rct::pk2rct(P_change); + + // Calculate the shared secret yF + rct::key key_y = (rct::key&)(y); + rct::key key_F = (rct::key&)(tx.return_address_list[i]); + rct::key key_yF = rct::scalarmultKey(key_F, key_y); + rct::key hs_yF = rct::hash_to_scalar(key_yF); + + // Now we can verify the proof itself + if (!rct::SAProof_Ver(tx.rct_signatures.sa_proof, key_P_change, hs_yF)) { + return false; + } + + // Return success to caller + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector &o_indices, const std::vector &asset_type_output_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map, size_t> *output_tracker_cache, bool ignore_callbacks) { PERF_TIMER(process_new_transaction); @@ -2784,6 +2855,15 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); } + + // Verify the spend authority proof + if (!verify_spend_authority_proof(tx, o, tx_scan_info[o])) { + // Freeze the output + LOG_ERROR("Spend authority proof for TX: " << txid << " failed verification. The output has been frozen."); + LOG_ERROR("Please review the transaction and verify that the sender is someone you trust before thawing this payment."); + td.m_frozen = true; + } + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, td.asset_type, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time, td.m_td_origin_idx); @@ -2901,6 +2981,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); + // Verify the spend authority proof + if (!verify_spend_authority_proof(tx, o, tx_scan_info[o])) { + // Freeze the output + LOG_ERROR("Spend authority proof for TX: " << txid << " failed verification. The output has been frozen."); + LOG_ERROR("Please review the transaction and verify that the sender is someone you trust before thawing this payment."); + td.m_frozen = true; + } + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, td.asset_type, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time, td.m_td_origin_idx); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index ee90e53ba..1273d03dc 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1191,6 +1191,8 @@ private: std::vector get_public_nodes(bool white_only = true); + bool verify_spend_authority_proof(const cryptonote::transaction &tx, const size_t i, const tx_scan_info_t &tx_scan_info); + template inline void serialize(t_archive &a, const unsigned int ver) {