added belt and braces to prevent CONVERT TXs from being attempted; integrated spend authority proof support into wallet; bumped version number

This commit is contained in:
Some Random Crypto Guy
2024-12-16 14:07:37 +00:00
parent 82d706aacb
commit e15dbb5db2
6 changed files with 113 additions and 52 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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<rct::key>& 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<uint8_t> 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<rct::key> 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));

View File

@@ -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"

View File

@@ -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<uint64_t>::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<crypto::public_key> 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<uint64_t> &o_indices, const std::vector<uint64_t> &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<std::pair<uint64_t, uint64_t>, 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);

View File

@@ -1191,6 +1191,8 @@ private:
std::vector<cryptonote::public_node> 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 <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{