diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 14046d61..3d1f18aa 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -16,10 +16,6 @@ namespace crypto #include "crypto/crypto-ops.h" } // extern "C" -#define CRYPTO_STR_(X) #X -#define CRYPTO_STR(X) CRYPTO_STR_(X) -#define CRYPTO_CHECK_AND_THROW_MES(cond, msg) if (!(cond)) { throw std::runtime_error(msg " @ " __FILE__ ":" CRYPTO_STR(__LINE__)); } - // // Helpers // @@ -1084,6 +1080,31 @@ namespace crypto return hs_calculator.calc_hash(); } + static scalar_t hs(const crypto::public_key& pk, const uint64_t i) + { + hs_t hs_calculator(2); + hs_calculator.add_pub_key(pk); + hs_calculator.add_scalar(scalar_t(i)); + return hs_calculator.calc_hash(); + } + + static scalar_t hs(const crypto::secret_key& sk, const uint64_t i) + { + hs_t hs_calculator(2); + hs_calculator.add_scalar(sk); + hs_calculator.add_scalar(scalar_t(i)); + return hs_calculator.calc_hash(); + } + + static scalar_t hs(const char(&str32)[32], const crypto::public_key& pk, const uint64_t i) + { + hs_t hs_calculator(3); + hs_calculator.add_32_chars(str32); + hs_calculator.add_pub_key(pk); + hs_calculator.add_scalar(scalar_t(i)); + return hs_calculator.calc_hash(); + } + static point_t hp(const point_t& p) { point_t result; diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 7210cd3c..37701cd7 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2022 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -19,9 +19,9 @@ #include "hash.h" #if !defined(NDEBUG) -# define crypto_assert(expression) assert(expression) +# define crypto_assert(expression) assert(expression); CRYPTO_CHECK_AND_THROW_MES(expression, #expression) #else -# define crypto_assert(expression) ((void)0) +# define crypto_assert(expression) CRYPTO_CHECK_AND_THROW_MES(expression, #expression) #endif namespace crypto { diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index aa79f4e0..160929f8 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -17,6 +17,9 @@ #include "hash.h" #include "warnings.h" +#define CRYPTO_STR_(X) #X +#define CRYPTO_STR(X) CRYPTO_STR_(X) +#define CRYPTO_CHECK_AND_THROW_MES(cond, msg) if (!(cond)) { throw std::runtime_error(msg " @ " __FILE__ ":" CRYPTO_STR(__LINE__)); } PUSH_GCC_WARNINGS DISABLE_CLANG_WARNING(unused-private-field) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index e193c6f2..f21cfbcf 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -2493,6 +2493,20 @@ bool blockchain_storage::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPU << out_ptr->out_no << " more than transaction outputs = " << tx_ptr->tx.vout.size() << ", for tx id = " << out_ptr->tx_id); const transaction& tx = tx_ptr->tx; + CHECK_AND_ASSERT_MES(tx_ptr->m_spent_flags.size() == tx.vout.size(), false, "internal error: spent_flag.size()=" << tx_ptr->m_spent_flags.size() << ", tx.vout.size()=" << tx.vout.size()); + + //do not use outputs that obviously spent for mixins + if (tx_ptr->m_spent_flags[out_ptr->out_no]) + return false; + + //check if transaction is unlocked + if (!is_tx_spendtime_unlocked(get_tx_unlock_time(tx, out_ptr->out_no))) + return false; + + // do not use burned coins + if (is_out_burned(tx.vout[out_ptr->out_no])) + return false; + VARIANT_SWITCH_BEGIN(tx.vout[out_ptr->out_no]); VARIANT_CASE_CONST(tx_out_bare, o) { @@ -2504,19 +2518,7 @@ bool blockchain_storage::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPU CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "unknown tx out type"); const txout_to_key& otk = boost::get(o.target); - CHECK_AND_ASSERT_MES(tx_ptr->m_spent_flags.size() == tx.vout.size(), false, "internal error"); - - //do not use outputs that obviously spent for mixins - if (tx_ptr->m_spent_flags[out_ptr->out_no]) - return false; - - // do not use burned coins - if (otk.key == null_pkey) - return false; - - //check if transaction is unlocked - if (!is_tx_spendtime_unlocked(get_tx_unlock_time(tx, out_ptr->out_no))) - return false; + // TODO #@#@ remove code duplication, make extracting mix_attr in a more generalized way //use appropriate mix_attr out uint8_t mix_attr = otk.mix_attr; @@ -2528,13 +2530,27 @@ bool blockchain_storage::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPU else if (mix_attr != CURRENCY_TO_KEY_OUT_RELAXED && mix_attr > mix_count) return false;//mix_attr set to specific minimum, and mix_count is less then desired count - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); oen.global_amount_index = i; oen.out_key = otk.key; } VARIANT_CASE_CONST(tx_out_zarcanum, toz) - //@#@ + { + //use appropriate mix_attr out + uint8_t mix_attr = toz.mix_attr; + + if (mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + return false; //COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS call means that ring signature will have more than one entry. + else if (use_only_forced_to_mix && mix_attr == CURRENCY_TO_KEY_OUT_RELAXED) + return false; //relaxed not allowed + else if (mix_attr != CURRENCY_TO_KEY_OUT_RELAXED && mix_attr > mix_count) + return false;//mix_attr set to specific minimum, and mix_count is less then desired count + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); + oen.global_amount_index = i; + oen.out_key = toz.amount_commitment; + // TODO @#@# this is certainly not enough + } VARIANT_SWITCH_END(); return true; @@ -3044,10 +3060,11 @@ void blockchain_storage::print_blockchain_outs_stats() const { VARIANT_SWITCH_BEGIN(p_tx->tx.vout[output_entry.out_no]); VARIANT_CASE_CONST(tx_out_bare, o) - if (boost::get(o.target).mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + if (o.target.type() == typeid(txout_to_key) && boost::get(o.target).mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) ++stat.mixable; VARIANT_CASE_CONST(tx_out_zarcanum, toz) - //@#@ + if (toz.mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + ++stat.mixable; VARIANT_SWITCH_END(); } return true; diff --git a/src/currency_core/crypto_config.h b/src/currency_core/crypto_config.h index 24ae3d6a..37b9378d 100644 --- a/src/currency_core/crypto_config.h +++ b/src/currency_core/crypto_config.h @@ -8,3 +8,4 @@ // #define CRYPTO_HDS_OUT_AMOUNT_MASK "ZANO_HDS_OUT_AMOUNT_MASK_______" #define CRYPTO_HDS_OUT_BLINDING_MASK "ZANO_HDS_OUT_BLINDING_MASK_____" +#define CRYPTO_HDS_OUT_CONCEALING_POINT "ZANO_HDS_OUT_CONCEALING_POINT__" diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 4ec62762..b173cf8f 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -391,6 +391,24 @@ namespace currency END_BOOST_SERIALIZATION() }; + // non-consoditated txs must have one of this objects in the attachments (outputs_count == vout.size()) + // consolidated -- one pre consolidated part (sum(outputs_count) == vout.size()) + struct zarcanum_outs_range_proof + { + crypto::bpp_signature_serialized bpp; + uint8_t outputs_count; // how many outputs are included in the proof + + BEGIN_SERIALIZE_OBJECT() + FIELD(bpp) + FIELD(outputs_count) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(bpp) + BOOST_SERIALIZE(outputs_count) + END_BOOST_SERIALIZATION() + }; + struct zarcanum_sig { struct input_proofs_t @@ -689,10 +707,10 @@ namespace currency END_SERIALIZE() }; - typedef boost::mpl::vector22< + typedef boost::mpl::vector23< 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 + tx_payer, tx_receiver, extra_alias_entry, zarcanum_tx_data_v1, zarcanum_outs_range_proof > all_payload_types; typedef boost::make_variant_over::type payload_items_v; diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index aa7a44fd..b36a4417 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -614,106 +614,165 @@ namespace currency //--------------------------------------------------------------- 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, finalized_tx& result, uint8_t tx_outs_attr) { - CHECK_AND_ASSERT_MES(de.addr.size() == 1 || (de.addr.size() > 1 && de.minimum_sigs <= de.addr.size()), false, "Invalid destination entry: amount: " << de.amount << " minimum_sigs: " << de.minimum_sigs << " addr.size(): " << de.addr.size()); - - std::vector target_keys; - target_keys.reserve(de.addr.size()); - for (auto& apa : de.addr) + if (tx.version > TRANSACTION_VERSION_PRE_HF4) { - crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + // create tx_out_zarcanum + CHECK_AND_ASSERT_MES(de.addr.size() == 1, false, "zarcanum multisig not implemented yet"); + // TODO @#@# implement multisig support + + tx_out_zarcanum out = AUTO_VAL_INIT(out); + const account_public_address& apa = de.addr.front(); if (apa.spend_public_key == null_pkey && apa.view_public_key == null_pkey) { - //burning money(for example alias reward) - out_eph_public_key = null_pkey; + // burn money + // calculate encrypted_amount and amount_commitment anyway, but using modified derivation + crypto::scalar_t h = crypto::hash_helper_t::hs(crypto::scalar_t(tx_sec_key), output_index); // h = Hs(r, i) + + out.stealth_address = null_pkey; + out.concealing_point = null_pkey; + + crypto::scalar_t amount_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_MASK, h); + out.encrypted_amount = de.amount ^ amount_mask.m_u64[0]; + + crypto::scalar_t blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + out.amount_commitment = (de.amount * crypto::c_point_H + blinding_mask * crypto::c_point_G).to_public_key(); + + out.mix_attr = tx_outs_attr; // TODO @#@# @CZ check this } else { - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - bool r = derive_public_key_from_target_address(apa, tx_sec_key, output_index, out_eph_public_key, derivation); - CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address"); + // normal output + crypto::public_key derivation = (crypto::scalar_t(tx_sec_key) * crypto::point_t(apa.view_public_key)).modify_mul8().to_public_key(); // d = 8 * r * V + crypto::scalar_t h = crypto::hash_helper_t::hs(derivation, output_index); - uint16_t hint = get_derivation_hint(derivation); + out.stealth_address = (h * crypto::c_point_G + crypto::point_t(apa.spend_public_key)).to_public_key(); + out.concealing_point = (crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_CONCEALING_POINT, h) * crypto::point_t(apa.view_public_key)).to_public_key(); // Q = Hs(domain_sep, h) * V + + crypto::scalar_t amount_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_MASK, h); + out.encrypted_amount = de.amount ^ amount_mask.m_u64[0]; + + crypto::scalar_t blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + out.amount_commitment = (de.amount * crypto::c_point_H + blinding_mask * crypto::c_point_G).to_public_key(); + + if (de.addr.front().is_auditable()) + out.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX; // override mix_attr to 1 for auditable target addresses + else + out.mix_attr = tx_outs_attr; + + uint16_t hint = get_derivation_hint(reinterpret_cast(derivation)); if (deriv_cache.count(hint) == 0) { tx.extra.push_back(make_tx_derivation_hint_from_uint16(hint)); deriv_cache.insert(hint); } } - target_keys.push_back(out_eph_public_key); - } - tx_out_bare out; - out.amount = de.amount; - if (de.htlc_options.expiration != 0) - { - const destination_option_htlc_out& htlc_dest = de.htlc_options; - //out htlc - CHECK_AND_ASSERT_MES(target_keys.size() == 1, false, "Unexpected htl keys count = " << target_keys.size() << ", expected ==1"); - txout_htlc htlc = AUTO_VAL_INIT(htlc); - htlc.expiration = htlc_dest.expiration; - htlc.flags = 0; //0 - SHA256, 1 - RIPEMD160, by default leave SHA256 - //receiver key - htlc.pkey_redeem = *target_keys.begin(); - //generate refund key - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); - bool r = derive_public_key_from_target_address(self.account_address, tx_sec_key, output_index, out_eph_public_key, derivation); - CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address"); - htlc.pkey_refund = out_eph_public_key; - //add derivation hint for refund address - uint16_t hint = get_derivation_hint(derivation); - if (deriv_cache.count(hint) == 0) - { - tx.extra.push_back(make_tx_derivation_hint_from_uint16(hint)); - deriv_cache.insert(hint); - } - - - if (htlc_dest.htlc_hash == null_hash) - { - //we use deterministic origin, to make possible access origin on different wallets copies - - result.htlc_origin = generate_origin_for_htlc(htlc, self); - - //calculate hash - if (!htlc.flags&CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK) - { - htlc.htlc_hash = crypto::sha256_hash(result.htlc_origin.data(), result.htlc_origin.size()); - } - else - { - crypto::hash160 h160 = crypto::RIPEMD160_hash(result.htlc_origin.data(), result.htlc_origin.size()); - std::memcpy(&htlc.htlc_hash, &h160, sizeof(h160)); - } - } - else - { - htlc.htlc_hash = htlc_dest.htlc_hash; - } - out.target = htlc; - } - else if (target_keys.size() == 1) - { - //out to key - txout_to_key tk = AUTO_VAL_INIT(tk); - tk.key = target_keys.back(); - - if (de.addr.front().is_auditable()) // check only the first address because there's only one in this branch - tk.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX; // override mix_attr to 1 for auditable target addresses - else - tk.mix_attr = tx_outs_attr; - - out.target = tk; + tx.vout.push_back(out); } else { - //multisig out - txout_multisig ms = AUTO_VAL_INIT(ms); - ms.keys = std::move(target_keys); - ms.minimum_sigs = de.minimum_sigs; - out.target = ms; + // create tx_out_bare, this section can be removed after HF4 + CHECK_AND_ASSERT_MES(de.addr.size() == 1 || (de.addr.size() > 1 && de.minimum_sigs <= de.addr.size()), false, "Invalid destination entry: amount: " << de.amount << " minimum_sigs: " << de.minimum_sigs << " addr.size(): " << de.addr.size()); + + std::vector target_keys; + target_keys.reserve(de.addr.size()); + for (auto& apa : de.addr) + { + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + if (apa.spend_public_key == null_pkey && apa.view_public_key == null_pkey) + { + //burning money(for example alias reward) + out_eph_public_key = null_pkey; + } + else + { + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + bool r = derive_public_key_from_target_address(apa, tx_sec_key, output_index, out_eph_public_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address"); + + uint16_t hint = get_derivation_hint(derivation); + if (deriv_cache.count(hint) == 0) + { + tx.extra.push_back(make_tx_derivation_hint_from_uint16(hint)); + deriv_cache.insert(hint); + } + } + target_keys.push_back(out_eph_public_key); + } + + tx_out_bare out; + out.amount = de.amount; + if (de.htlc_options.expiration != 0) + { + const destination_option_htlc_out& htlc_dest = de.htlc_options; + //out htlc + CHECK_AND_ASSERT_MES(target_keys.size() == 1, false, "Unexpected htl keys count = " << target_keys.size() << ", expected ==1"); + txout_htlc htlc = AUTO_VAL_INIT(htlc); + htlc.expiration = htlc_dest.expiration; + htlc.flags = 0; //0 - SHA256, 1 - RIPEMD160, by default leave SHA256 + //receiver key + htlc.pkey_redeem = *target_keys.begin(); + //generate refund key + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); + bool r = derive_public_key_from_target_address(self.account_address, tx_sec_key, output_index, out_eph_public_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key_from_target_address"); + htlc.pkey_refund = out_eph_public_key; + //add derivation hint for refund address + uint16_t hint = get_derivation_hint(derivation); + if (deriv_cache.count(hint) == 0) + { + tx.extra.push_back(make_tx_derivation_hint_from_uint16(hint)); + deriv_cache.insert(hint); + } + + + if (htlc_dest.htlc_hash == null_hash) + { + //we use deterministic origin, to make possible access origin on different wallets copies + + result.htlc_origin = generate_origin_for_htlc(htlc, self); + + //calculate hash + if (!htlc.flags&CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK) + { + htlc.htlc_hash = crypto::sha256_hash(result.htlc_origin.data(), result.htlc_origin.size()); + } + else + { + crypto::hash160 h160 = crypto::RIPEMD160_hash(result.htlc_origin.data(), result.htlc_origin.size()); + std::memcpy(&htlc.htlc_hash, &h160, sizeof(h160)); + } + } + else + { + htlc.htlc_hash = htlc_dest.htlc_hash; + } + out.target = htlc; + } + else if (target_keys.size() == 1) + { + //out to key + txout_to_key tk = AUTO_VAL_INIT(tk); + tk.key = target_keys.back(); + + if (de.addr.front().is_auditable()) // check only the first address because there's only one in this branch + tk.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX; // override mix_attr to 1 for auditable target addresses + else + tk.mix_attr = tx_outs_attr; + + out.target = tk; + } + else + { + //multisig out + txout_multisig ms = AUTO_VAL_INIT(ms); + ms.keys = std::move(target_keys); + ms.minimum_sigs = de.minimum_sigs; + out.target = ms; + } + tx.vout.push_back(out); } - tx.vout.push_back(out); return true; } //--------------------------------------------------------------- @@ -1965,13 +2024,13 @@ namespace currency bool is_out_to_acc(const account_keys& acc, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount) { crypto::scalar_t h = {}; - crypto::derivation_to_scalar(derivation, output_index, h.as_secret_key()); // h = Hs(r * V, i) + crypto::derivation_to_scalar(derivation, output_index, h.as_secret_key()); // h = Hs(8 * r * V, i) - crypto::point_t P_prime = h * crypto::c_point_G + crypto::point_t(acc.account_address.spend_public_key); // P =? Hs(rV, i) * G + S + crypto::point_t P_prime = h * crypto::c_point_G + crypto::point_t(acc.account_address.spend_public_key); // P =? Hs(8rV, i) * G + S if (P_prime.to_public_key() != zo.stealth_address) return false; - crypto::point_t Q_prime = h * crypto::point_t(acc.account_address.view_public_key); // Q =? v * Hs(rv, i) * G + crypto::point_t Q_prime = h * crypto::point_t(acc.account_address.view_public_key); // Q =? v * Hs(8rV, i) * G if (Q_prime.to_public_key() != zo.concealing_point) return false; diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 7e28632a..0724d822 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -405,6 +405,25 @@ namespace currency return stub; } + template + bool is_out_burned(const out_t& out) { CHECK_AND_ASSERT_THROW_MES(false, "incorrect out type: " << typeid(out).name()); } + bool is_out_burned(const tx_out_bare& o) { return is_out_burned(o.target); } + bool is_out_burned(const txout_to_key& o) { return o.key == null_pkey; } + bool is_out_burned(const tx_out_zarcanum& o) { return o.stealth_address == null_pkey; } + struct zz_is_out_burned_helper_visitor : boost::static_visitor + { + template + bool operator()(const T& v) const { return is_out_burned(v); } + }; + bool is_out_burned(const tx_out_v& v) + { + return boost::apply_visitor(zz_is_out_burned_helper_visitor(), v); + } + bool is_out_burned(const txout_target_v& v) + { + return boost::apply_visitor(zz_is_out_burned_helper_visitor(), v); + } + template bool add_attachments_info_to_extra(t_extra_container& extra_container, const std::vector& attachments) { diff --git a/src/currency_core/currency_format_utils_transactions.h b/src/currency_core/currency_format_utils_transactions.h index 337d40d7..b1424c96 100644 --- a/src/currency_core/currency_format_utils_transactions.h +++ b/src/currency_core/currency_format_utils_transactions.h @@ -16,7 +16,7 @@ namespace currency { struct tx_source_entry { - typedef serializable_pair output_entry; // txout_v is either global output index or ref_by_id; public_key - is output ephemeral pub key + typedef serializable_pair output_entry; // txout_ref_v is either global output index or ref_by_id; public_key - is output's stealth address std::vector outputs; //index + key uint64_t real_output; //index in outputs vector of real output_entry