diff --git a/src/seraphis_core/legacy_decoy_selector.h b/src/seraphis_core/legacy_decoy_selector.h index 0ec6f28fe7..1663d901a4 100644 --- a/src/seraphis_core/legacy_decoy_selector.h +++ b/src/seraphis_core/legacy_decoy_selector.h @@ -31,12 +31,13 @@ #pragma once //local headers +#include "legacy_output_index.h" //third party headers //standard headers #include -#include +#include //forward declarations @@ -60,9 +61,9 @@ class LegacyDecoySelector //member functions /// request a set of ring members as on-chain enote indices - virtual void get_ring_members(const std::uint64_t real_ring_member_index, + virtual void get_ring_members(const legacy_output_index_t real_ring_member_index, const std::uint64_t num_ring_members, - std::vector &ring_members_out, + std::set &ring_members_out, std::uint64_t &real_ring_member_index_in_ref_set_out) const = 0; }; diff --git a/src/seraphis_core/legacy_decoy_selector_flat.cpp b/src/seraphis_core/legacy_decoy_selector_flat.cpp index 0c5b1ec83b..6a6a4d0015 100644 --- a/src/seraphis_core/legacy_decoy_selector_flat.cpp +++ b/src/seraphis_core/legacy_decoy_selector_flat.cpp @@ -45,50 +45,52 @@ namespace sp { //------------------------------------------------------------------------------------------------------------------- -LegacyDecoySelectorFlat::LegacyDecoySelectorFlat(const std::uint64_t min_index, const std::uint64_t max_index) : - m_min_index{min_index}, - m_max_index{max_index} +LegacyDecoySelectorFlat::LegacyDecoySelectorFlat(index_bounds_by_amount_t index_bounds_by_amount) : + m_index_bounds_by_amount(std::move(index_bounds_by_amount)) { // checks - CHECK_AND_ASSERT_THROW_MES(m_max_index >= m_min_index, "legacy decoy selector (flat): invalid element range."); + for (const auto &p : m_index_bounds_by_amount) + { + const std::uint64_t min_ind{p.second.first}; + const std::uint64_t max_ind{p.second.second}; + CHECK_AND_ASSERT_THROW_MES(max_ind >= min_ind, "legacy decoy selector (flat): min > max index."); + } } //------------------------------------------------------------------------------------------------------------------- -void LegacyDecoySelectorFlat::get_ring_members(const std::uint64_t real_ring_member_index, +void LegacyDecoySelectorFlat::get_ring_members(const legacy_output_index_t real_ring_member_index, const std::uint64_t num_ring_members, - std::vector &ring_members_out, + std::set &ring_members_out, std::uint64_t &real_ring_member_index_in_ref_set_out) const { - CHECK_AND_ASSERT_THROW_MES(real_ring_member_index >= m_min_index, + const rct::xmr_amount amount{real_ring_member_index.ledger_indexing_amount}; + const std::uint64_t min_index{this->get_min_index(amount)}; + const std::uint64_t max_index{this->get_max_index(amount)}; + + CHECK_AND_ASSERT_THROW_MES(real_ring_member_index.index >= min_index, "legacy decoy selector (flat): real ring member index below available index range."); - CHECK_AND_ASSERT_THROW_MES(real_ring_member_index <= m_max_index, + CHECK_AND_ASSERT_THROW_MES(real_ring_member_index.index <= max_index, "legacy decoy selector (flat): real ring member index above available index range."); - CHECK_AND_ASSERT_THROW_MES(num_ring_members <= m_max_index - m_min_index + 1, + CHECK_AND_ASSERT_THROW_MES(num_ring_members <= max_index - min_index + 1, "legacy decoy selector (flat): insufficient available legacy enotes to have unique ring members."); // fill in ring members ring_members_out.clear(); - ring_members_out.reserve(num_ring_members); - ring_members_out.emplace_back(real_ring_member_index); + ring_members_out.insert(real_ring_member_index); while (ring_members_out.size() < num_ring_members) { - // select a new ring member from indices in the specified range that aren't used yet (only unique ring members - // are allowed) - std::uint64_t new_ring_member; - do { new_ring_member = crypto::rand_range(m_min_index, m_max_index); } - while (std::find(ring_members_out.begin(), ring_members_out.end(), new_ring_member) != ring_members_out.end()); - - ring_members_out.emplace_back(new_ring_member); + // select a new ring member from indices in the specified range with uniform distribution + const std::uint64_t new_ring_member_index{crypto::rand_range(min_index, max_index)}; + // add to set (only unique values will remain) + ring_members_out.insert({amount, new_ring_member_index}); } - // sort reference set - std::sort(ring_members_out.begin(), ring_members_out.end()); - // find location in reference set where the real reference sits // note: the reference set does not contain duplicates, so we don't have to handle the case of multiple real references + // note2: `ring_members_out` is a `std::set`, which contains ordered keys, so the index selected will be correct. real_ring_member_index_in_ref_set_out = 0; - for (const std::uint64_t reference : ring_members_out) + for (const legacy_output_index_t reference : ring_members_out) { if (reference == real_ring_member_index) return; @@ -97,4 +99,14 @@ void LegacyDecoySelectorFlat::get_ring_members(const std::uint64_t real_ring_mem } } //------------------------------------------------------------------------------------------------------------------- +std::uint64_t LegacyDecoySelectorFlat::get_min_index(const rct::xmr_amount amount) const +{ + return m_index_bounds_by_amount.at(amount).first; +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t LegacyDecoySelectorFlat::get_max_index(const rct::xmr_amount amount) const +{ + return m_index_bounds_by_amount.at(amount).second; +} +//------------------------------------------------------------------------------------------------------------------- } //namespace sp diff --git a/src/seraphis_core/legacy_decoy_selector_flat.h b/src/seraphis_core/legacy_decoy_selector_flat.h index 0447d653ec..d70589d756 100644 --- a/src/seraphis_core/legacy_decoy_selector_flat.h +++ b/src/seraphis_core/legacy_decoy_selector_flat.h @@ -36,8 +36,8 @@ //third party headers //standard headers -#include -#include +#include +#include //forward declarations @@ -47,29 +47,37 @@ namespace sp //// // LegacyDecoySelectorFlat -// - get a set of unique legacy ring members, selected from a flat distribution across the range of available enotes +// - get a set of unique legacy ring members, selected from a flat distribution across the range of available +// enotes with the same ledger indexing amount /// class LegacyDecoySelectorFlat final : public LegacyDecoySelector { public: +//member types + /// [ ledger amount : {min index, max index} ] + using index_bounds_by_amount_t = std::map>; + //constructors /// default constructor: disabled /// normal constructor - LegacyDecoySelectorFlat(const std::uint64_t min_index, const std::uint64_t max_index); + LegacyDecoySelectorFlat(index_bounds_by_amount_t index_bounds_by_amount); //destructor: default //member functions /// request a set of ring members from range [min_index, max_index] - void get_ring_members(const std::uint64_t real_ring_member_index, + void get_ring_members(const legacy_output_index_t real_ring_member_index, const std::uint64_t num_ring_members, - std::vector &ring_members_out, + std::set &ring_members_out, std::uint64_t &real_ring_member_index_in_ref_set_out) const override; //member variables private: - std::uint64_t m_min_index; - std::uint64_t m_max_index; + index_bounds_by_amount_t m_index_bounds_by_amount; + +//member functions (private) + std::uint64_t get_min_index(const rct::xmr_amount amount) const; + std::uint64_t get_max_index(const rct::xmr_amount amount) const; }; } //namespace sp diff --git a/src/seraphis_core/legacy_enote_types.cpp b/src/seraphis_core/legacy_enote_types.cpp index 521d507cc3..dd67deb6f4 100644 --- a/src/seraphis_core/legacy_enote_types.cpp +++ b/src/seraphis_core/legacy_enote_types.cpp @@ -76,6 +76,21 @@ rct::key amount_commitment_ref(const LegacyEnoteVariant &variant) return variant.visit(visitor()); } //------------------------------------------------------------------------------------------------------------------- +rct::xmr_amount cleartext_amount_ref(const LegacyEnoteVariant &variant) +{ + struct visitor final : public tools::variant_static_visitor + { + using variant_static_visitor::operator(); //for blank overload + rct::xmr_amount operator()(const LegacyEnoteV1 &enote) const { return enote.amount; } + rct::xmr_amount operator()(const LegacyEnoteV2 &enote) const { return 0; } + rct::xmr_amount operator()(const LegacyEnoteV3 &enote) const { return 0; } + rct::xmr_amount operator()(const LegacyEnoteV4 &enote) const { return enote.amount; } + rct::xmr_amount operator()(const LegacyEnoteV5 &enote) const { return 0; } + }; + + return variant.visit(visitor()); +} +//------------------------------------------------------------------------------------------------------------------- LegacyEnoteV1 gen_legacy_enote_v1() { LegacyEnoteV1 temp; diff --git a/src/seraphis_core/legacy_enote_types.h b/src/seraphis_core/legacy_enote_types.h index afffdfc5e1..d1ce864ce6 100644 --- a/src/seraphis_core/legacy_enote_types.h +++ b/src/seraphis_core/legacy_enote_types.h @@ -47,7 +47,7 @@ namespace sp { //// -// LegacyEnoteV1 +// LegacyEnoteV1 (all pre-RingCT enotes, then post-RingCT pre-viewtag coinbase) // - onetime address // - cleartext amount /// @@ -104,7 +104,7 @@ struct LegacyEnoteV3 final inline std::size_t legacy_enote_v3_size_bytes() { return 2*32 + 8; } //// -// LegacyEnoteV4 +// LegacyEnoteV4 (post-viewtag coinbase, also post-viewtag v1 unmixable dust txs) // - onetime address // - cleartext amount // - view tag @@ -151,10 +151,12 @@ inline std::size_t legacy_enote_v5_size_bytes() { return 2*32 + 8 + sizeof(crypt // onetime_address_ref(): get the enote's onetime address // amount_commitment_ref(): get the enote's amount commitment (this is a copy because V1 enotes need to // compute the commitment) +// cleartext_amount_ref(): get the enote's cleartext amount if applicable, or 0 otherwise /// using LegacyEnoteVariant = tools::variant; const rct::key& onetime_address_ref(const LegacyEnoteVariant &variant); rct::key amount_commitment_ref(const LegacyEnoteVariant &variant); +rct::xmr_amount cleartext_amount_ref(const LegacyEnoteVariant &variant); /** * brief: gen_legacy_enote_v1() - generate a legacy v1 enote (all random) diff --git a/src/seraphis_core/legacy_enote_utils.cpp b/src/seraphis_core/legacy_enote_utils.cpp index 34c3686b21..fbad026981 100644 --- a/src/seraphis_core/legacy_enote_utils.cpp +++ b/src/seraphis_core/legacy_enote_utils.cpp @@ -58,6 +58,11 @@ void get_legacy_enote_identifier(const rct::key &onetime_address, const rct::xmr sp_hash_to_32(transcript.data(), transcript.size(), identifier_out.bytes); } //------------------------------------------------------------------------------------------------------------------- +rct::xmr_amount get_legacy_ledger_indexing_amount(const LegacyEnoteVariant &enote, const bool is_rct) +{ + return is_rct ? 0 : cleartext_amount_ref(enote); +} +//------------------------------------------------------------------------------------------------------------------- void make_legacy_enote_v1(const rct::key &destination_spendkey, const rct::key &destination_viewkey, const rct::xmr_amount amount, diff --git a/src/seraphis_core/legacy_enote_utils.h b/src/seraphis_core/legacy_enote_utils.h index 29a63c8b28..b4943b95eb 100644 --- a/src/seraphis_core/legacy_enote_utils.h +++ b/src/seraphis_core/legacy_enote_utils.h @@ -59,6 +59,13 @@ namespace sp * outparam: identifier_out - H_32(Ko, a) */ void get_legacy_enote_identifier(const rct::key &onetime_address, const rct::xmr_amount amount, rct::key &identifier_out); +/** + * brief: get_legacy_ledger_indexing_amount - get the "amount" key used for indexing this enote in the ledger + * param: enote - + * param: is_rct - true if this is a RingCT enote + * return: the enote's cleartext amount if pre-RingCT or 0 if RingCT +*/ +rct::xmr_amount get_legacy_ledger_indexing_amount(const LegacyEnoteVariant &enote, const bool is_rct); /** * brief: make_legacy_enote_v1 - make a v1 legacy enote sending to an address or subaddress * param: destination_spendkey - [address: K^s = k^s G] [subaddress: K^{s,i} = (Hn(k^v, i) + k^s) G] diff --git a/src/seraphis_core/legacy_output_index.h b/src/seraphis_core/legacy_output_index.h new file mode 100644 index 0000000000..129c71fd77 --- /dev/null +++ b/src/seraphis_core/legacy_output_index.h @@ -0,0 +1,99 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// 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. + +// Convenience type for indexing legacy enotes in (ledger amount, index in amount) form. + +#pragma once + +//local headers + +//third party headers + +//standard headers +#include // std::uint64_t +#include // std::hash + +//forward declarations +namespace rct { typedef uint64_t xmr_amount; } + +namespace sp +{ +//// +// legacy_output_index_t +// - used to index legacy enotes the same way cryptonote inputs do: (ledger amount, index in amount) +// +struct legacy_output_index_t +{ + /// the public ledger amount used to index the enote, 0 for everything post RingCT (even coinbase) + rct::xmr_amount ledger_indexing_amount; + /// the nth position of this enote in the chain for the given amount + std::uint64_t index; +}; + +inline bool operator==(const legacy_output_index_t &a, const legacy_output_index_t &b) +{ + return a.ledger_indexing_amount == b.ledger_indexing_amount && a.index == b.index; +} + +inline bool operator!=(const legacy_output_index_t &a, const legacy_output_index_t &b) +{ + return !(a == b); +} + +/// the only purpose of this total ordering is consistency, it can't tell which came first on-chain +inline bool operator<(const legacy_output_index_t &a, const legacy_output_index_t &b) +{ + if (a.ledger_indexing_amount < b.ledger_indexing_amount) return true; + else if (a.ledger_indexing_amount > b.ledger_indexing_amount) return false; + else if (a.index < b.index) return true; + else return false; +} + +inline bool operator>(const legacy_output_index_t &a, const legacy_output_index_t &b) +{ + if (a.ledger_indexing_amount > b.ledger_indexing_amount) return true; + else if (a.ledger_indexing_amount < b.ledger_indexing_amount) return false; + else if (a.index > b.index) return true; + else return false; +} +} // namespace sp + +namespace std +{ +template <> +struct hash +{ + size_t operator()(const sp::legacy_output_index_t x) const + { + const std::size_t h1{hash{}(x.ledger_indexing_amount)}; + const std::size_t h2{hash{}(x.index)}; + return h2 ^ (h1 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2)); // see boost::hash_combine + } +}; +} // namespace std + diff --git a/src/seraphis_impl/enote_finding_context_legacy.cpp b/src/seraphis_impl/enote_finding_context_legacy.cpp index fe3bc880f7..163c01700b 100644 --- a/src/seraphis_impl/enote_finding_context_legacy.cpp +++ b/src/seraphis_impl/enote_finding_context_legacy.cpp @@ -63,7 +63,7 @@ void EnoteFindingContextLegacySimple::view_scan_chunk(const LegacyUnscannedChunk blk.block_index, blk.block_timestamp, tx.transaction_id, - tx.total_enotes_before_tx, + tx.legacy_output_index_per_enote, tx.unlock_time, tx.tx_memo, tx.enotes, @@ -138,7 +138,7 @@ void EnoteFindingContextLegacyMultithreaded::view_scan_chunk( blk.block_index, blk.block_timestamp, tx.transaction_id, - tx.total_enotes_before_tx, + tx.legacy_output_index_per_enote, tx.unlock_time, tx.tx_memo, tx.enotes, diff --git a/src/seraphis_impl/seraphis_serialization.h b/src/seraphis_impl/seraphis_serialization.h index 02a8d7901f..c1e39ddbac 100644 --- a/src/seraphis_impl/seraphis_serialization.h +++ b/src/seraphis_impl/seraphis_serialization.h @@ -160,8 +160,8 @@ END_SERIALIZE() BEGIN_SERIALIZE_OBJECT_FN(GrootleProof) FIELD_F(A) FIELD_F(B) - FIELD_F(f) // @TODO: sizeless f serialization - FIELD_F(X) // @TODO: sizeless X serialization + FIELD_F(f) /// @TODO: sizeless f serialization + FIELD_F(X) /// @TODO: sizeless X serialization FIELD_F(zA) FIELD_F(z) END_SERIALIZE() @@ -193,14 +193,148 @@ BEGIN_SERIALIZE_OBJECT_FN(SpBalanceProofV1, const size_t implied_lr_size = SIZE_ FIELD_F(remainder_blinding_factor) END_SERIALIZE() //-------------------------------------------------------------------------------------------------- +BEGIN_SERIALIZE_OBJECT_FN(LegacyReferenceSetV2, const size_t implied_ring_size = SIZE_MAX) + // if writing and we don't match the passed implied ring size, then fail + if (W && implied_ring_size != SIZE_MAX && v.indices.size() != implied_ring_size) + return false; + + // check that the passed implied ring size is not bigger than the number of remaining bytes + if constexpr (!W) + { + if (implied_ring_size != SIZE_MAX && ar.remaining_bytes() < implied_ring_size) + return false; + } + + // if we don't have an implied size, serialize the actual size + size_t actual_ring_size{v.indices.size()}; + if (implied_ring_size == SIZE_MAX) + VARINT_FIELD_N("ring_size", actual_ring_size) + else + actual_ring_size = implied_ring_size; + + // if the ring size is 0, we can stop here + if (actual_ring_size == 0) + return ar.good(); + + // start compacted indices data array + ar.tag("indices_compressed"); + ar.begin_array(); + + // if storing, construct the number of indices per ledger indexing amount + std::vector> index_quantities_by_amount; + if constexpr (W) + { + index_quantities_by_amount.reserve(actual_ring_size); + index_quantities_by_amount.push_back({v.indices.begin()->ledger_indexing_amount, 0}); + for (const legacy_output_index_t i : v.indices) + { + if (i.ledger_indexing_amount != index_quantities_by_amount.back().first) + index_quantities_by_amount.push_back({i.ledger_indexing_amount, 0}); + ++index_quantities_by_amount.back().second; + } + } + + // serialize the number of unique ledger indexing amounts + size_t num_unique_amounts{index_quantities_by_amount.size() - 1}; + ar.serialize_varint(num_unique_amounts); + ++num_unique_amounts; + + // sanity check num_unique_amounts + if (num_unique_amounts == 0 || num_unique_amounts > actual_ring_size) + return false; + + // for each unique indexing amount... + rct::xmr_amount current_amount{0}; + size_t remaining_indices{actual_ring_size}; + auto writer_index_it{v.indices.begin()}; + for (size_t nth_amount{0}; nth_amount < num_unique_amounts; ++nth_amount) + { + // serialize ledger amount offset (-1 in the data if not the first amount) + rct::xmr_amount amount_offset; + if constexpr (W) + { + amount_offset = index_quantities_by_amount[nth_amount].first - current_amount; + if (nth_amount) + --amount_offset; + } + ar.delimit_array(); + ar.serialize_varint(amount_offset); + if (nth_amount) + ++amount_offset; + + // accumulate the ledger amount, checking for overflow + if (amount_offset > MONEY_SUPPLY - current_amount) + return false; + current_amount += amount_offset; + + // serialize number of indices for this amount (-1 in the data), unless this is the last + // amount in the list, we can imply the number as the number of indices not already serialized + size_t num_indices_for_this_amount; + if (nth_amount == num_unique_amounts - 1) + { + num_indices_for_this_amount = remaining_indices; + } + else // not last amount + { + if constexpr (W) + num_indices_for_this_amount = index_quantities_by_amount[nth_amount].second - 1; + ar.delimit_array(); + ar.serialize_varint(num_indices_for_this_amount); + ++num_indices_for_this_amount; + } + + // sanity check number of indices + if (num_indices_for_this_amount > remaining_indices) + return false; + + // serialize the indices as a list of cumulative offsets (-1 in the data if not first index) + size_t current_index{0}; + for (size_t nth_index{0}; nth_index < num_indices_for_this_amount; ++nth_index) + { + // serialize index offset + size_t index_offset; + if constexpr (W) + { + index_offset = writer_index_it->index; + if (nth_index) + { + index_offset -= current_index + 1; + } + } + ar.delimit_array(); + ar.serialize_varint(index_offset); + if (nth_index) + ++index_offset; + + // update iterators and check for index overflow + --remaining_indices; + if constexpr (W) + ++writer_index_it; + if (index_offset > SIZE_MAX - current_index) + return false; + current_index += index_offset; + + // if loading, insert legacy output index into the set + if constexpr (!W) + v.indices.insert({current_amount, current_index}); + } + } + + // check that we loaded the right number of unique legacy indices + if (v.indices.size() != actual_ring_size) + return false; + + // end compacted indices data array + ar.end_array(); +END_SERIALIZE() +//-------------------------------------------------------------------------------------------------- BEGIN_SERIALIZE_OBJECT_FN(LegacyRingSignatureV4, const size_t implied_ring_size = SIZE_MAX) FIELD_F(clsag_proof, implied_ring_size) - // @TODO: accumlate/decumulate CLSAG ref set offsets - VEC_FIELD_OPT_EXACT_F(reference_set, implied_ring_size) + FIELD_F(reference_set, implied_ring_size) END_SERIALIZE() //-------------------------------------------------------------------------------------------------- BEGIN_SERIALIZE_OBJECT_FN(SpImageProofV1) - // @TODO: sizeless f, X serialization + /// @TODO: sizeless f, X serialization FIELD_F(composition_proof) END_SERIALIZE() //-------------------------------------------------------------------------------------------------- @@ -239,7 +373,7 @@ BEGIN_SERIALIZE_OBJECT_FN(SpTxSquashedV1) FIELD_F(balance_proof, implied_bpp_lr_size) size_t clsag_ring_size = v.legacy_ring_signatures.size() ? - v.legacy_ring_signatures[0].reference_set.size() : 0; + v.legacy_ring_signatures[0].reference_set.indices.size() : 0; VARINT_FIELD(clsag_ring_size) VEC_FIELD_EXACT_F(legacy_ring_signatures, num_legacy_inputs, clsag_ring_size) diff --git a/src/seraphis_main/contextual_enote_record_types.cpp b/src/seraphis_main/contextual_enote_record_types.cpp index 7de583b18a..190e96af90 100644 --- a/src/seraphis_main/contextual_enote_record_types.cpp +++ b/src/seraphis_main/contextual_enote_record_types.cpp @@ -86,41 +86,52 @@ rct::xmr_amount amount_ref(const SpContextualEnoteRecordV1 &record) return record.record.amount; } //------------------------------------------------------------------------------------------------------------------- -const SpEnoteOriginContextV1& origin_context_ref(const ContextualBasicRecordVariant &variant) +std::uint64_t block_index_ref(const ContextualBasicRecordVariant &variant) { - struct visitor final : public tools::variant_static_visitor + struct visitor final : public tools::variant_static_visitor { - using variant_static_visitor::operator(); //for blank overload - const SpEnoteOriginContextV1& operator()(const LegacyContextualBasicEnoteRecordV1 &record) const - { return record.origin_context; } - const SpEnoteOriginContextV1& operator()(const SpContextualBasicEnoteRecordV1 &record) const - { return record.origin_context; } + std::uint64_t operator()(const LegacyContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.block_index; } + std::uint64_t operator()(const SpContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.block_index; } }; return variant.visit(visitor()); } //------------------------------------------------------------------------------------------------------------------- -rct::xmr_amount amount_ref(const ContextualRecordVariant &variant) +const rct::key& transaction_id_ref(const ContextualBasicRecordVariant &variant) { - struct visitor final : public tools::variant_static_visitor + struct visitor final : public tools::variant_static_visitor { - using variant_static_visitor::operator(); //for blank overload - rct::xmr_amount operator()(const LegacyContextualEnoteRecordV1 &record) const { return amount_ref(record); } - rct::xmr_amount operator()(const SpContextualEnoteRecordV1 &record) const { return amount_ref(record); } + const rct::key& operator()(const LegacyContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.transaction_id; } + const rct::key& operator()(const SpContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.transaction_id; } + }; + + return variant.visit(visitor()); +} +//------------------------------------------------------------------------------------------------------------------- +SpEnoteOriginStatus origin_status_ref(const ContextualBasicRecordVariant &variant) +{ + struct visitor final : public tools::variant_static_visitor + { + SpEnoteOriginStatus operator()(const LegacyContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.origin_status; } + SpEnoteOriginStatus operator()(const SpContextualBasicEnoteRecordV1 &record) const + { return record.origin_context.origin_status; } }; return variant.visit(visitor()); } //------------------------------------------------------------------------------------------------------------------- -const SpEnoteOriginContextV1& origin_context_ref(const ContextualRecordVariant &variant) +rct::xmr_amount amount_ref(const ContextualRecordVariant &variant) { - struct visitor final : public tools::variant_static_visitor + struct visitor final : public tools::variant_static_visitor { using variant_static_visitor::operator(); //for blank overload - const SpEnoteOriginContextV1& operator()(const LegacyContextualEnoteRecordV1 &record) const - { return record.origin_context; } - const SpEnoteOriginContextV1& operator()(const SpContextualEnoteRecordV1 &record) const - { return record.origin_context; } + rct::xmr_amount operator()(const LegacyContextualEnoteRecordV1 &record) const { return amount_ref(record); } + rct::xmr_amount operator()(const SpContextualEnoteRecordV1 &record) const { return amount_ref(record); } }; return variant.visit(visitor()); @@ -140,6 +151,42 @@ const SpEnoteSpentContextV1& spent_context_ref(const ContextualRecordVariant &va return variant.visit(visitor()); } //------------------------------------------------------------------------------------------------------------------- +bool is_older_than(const LegacyEnoteOriginContext &context, const LegacyEnoteOriginContext &other_context) +{ + // 1. origin status (higher statuses are assumed to be 'older') + if (context.origin_status > other_context.origin_status) + return true; + if (context.origin_status < other_context.origin_status) + return false; + + // 2. block index + if (context.block_index < other_context.block_index) + return true; + if (context.block_index > other_context.block_index) + return false; + + // note: don't assess the tx output index + + // 3. assert that the ledger indexing amounts are the same (else comparing age is undecidable) + CHECK_AND_ASSERT_THROW_MES(context.legacy_enote_ledger_index.ledger_indexing_amount + == other_context.legacy_enote_ledger_index.ledger_indexing_amount, + "is_older_than: legacy enote origin contexts have different ledger indexing amounts"); + + // 4. enote ledger index + if (context.legacy_enote_ledger_index < other_context.legacy_enote_ledger_index) + return true; + if (context.legacy_enote_ledger_index > other_context.legacy_enote_ledger_index) + return false; + + // 5. block timestamp + if (context.block_timestamp < other_context.block_timestamp) + return true; + if (context.block_timestamp > other_context.block_timestamp) + return false; + + return false; +} +//------------------------------------------------------------------------------------------------------------------- bool is_older_than(const SpEnoteOriginContextV1 &context, const SpEnoteOriginContextV1 &other_context) { // 1. origin status (higher statuses are assumed to be 'older') diff --git a/src/seraphis_main/contextual_enote_record_types.h b/src/seraphis_main/contextual_enote_record_types.h index 47a12a7393..f392d6441c 100644 --- a/src/seraphis_main/contextual_enote_record_types.h +++ b/src/seraphis_main/contextual_enote_record_types.h @@ -37,6 +37,7 @@ #include "ringct/rctOps.h" #include "ringct/rctTypes.h" #include "seraphis_core/jamtis_support_types.h" +#include "seraphis_core/legacy_output_index.h" #include "seraphis_core/sp_core_types.h" #include "seraphis_core/tx_extra.h" #include "tx_component_types.h" @@ -86,9 +87,32 @@ enum class SpEnoteSpentStatus : unsigned char SPENT_ONCHAIN }; +//// +// LegacyEnoteOriginContext +// - info related to the transaction where a legacy enote was found +// - note that an enote may originate off-chain in a partial tx where the tx id is unknown +/// +struct LegacyEnoteOriginContext final +{ + /// block index of tx (-1 if index is unknown) + std::uint64_t block_index{static_cast(-1)}; + /// timestamp of tx's block (-1 if timestamp is unknown) + std::uint64_t block_timestamp{static_cast(-1)}; + /// tx id of the tx (0 if tx is unknown) + rct::key transaction_id{rct::zero()}; + /// index of the enote in the tx's output set (-1 if index is unknown) + std::uint64_t enote_tx_index{static_cast(-1)}; + /// ledger index of the enote (-1 for indexing amount if index is unknown) + legacy_output_index_t legacy_enote_ledger_index{static_cast(-1), 0}; + /// origin status (off-chain by default) + SpEnoteOriginStatus origin_status{SpEnoteOriginStatus::OFFCHAIN}; + + /// associated memo field (none by default) + TxExtra memo{}; +}; //// // SpEnoteOriginContextV1 -// - info related to the transaction where an enote was found +// - info related to the transaction where a Seraphis enote was found // - note that an enote may originate off-chain in a partial tx where the tx id is unknown /// struct SpEnoteOriginContextV1 final @@ -140,7 +164,7 @@ struct LegacyContextualBasicEnoteRecordV1 final /// basic info about the enote LegacyBasicEnoteRecord record; /// info about where the enote was found - SpEnoteOriginContextV1 origin_context; + LegacyEnoteOriginContext origin_context; }; //// @@ -153,7 +177,7 @@ struct LegacyContextualIntermediateEnoteRecordV1 final /// intermediate info about the enote LegacyIntermediateEnoteRecord record; /// info about where the enote was found - SpEnoteOriginContextV1 origin_context; + LegacyEnoteOriginContext origin_context; }; /// get the record's onetime address @@ -170,7 +194,7 @@ struct LegacyContextualEnoteRecordV1 final /// info about the enote LegacyEnoteRecord record; /// info about where the enote was found - SpEnoteOriginContextV1 origin_context; + LegacyEnoteOriginContext origin_context; /// info about where the enote was spent SpEnoteSpentContextV1 spent_context; }; @@ -241,22 +265,24 @@ rct::xmr_amount amount_ref(const SpContextualEnoteRecordV1 &record); // ContextualBasicRecordVariant // - variant of all contextual basic enote record types // -// origin_context_ref(): get the record's origin context +// block_index_ref(): get the record's block index +// transaction_id_ref(): get the record's transaction id +// origin_status_ref(): get the record's origin status /// using ContextualBasicRecordVariant = tools::variant; -const SpEnoteOriginContextV1& origin_context_ref(const ContextualBasicRecordVariant &variant); +std::uint64_t block_index_ref(const ContextualBasicRecordVariant &variant); +const rct::key& transaction_id_ref(const ContextualBasicRecordVariant &variant); +SpEnoteOriginStatus origin_status_ref(const ContextualBasicRecordVariant &variant); //// // ContextualRecordVariant // - variant of all contextual full enote record types // // amount_ref(): get the record's amount -// origin_context_ref(): get the record's origin context // spent_context_ref(): get the record's spent context /// using ContextualRecordVariant = tools::variant; rct::xmr_amount amount_ref(const ContextualRecordVariant &variant); -const SpEnoteOriginContextV1& origin_context_ref(const ContextualRecordVariant &variant); const SpEnoteSpentContextV1& spent_context_ref(const ContextualRecordVariant &variant); //// @@ -278,6 +304,8 @@ struct SpContextualKeyImageSetV1 final //////////////////////////////////////////////////////////////////////////////////////////////////////////// /// check if a context is older than another (returns false if apparently the same age, or younger) +/// throws if undecidable (i.e. legacy origin contexts have same block, different ledger indexing amount) +bool is_older_than(const LegacyEnoteOriginContext &context, const LegacyEnoteOriginContext &other_context); bool is_older_than(const SpEnoteOriginContextV1 &context, const SpEnoteOriginContextV1 &other_context); bool is_older_than(const SpEnoteSpentContextV1 &context, const SpEnoteSpentContextV1 &other_context); /// check if records have onetime address equivalence diff --git a/src/seraphis_main/contextual_enote_record_utils.cpp b/src/seraphis_main/contextual_enote_record_utils.cpp index b2be59dc0b..e4e5f0d544 100644 --- a/src/seraphis_main/contextual_enote_record_utils.cpp +++ b/src/seraphis_main/contextual_enote_record_utils.cpp @@ -191,7 +191,7 @@ boost::multiprecision::uint128_t total_amount(const std::vector &contextual_records, - std::unordered_map &enote_ledger_mappings_out) + std::unordered_map &enote_ledger_mappings_out) { enote_ledger_mappings_out.clear(); enote_ledger_mappings_out.reserve(contextual_records.size()); @@ -204,7 +204,7 @@ bool try_get_membership_proof_real_reference_mappings(const std::vector &contextual_records, - std::unordered_map &enote_ledger_mappings_out); + std::unordered_map &enote_ledger_mappings_out); bool try_get_membership_proof_real_reference_mappings(const std::vector &contextual_records, std::unordered_map &enote_ledger_mappings_out); /** @@ -122,7 +123,11 @@ bool try_get_membership_proof_real_reference_mappings(const std::vector legacy_output_index_per_enote; std::vector enotes; std::vector legacy_key_images; }; diff --git a/src/seraphis_main/scan_balance_recovery_utils.cpp b/src/seraphis_main/scan_balance_recovery_utils.cpp index 46b6e19d22..460335d008 100644 --- a/src/seraphis_main/scan_balance_recovery_utils.cpp +++ b/src/seraphis_main/scan_balance_recovery_utils.cpp @@ -72,8 +72,8 @@ static bool try_view_scan_legacy_enote_v1(const rct::key &legacy_base_spend_pubk const std::uint64_t block_index, const std::uint64_t block_timestamp, const rct::key &transaction_id, - const std::uint64_t total_enotes_before_tx, - const std::uint64_t enote_index, + const sp::legacy_output_index_t &legacy_ledger_enote_index, + const std::uint64_t enote_tx_index, const std::uint64_t unlock_time, const TxExtra &tx_memo, const LegacyEnoteVariant &legacy_enote, @@ -88,7 +88,7 @@ static bool try_view_scan_legacy_enote_v1(const rct::key &legacy_base_spend_pubk { if (!try_get_legacy_basic_enote_record(legacy_enote, rct::pk2rct(legacy_enote_ephemeral_pubkey), - enote_index, + enote_tx_index, unlock_time, DH_derivation, legacy_base_spend_pubkey, @@ -100,14 +100,14 @@ static bool try_view_scan_legacy_enote_v1(const rct::key &legacy_base_spend_pubk // 2. set the origin context contextual_record_out.origin_context = - SpEnoteOriginContextV1{ - .block_index = block_index, - .block_timestamp = block_timestamp, - .transaction_id = transaction_id, - .enote_tx_index = enote_index, - .enote_ledger_index = total_enotes_before_tx + enote_index, - .origin_status = origin_status, - .memo = tx_memo + LegacyEnoteOriginContext{ + .block_index = block_index, + .block_timestamp = block_timestamp, + .transaction_id = transaction_id, + .enote_tx_index = enote_tx_index, + .legacy_enote_ledger_index = legacy_ledger_enote_index, + .origin_status = origin_status, + .memo = tx_memo }; return true; @@ -115,7 +115,7 @@ static bool try_view_scan_legacy_enote_v1(const rct::key &legacy_base_spend_pubk //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- static void update_with_new_intermediate_record_legacy(const LegacyIntermediateEnoteRecord &new_enote_record, - const SpEnoteOriginContextV1 &new_record_origin_context, + const LegacyEnoteOriginContext &new_record_origin_context, std::unordered_map &found_enote_records_inout) { // 1. add new intermediate legacy record to found enotes (or refresh if already there) @@ -133,7 +133,7 @@ static void update_with_new_intermediate_record_legacy(const LegacyIntermediateE //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- static void update_with_new_record_legacy(const LegacyEnoteRecord &new_enote_record, - const SpEnoteOriginContextV1 &new_record_origin_context, + const LegacyEnoteOriginContext &new_record_origin_context, const std::list &chunk_contextual_key_images, std::unordered_map &found_enote_records_inout, std::unordered_map &found_spent_key_images_inout) @@ -308,18 +308,19 @@ static std::unordered_set process_chunk_sp_selfsend_pass( for (const ContextualBasicRecordVariant &contextual_basic_record : chunk_basic_records_per_tx.at(tx_with_spent_enotes)) { - if (!contextual_basic_record.is_type()) + const auto * const sp_contextual_basic_record{ + contextual_basic_record.try_unwrap() + }; + if (sp_contextual_basic_record == nullptr) continue; try { // a. check if the enote is owned by attempting to convert it to a full enote record (selfsend conversion) if (!try_get_enote_record_v1_selfsend( - contextual_basic_record.unwrap().record.enote, - contextual_basic_record.unwrap().record - .enote_ephemeral_pubkey, - contextual_basic_record.unwrap().record - .input_context, + sp_contextual_basic_record->record.enote, + sp_contextual_basic_record->record.enote_ephemeral_pubkey, + sp_contextual_basic_record->record.input_context, jamtis_spend_pubkey, k_view_balance, xk_find_received, @@ -332,7 +333,7 @@ static std::unordered_set process_chunk_sp_selfsend_pass( // - this will also check if the enote was spent in this chunk, and update 'txs_have_spent_enotes' // accordingly update_with_new_record_sp(new_enote_record, - origin_context_ref(contextual_basic_record), + sp_contextual_basic_record->origin_context, chunk_contextual_key_images, found_enote_records_inout, found_spent_sp_key_images_inout, @@ -343,7 +344,7 @@ static std::unordered_set process_chunk_sp_selfsend_pass( // txs with selfsend outputs, but during seraphis scanning it isn't guaranteed that we will be able // to check if legacy key images attached to selfsend owned enotes are associated with owned legacy // enotes; therefore we cache those legacy key images so they can be handled outside this scan process - collect_legacy_key_images_from_tx(origin_context_ref(contextual_basic_record).transaction_id, + collect_legacy_key_images_from_tx(sp_contextual_basic_record->origin_context.transaction_id, chunk_contextual_key_images, legacy_key_images_in_sp_selfspends_inout); } catch (...) {} @@ -384,7 +385,7 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, const std::uint64_t block_index, const std::uint64_t block_timestamp, const rct::key &transaction_id, - const std::uint64_t total_enotes_before_tx, + const std::vector &legacy_output_index_per_enote, const std::uint64_t unlock_time, const TxExtra &tx_memo, const std::vector &enotes_in_tx, @@ -394,6 +395,9 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, { basic_records_in_tx_out.clear(); + if (legacy_output_index_per_enote.size() != enotes_in_tx.size()) + return false; + // 1. extract enote ephemeral pubkeys from the memo rct::key_keyV_variant legacy_main_enote_ephemeral_pubkeys = rct::key{}; std::vector legacy_additional_enote_ephemeral_pubkeys; @@ -423,7 +427,7 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, block_index, block_timestamp, transaction_id, - total_enotes_before_tx, + legacy_output_index_per_enote[enote_index], enote_index, unlock_time, tx_memo, @@ -485,7 +489,7 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, block_index, block_timestamp, transaction_id, - total_enotes_before_tx, + legacy_output_index_per_enote[enote_index], enote_index, unlock_time, tx_memo, @@ -651,14 +655,17 @@ void process_chunk_intermediate_legacy(const rct::key &legacy_base_spend_pubkey, { for (const ContextualBasicRecordVariant &contextual_basic_record : tx_basic_records.second) { - if (!contextual_basic_record.is_type()) + const auto * const legacy_contextual_basic_record{ + contextual_basic_record.try_unwrap() + }; + if (legacy_contextual_basic_record == nullptr) continue; try { // a. check if we own the enote by attempting to convert it to an intermediate enote record if (!try_get_legacy_intermediate_enote_record( - contextual_basic_record.unwrap().record, + legacy_contextual_basic_record->record, legacy_base_spend_pubkey, legacy_view_privkey, hwdev, @@ -667,7 +674,7 @@ void process_chunk_intermediate_legacy(const rct::key &legacy_base_spend_pubkey, // b. we found an owned enote, so handle it update_with_new_intermediate_record_legacy(new_enote_record, - origin_context_ref(contextual_basic_record), + legacy_contextual_basic_record->origin_context, found_enote_records_out); } catch (...) {} } @@ -716,14 +723,17 @@ void process_chunk_full_legacy(const rct::key &legacy_base_spend_pubkey, { for (const ContextualBasicRecordVariant &contextual_basic_record : tx_basic_records.second) { - if (!contextual_basic_record.is_type()) + const auto * const legacy_contextual_basic_record{ + contextual_basic_record.try_unwrap() + }; + if (legacy_contextual_basic_record == nullptr) continue; try { // a. check if we own the enote by attempting to convert it to a full enote record if (!try_get_legacy_enote_record( - contextual_basic_record.unwrap().record, + legacy_contextual_basic_record->record, legacy_base_spend_pubkey, legacy_spend_privkey, legacy_view_privkey, @@ -733,7 +743,7 @@ void process_chunk_full_legacy(const rct::key &legacy_base_spend_pubkey, // b. we found an owned enote, so handle it update_with_new_record_legacy(new_enote_record, - origin_context_ref(contextual_basic_record), + legacy_contextual_basic_record->origin_context, chunk_contextual_key_images, found_enote_records_out, found_spent_key_images_out); @@ -759,14 +769,17 @@ void process_chunk_intermediate_sp(const rct::key &jamtis_spend_pubkey, { for (const ContextualBasicRecordVariant &contextual_basic_record : tx_basic_records.second) { - if (!contextual_basic_record.is_type()) + const auto * const sp_contextual_basic_record{ + contextual_basic_record.try_unwrap() + }; + if (sp_contextual_basic_record == nullptr) continue; try { // a. check if we own the enote by attempting to convert it to an intermediate enote record if (!try_get_intermediate_enote_record_v1( - contextual_basic_record.unwrap().record, + sp_contextual_basic_record->record, jamtis_spend_pubkey, xk_unlock_amounts, xk_find_received, @@ -777,7 +790,7 @@ void process_chunk_intermediate_sp(const rct::key &jamtis_spend_pubkey, // b. we found an owned enote, so handle it update_with_new_intermediate_record_sp(new_enote_record, - origin_context_ref(contextual_basic_record), + sp_contextual_basic_record->origin_context, found_enote_records_out); } catch (...) {} } @@ -848,14 +861,17 @@ void process_chunk_full_sp(const rct::key &jamtis_spend_pubkey, { for (const ContextualBasicRecordVariant &contextual_basic_record : tx_basic_records.second) { - if (!contextual_basic_record.is_type()) + const auto * const sp_contextual_basic_record{ + contextual_basic_record.try_unwrap() + }; + if (sp_contextual_basic_record == nullptr) continue; try { // a. check if we own the enote by attempting to convert it to a full enote record if (!try_get_enote_record_v1_plain( - contextual_basic_record.unwrap().record, + sp_contextual_basic_record->record, jamtis_spend_pubkey, k_view_balance, xk_unlock_amounts, @@ -869,7 +885,7 @@ void process_chunk_full_sp(const rct::key &jamtis_spend_pubkey, // - this will also check if the enote was spent in this chunk, and update 'txs_have_spent_enotes' // accordingly update_with_new_record_sp(new_enote_record, - origin_context_ref(contextual_basic_record), + sp_contextual_basic_record->origin_context, chunk_contextual_key_images, found_enote_records_out, found_spent_sp_key_images_out, diff --git a/src/seraphis_main/scan_balance_recovery_utils.h b/src/seraphis_main/scan_balance_recovery_utils.h index 709b1ae957..eb38ec842c 100644 --- a/src/seraphis_main/scan_balance_recovery_utils.h +++ b/src/seraphis_main/scan_balance_recovery_utils.h @@ -67,7 +67,7 @@ namespace scanning * param: block_index - * param: block_timestamp - * param: transaction_id - -* param: total_enotes_before_tx - number of legacy enotes ordered before this tx (set to '0' if tx is non-ledger) +* param: legacy_output_index_per_enote - legacy output indices for each enote in the tx * param: unlock_time - * param: tx_memo - * param: enotes_in_tx - @@ -81,7 +81,7 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, const std::uint64_t block_index, const std::uint64_t block_timestamp, const rct::key &transaction_id, - const std::uint64_t total_enotes_before_tx, + const std::vector &legacy_output_index_per_enote, const std::uint64_t unlock_time, const TxExtra &tx_memo, const std::vector &enotes_in_tx, diff --git a/src/seraphis_main/scan_misc_utils.cpp b/src/seraphis_main/scan_misc_utils.cpp index 29c8b800b6..eea50614c2 100644 --- a/src/seraphis_main/scan_misc_utils.cpp +++ b/src/seraphis_main/scan_misc_utils.cpp @@ -75,19 +75,19 @@ void check_chunk_data_semantics(const ChunkData &chunk_data, { for (const ContextualBasicRecordVariant &contextual_basic_record : tx_basic_records.second) { - CHECK_AND_ASSERT_THROW_MES(origin_context_ref(contextual_basic_record).origin_status == + CHECK_AND_ASSERT_THROW_MES(origin_status_ref(contextual_basic_record) == expected_origin_status, "scan chunk data semantics check: contextual basic record doesn't have expected origin status."); - CHECK_AND_ASSERT_THROW_MES(origin_context_ref(contextual_basic_record).transaction_id == + CHECK_AND_ASSERT_THROW_MES(transaction_id_ref(contextual_basic_record) == tx_basic_records.first, "scan chunk data semantics check: contextual basic record doesn't have origin tx id matching mapped id."); - CHECK_AND_ASSERT_THROW_MES(origin_context_ref(contextual_basic_record).block_index == - origin_context_ref(*tx_basic_records.second.begin()).block_index, + CHECK_AND_ASSERT_THROW_MES(block_index_ref(contextual_basic_record) == + block_index_ref(*tx_basic_records.second.begin()), "scan chunk data semantics check: contextual record tx index doesn't match other records in tx."); CHECK_AND_ASSERT_THROW_MES( - origin_context_ref(contextual_basic_record).block_index >= allowed_lowest_index && - origin_context_ref(contextual_basic_record).block_index <= allowed_highest_index, + block_index_ref(contextual_basic_record) >= allowed_lowest_index && + block_index_ref(contextual_basic_record) <= allowed_highest_index, "scan chunk data semantics check: contextual record block index is out of the expected range."); } } diff --git a/src/seraphis_main/tx_builder_types_legacy.h b/src/seraphis_main/tx_builder_types_legacy.h index ba666bac2e..50f0cc41f2 100644 --- a/src/seraphis_main/tx_builder_types_legacy.h +++ b/src/seraphis_main/tx_builder_types_legacy.h @@ -80,10 +80,10 @@ struct LegacyRingSignaturePrepV1 final /// tx proposal prefix (message to sign in the proof) rct::key tx_proposal_prefix; /// ledger indices of legacy enotes to be referenced by the proof - std::vector reference_set; + LegacyReferenceSetV2 reference_set; /// the referenced enotes ({Ko, C}((legacy)) representation) rct::ctkeyV referenced_enotes; - /// the index of the real enote being referenced within the reference set + /// the index of the real enote being referenced within the referenced enotes vector std::uint64_t real_reference_index; /// enote image of the real reference (useful for sorting) LegacyEnoteImageV2 reference_image; diff --git a/src/seraphis_main/tx_builder_types_multisig.cpp b/src/seraphis_main/tx_builder_types_multisig.cpp index 70cd3e5de6..fb9ff4c969 100644 --- a/src/seraphis_main/tx_builder_types_multisig.cpp +++ b/src/seraphis_main/tx_builder_types_multisig.cpp @@ -216,7 +216,7 @@ bool matches_with(const LegacyMultisigInputProposalV1 &multisig_input_proposal, return false; // references line up 1:1 - if (multisig_input_proposal.reference_set.size() != proof_proposal.ring_members.size()) + if (multisig_input_proposal.reference_set.indices.size() != proof_proposal.ring_members.size()) return false; return true; diff --git a/src/seraphis_main/tx_builder_types_multisig.h b/src/seraphis_main/tx_builder_types_multisig.h index e093ced8be..ccb7b1b1b3 100644 --- a/src/seraphis_main/tx_builder_types_multisig.h +++ b/src/seraphis_main/tx_builder_types_multisig.h @@ -70,7 +70,7 @@ namespace sp struct LegacyMultisigRingSignaturePrepV1 final { /// ledger indices of legacy enotes referenced by the proof - std::vector reference_set; + LegacyReferenceSetV2 reference_set; /// the referenced enotes ({Ko, C}((legacy)) representation) rct::ctkeyV referenced_enotes; /// the index of the real enote being referenced within the reference set @@ -100,7 +100,7 @@ struct LegacyMultisigInputProposalV1 final crypto::secret_key commitment_mask; /// cached legacy enote indices for a legacy ring signature (should include a reference to this input proposal's enote) - std::vector reference_set; + LegacyReferenceSetV2 reference_set; }; //// diff --git a/src/seraphis_main/tx_builders_legacy_inputs.cpp b/src/seraphis_main/tx_builders_legacy_inputs.cpp index 8efdd214c2..6bebcc2498 100644 --- a/src/seraphis_main/tx_builders_legacy_inputs.cpp +++ b/src/seraphis_main/tx_builders_legacy_inputs.cpp @@ -155,22 +155,22 @@ void make_v1_legacy_input_proposal_v1(const LegacyEnoteRecord &enote_record, } //------------------------------------------------------------------------------------------------------------------- void make_tx_legacy_ring_signature_message_v1(const rct::key &tx_proposal_message, - const std::vector &reference_set_indices, + const LegacyReferenceSetV2 &reference_set, rct::key &message_out) { // m = H_32(tx proposal message, {reference set indices}) SpFSTranscript transcript{ config::HASH_KEY_LEGACY_RING_SIGNATURES_MESSAGE_V1, - 32 + reference_set_indices.size() * 8 + 32 + legacy_ref_set_v2_size_bytes(reference_set) }; transcript.append("tx_proposal_message", tx_proposal_message); - transcript.append("reference_set_indices", reference_set_indices); + transcript.append("reference_set", reference_set); sp_hash_to_32(transcript.data(), transcript.size(), message_out.bytes); } //------------------------------------------------------------------------------------------------------------------- void make_v4_legacy_ring_signature(const rct::key &message, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, const rct::ctkeyV &referenced_enotes, const std::uint64_t real_reference_index, const rct::key &masked_commitment, @@ -185,9 +185,7 @@ void make_v4_legacy_ring_signature(const rct::key &message, /// checks // 1. reference sets - CHECK_AND_ASSERT_THROW_MES(tools::is_sorted_and_unique(reference_set), - "make v4 legacy ring signature: reference set indices are not sorted and unique."); - CHECK_AND_ASSERT_THROW_MES(reference_set.size() == referenced_enotes.size(), + CHECK_AND_ASSERT_THROW_MES(reference_set.indices.size() == referenced_enotes.size(), "make v4 legacy ring signature: reference set indices don't match referenced enotes."); CHECK_AND_ASSERT_THROW_MES(real_reference_index < referenced_enotes.size(), "make v4 legacy ring signature: real reference index is outside range of referenced enotes."); @@ -310,9 +308,7 @@ void check_v1_legacy_input_semantics_v1(const LegacyInputV1 &input) "legacy input semantics (v1): could not reproduce masked commitment (pseudo-output commitment)."); // 2. ring signature reference indices are sorted and unique and match with the cached reference enotes - CHECK_AND_ASSERT_THROW_MES(tools::is_sorted_and_unique(input.ring_signature.reference_set), - "legacy input semantics (v1): reference set indices are not sorted and unique."); - CHECK_AND_ASSERT_THROW_MES(input.ring_signature.reference_set.size() == input.ring_members.size(), + CHECK_AND_ASSERT_THROW_MES(input.ring_signature.reference_set.indices.size() == input.ring_members.size(), "legacy input semantics (v1): reference set indices don't match referenced enotes."); // 3. ring signature message diff --git a/src/seraphis_main/tx_builders_legacy_inputs.h b/src/seraphis_main/tx_builders_legacy_inputs.h index 06cf215536..ec77cfb3f6 100644 --- a/src/seraphis_main/tx_builders_legacy_inputs.h +++ b/src/seraphis_main/tx_builders_legacy_inputs.h @@ -87,10 +87,10 @@ void make_v1_legacy_input_proposal_v1(const LegacyEnoteRecord &enote_record, * outparam: message_out - the message to sign in a legacy ring signature */ void make_tx_legacy_ring_signature_message_v1(const rct::key &tx_proposal_message, - const std::vector &reference_set_indices, + const LegacyReferenceSetV2 &reference_set_indices, rct::key &message_out); /** -* brief: make_v4_legacy_ring_signature - make a legacy v3 ring signature +* brief: make_v4_legacy_ring_signature - make a legacy v4 ring signature * param: message - * param: reference_set - * param: referenced_enotes - @@ -103,7 +103,7 @@ void make_tx_legacy_ring_signature_message_v1(const rct::key &tx_proposal_messag * outparam: ring_signature_out - */ void make_v4_legacy_ring_signature(const rct::key &message, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, const rct::ctkeyV &referenced_enotes, const std::uint64_t real_reference_index, const rct::key &masked_commitment, diff --git a/src/seraphis_main/tx_builders_mixed.cpp b/src/seraphis_main/tx_builders_mixed.cpp index b4d6d5c9bd..7b0139fb4c 100644 --- a/src/seraphis_main/tx_builders_mixed.cpp +++ b/src/seraphis_main/tx_builders_mixed.cpp @@ -81,7 +81,7 @@ class TxValidationContextSimple final : public TxValidationContext public: //constructors TxValidationContextSimple(const SemanticConfigSpRefSetV1 &sp_ref_set_config, - const std::unordered_map &legacy_reference_set_proof_elements, + const std::unordered_map &legacy_reference_set_proof_elements, const std::unordered_map &sp_reference_set_proof_elements) : m_sp_ref_set_config{sp_ref_set_config}, m_legacy_reference_set_proof_elements{legacy_reference_set_proof_elements}, @@ -115,19 +115,19 @@ class TxValidationContextSimple final : public TxValidationContext } /** * brief: get_reference_set_proof_elements_v1 - gets legacy {KI, C} pairs stored in the validation context - * param: indices - + * - note: should only return elements that are valid to reference in a tx (e.g. locked elements are invalid) + * param: indices - (output amount, global output index) reference pairs to existing on-chain legacy enotes * outparam: proof_elements_out - {KI, C} */ - void get_reference_set_proof_elements_v1(const std::vector &indices, + virtual void get_reference_set_proof_elements_v1(const std::set &indices, rct::ctkeyV &proof_elements_out) const override { proof_elements_out.clear(); proof_elements_out.reserve(indices.size()); - - for (const std::uint64_t index : indices) + for (const legacy_output_index_t &index : indices) { - if (m_legacy_reference_set_proof_elements.find(index) != m_legacy_reference_set_proof_elements.end()) - proof_elements_out.emplace_back(m_legacy_reference_set_proof_elements.at(index)); + if (m_legacy_reference_set_proof_elements.count(index)) + proof_elements_out.push_back(m_legacy_reference_set_proof_elements.at(index)); else proof_elements_out.emplace_back(rct::ctkey{}); } @@ -155,7 +155,7 @@ class TxValidationContextSimple final : public TxValidationContext //member variables private: const SemanticConfigSpRefSetV1 m_sp_ref_set_config; - const std::unordered_map &m_legacy_reference_set_proof_elements; + const std::unordered_map &m_legacy_reference_set_proof_elements; const std::unordered_map &m_sp_reference_set_proof_elements; }; @@ -503,7 +503,7 @@ static void check_tx_proposal_semantics_output_proposals_v1(const std::vector &legacy_ring_signatures, const std::vector &legacy_ring_signature_rings, - std::unordered_map &legacy_reference_set_proof_elements_out) + std::unordered_map &legacy_reference_set_proof_elements_out) { // map legacy ring members onto their on-chain legacy enote indices CHECK_AND_ASSERT_THROW_MES(legacy_ring_signatures.size() == legacy_ring_signature_rings.size(), @@ -512,15 +512,17 @@ static void collect_legacy_ring_signature_ring_members(const std::vector legacy_reference_set_proof_elements; + std::unordered_map legacy_reference_set_proof_elements; collect_legacy_ring_signature_ring_members(partial_tx.legacy_ring_signatures, partial_tx.legacy_ring_signature_rings, diff --git a/src/seraphis_main/tx_builders_multisig.cpp b/src/seraphis_main/tx_builders_multisig.cpp index e81f9a399f..3c464350bc 100644 --- a/src/seraphis_main/tx_builders_multisig.cpp +++ b/src/seraphis_main/tx_builders_multisig.cpp @@ -285,20 +285,27 @@ static void replace_legacy_input_proposal_destinations_for_tx_simulation_v1( { for (LegacyRingSignaturePrepV1 &prep_to_repair : legacy_ring_signature_preps_out) { - // a. see if the reference prep's real reference is a decoy in this prep's reference set - auto ref_set_it = - std::find(prep_to_repair.reference_set.begin(), - prep_to_repair.reference_set.end(), - reference_prep.reference_set.at(reference_prep.real_reference_index)); - - // b. if not, skip it - if (ref_set_it == prep_to_repair.reference_set.end()) + // a. sanity check real_reference_index + CHECK_AND_ASSERT_THROW_MES( + reference_prep.real_reference_index < reference_prep.reference_set.indices.size(), + "real reference index is too large for given reference set prep"); + + // b. get legacy output index at "real reference index" into reference set indices + const legacy_output_index_t real_reference_index_global{ + *std::next(reference_prep.reference_set.indices.cbegin(), reference_prep.real_reference_index) + }; + + // c. see if the reference prep's real reference is a decoy in this prep's reference set, if not skip it + const auto ref_set_it = prep_to_repair.reference_set.indices.find(real_reference_index_global); + + // d. if not, skip it + if (ref_set_it == prep_to_repair.reference_set.indices.cend()) continue; - // c. otherwise, update the decoy's onetime address + // e. otherwise, update the decoy's onetime address prep_to_repair .referenced_enotes - .at(std::distance(prep_to_repair.reference_set.begin(), ref_set_it)) + .at(std::distance(prep_to_repair.reference_set.indices.begin(), ref_set_it)) .dest = reference_prep .referenced_enotes @@ -510,7 +517,7 @@ static void collect_sp_composition_proof_privkeys_for_multisig(const std::vector //------------------------------------------------------------------------------------------------------------------- static bool try_make_v1_legacy_input_v1(const rct::key &tx_proposal_prefix, const LegacyInputProposalV1 &input_proposal, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, rct::ctkeyV referenced_enotes, const rct::key &masked_commitment, const std::vector &input_proof_partial_sigs, @@ -626,7 +633,7 @@ static bool try_make_legacy_inputs_for_multisig_v1(const rct::key &tx_proposal_p // - map ring signature messages to onetime addresses // - map legacy reference sets to onetime addresses std::unordered_map legacy_proof_contexts; //[ proof key : proof message ] - std::unordered_map> mapped_reference_sets; + std::unordered_map mapped_reference_sets; rct::key message_temp; for (const LegacyMultisigInputProposalV1 &legacy_multisig_input_proposal : legacy_multisig_input_proposals) @@ -768,13 +775,6 @@ void check_v1_legacy_multisig_input_proposal_semantics_v1(const LegacyMultisigIn "semantics check legacy multisig input proposal v1: bad address mask (zero)."); CHECK_AND_ASSERT_THROW_MES(sc_check(to_bytes(multisig_input_proposal.commitment_mask)) == 0, "semantics check legacy multisig input proposal v1: bad address mask (not canonical)."); - CHECK_AND_ASSERT_THROW_MES(std::find(multisig_input_proposal.reference_set.begin(), - multisig_input_proposal.reference_set.end(), - multisig_input_proposal.tx_output_index) != - multisig_input_proposal.reference_set.end(), - "semantics check legacy multisig input proposal v1: referenced enote index is not in the reference set."); - CHECK_AND_ASSERT_THROW_MES(tools::is_sorted_and_unique(multisig_input_proposal.reference_set), - "semantics check legacy multisig input proposal v1: reference set indices are not sorted and unique."); } //------------------------------------------------------------------------------------------------------------------- void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteVariant &enote, @@ -783,7 +783,7 @@ void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteVariant &enote, const std::uint64_t tx_output_index, const std::uint64_t unlock_time, const crypto::secret_key &commitment_mask, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, LegacyMultisigInputProposalV1 &proposal_out) { // add components @@ -798,7 +798,7 @@ void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteVariant &enote, //------------------------------------------------------------------------------------------------------------------- void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteRecord &enote_record, const crypto::secret_key &commitment_mask, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, LegacyMultisigInputProposalV1 &proposal_out) { make_v1_legacy_multisig_input_proposal_v1(enote_record.enote, diff --git a/src/seraphis_main/tx_builders_multisig.h b/src/seraphis_main/tx_builders_multisig.h index d6e9f76314..41487daafc 100644 --- a/src/seraphis_main/tx_builders_multisig.h +++ b/src/seraphis_main/tx_builders_multisig.h @@ -100,11 +100,11 @@ void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteVariant &enote, const std::uint64_t tx_output_index, const std::uint64_t unlock_time, const crypto::secret_key &commitment_mask, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, LegacyMultisigInputProposalV1 &proposal_out); void make_v1_legacy_multisig_input_proposal_v1(const LegacyEnoteRecord &enote_record, const crypto::secret_key &commitment_mask, - std::vector reference_set, + LegacyReferenceSetV2 reference_set, LegacyMultisigInputProposalV1 &proposal_out); /** * brief: check_v1_sp_multisig_input_proposal_semantics_v1 - check semantics of a seraphis multisig input proposal diff --git a/src/seraphis_main/tx_component_types_legacy.cpp b/src/seraphis_main/tx_component_types_legacy.cpp index 70c7504efc..48c057baf3 100644 --- a/src/seraphis_main/tx_component_types_legacy.cpp +++ b/src/seraphis_main/tx_component_types_legacy.cpp @@ -69,6 +69,18 @@ void append_to_transcript(const LegacyEnoteImageV2 &container, SpTranscriptBuild transcript_inout.append("KI", container.key_image); } //------------------------------------------------------------------------------------------------------------------- +void append_to_transcript(const LegacyReferenceSetV2 &container, SpTranscriptBuilder &transcript_inout) +{ + std::vector refset_data; + refset_data.reserve(2 * container.indices.size()); + for (const legacy_output_index_t &i : container.indices) + { + refset_data.push_back(i.ledger_indexing_amount); + refset_data.push_back(i.index); + } + transcript_inout.append("indices", refset_data); +} +//------------------------------------------------------------------------------------------------------------------- void append_to_transcript(const LegacyRingSignatureV4 &container, SpTranscriptBuilder &transcript_inout) { transcript_inout.append("clsag_proof", container.clsag_proof); @@ -77,15 +89,15 @@ void append_to_transcript(const LegacyRingSignatureV4 &container, SpTranscriptBu //------------------------------------------------------------------------------------------------------------------- std::size_t legacy_ring_signature_v4_size_bytes(const std::size_t num_ring_members) { - return clsag_size_bytes(num_ring_members) + num_ring_members * 8; + return clsag_size_bytes(num_ring_members) + num_ring_members * 16; } //------------------------------------------------------------------------------------------------------------------- std::size_t legacy_ring_signature_v4_size_bytes(const LegacyRingSignatureV4 &ring_signature) { - CHECK_AND_ASSERT_THROW_MES(ring_signature.clsag_proof.s.size() == ring_signature.reference_set.size(), + CHECK_AND_ASSERT_THROW_MES(ring_signature.clsag_proof.s.size() == ring_signature.reference_set.indices.size(), "legacy ring signature v4 size: clsag proof doesn't match reference set size."); - return legacy_ring_signature_v4_size_bytes(ring_signature.reference_set.size()); + return legacy_ring_signature_v4_size_bytes(ring_signature.reference_set.indices.size()); } //------------------------------------------------------------------------------------------------------------------- bool compare_KI(const LegacyEnoteImageV2 &a, const LegacyEnoteImageV2 &b) diff --git a/src/seraphis_main/tx_component_types_legacy.h b/src/seraphis_main/tx_component_types_legacy.h index c91c8b8fbb..942744b5ca 100644 --- a/src/seraphis_main/tx_component_types_legacy.h +++ b/src/seraphis_main/tx_component_types_legacy.h @@ -33,6 +33,7 @@ //local headers #include "crypto/crypto.h" #include "ringct/rctTypes.h" +#include "seraphis_core/legacy_output_index.h" //third party headers #include @@ -88,6 +89,28 @@ void append_to_transcript(const LegacyEnoteImageV2 &container, SpTranscriptBuild /// get the size in bytes inline std::size_t legacy_enote_image_v2_size_bytes() { return 32 + 32; } +//// +// LegacyReferenceSetV1: not used in seraphis +// - ledger indexing amount +// - list of global output indices for given amount +/// + +//// +// LegacyReferenceSetV2: +// - sorted list of (ledger indexing amount, global index) pairs +/// +struct LegacyReferenceSetV2 +{ + std::set indices; +}; +inline const boost::string_ref container_name(const LegacyReferenceSetV2&) { return "LegacyReferenceSetV2"; } +void append_to_transcript(const LegacyReferenceSetV2 &container, SpTranscriptBuilder &transcript_inout); + +/// get the size in bytes +inline std::size_t legacy_ref_set_v2_size_bytes(const std::size_t num_indices) { return 16 * num_indices; } +inline std::size_t legacy_ref_set_v2_size_bytes(const LegacyReferenceSetV2 &legacy_ref_set) +{ return legacy_ref_set_v2_size_bytes(legacy_ref_set.indices.size()); } + //// // LegacyRingSignatureV1: not used in seraphis // - Cryptonote ring signature (using LegacyEnoteImageV1) @@ -105,14 +128,14 @@ inline std::size_t legacy_enote_image_v2_size_bytes() { return 32 + 32; } //// // LegacyRingSignatureV4 -// - CLSAG (using LegacyEnoteImageV2) +// - CLSAG (using LegacyEnoteImageV2 and mixed ledger amount reference set [Seraphis-only]) /// struct LegacyRingSignatureV4 final { /// a clsag proof LegacyClsagProof clsag_proof; - /// on-chain indices of the proof's ring members - std::vector reference_set; + /// sorted on-chain indices of the proof's ring members in (amount, index) form + LegacyReferenceSetV2 reference_set; }; inline const boost::string_ref container_name(const LegacyRingSignatureV4&) { return "LegacyRingSignatureV4"; } void append_to_transcript(const LegacyRingSignatureV4 &container, SpTranscriptBuilder &transcript_inout); diff --git a/src/seraphis_main/tx_validation_context.h b/src/seraphis_main/tx_validation_context.h index c18e6c07d0..d017138392 100644 --- a/src/seraphis_main/tx_validation_context.h +++ b/src/seraphis_main/tx_validation_context.h @@ -33,6 +33,7 @@ //local headers #include "crypto/crypto.h" #include "ringct/rctTypes.h" +#include "seraphis_core/legacy_output_index.h" #include "txtype_base.h" #include "tx_validators.h" @@ -83,7 +84,7 @@ class TxValidationContext * param: indices - * outparam: proof_elements_out - {KI, C} */ - virtual void get_reference_set_proof_elements_v1(const std::vector &indices, + virtual void get_reference_set_proof_elements_v1(const std::set &indices, rct::ctkeyV &proof_elements_out) const = 0; /** * brief: get_reference_set_proof_elements_v2 - gets seraphis squashed enotes stored in the validation context diff --git a/src/seraphis_main/tx_validators.cpp b/src/seraphis_main/tx_validators.cpp index 33be1b65b2..6482e69a14 100644 --- a/src/seraphis_main/tx_validators.cpp +++ b/src/seraphis_main/tx_validators.cpp @@ -170,13 +170,14 @@ bool validate_sp_semantics_legacy_reference_sets_v1(const SemanticConfigLegacyRe // check ring size in each ring signature for (const LegacyRingSignatureV4 &legacy_ring_signature : legacy_ring_signatures) { + const std::size_t ring_size{legacy_ring_signature.reference_set.indices.size()}; + // reference set - if (legacy_ring_signature.reference_set.size() < config.ring_size_min || - legacy_ring_signature.reference_set.size() > config.ring_size_max) + if (ring_size < config.ring_size_min || ring_size > config.ring_size_max) return false; // CLSAG signature size - if (legacy_ring_signature.reference_set.size() != legacy_ring_signature.clsag_proof.s.size()) + if (ring_size != legacy_ring_signature.clsag_proof.s.size()) return false; } @@ -312,12 +313,17 @@ bool validate_sp_semantics_layout_v1(const std::vector &l const std::vector &enote_ephemeral_pubkeys, const TxExtra &tx_extra) { - // legacy reference sets should be sorted (ascending) without duplicates - for (const LegacyRingSignatureV4 &legacy_ring_signature : legacy_ring_signatures) - { - if (!tools::is_sorted_and_unique(legacy_ring_signature.reference_set)) - return false; - } + using legacy_ring_sig_element_t = + std::remove_cv_t::value_type>; + using legacy_indices_t = + decltype(decltype(legacy_ring_sig_element_t::reference_set)::indices); + + // legacy ring signature reference sets should be sorted, which is done automatically by having + // them in a std::set + static_assert(std::is_same_v, + "legacy ring sigs list element type is not LegacyRingSignatureV4"); + static_assert(std::is_same_v>, + "legacy ring sig ref set indices is not a sorted set"); // seraphis membership proof binned reference set bins should be sorted (ascending) // note: duplicate bin locations are allowed @@ -444,7 +450,7 @@ bool validate_sp_legacy_input_proofs_v1(const std::vector // collect CLSAG ring members ring_members_temp.clear(); tx_validation_context.get_reference_set_proof_elements_v1( - legacy_ring_signatures[legacy_input_index].reference_set, + legacy_ring_signatures[legacy_input_index].reference_set.indices, ring_members_temp); // make legacy proof message diff --git a/src/seraphis_main/txtype_squashed_v1.cpp b/src/seraphis_main/txtype_squashed_v1.cpp index 3129fc5303..d72e709f81 100644 --- a/src/seraphis_main/txtype_squashed_v1.cpp +++ b/src/seraphis_main/txtype_squashed_v1.cpp @@ -160,7 +160,7 @@ std::size_t sp_tx_squashed_v1_size_bytes(const SpTxSquashedV1 &tx) { const std::size_t legacy_ring_size{ tx.legacy_ring_signatures.size() - ? tx.legacy_ring_signatures[0].reference_set.size() + ? tx.legacy_ring_signatures[0].reference_set.indices.size() : 0 }; @@ -202,7 +202,7 @@ std::size_t sp_tx_squashed_v1_weight(const SpTxSquashedV1 &tx) { const std::size_t legacy_ring_size{ tx.legacy_ring_signatures.size() - ? tx.legacy_ring_signatures[0].reference_set.size() + ? tx.legacy_ring_signatures[0].reference_set.indices.size() : 0 }; @@ -738,7 +738,7 @@ bool try_get_tx_contextual_validation_id(const SpTxSquashedV1 &tx, { // get the legacy ring members tx_validation_context.get_reference_set_proof_elements_v1( - legacy_ring_signature.reference_set, + legacy_ring_signature.reference_set.indices, tools::add_element(legacy_ring_members)); } diff --git a/src/seraphis_mocks/mock_ledger_context.cpp b/src/seraphis_mocks/mock_ledger_context.cpp index 79def24229..d5f8df8dac 100644 --- a/src/seraphis_mocks/mock_ledger_context.cpp +++ b/src/seraphis_mocks/mock_ledger_context.cpp @@ -32,6 +32,7 @@ #include "mock_ledger_context.h" //local headers +#include "common/container_helpers.h" #include "crypto/crypto.h" #include "crypto/x25519.h" #include "cryptonote_basic/subaddress_index.h" @@ -40,7 +41,7 @@ #include "misc_log_ex.h" #include "ringct/rctTypes.h" #include "seraphis_core/jamtis_enote_utils.h" -#include "seraphis_core/legacy_enote_types.h" +#include "seraphis_core/legacy_enote_utils.h" #include "seraphis_core/sp_core_enote_utils.h" #include "seraphis_crypto/sp_crypto_utils.h" #include "seraphis_main/scan_balance_recovery_utils.h" @@ -124,18 +125,21 @@ bool MockLedgerContext::seraphis_key_image_exists_onchain(const crypto::key_imag return m_sp_key_images.find(key_image) != m_sp_key_images.end(); } //------------------------------------------------------------------------------------------------------------------- -void MockLedgerContext::get_reference_set_proof_elements_v1(const std::vector &indices, +void MockLedgerContext::get_reference_set_proof_elements_v1(const std::set &indices, rct::ctkeyV &proof_elements_out) const { // get legacy enotes: {KI, C} proof_elements_out.clear(); proof_elements_out.reserve(indices.size()); - for (const std::uint64_t index : indices) + for (const legacy_output_index_t index : indices) { - CHECK_AND_ASSERT_THROW_MES(index < m_legacy_enote_references.size(), - "Tried to get legacy enote that doesn't exist."); - proof_elements_out.emplace_back(m_legacy_enote_references.at(index)); + CHECK_AND_ASSERT_THROW_MES(m_legacy_enote_references.count(index.ledger_indexing_amount), + "Tried to get legacy enote for amount that doesn't exist in the ledger."); + const auto &amount_outputs = m_legacy_enote_references.at(index.ledger_indexing_amount); + CHECK_AND_ASSERT_THROW_MES(index.index < amount_outputs.size(), + "Tried to get legacy enote for too high index."); + proof_elements_out.emplace_back(amount_outputs[index.index]); } } //------------------------------------------------------------------------------------------------------------------- @@ -153,9 +157,12 @@ void MockLedgerContext::get_reference_set_proof_elements_v2(const std::vector::max(); } //------------------------------------------------------------------------------------------------------------------- std::uint64_t MockLedgerContext::max_sp_enote_index() const @@ -189,6 +196,7 @@ void MockLedgerContext::clear_unconfirmed_cache() } //------------------------------------------------------------------------------------------------------------------- std::uint64_t MockLedgerContext::add_legacy_coinbase(const rct::key &tx_id, + const bool is_rct, const std::uint64_t unlock_time, TxExtra memo, std::vector legacy_key_images_for_block, @@ -201,15 +209,27 @@ std::uint64_t MockLedgerContext::add_legacy_coinbase(const rct::key &tx_id, "mock tx ledger (adding legacy coinbase tx): chain index is above last block that can have a legacy coinbase " "tx."); - // b. accumulated output count is consistent - const std::uint64_t accumulated_output_count = - m_accumulated_legacy_output_counts.size() - ? (m_accumulated_legacy_output_counts.rbegin())->second //last block's accumulated legacy output count - : 0; + // b. accumulated output counts is consistent + static const std::map empty_map{}; + const std::map &last_block_legacy_output_counts_per_amount{ + m_accumulated_legacy_output_counts.empty() ? + empty_map : m_accumulated_legacy_output_counts.crbegin()->second + }; - CHECK_AND_ASSERT_THROW_MES(accumulated_output_count == m_legacy_enote_references.size(), - "mock tx ledger (adding legacy coinbase tx): inconsistent number of accumulated outputs (bug)."); + CHECK_AND_ASSERT_THROW_MES(last_block_legacy_output_counts_per_amount.size() == m_legacy_enote_references.size(), + "mock tx ledger (adding legacy coinbase tx): inconsistent number unique output amounts from last block (bug)."); + for (const auto &amount_and_output_count : last_block_legacy_output_counts_per_amount) + { + CHECK_AND_ASSERT_THROW_MES(m_legacy_enote_references.count(amount_and_output_count.first), + "mock tx ledger (adding legacy coinbase tx): amount key not present in legacy enote references (bug)."); + + const std::uint64_t accumulated_output_count{amount_and_output_count.second}; + const std::uint64_t reference_set_count{m_legacy_enote_references.at(amount_and_output_count.first).size()}; + + CHECK_AND_ASSERT_THROW_MES(accumulated_output_count == reference_set_count, + "mock tx ledger (adding legacy coinbase tx): inconsistent number of accumulated outputs (bug)."); + } /// update state const std::uint64_t new_index{this->top_block_index() + 1}; @@ -222,25 +242,34 @@ std::uint64_t MockLedgerContext::add_legacy_coinbase(const rct::key &tx_id, // 2. add tx outputs - // a. initialize with current total legacy output count - std::uint64_t total_legacy_output_count{m_legacy_enote_references.size()}; + // a. initialize the amount -> total legacy output count map for next block + std::map next_block_legacy_output_counts_per_amount{ + last_block_legacy_output_counts_per_amount + }; - // b. insert all legacy enotes to the reference set + // b. insert all legacy enotes to the reference set and update output counts for next block for (const LegacyEnoteVariant &enote : output_enotes) { - m_legacy_enote_references[total_legacy_output_count] = {onetime_address_ref(enote), amount_commitment_ref(enote)}; + const rct::xmr_amount enote_amount{get_legacy_ledger_indexing_amount(enote, is_rct)}; + m_legacy_enote_references[enote_amount].push_back({onetime_address_ref(enote), amount_commitment_ref(enote)}); - ++total_legacy_output_count; + next_block_legacy_output_counts_per_amount[enote_amount] = m_legacy_enote_references[enote_amount].size(); } // c. add this block's accumulated output count - m_accumulated_legacy_output_counts[new_index] = total_legacy_output_count; + m_accumulated_legacy_output_counts[new_index] = std::move(next_block_legacy_output_counts_per_amount); if (new_index >= m_first_seraphis_allowed_block) m_accumulated_sp_output_counts[new_index] = m_sp_squashed_enotes.size(); // d. add this block's tx output contents - m_blocks_of_legacy_tx_output_contents[new_index][tx_id] = {unlock_time, std::move(memo), std::move(output_enotes)}; + m_blocks_of_legacy_tx_output_contents[new_index] = {{ + tx_id, + is_rct, + unlock_time, + std::move(memo), + std::move(output_enotes) + }}; if (new_index >= m_first_seraphis_allowed_block) m_blocks_of_sp_tx_output_contents[new_index]; @@ -448,7 +477,10 @@ std::uint64_t MockLedgerContext::commit_unconfirmed_txs_v1(const rct::key &coinb m_accumulated_sp_output_counts[new_index] = total_sp_output_count; if (new_index < m_first_seraphis_only_block) - m_accumulated_legacy_output_counts[new_index] = m_legacy_enote_references.size(); + { + m_accumulated_legacy_output_counts[new_index] = m_accumulated_legacy_output_counts.empty() ? + std::map() : m_accumulated_legacy_output_counts.crbegin()->second; + } // d. steal the unconfirmed cache's tx output contents m_blocks_of_sp_tx_output_contents[new_index] = std::move(m_unconfirmed_tx_output_contents); @@ -528,16 +560,28 @@ std::uint64_t MockLedgerContext::pop_chain_at_index(const std::uint64_t pop_inde CHECK_AND_ASSERT_THROW_MES(m_accumulated_legacy_output_counts.find(pop_index - 1) != m_accumulated_legacy_output_counts.end(), "mock ledger context (popping chain): accumulated legacy output counts has a hole (bug)."); - } - // remove all outputs starting in the pop_index block - const std::uint64_t first_output_to_remove = - pop_index > 0 - ? m_accumulated_legacy_output_counts[pop_index - 1] - : 0; + const std::map &accumulated_legacy_output_counts_top_new{ + m_accumulated_legacy_output_counts.at(pop_index - 1) + }; + + for (auto &amount_and_enotes : m_legacy_enote_references) + { + // remove all outputs starting in the pop_index block + const std::uint64_t first_output_to_remove = + accumulated_legacy_output_counts_top_new.count(amount_and_enotes.first) ? + accumulated_legacy_output_counts_top_new.at(amount_and_enotes.first) : 0; + + CHECK_AND_ASSERT_THROW_MES(first_output_to_remove <= amount_and_enotes.second.size(), + "mock ledger context (popping chain): accumulated legacy output count too high (bug)."); - m_legacy_enote_references.erase(m_legacy_enote_references.find(first_output_to_remove), - m_legacy_enote_references.end()); + amount_and_enotes.second.resize(first_output_to_remove); + } + } + else + { + m_legacy_enote_references.clear(); + } } // 3. remove squashed enotes @@ -731,29 +775,19 @@ void MockLedgerContext::get_onchain_chunk_legacy(const std::uint64_t chunk_start m_blocks_of_tx_key_images.end(), "onchain chunk legacy-view scanning (mock ledger context): end of chunk not known in key images map (bug)."); - // a. initialize output count to the total number of legacy enotes in the ledger before the first block to scan - std::uint64_t total_output_count_before_tx{0}; + // a. setup getters for the count of the total number of legacy enotes in the ledger before the each tx + std::map accumulated_legacy_output_counts_this_chunk; if (chunk_context_out.start_index > 0) { - CHECK_AND_ASSERT_THROW_MES(m_accumulated_legacy_output_counts.find(chunk_context_out.start_index - 1) != - m_accumulated_legacy_output_counts.end(), - "onchain chunk legacy-view scanning (mock ledger context): output counts missing a block (bug)."); - - total_output_count_before_tx = m_accumulated_legacy_output_counts.at(chunk_context_out.start_index - 1); - } - - // b. optimization: reserve size in the chunk map - // - use output counts as a proxy for the number of txs in the chunk range - if (chunk_context_out.block_ids.size() > 0) - { - CHECK_AND_ASSERT_THROW_MES(m_accumulated_legacy_output_counts.find(chunk_end_index - 1) != + const auto previous_accumulated_legacy_output_counts_it{ + m_accumulated_legacy_output_counts.find(chunk_context_out.start_index - 1) + }; + CHECK_AND_ASSERT_THROW_MES(previous_accumulated_legacy_output_counts_it != m_accumulated_legacy_output_counts.end(), "onchain chunk legacy-view scanning (mock ledger context): output counts missing a block (bug)."); - chunk_data_out.basic_records_per_tx.reserve( - (m_accumulated_legacy_output_counts.at(chunk_end_index - 1) - total_output_count_before_tx) / 2 - ); + accumulated_legacy_output_counts_this_chunk = previous_accumulated_legacy_output_counts_it->second; } // c. legacy-view scan each block in the range @@ -770,7 +804,26 @@ void MockLedgerContext::get_onchain_chunk_legacy(const std::uint64_t chunk_start for (const auto &tx_with_output_contents : block_of_tx_output_contents.second) { - const rct::key &tx_id{sortable2rct(tx_with_output_contents.first)}; + const rct::key &tx_id{sortable2rct(std::get(tx_with_output_contents))}; + const bool is_rct{std::get(tx_with_output_contents)}; + const std::vector &enotes{ + std::get>(tx_with_output_contents) + }; + + // calculate the legacy ledger output indices for each enote in this tx + std::vector legacy_output_index_per_enote; + for (const LegacyEnoteVariant &enote : enotes) + { + const rct::xmr_amount legacy_ledger_indexing_amount{ + get_legacy_ledger_indexing_amount(enote, is_rct) + }; + // notice we increment the index for this enote's amount in chunk + const std::uint64_t this_enote_amount_index{ + accumulated_legacy_output_counts_this_chunk[legacy_ledger_indexing_amount]++ + }; + legacy_output_index_per_enote.push_back({legacy_ledger_indexing_amount, + this_enote_amount_index}); + } // legacy view-scan the tx if in scan mode if (legacy_scan_mode == LegacyScanMode::SCAN) @@ -781,10 +834,10 @@ void MockLedgerContext::get_onchain_chunk_legacy(const std::uint64_t chunk_start block_of_tx_output_contents.first, std::get(m_block_infos.at(block_of_tx_output_contents.first)), tx_id, - total_output_count_before_tx, - std::get(tx_with_output_contents.second), - std::get(tx_with_output_contents.second), - std::get>(tx_with_output_contents.second), + legacy_output_index_per_enote, + std::get(tx_with_output_contents), + std::get(tx_with_output_contents), + enotes, SpEnoteOriginStatus::ONCHAIN, hw::get_device("default"), collected_records)) @@ -796,7 +849,7 @@ void MockLedgerContext::get_onchain_chunk_legacy(const std::uint64_t chunk_start } // always add an entry for this tx in the basic records map (since we save key images for every tx) - chunk_data_out.basic_records_per_tx[sortable2rct(tx_with_output_contents.first)]; + chunk_data_out.basic_records_per_tx[tx_id]; // collect key images from the tx (always do this for legacy txs) // - optimization not implemented here: only key images of rings which include a received @@ -804,27 +857,30 @@ void MockLedgerContext::get_onchain_chunk_legacy(const std::uint64_t chunk_start // include all key images CHECK_AND_ASSERT_THROW_MES( m_blocks_of_tx_key_images - .at(block_of_tx_output_contents.first).find(tx_with_output_contents.first) != + .at(block_of_tx_output_contents.first).find(tx_id) != m_blocks_of_tx_key_images .at(block_of_tx_output_contents.first).end(), "onchain chunk legacy-view scanning (mock ledger context): key image map missing tx (bug)."); if (scanning::try_collect_key_images_from_tx(block_of_tx_output_contents.first, std::get(m_block_infos.at(block_of_tx_output_contents.first)), - sortable2rct(tx_with_output_contents.first), + tx_id, std::get<0>(m_blocks_of_tx_key_images .at(block_of_tx_output_contents.first) - .at(tx_with_output_contents.first)), + .at(tx_id)), std::get<1>(m_blocks_of_tx_key_images .at(block_of_tx_output_contents.first) - .at(tx_with_output_contents.first)), + .at(tx_id)), SpEnoteSpentStatus::SPENT_ONCHAIN, collected_key_images)) chunk_data_out.contextual_key_images.emplace_back(std::move(collected_key_images)); - // add this tx's number of outputs to the total output count - total_output_count_before_tx += - std::get>(tx_with_output_contents.second).size(); + // add this tx's number of outputs to the chunk's total output count per amount + for (const LegacyEnoteVariant &enote : enotes) + { + const rct::xmr_amount enote_amount{get_legacy_ledger_indexing_amount(enote, is_rct)}; + accumulated_legacy_output_counts_this_chunk[enote_amount]++; + } } } ); diff --git a/src/seraphis_mocks/mock_ledger_context.h b/src/seraphis_mocks/mock_ledger_context.h index d579194cb4..dec43a2946 100644 --- a/src/seraphis_mocks/mock_ledger_context.h +++ b/src/seraphis_mocks/mock_ledger_context.h @@ -42,6 +42,7 @@ #include "ringct/rctOps.h" #include "ringct/rctTypes.h" #include "seraphis_core/legacy_enote_types.h" +#include "seraphis_core/legacy_output_index.h" #include "seraphis_crypto/sp_crypto_utils.h" #include "seraphis_main/scan_core_types.h" #include "seraphis_main/tx_component_types.h" @@ -106,7 +107,7 @@ class MockLedgerContext final * param: indices - * outparam: proof_elements_out - {KI, C} */ - void get_reference_set_proof_elements_v1(const std::vector &indices, + void get_reference_set_proof_elements_v1(const std::set &indices, rct::ctkeyV &proof_elements_out) const; /** * brief: get_reference_set_proof_elements_v2 - get seraphis squashed enotes stored in the ledger @@ -117,9 +118,10 @@ class MockLedgerContext final rct::keyV &proof_elements_out) const; /** * brief: max_legacy_enote_index - highest index of a legacy enote in the ledger + * param: ledger_indexing_amount - * return: highest legacy enote index (defaults to std::uint64_t::max if no enotes) */ - std::uint64_t max_legacy_enote_index() const; + std::uint64_t max_legacy_enote_index(const rct::xmr_amount ledger_indexing_amount) const; /** * brief: max_sp_enote_index - highest index of a seraphis enote in the ledger * return: highest seraphis enote index (defaults to std::uint64_t::max if no enotes) @@ -127,9 +129,11 @@ class MockLedgerContext final std::uint64_t max_sp_enote_index() const; /** * brief: num_legacy_enotes - number of legacy enotes in the ledger + * param: ledger_indexing_amount - * return: number of legacy enotes in the ledger */ - std::uint64_t num_legacy_enotes() const { return max_legacy_enote_index() + 1; } + std::uint64_t num_legacy_enotes(const rct::xmr_amount ledger_indexing_amount) const + { return this->max_legacy_enote_index(ledger_indexing_amount) + 1; } /** * brief: num_sp_enotes - number of seraphis enotes in the ledger * return: number of seraphis enotes in the ledger @@ -147,6 +151,7 @@ class MockLedgerContext final /** * brief: add_legacy_coinbase - make a block with a mock legacy coinbase tx (containing legacy key images) * param: tx_id - + * param: is_rct - * param: unlock_time - * param: memo - * param: legacy_key_images_for_block - @@ -154,6 +159,7 @@ class MockLedgerContext final * return: block index of newly added block */ std::uint64_t add_legacy_coinbase(const rct::key &tx_id, + const bool is_rct, const std::uint64_t unlock_time, TxExtra memo, std::vector legacy_key_images_for_block, @@ -305,14 +311,14 @@ class MockLedgerContext final > > > m_blocks_of_tx_key_images; - /// legacy enote references {KI, C} (mapped to output index) - std::map m_legacy_enote_references; + /// legacy enote references {KI, C} (mapped by indexable amount, then output index) + std::map> m_legacy_enote_references; /// seraphis squashed enotes (mapped to output index) std::map m_sp_squashed_enotes; /// map of accumulated output counts (legacy) std::map< - std::uint64_t, // block index - std::uint64_t // total number of legacy enotes including those in this block + std::uint64_t, // block index + std::map // total number of legacy enotes including those in this block, per amount > m_accumulated_legacy_output_counts; /// map of accumulated output counts (seraphis) std::map< @@ -322,9 +328,10 @@ class MockLedgerContext final /// map of legacy tx outputs std::map< std::uint64_t, // block index - std::map< - sortable_key, // tx id - std::tuple< // tx output contents + std::vector< // ordered list of txs + std::tuple< // tx contents + sortable_key, // txid + bool, // is_rct std::uint64_t, // unlock time TxExtra, // tx memo std::vector // output enotes diff --git a/src/seraphis_mocks/mock_send_receive.cpp b/src/seraphis_mocks/mock_send_receive.cpp index b626c51607..b2636107da 100644 --- a/src/seraphis_mocks/mock_send_receive.cpp +++ b/src/seraphis_mocks/mock_send_receive.cpp @@ -129,7 +129,7 @@ void send_legacy_coinbase_amounts_to_user(const std::vector &co "send legacy coinbase amounts to user: appending enote ephemeral pubkeys to tx extra failed."); // 3. commit coinbase enotes as new block - ledger_context_inout.add_legacy_coinbase(rct::pkGen(), 0, std::move(tx_extra), {}, std::move(coinbase_enotes)); + ledger_context_inout.add_legacy_coinbase(rct::pkGen(), true, 0, std::move(tx_extra), {}, std::move(coinbase_enotes)); } //------------------------------------------------------------------------------------------------------------------- void send_sp_coinbase_amounts_to_user(const std::vector &coinbase_amounts, @@ -285,7 +285,7 @@ void construct_tx_for_mock_ledger_v1(const legacy_mock_keys &local_user_legacy_k // 6. get ledger mappings for the input membership proofs // note: do this after making the tx proposal to demo that inputs don't have to be on-chain when proposing a tx - std::unordered_map legacy_input_ledger_mappings; + std::unordered_map legacy_input_ledger_mappings; std::unordered_map sp_input_ledger_mappings; try_get_membership_proof_real_reference_mappings(legacy_contextual_inputs, legacy_input_ledger_mappings); try_get_membership_proof_real_reference_mappings(sp_contextual_inputs, sp_input_ledger_mappings); diff --git a/src/seraphis_mocks/mock_tx_builders_legacy_inputs.cpp b/src/seraphis_mocks/mock_tx_builders_legacy_inputs.cpp index f83ab18001..f46597ff6a 100644 --- a/src/seraphis_mocks/mock_tx_builders_legacy_inputs.cpp +++ b/src/seraphis_mocks/mock_tx_builders_legacy_inputs.cpp @@ -72,10 +72,11 @@ std::vector gen_mock_legacy_input_proposals_v1(const cryp return input_proposals; } //------------------------------------------------------------------------------------------------------------------- -void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1(const std::uint64_t real_reference_index_in_ledger, +void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1( + const legacy_output_index_t real_reference_index_in_ledger, const std::uint64_t ring_size, const MockLedgerContext &ledger_context, - std::vector &reference_set_out, + LegacyReferenceSetV2 &reference_set_out, rct::ctkeyV &referenced_enotes_out, std::uint64_t &real_reference_index_out) { @@ -85,7 +86,10 @@ void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1(const std::uint6 LegacyRingSignaturePrepV1 proof_prep; // 1. flat decoy selector for mock-up - const LegacyDecoySelectorFlat decoy_selector{0, ledger_context.max_legacy_enote_index()}; + const rct::xmr_amount ledger_indexing_amount{real_reference_index_in_ledger.ledger_indexing_amount}; + const LegacyDecoySelectorFlat decoy_selector{LegacyDecoySelectorFlat::index_bounds_by_amount_t{ + {ledger_indexing_amount, {0, ledger_context.max_legacy_enote_index(ledger_indexing_amount)}} + }}; // 2. reference set CHECK_AND_ASSERT_THROW_MES(ring_size > 0, @@ -93,23 +97,23 @@ void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1(const std::uint6 decoy_selector.get_ring_members(real_reference_index_in_ledger, ring_size, - reference_set_out, + reference_set_out.indices, real_reference_index_out); - CHECK_AND_ASSERT_THROW_MES(real_reference_index_out < reference_set_out.size(), + CHECK_AND_ASSERT_THROW_MES(real_reference_index_out < reference_set_out.indices.size(), "gen mock legacy ring signature members (for enote at pos): real reference index is outside of reference set."); /// copy all referenced legacy enotes from the ledger - ledger_context.get_reference_set_proof_elements_v1(reference_set_out, referenced_enotes_out); + ledger_context.get_reference_set_proof_elements_v1(reference_set_out.indices, referenced_enotes_out); - CHECK_AND_ASSERT_THROW_MES(reference_set_out.size() == referenced_enotes_out.size(), + CHECK_AND_ASSERT_THROW_MES(reference_set_out.indices.size() == referenced_enotes_out.size(), "gen mock legacy ring signature members (for enote at pos): reference set doesn't line up with reference " "enotes."); } //------------------------------------------------------------------------------------------------------------------- LegacyRingSignaturePrepV1 gen_mock_legacy_ring_signature_prep_for_enote_at_pos_v1(const rct::key &tx_proposal_prefix, - const std::uint64_t real_reference_index_in_ledger, + const legacy_output_index_t real_reference_index_in_ledger, const LegacyEnoteImageV2 &real_reference_image, const crypto::secret_key &real_reference_view_privkey, const crypto::secret_key &commitment_mask, @@ -168,10 +172,11 @@ LegacyRingSignaturePrepV1 gen_mock_legacy_ring_signature_prep_v1(const rct::key } // 2. add mock legacy enotes as the outputs of a mock legacy coinbase tx - const std::uint64_t real_reference_index_in_ledger{ - ledger_context_inout.max_legacy_enote_index() + add_real_at_pos + 1 + const legacy_output_index_t real_reference_index_in_ledger{ + 0, // 0 amount since v5 legacy enotes all have ledger indexing amount 0 + ledger_context_inout.max_legacy_enote_index(0) + add_real_at_pos + 1 }; - ledger_context_inout.add_legacy_coinbase(rct::pkGen(), 0, TxExtra{}, {}, std::move(mock_enotes)); + ledger_context_inout.add_legacy_coinbase(rct::pkGen(), true, 0, TxExtra{}, {}, std::move(mock_enotes)); /// finish making the proof prep @@ -260,7 +265,7 @@ std::vector gen_mock_legacy_ring_signature_preps_v1(c } //------------------------------------------------------------------------------------------------------------------- void make_mock_legacy_ring_signature_preps_for_inputs_v1(const rct::key &tx_proposal_prefix, - const std::unordered_map &input_ledger_mappings, + const std::unordered_map &input_ledger_mappings, const std::vector &input_proposals, const std::uint64_t ring_size, const MockLedgerContext &ledger_context, @@ -298,7 +303,7 @@ bool try_gen_legacy_multisig_ring_signature_preps_v1(const std::vector &mapped_preps_out) { // 1. extract map [ legacy KI : enote ledger index ] from contextual records - std::unordered_map enote_ledger_mappings; + std::unordered_map enote_ledger_mappings; if (!try_get_membership_proof_real_reference_mappings(contextual_records, enote_ledger_mappings)) return false; diff --git a/src/seraphis_mocks/mock_tx_builders_legacy_inputs.h b/src/seraphis_mocks/mock_tx_builders_legacy_inputs.h index b9cd921914..846b3ac1e4 100644 --- a/src/seraphis_mocks/mock_tx_builders_legacy_inputs.h +++ b/src/seraphis_mocks/mock_tx_builders_legacy_inputs.h @@ -57,14 +57,15 @@ namespace mocks std::vector gen_mock_legacy_input_proposals_v1(const crypto::secret_key &legacy_spend_privkey, const std::vector &input_amounts); /// make mock legacy ring signature preps -void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1(const std::uint64_t real_reference_index_in_ledger, +void gen_mock_legacy_ring_signature_members_for_enote_at_pos_v1( + const legacy_output_index_t real_reference_index_in_ledger, const std::uint64_t ring_size, const MockLedgerContext &ledger_context, - std::vector &reference_set_out, + LegacyReferenceSetV2 &reference_set_out, rct::ctkeyV &referenced_enotes_out, std::uint64_t &real_reference_index_out); LegacyRingSignaturePrepV1 gen_mock_legacy_ring_signature_prep_for_enote_at_pos_v1(const rct::key &tx_proposal_prefix, - const std::uint64_t real_reference_index_in_ledger, + const legacy_output_index_t real_reference_index_in_ledger, const LegacyEnoteImageV2 &real_reference_image, const crypto::secret_key &real_reference_view_privkey, const crypto::secret_key &commitment_mask, @@ -90,7 +91,7 @@ std::vector gen_mock_legacy_ring_signature_preps_v1(c MockLedgerContext &ledger_context_inout); /// prepare membership proofs for enotes in a mock ledger void make_mock_legacy_ring_signature_preps_for_inputs_v1(const rct::key &tx_proposal_prefix, - const std::unordered_map &input_ledger_mappings, + const std::unordered_map &input_ledger_mappings, const std::vector &input_proposals, const std::uint64_t ring_size, const MockLedgerContext &ledger_context, diff --git a/src/seraphis_mocks/scan_context_async_mock.cpp b/src/seraphis_mocks/scan_context_async_mock.cpp index cb08163e25..9a6773220f 100644 --- a/src/seraphis_mocks/scan_context_async_mock.cpp +++ b/src/seraphis_mocks/scan_context_async_mock.cpp @@ -35,6 +35,7 @@ #include "misc_language.h" #include "misc_log_ex.h" #include "net/http.h" +#include "seraphis_core/legacy_enote_utils.h" #include "seraphis_impl/scan_ledger_chunk_simple.h" #include "seraphis_main/contextual_enote_record_types.h" #include "seraphis_main/enote_finding_context.h" @@ -102,25 +103,14 @@ static void validate_get_blocks_res(const ChunkRequest &req, } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- -static std::uint64_t get_total_output_count_before_tx(const std::vector &output_indices) -{ - // total_output_count_before_tx == global output index of first output in tx. - // Some txs have no enotes, in which case we set this value to 0 as it isn't useful. - // TODO: pre-RCT outputs yield incorrect values here but this is only used for spending - // need https://github.com/UkoeHB/monero/pull/40 in order to handle pre-RCT outputs - return !output_indices.empty() ? output_indices[0] : 0; -} -//------------------------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------------------------- static void prepare_unscanned_legacy_transaction(const crypto::hash &tx_hash, const cryptonote::transaction &tx, - const std::uint64_t total_output_count_before_tx, + const std::vector legacy_output_index_number_per_enote, sp::LegacyUnscannedTransaction &unscanned_tx_out) { unscanned_tx_out = LegacyUnscannedTransaction{}; unscanned_tx_out.transaction_id = rct::hash2rct(tx_hash); - unscanned_tx_out.total_enotes_before_tx = total_output_count_before_tx; unscanned_tx_out.unlock_time = tx.unlock_time; unscanned_tx_out.tx_memo = sp::TxExtra( @@ -130,6 +120,10 @@ static void prepare_unscanned_legacy_transaction(const crypto::hash &tx_hash, sp::legacy_outputs_to_enotes(tx, unscanned_tx_out.enotes); + CHECK_AND_ASSERT_THROW_MES(legacy_output_index_number_per_enote.empty() || + legacy_output_index_number_per_enote.size() == unscanned_tx_out.enotes.size(), + "bad number of output indices compared to number of legacy tx enotes"); + unscanned_tx_out.legacy_key_images.reserve(tx.vin.size()); for (const auto &in: tx.vin) { @@ -138,6 +132,21 @@ static void prepare_unscanned_legacy_transaction(const crypto::hash &tx_hash, const auto &txin = boost::get(in); unscanned_tx_out.legacy_key_images.emplace_back(txin.k_image); } + + const bool is_rct{tx.version == 2}; + + unscanned_tx_out.legacy_output_index_per_enote.clear(); + unscanned_tx_out.legacy_output_index_per_enote.reserve(unscanned_tx_out.enotes.size()); + for (size_t i = 0; i < unscanned_tx_out.enotes.size(); ++i) + { + const rct::xmr_amount ledger_indexing_amount{ + get_legacy_ledger_indexing_amount(unscanned_tx_out.enotes[i], is_rct) + }; + const std::uint64_t global_index{ + legacy_output_index_number_per_enote.empty() ? 0 : legacy_output_index_number_per_enote[i] + }; + unscanned_tx_out.legacy_output_index_per_enote.push_back({ledger_indexing_amount, global_index}); + } } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- @@ -242,7 +251,7 @@ static void prepare_unscanned_block(const cryptonote::block_complete_entry &res_ crypto::hash miner_tx_hash = cryptonote::get_transaction_hash(block.miner_tx); prepare_unscanned_legacy_transaction(miner_tx_hash, block.miner_tx, - get_total_output_count_before_tx(output_indices[0].indices), + output_indices[0].indices, unscanned_block_out.unscanned_txs[0]); // Prepare non-miner txs @@ -258,7 +267,7 @@ static void prepare_unscanned_block(const cryptonote::block_complete_entry &res_ prepare_unscanned_legacy_transaction(block.tx_hashes[tx_idx], std::move(tx), - get_total_output_count_before_tx(output_indices[unscanned_tx_idx].indices), + output_indices[unscanned_tx_idx].indices, unscanned_tx); } } diff --git a/src/seraphis_mocks/tx_validation_context_mock.h b/src/seraphis_mocks/tx_validation_context_mock.h index 9d7ca63610..cd00c16c3b 100644 --- a/src/seraphis_mocks/tx_validation_context_mock.h +++ b/src/seraphis_mocks/tx_validation_context_mock.h @@ -98,7 +98,7 @@ class TxValidationContextMock final : public TxValidationContext * param: indices - * outparam: proof_elements_out - {KI, C} */ - void get_reference_set_proof_elements_v1(const std::vector &indices, + void get_reference_set_proof_elements_v1(const std::set &indices, rct::ctkeyV &proof_elements_out) const override { m_mock_ledger_context.get_reference_set_proof_elements_v1(indices, proof_elements_out); diff --git a/tests/unit_tests/seraphis_enote_scanning.cpp b/tests/unit_tests/seraphis_enote_scanning.cpp index 715e04b6c0..bac13f334c 100644 --- a/tests/unit_tests/seraphis_enote_scanning.cpp +++ b/tests/unit_tests/seraphis_enote_scanning.cpp @@ -2382,6 +2382,7 @@ TEST(seraphis_enote_scanning, legacy_non_standard_tx_nth_pub_key) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, 0, tx_extra, {}, @@ -2498,6 +2499,7 @@ TEST(seraphis_enote_scanning, legacy_non_standard_more_additional_pub_keys_than_ )); ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, 0, tx_extra_1, {}, @@ -2618,6 +2620,7 @@ TEST(seraphis_enote_scanning, legacy_non_standard_less_additional_pub_keys_than_ )); ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, 0, tx_extra_1, {}, @@ -2815,6 +2818,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_1) )); ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -2914,6 +2918,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_2) //add legacy enote in block 0 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -2947,6 +2952,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_2) //spend enote in block 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -2999,6 +3005,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_2) //add empty block 2 (inject to test ledger index trackers) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -3159,6 +3166,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_3) //block 0: 1 -> user, 1 -> rand ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -3215,6 +3223,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_3) //block 1: 2 -> user ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, {}, @@ -3326,6 +3335,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_3) //block 2: spend enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -3429,6 +3439,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_3) //block 2: 4 -> user, spend enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_3, { @@ -3492,6 +3503,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_3) //block 3: spend enote 3 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -3639,6 +3651,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_4) //block 0: 1 -> user ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -3676,6 +3689,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_4) //block 1: 2 -> user ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, {}, @@ -3834,6 +3848,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_5) //block 0: 1 -> user ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -3871,6 +3886,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_5) //block 1: 2 -> user ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, {}, @@ -3908,6 +3924,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_5) //block 1: empty ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -4039,6 +4056,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_6) //block 0: enote 1-a ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -4079,6 +4097,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_6) //block 1: enote 1-b ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -4199,6 +4218,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_6) //block 1: enote 1-c ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -4303,6 +4323,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_6) //block 1: enote 1-d ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -4358,6 +4379,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_6) //block 2: spend enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -4531,6 +4553,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_7) //block 0: enote 1-a (amount 3) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -4571,6 +4594,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_7) //block 1: enote 1-b (amount 5) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -4700,6 +4724,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_7) //block 1: enote 1-c (amount 1) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -4711,6 +4736,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_7) //block 2: enote 1-d (amount 4) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -4766,6 +4792,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_7) //block 3: spend enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -4985,6 +5012,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_8) //block 0: enote 1 (unlock at block 0) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -5032,6 +5060,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_8) //block 1: enote 2 (unlock at block 3) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 3, tx_extra_2, {}, @@ -5079,6 +5108,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_8) //block 2: enote 3 (unlock at block 5) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 5, tx_extra_3, {}, @@ -5126,6 +5156,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_8) //block 3: empty ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -5171,6 +5202,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_8) //block 4: empty ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -5370,6 +5402,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_9) //block 0: enote 1-a (amount 1; unlock 0) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -5417,6 +5450,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_9) //block 1: empty ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -5462,6 +5496,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_9) //block 2: enote 1-b (amount 2; unlock 0) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, {}, @@ -5509,6 +5544,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_9) //block 3: empty ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, {}, @@ -5604,6 +5640,7 @@ TEST(seraphis_enote_scanning, legacy_pre_transition_9) //block 4: enote 1-c (amount 3; unlock 0), spend enote 1 (check balance with a locked and spent enote [enote 1-c]) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra, { @@ -6000,6 +6037,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_1) //block 0: legacy enote 1, legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -6079,6 +6117,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_1) //block 1: legacy enote 3, spend legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_3, { @@ -6372,6 +6411,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_1) //block 1: legacy enote 4, legacy enote 5 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_4, {}, @@ -6642,6 +6682,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_1) //block 1: legacy enote 4, legacy enote 5 (reuse these) ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_4, {}, @@ -7088,6 +7129,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_2) //block 0: legacy enote 1, legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -7167,6 +7209,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_2) //block 1: legacy enote 3, spend legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, { @@ -7441,6 +7484,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_2) //block 2: legacy enote 3, spend legacy enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, { @@ -8085,6 +8129,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_3) //block 0: legacy enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -8211,6 +8256,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_3) //block 0: legacy enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -8648,6 +8694,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_4) //block 0: legacy enote 1, legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -8727,6 +8774,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_4) //block 1: legacy enote 3, spend legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_2, { @@ -9045,6 +9093,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_4) //block 1: legacy enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -9267,6 +9316,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_5) //block 0: legacy enote 1, legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, tx_extra_1, {}, @@ -9320,6 +9370,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_5) //block 1: legacy enote 1 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { @@ -9552,6 +9603,7 @@ TEST(seraphis_enote_scanning, legacy_sp_transition_5) //block 2: legacy enote 2 ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( rct::pkGen(), + true, // is_rct 0, TxExtra{}, { diff --git a/tests/unit_tests/seraphis_multisig.cpp b/tests/unit_tests/seraphis_multisig.cpp index a78562fad7..af6eb05dd1 100644 --- a/tests/unit_tests/seraphis_multisig.cpp +++ b/tests/unit_tests/seraphis_multisig.cpp @@ -234,7 +234,7 @@ static bool sp_multisig_input_is_ready_to_spend(const SpMultisigInputProposalV1 } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- -static bool legacy_ring_members_are_ready_to_spend(const std::vector &reference_set, +static bool legacy_ring_members_are_ready_to_spend(const std::set &reference_set, const rct::ctkeyV &legacy_ring_members, const MockLedgerContext &ledger_context) { @@ -326,7 +326,7 @@ static void validate_multisig_tx_proposal(const SpMultisigTxProposalV1 &multisig ++legacy_input_index) { ASSERT_TRUE(legacy_ring_members_are_ready_to_spend( - multisig_tx_proposal.legacy_multisig_input_proposals[legacy_input_index].reference_set, + multisig_tx_proposal.legacy_multisig_input_proposals[legacy_input_index].reference_set.indices, multisig_tx_proposal.legacy_input_proof_proposals[legacy_input_index].ring_members, ledger_context)); }