diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index e6eff80c..9879a6e1 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1307,8 +1307,9 @@ bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t if (is_hardfork_active(ZANO_HARDFORK_04_ZARCANUM)) { - CHECK_AND_ASSERT_MES(b.miner_tx.attachment.size() == 1, false, "coinbase transaction wrong attachments number(expeted 1 - rangeproofs)"); - CHECK_AND_ASSERT_MES(b.miner_tx.attachment[0].type() == typeid(zarcanum_outs_range_proof), false, "coinbase transaction wrong attachmenttype (expeted - zarcanum_outs_range_proof)"); + CHECK_AND_ASSERT_MES(b.miner_tx.attachment.size() == 2, false, "coinbase transaction has incorrect number of attachments (" << b.miner_tx.attachment.size() << "), expected 2"); + CHECK_AND_ASSERT_MES(b.miner_tx.attachment[0].type() == typeid(zarcanum_outs_range_proof), false, "coinbase transaction wrong attachment #0 type (expected: zarcanum_outs_range_proof)"); + CHECK_AND_ASSERT_MES(b.miner_tx.attachment[1].type() == typeid(zc_balance_proof), false, "coinbase transaction wrong attachmenttype #1 (expected: zc_balance_proof)"); } else { @@ -1325,15 +1326,6 @@ bool blockchain_storage::validate_miner_transaction(const block& b, const boost::multiprecision::uint128_t& already_generated_coins) const { CRITICAL_REGION_LOCAL(m_read_lock); - //validate reward - uint64_t money_in_use = get_outs_money_amount(b.miner_tx); - - uint64_t pos_income = 0; - if (is_pos_block(b)) - { - CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(txin_to_key), false, "Wrong miner tx_in"); - pos_income = boost::get(b.miner_tx.vin[1]).amount; - } std::vector last_blocks_sizes; get_last_n_blocks_sizes(last_blocks_sizes, CURRENCY_REWARD_BLOCKS_WINDOW); @@ -1343,20 +1335,10 @@ bool blockchain_storage::validate_miner_transaction(const block& b, LOG_PRINT_L0("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); return false; } - if (base_reward + pos_income + fee < money_in_use) + + if (!check_tx_balance(b.miner_tx, base_reward + fee)) { - LOG_ERROR("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + pos_income + fee) << "(" << print_money(base_reward) << "+" << print_money(pos_income) << "+" << print_money(fee) - << ", blocks_size_median = " << blocks_size_median - << ", cumulative_block_size = " << cumulative_block_size - << ", fee = " << fee - << ", already_generated_coins = " << already_generated_coins - << "), tx:"); - LOG_PRINT_L0(currency::obj_to_json_str(b.miner_tx)); - return false; - } - if (base_reward + pos_income + fee != money_in_use) - { - LOG_ERROR("coinbase transaction doesn't use full amount of block reward: spent: (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + pos_income + fee) << "(" << print_money(base_reward) << "+" << print_money(pos_income) << "+" << print_money(fee) + LOG_ERROR("coinbase transaction balance check failed. Block reward is " << print_money_brief(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ", blocks_size_median = " << blocks_size_median << ", cumulative_block_size = " << cumulative_block_size << ", fee = " << fee @@ -1365,6 +1347,7 @@ bool blockchain_storage::validate_miner_transaction(const block& b, LOG_PRINT_L0(currency::obj_to_json_str(b.miner_tx)); return false; } + LOG_PRINT_MAGENTA("Mining tx verification ok, blocks_size_median = " << blocks_size_median, LOG_LEVEL_2); return true; } @@ -3377,15 +3360,17 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, block_verif bool blockchain_storage::push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector& global_indexes) { CRITICAL_REGION_LOCAL(m_read_lock); - size_t i = 0; - BOOST_FOREACH(const auto& otv, tx.vout) + size_t output_index = 0; + for(const auto& otv : tx.vout) { VARIANT_SWITCH_BEGIN(otv); VARIANT_CASE_CONST(tx_out_bare, ot) if (ot.target.type() == typeid(txout_to_key) || ot.target.type() == typeid(txout_htlc)) { - m_db_outputs.push_back_item(ot.amount, global_output_entry::construct(tx_id, i)); + m_db_outputs.push_back_item(ot.amount, global_output_entry::construct(tx_id, output_index)); global_indexes.push_back(m_db_outputs.get_item_size(ot.amount) - 1); + + // TODO: CZ, consider removing this check if (ot.target.type() == typeid(txout_htlc) && !is_hardfork_active(3)) { LOG_ERROR("Error: Transaction with txout_htlc before hardfork 3 (before height " << m_core_runtime_config.hard_forks.get_str_height_the_hardfork_active_after(3) << ")"); @@ -3394,18 +3379,19 @@ bool blockchain_storage::push_transaction_to_global_outs_index(const transaction } else if (ot.target.type() == typeid(txout_multisig)) { - - crypto::hash multisig_out_id = get_multisig_out_id(tx, i); + crypto::hash multisig_out_id = get_multisig_out_id(tx, output_index); CHECK_AND_ASSERT_MES(multisig_out_id != null_hash, false, "internal error during handling get_multisig_out_id() with tx id " << tx_id); CHECK_AND_ASSERT_MES(!m_db_multisig_outs.find(multisig_out_id), false, "Internal error: already have multisig_out_id " << multisig_out_id << "in multisig outs index"); - m_db_multisig_outs.set(multisig_out_id, ms_output_entry::construct(tx_id, i)); + m_db_multisig_outs.set(multisig_out_id, ms_output_entry::construct(tx_id, output_index)); global_indexes.push_back(0); // just stub to make other code easier } VARIANT_CASE_CONST(tx_out_zarcanum, toz) - //@#@ + // TODO: CZ, consider using separate table for hidden amounts + m_db_outputs.push_back_item(0, global_output_entry::construct(tx_id, output_index)); + global_indexes.push_back(m_db_outputs.get_item_size(0) - 1); VARIANT_CASE_THROW_ON_OTHER(); VARIANT_SWITCH_END(); - ++i; + ++output_index; } return true; } @@ -5538,7 +5524,6 @@ bool get_tx_from_cache(const crypto::hash& tx_id, transactions_map& tx_cache, tr //------------------------------------------------------------------ bool blockchain_storage::collect_rangeproofs_data_from_tx(std::vector& agregated_proofs, const transaction& tx /*, std::vector& tx_outs_commitments*/) { - if (tx.version <= TRANSACTION_VERSION_PRE_HF4) { return true; diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 90b64500..f3581340 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -439,7 +439,7 @@ namespace currency // Zarcanum-aware CLSAG signature struct ZC_sig { - crypto::public_key pseudo_out_amount_commitment; + crypto::public_key pseudo_out_amount_commitment; // premultiplied by 1/8 crypto::CLSAG_GG_signature_serialized clsags_gg; BEGIN_SERIALIZE_OBJECT() @@ -452,6 +452,20 @@ namespace currency BOOST_SERIALIZE(clsags_gg) END_BOOST_SERIALIZATION() }; + + struct zc_balance_proof + { + crypto::signature s; + + BEGIN_SERIALIZE_OBJECT() + FIELD(s) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(s) + END_BOOST_SERIALIZATION() + }; + //#pragma pack(pop) typedef boost::variant txin_v; @@ -721,10 +735,10 @@ namespace currency END_SERIALIZE() }; - typedef boost::mpl::vector23< + typedef boost::mpl::vector24< tx_service_attachment, tx_comment, tx_payer_old, tx_receiver_old, tx_derivation_hint, std::string, tx_crypto_checksum, etc_tx_time, etc_tx_details_unlock_time, etc_tx_details_expiration_time, etc_tx_details_flags, crypto::public_key, extra_attachment_info, extra_alias_entry_old, extra_user_data, extra_padding, etc_tx_flags16_t, etc_tx_details_unlock_time2, - tx_payer, tx_receiver, extra_alias_entry, zarcanum_tx_data_v1, zarcanum_outs_range_proof + tx_payer, tx_receiver, extra_alias_entry, zarcanum_tx_data_v1, zarcanum_outs_range_proof, zc_balance_proof > all_payload_types; typedef boost::make_variant_over::type payload_items_v; @@ -1041,6 +1055,7 @@ SET_VARIANT_TAGS(currency::NLSAG_sig, 42, "NLSAG_sig"); SET_VARIANT_TAGS(currency::ZC_sig, 43, "ZC_sig"); SET_VARIANT_TAGS(currency::void_sig, 44, "void_sig"); SET_VARIANT_TAGS(currency::zarcanum_outs_range_proof, 45, "zarcanum_outs_range_proof"); +SET_VARIANT_TAGS(currency::zc_balance_proof, 46, "zc_balance_proof"); diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index b94e10a6..c6246d79 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -20,6 +20,7 @@ #define CURRENCY_MAX_BLOCK_NUMBER 500000000 #define CURRENCY_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! #define CURRENCY_TX_MAX_ALLOWED_OUTS 2000 +#define CURRENCY_TX_MIN_ALLOWED_OUTS 2 // effective starting HF4 Zarcanum #define CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX 0xc5 // addresses start with 'Zx' #define CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX 0x3678 // integrated addresses start with 'iZ' #define CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX 0x36f8 // integrated addresses start with 'iZ' (new format) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index b22683b3..6622c842 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -103,63 +103,56 @@ namespace currency return diff; } //------------------------------------------------------------------ - bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, - size_t current_block_size, - uint64_t fee, - const account_public_address &miner_address, - const account_public_address &stakeholder_address, - transaction& tx, - uint64_t tx_version, - const blobdata& extra_nonce, - size_t max_outs, - bool pos, - const pos_entry& pe) + // for txs with no zc inputs (and thus no zc signatures) but with zc outputs + bool generate_tx_balance_proof(transaction &tx, const crypto::scalar_t& outputs_blinding_masks_sum, uint64_t block_reward_for_miner_tx = 0) { - uint64_t block_reward = 0; - if (!get_block_reward(pos, median_size, current_block_size, already_generated_coins, block_reward, height)) - { - LOG_ERROR("Block is too big"); - return false; - } - block_reward += fee; - - std::vector out_amounts; - decompose_amount_into_digits(block_reward, DEFAULT_DUST_THRESHOLD, - [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, - [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); + CHECK_AND_ASSERT_MES(tx.version > TRANSACTION_VERSION_PRE_HF4, false, "unsupported tx.version: " << tx.version); + CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.signatures) == 0, false, ""); - CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); - while (max_outs < out_amounts.size()) + uint64_t bare_inputs_sum = block_reward_for_miner_tx; + // TODO: condider remove the followin cycle + for(auto& vin : tx.vin) { - out_amounts[out_amounts.size() - 2] += out_amounts.back(); - out_amounts.resize(out_amounts.size() - 1); + VARIANT_SWITCH_BEGIN(vin); + VARIANT_CASE(txin_to_key, tk) + bare_inputs_sum += tk.amount; + VARIANT_CASE(txin_htlc, foo); + CHECK_AND_ASSERT_MES(false, false, "unexpected txin_htlc input"); + VARIANT_CASE(txin_multisig, ms); + bare_inputs_sum += ms.amount; + VARIANT_CASE(txin_zc_input, foo); + CHECK_AND_ASSERT_MES(false, false, "unexpected txin_zc_input input"); + VARIANT_SWITCH_END(); } - - std::vector destinations; - for (auto a : out_amounts) + crypto::point_t outs_commitments_sum = crypto::c_point_0; + for(auto& vout : tx.vout) { - tx_destination_entry de = AUTO_VAL_INIT(de); - de.addr.push_back(miner_address); - de.amount = a; - if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) - { - //this means that block is creating after hardfork_1 and unlock_time is needed to set for every destination separately - de.unlock_time = height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW; - } - destinations.push_back(de); + CHECK_AND_ASSERT_MES(vout.type() == typeid(tx_out_zarcanum), false, "unexpected type in outs: " << vout.type().name()); + const tx_out_zarcanum& ozc = boost::get(vout); + outs_commitments_sum += crypto::point_t(ozc.amount_commitment); // amount_commitment premultiplied by 1/8 } + outs_commitments_sum.modify_mul8(); - if (pos) - { - uint64_t stake_lock_time = 0; - if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) - stake_lock_time = pe.stake_unlock_time; - destinations.push_back(tx_destination_entry(pe.amount, stakeholder_address, stake_lock_time)); - } - + uint64_t fee = 0; + CHECK_AND_ASSERT_MES(get_tx_fee(tx, fee), false, "unable to get tx fee"); - return construct_miner_tx(height, median_size, already_generated_coins, current_block_size, fee, destinations, tx, tx_version, extra_nonce, max_outs, pos, pe); + // sum(bare inputs' amounts) * H + sum(pseudo outs commitments for ZC inputs) + residual * G = sum(outputs' commitments) + fee * H + // <=> + // (fee - sum(bare inputs' amounts)) * H - sum(pseudo outs commitments for ZC inputs) + sum(outputs' commitments) = residual * G + + // tx doesn't have any zc inputs --> add Schnorr proof for commitment to zero + CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.attachment) == 0, false, ""); + zc_balance_proof balance_proof = AUTO_VAL_INIT(balance_proof); + + crypto::point_t commitment_to_zero = outs_commitments_sum + (crypto::scalar_t(fee) - crypto::scalar_t(bare_inputs_sum)) * crypto::c_point_H; + //crypto::scalar_t witness = outputs_blinding_masks_sum; + + // TODO: consider adding more data to message + crypto::generate_signature(null_hash, commitment_to_zero.to_public_key(), outputs_blinding_masks_sum.as_secret_key(), balance_proof.s); + tx.attachment.push_back(balance_proof); + + return true; } //------------------------------------------------------------------ bool apply_unlock_time(const std::vector& destinations, transaction& tx) @@ -182,11 +175,12 @@ namespace currency return true; } - //------------------------------------------------------------------ + //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, size_t current_block_size, uint64_t fee, - const std::vector& destinations, + const account_public_address &miner_address, + const account_public_address &stakeholder_address, transaction& tx, uint64_t tx_version, const blobdata& extra_nonce, @@ -194,6 +188,71 @@ namespace currency bool pos, const pos_entry& pe) { + CHECK_AND_ASSERT_THROW_MES(!pos || tx.version <= TRANSACTION_VERSION_PRE_HF4, "PoS miner tx is currently unsupported for HF4 -- sowle"); + + uint64_t block_reward = 0; + if (!get_block_reward(pos, median_size, current_block_size, already_generated_coins, block_reward, height)) + { + LOG_ERROR("Block is too big"); + return false; + } + block_reward += fee; + + // + // prepare destinations + // + // 1. split block_reward into out_amounts + std::vector out_amounts; + if (tx.version > TRANSACTION_VERSION_PRE_HF4) + { + // randomly split into CURRENCY_TX_MIN_ALLOWED_OUTS outputs + // TODO: consider refactoring + uint64_t amount_remaining = block_reward; + for(size_t i = 1; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) // starting from 1 for one less iteration + { + uint64_t amount = crypto::rand() % amount_remaining; + amount_remaining -= amount; + out_amounts.push_back(amount); + } + out_amounts.push_back(amount_remaining); + // std::shuffle(out_amounts.begin(), out_amounts.end(), crypto::uniform_random_bit_generator()); + } + else + { + // non-hidden outs: split into digits + decompose_amount_into_digits(block_reward, DEFAULT_DUST_THRESHOLD, + [&out_amounts](uint64_t a_chunk) { out_amounts.push_back(a_chunk); }, + [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); + CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); + while (max_outs < out_amounts.size()) + { + out_amounts[out_amounts.size() - 2] += out_amounts.back(); + out_amounts.resize(out_amounts.size() - 1); + } + } + // 2. construct destinations using out_amounts + std::vector destinations; + for (auto a : out_amounts) + { + tx_destination_entry de = AUTO_VAL_INIT(de); + de.addr.push_back(miner_address); + de.amount = a; + if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) + { + //this means that block is creating after hardfork_1 and unlock_time is needed to set for every destination separately + de.unlock_time = height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW; + } + destinations.push_back(de); + } + + if (pos) + { + uint64_t stake_lock_time = 0; + if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time = pe.stake_unlock_time; + destinations.push_back(tx_destination_entry(pe.amount, stakeholder_address, stake_lock_time)); + } + CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS || height == 0, false, "Too many outs (" << destinations.size() << ")! Miner tx can't be constructed."); tx.version = tx_version; tx.vin.clear(); @@ -259,17 +318,122 @@ namespace currency bool r = generate_zarcanum_outs_range_proof(range_proof_start_index, amounts.size(), amounts, blinding_masks, tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zarcanum_outs_range_proof()"); tx.attachment.push_back(range_proofs); + + if (!pos) + { + r = generate_tx_balance_proof(tx, blinding_masks_sum, block_reward); + CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); + } } + if (tx.attachment.size()) + add_attachments_info_to_extra(tx.extra, tx.attachment); + if (!have_type_in_variant_container(tx.extra)) { //if stake unlock time was not set, then we can use simple "whole transaction" lock scheme set_tx_unlock_time(tx, height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW); } - + return true; } - //--------------------------------------------------------------- + //------------------------------------------------------------------ + bool check_tx_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx /* = 0 */) + { + if (tx.version > TRANSACTION_VERSION_PRE_HF4) + { + size_t zc_inputs_count = 0; + uint64_t bare_inputs_sum = additional_inputs_amount_and_fees_for_mining_tx; + for(auto& vin : tx.vin) + { + VARIANT_SWITCH_BEGIN(vin); + VARIANT_CASE_CONST(txin_to_key, tk) + bare_inputs_sum += tk.amount; + VARIANT_CASE_CONST(txin_htlc, htlc); + bare_inputs_sum += htlc.amount; + VARIANT_CASE_CONST(txin_multisig, ms); + bare_inputs_sum += ms.amount; + VARIANT_CASE_CONST(txin_zc_input, foo); + ++zc_inputs_count; + VARIANT_SWITCH_END(); + } + + crypto::point_t outs_commitments_sum = crypto::c_point_0; + for(auto& vout : tx.vout) + { + CHECK_AND_ASSERT_MES(vout.type() == typeid(tx_out_zarcanum), false, "unexpected type in outs: " << vout.type().name()); + const tx_out_zarcanum& ozc = boost::get(vout); + outs_commitments_sum += crypto::point_t(ozc.amount_commitment); // amount_commitment premultiplied by 1/8 + } + outs_commitments_sum.modify_mul8(); + + uint64_t fee = 0; + CHECK_AND_ASSERT_MES(get_tx_fee(tx, fee), false, "unable to get tx fee"); + + CHECK_AND_ASSERT_MES(additional_inputs_amount_and_fees_for_mining_tx == 0 || fee == 0, false, "invalid tx: fee = " << print_money_brief(fee) << + ", additional inputs + fees = " << print_money_brief(additional_inputs_amount_and_fees_for_mining_tx)); + + size_t zc_sigs_count = 0; + crypto::point_t sum_of_pseudo_out_amount_commitments = crypto::c_point_0; + for(auto& sig_v : tx.signatures) + { + VARIANT_SWITCH_BEGIN(sig_v); + VARIANT_CASE_CONST(ZC_sig, zc_sig); + sum_of_pseudo_out_amount_commitments += crypto::point_t(zc_sig.pseudo_out_amount_commitment); // *1/8 + ++zc_sigs_count; + VARIANT_SWITCH_END(); + } + sum_of_pseudo_out_amount_commitments.modify_mul8(); + + CHECK_AND_ASSERT_MES(zc_inputs_count == zc_sigs_count, false, "zc inputs count (" << zc_inputs_count << ") and zc sigs count (" << zc_sigs_count << ") missmatch"); + + if (zc_inputs_count > 0) + { + // no need for additional Schnorr proof for commitment to zero + + // sum(bare inputs' amounts) * H + sum(pseudo outs commitments for ZC inputs) = sum(outputs' commitments) + fee * H + // <=> + // (sum(bare inputs' amounts) - fee) * H + sum(pseudo outs commitments for ZC inputs) - sum(outputs' commitments) = 0 + crypto::point_t Z = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * crypto::c_point_H + sum_of_pseudo_out_amount_commitments - outs_commitments_sum; + CHECK_AND_ASSERT_MES(Z.is_zero(), false, "balace equation does not hold"); + } + else + { + // no zc inputs -- there should be Schnorr proof for commitment to zero + zc_balance_proof balance_proof = AUTO_VAL_INIT(balance_proof); + bool r = get_type_in_variant_container(tx.attachment, balance_proof); + CHECK_AND_ASSERT_MES(r, false, "no zc inputs are present, but at the same time there's no zc_balance_proof in attachment"); + + // (fee - sum(bare inputs' amounts)) * H + sum(outputs' commitments) = residual * G + crypto::point_t commitment_to_zero = (crypto::scalar_t(fee) - crypto::scalar_t(bare_inputs_sum)) * crypto::c_point_H + outs_commitments_sum; + r = crypto::check_signature(null_hash, commitment_to_zero.to_public_key(), balance_proof.s); + CHECK_AND_ASSERT_MES(r, false, "zc_balance_proof is invalid"); + } + } + else + { + // old fashioned tx with non-hidden amounts + uint64_t bare_outputs_sum = get_outs_money_amount(tx); + uint64_t bare_inputs_sum = get_inputs_money_amount(tx); + + if (additional_inputs_amount_and_fees_for_mining_tx == 0) + { + // normal tx + CHECK_AND_ASSERT_MES(bare_inputs_sum >= bare_outputs_sum, false, "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) + << ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); + } + else + { + // miner tx + CHECK_AND_ASSERT_MES(bare_inputs_sum + additional_inputs_amount_and_fees_for_mining_tx == bare_outputs_sum, false, + "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) << + ") + additional inputs and fees (" << print_money_brief(additional_inputs_amount_and_fees_for_mining_tx) << + ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); + } + } + return true; + } + //------------------------------------------------------------------ bool derive_ephemeral_key_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral) { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); @@ -615,7 +779,7 @@ namespace currency //--------------------------------------------------------------- bool add_tx_extra_userdata(transaction& tx, const blobdata& extra_nonce) { - CHECK_AND_ASSERT_MES(extra_nonce.size() <= 255, false, "extra nonce could be 255 bytes max"); + CHECK_AND_ASSERT_MES(extra_nonce.size() <= 255, false, "extra nonce size exceeded (255 bytes max)"); extra_user_data eud = AUTO_VAL_INIT(eud); eud.buff = extra_nonce; tx.extra.push_back(eud); @@ -2037,8 +2201,6 @@ namespace currency for(const auto& in : tx.vin) { uint64_t this_amount = get_amount_from_variant(in); - if (!this_amount) - return false; money += this_amount; } return true; @@ -2223,8 +2385,7 @@ namespace currency VARIANT_SWITCH_BEGIN(o); VARIANT_CASE_CONST(tx_out_bare, o) outputs_amount += o.amount; - VARIANT_CASE_CONST(tx_out_zarcanum, o) - //@#@ + // ignore outputs with hidden amounts VARIANT_SWITCH_END(); } return outputs_amount; @@ -3110,6 +3271,11 @@ namespace currency tv.short_view = "outputs_count = " + std::to_string(rp.outputs_count); return true; } + bool operator()(const zc_balance_proof& bp) + { + tv.type = "zc_balance_proof"; + return true; + } }; //------------------------------------------------------------------ template @@ -3695,12 +3861,13 @@ namespace currency return true; std::vector sigs; - for(auto el : range_proofs) + sigs.reserve(range_proofs.size()); + for(auto& el : range_proofs) sigs.emplace_back(el.range_proof.bpp, el.amount_commitments); uint8_t err = 0; bool r = crypto::bpp_verify<>(sigs, &err); - CHECK_AND_ASSERT_MES(r, false, "bpp_verify failed with error " << err); + CHECK_AND_ASSERT_MES(r, false, "bpp_verify failed with error " << (int)err); return true; } diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 7084be7e..1f9a967b 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -223,6 +223,7 @@ namespace currency }; bool verify_multiple_zarcanum_outs_range_proofs(const std::vector& range_proofs); + bool check_tx_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, size_t current_block_size, @@ -235,19 +236,6 @@ namespace currency size_t max_outs = CURRENCY_MINER_TX_MAX_OUTS, bool pos = false, const pos_entry& pe = pos_entry()); - - bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, - size_t current_block_size, - uint64_t fee, - const std::vector& destinations, - transaction& tx, - uint64_t tx_version, - const blobdata& extra_nonce = blobdata(), - size_t max_outs = CURRENCY_MINER_TX_MAX_OUTS, - bool pos = false, - const pos_entry& pe = pos_entry()); - - //--------------------------------------------------------------- uint64_t get_string_uint64_hash(const std::string& str); bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, crypto::scalar_t& out_blinding_mask, finalized_tx& result, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED); diff --git a/src/currency_core/tx_semantic_validation.cpp b/src/currency_core/tx_semantic_validation.cpp index 252c60be..ec7019f4 100644 --- a/src/currency_core/tx_semantic_validation.cpp +++ b/src/currency_core/tx_semantic_validation.cpp @@ -66,13 +66,9 @@ namespace currency return false; } - uint64_t amount_in = 0; - get_inputs_money_amount(tx, amount_in); - uint64_t amount_out = get_outs_money_amount(tx); - - if (amount_in < amount_out) + if (!check_tx_balance(tx)) { - LOG_PRINT_RED_L0("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + LOG_PRINT_RED_L0("tx balance check failed, tx id= " << get_transaction_hash(tx)); return false; }