From 111fe517e4c2045934ef1ba2848f6103aac14762 Mon Sep 17 00:00:00 2001 From: SNeedlewoods <43108541+SNeedlewoods@users.noreply.github.com> Date: Wed, 22 May 2024 22:11:23 +0200 Subject: [PATCH] Identify non-standard received legacy enotes (#43) --------- Co-authored-by: SNeedlewoods --- src/ringct/rctTypes.h | 2 + src/seraphis_core/legacy_core_utils.cpp | 28 +- src/seraphis_core/legacy_core_utils.h | 4 +- .../scan_balance_recovery_utils.cpp | 121 ++++-- tests/unit_tests/seraphis_enote_scanning.cpp | 347 ++++++++++++++++++ 5 files changed, 455 insertions(+), 47 deletions(-) diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 26bf77ddf1..3af4d3c83a 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -53,6 +53,7 @@ extern "C" { #include "serialization/debug_archive.h" #include "serialization/binary_archive.h" #include "serialization/json_archive.h" +#include "common/variant.h" //Define this flag when debugging to get additional info on the console @@ -89,6 +90,7 @@ namespace rct { }; typedef std::vector keyV; //vector of keys typedef std::vector keyM; //matrix of keys (indexed by column first) + using key_keyV_variant = tools::variant; //containers For CT operations //if it's representing a private ctkey then "dest" contains the secret key of the address diff --git a/src/seraphis_core/legacy_core_utils.cpp b/src/seraphis_core/legacy_core_utils.cpp index d6e0fb1feb..1875fb3534 100644 --- a/src/seraphis_core/legacy_core_utils.cpp +++ b/src/seraphis_core/legacy_core_utils.cpp @@ -373,8 +373,8 @@ bool try_append_legacy_enote_ephemeral_pubkeys_to_tx_extra(const std::vector &legacy_additional_enote_ephemeral_pubkeys) + rct::key_keyV_variant &legacy_main_enote_ephemeral_pubkeys_out, + std::vector &legacy_additional_enote_ephemeral_pubkeys_out) { // 1. parse field std::vector tx_extra_fields; @@ -385,17 +385,29 @@ void extract_legacy_enote_ephemeral_pubkeys_from_tx_extra(const TxExtra &tx_extr // main enote ephemeral pubkey for key derivations cryptonote::tx_extra_pub_key pub_key_field; - if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, pub_key_field)) - legacy_main_enote_ephemeral_pubkey_out = pub_key_field.pub_key; - else - legacy_main_enote_ephemeral_pubkey_out = rct::rct2pk(rct::I); + size_t pk_index = 0; + while (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) + { + if (pk_index == 1) // first iteration + { + legacy_main_enote_ephemeral_pubkeys_out = rct::pk2rct(pub_key_field.pub_key); + continue; + } + else if (pk_index == 2) // second iteration + legacy_main_enote_ephemeral_pubkeys_out = rct::keyV{legacy_main_enote_ephemeral_pubkeys_out.unwrap()}; + legacy_main_enote_ephemeral_pubkeys_out.unwrap().push_back(rct::pk2rct(pub_key_field.pub_key)); + } + + if (legacy_main_enote_ephemeral_pubkeys_out.is_type() && + legacy_main_enote_ephemeral_pubkeys_out.unwrap() == rct::key{}) + legacy_main_enote_ephemeral_pubkeys_out = rct::I; // 3. try to get 'additional' enote ephemeral pubkeys (one per output): r_t K^v_t cryptonote::tx_extra_additional_pub_keys additional_pub_keys_field; - legacy_additional_enote_ephemeral_pubkeys.clear(); + legacy_additional_enote_ephemeral_pubkeys_out.clear(); if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, additional_pub_keys_field)) - legacy_additional_enote_ephemeral_pubkeys = additional_pub_keys_field.data; + legacy_additional_enote_ephemeral_pubkeys_out = additional_pub_keys_field.data; } //------------------------------------------------------------------------------------------------------------------- } //namespace sp diff --git a/src/seraphis_core/legacy_core_utils.h b/src/seraphis_core/legacy_core_utils.h index ea9ead650f..a87fb7033c 100644 --- a/src/seraphis_core/legacy_core_utils.h +++ b/src/seraphis_core/legacy_core_utils.h @@ -274,12 +274,12 @@ bool try_append_legacy_enote_ephemeral_pubkeys_to_tx_extra(const std::vector &legacy_additional_enote_ephemeral_pubkeys_out); } //namespace sp diff --git a/src/seraphis_main/scan_balance_recovery_utils.cpp b/src/seraphis_main/scan_balance_recovery_utils.cpp index 73d9c0e038..46b6e19d22 100644 --- a/src/seraphis_main/scan_balance_recovery_utils.cpp +++ b/src/seraphis_main/scan_balance_recovery_utils.cpp @@ -354,6 +354,30 @@ static std::unordered_set process_chunk_sp_selfsend_pass( } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +static rct::key& key_by_index_ref(rct::key_keyV_variant &variant, size_t index) +{ + struct visitor final : public tools::variant_static_visitor + { + visitor(size_t index) : id{index} {} + size_t id; + + using variant_static_visitor::operator(); //for blank overload + rct::key& operator()(rct::key &key_variant) const + { + CHECK_AND_ASSERT_THROW_MES(id == 0, "invalid index for rct::key."); + return key_variant; + } + rct::key& operator()(rct::keyV &key_variant) const + { + CHECK_AND_ASSERT_THROW_MES(id < key_variant.size(), "index greater than vector size."); + return key_variant[id]; + } + }; + + return variant.visit(visitor{index}); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, const std::unordered_map &legacy_subaddress_map, const crypto::secret_key &legacy_view_privkey, @@ -371,25 +395,22 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, basic_records_in_tx_out.clear(); // 1. extract enote ephemeral pubkeys from the memo - crypto::public_key legacy_main_enote_ephemeral_pubkey; + rct::key_keyV_variant legacy_main_enote_ephemeral_pubkeys = rct::key{}; std::vector legacy_additional_enote_ephemeral_pubkeys; extract_legacy_enote_ephemeral_pubkeys_from_tx_extra(tx_memo, - legacy_main_enote_ephemeral_pubkey, + legacy_main_enote_ephemeral_pubkeys, legacy_additional_enote_ephemeral_pubkeys); - // 2. check if there are a valid number of additional enote ephemeral pubkeys - if (legacy_additional_enote_ephemeral_pubkeys.size() > 0 && - legacy_additional_enote_ephemeral_pubkeys.size() != enotes_in_tx.size()) - return false; - - // 3. scan each enote in the tx using the 'additional enote ephemeral pubkeys' + // 2. scan each enote in the tx using the 'additional enote ephemeral pubkeys' // - this step is automatically skipped if legacy_additional_enote_ephemeral_pubkeys.size() == 0 crypto::key_derivation temp_DH_derivation; LegacyContextualBasicEnoteRecordV1 temp_contextual_record{}; bool found_an_enote{false}; - for (std::size_t enote_index{0}; enote_index < legacy_additional_enote_ephemeral_pubkeys.size(); ++enote_index) + for (std::size_t enote_index{0}; enote_index < legacy_additional_enote_ephemeral_pubkeys.size() && + enote_index < enotes_in_tx.size(); + ++enote_index) { // a. compute the DH derivation for this enote ephemeral pubkey hwdev.generate_key_derivation(legacy_additional_enote_ephemeral_pubkeys[enote_index], @@ -423,41 +444,67 @@ bool try_find_legacy_enotes_in_tx(const rct::key &legacy_base_spend_pubkey, found_an_enote = true; } - // 4. check if there is a main enote ephemeral pubkey - if (legacy_main_enote_ephemeral_pubkey == rct::rct2pk(rct::I)) + // 3. check if there is a main enote ephemeral pubkey + if (legacy_main_enote_ephemeral_pubkeys.is_type() && + legacy_main_enote_ephemeral_pubkeys.unwrap() == rct::I) return found_an_enote; - // 5. compute the key derivation for the main enote ephemeral pubkey - hwdev.generate_key_derivation(legacy_main_enote_ephemeral_pubkey, legacy_view_privkey, temp_DH_derivation); + // 4. compute the key derivations for all main enote ephemeral pubkeys + rct::key_keyV_variant temp_DH_derivations = rct::key{}; + const auto pubkeys = legacy_main_enote_ephemeral_pubkeys.try_unwrap(); + if (pubkeys) + { + rct::keyV temp{}; + temp.reserve(pubkeys->size()); + for (const rct::key &enote_ephemeral_pubkey : *pubkeys) { + hwdev.generate_key_derivation(rct::rct2pk(enote_ephemeral_pubkey), legacy_view_privkey, temp_DH_derivation); + temp.emplace_back((rct::key &) temp_DH_derivation); + } + temp_DH_derivations = temp; + } + else + { + hwdev.generate_key_derivation(rct::rct2pk(legacy_main_enote_ephemeral_pubkeys.unwrap()), legacy_view_privkey, temp_DH_derivation); + temp_DH_derivations = (rct::key &) temp_DH_derivation; + } - // 6. scan all enotes using the main key derivation + // 5. scan all enotes using key derivations for every ephemeral pub key for (std::size_t enote_index{0}; enote_index < enotes_in_tx.size(); ++enote_index) { - // a. try to recover a contextual basic record from the enote - if (!try_view_scan_legacy_enote_v1(legacy_base_spend_pubkey, - legacy_subaddress_map, - block_index, - block_timestamp, - transaction_id, - total_enotes_before_tx, - enote_index, - unlock_time, - tx_memo, - enotes_in_tx[enote_index], - legacy_main_enote_ephemeral_pubkey, - temp_DH_derivation, - origin_status, - hwdev, - temp_contextual_record)) - continue; + // all ephemeral pub key - DH_derivation pairs + for (std::size_t pk_index{0}; + (temp_DH_derivations.is_type() && pk_index < temp_DH_derivations.unwrap().size()) || + (temp_DH_derivations.is_type() && pk_index < 1); + ++pk_index) + { + crypto::public_key temp_legacy_main_enote_ephemeral_pubkey = rct2pk(key_by_index_ref(legacy_main_enote_ephemeral_pubkeys, pk_index)); + temp_DH_derivation = (crypto::key_derivation &) key_by_index_ref(temp_DH_derivations, pk_index); + // a. try to recover a contextual basic record from the enote + if (!try_view_scan_legacy_enote_v1(legacy_base_spend_pubkey, + legacy_subaddress_map, + block_index, + block_timestamp, + transaction_id, + total_enotes_before_tx, + enote_index, + unlock_time, + tx_memo, + enotes_in_tx[enote_index], + temp_legacy_main_enote_ephemeral_pubkey, + temp_DH_derivation, + origin_status, + hwdev, + temp_contextual_record)) + continue; - // b. save the contextual basic record - // note: it is possible for enotes with duplicate onetime addresses to be added here; it is assumed the - // upstream caller will be able to handle those without problems - basic_records_in_tx_out.emplace_back(temp_contextual_record); + // b. save the contextual basic record + // note: it is possible for enotes with duplicate onetime addresses to be added here; it is assumed the + // upstream caller will be able to handle those without problems + basic_records_in_tx_out.emplace_back(temp_contextual_record); - // c. record that an owned enote has been found - found_an_enote = true; + // c. record that an owned enote has been found + found_an_enote = true; + } } return found_an_enote; diff --git a/tests/unit_tests/seraphis_enote_scanning.cpp b/tests/unit_tests/seraphis_enote_scanning.cpp index ef8aeac4ff..715e04b6c0 100644 --- a/tests/unit_tests/seraphis_enote_scanning.cpp +++ b/tests/unit_tests/seraphis_enote_scanning.cpp @@ -29,6 +29,7 @@ #include "crypto/crypto.h" #include "crypto/x25519.h" #include "cryptonote_basic/subaddress_index.h" +#include "cryptonote_basic/cryptonote_format_utils.h" #include "misc_language.h" #include "ringct/rctOps.h" #include "ringct/rctTypes.h" @@ -2297,6 +2298,352 @@ TEST(seraphis_enote_scanning, reorgs_while_scanning_5) //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +TEST(seraphis_enote_scanning, legacy_non_standard_tx_nth_pub_key) +{ + // scan using all tx pub keys in tx extra + + /// setup + + // 1. config + const scanning::ScanMachineConfig refresh_config{ + .reorg_avoidance_increment = 1, + .max_chunk_size_hint = 1, + .max_partialscan_attempts = 0 + }; + + // 2. user keys + legacy_mock_keys legacy_keys; + make_legacy_mock_keys(legacy_keys); + + // 3. user normal addresses + const rct::key normal_addr_spendkey{legacy_keys.Ks}; + const rct::key normal_addr_viewkey{rct::scalarmultBase(rct::sk2rct(legacy_keys.k_v))}; + + // 4. user subaddress + rct::key subaddr_spendkey; + rct::key subaddr_viewkey; + cryptonote::subaddress_index subaddr_index; + + gen_legacy_subaddress(legacy_keys.Ks, legacy_keys.k_v, subaddr_spendkey, subaddr_viewkey, subaddr_index); + + std::unordered_map legacy_subaddress_map; + legacy_subaddress_map[subaddr_spendkey] = subaddr_index; + + /// test + + // 1. v5 legacy enotes (normal destinations) + MockLedgerContext ledger_context{10000, 10000}; + SpEnoteStore enote_store{0, 10000, 0}; + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + + LegacyEnoteV5 enote_v5_1; //to normal destination + const crypto::secret_key enote_ephemeral_privkey_1{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_1{ + rct::scalarmultBase(rct::sk2rct(enote_ephemeral_privkey_1)) + }; + + LegacyEnoteV5 enote_v5_2; //to normal destination + const crypto::secret_key enote_ephemeral_privkey_2{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_2{ + rct::scalarmultBase(rct::sk2rct(enote_ephemeral_privkey_2)) + }; + + ASSERT_NO_THROW(make_legacy_enote_v5(normal_addr_spendkey, + normal_addr_viewkey, + 1, //amount + 0, //index in planned mock coinbase tx + enote_ephemeral_privkey_1, + enote_v5_1)); + + ASSERT_NO_THROW(make_legacy_enote_v5(normal_addr_spendkey, + normal_addr_viewkey, + 1, //amount + 1, //index in planned mock coinbase tx + enote_ephemeral_privkey_2, + enote_v5_2)); + + TxExtra tx_extra; + cryptonote::add_tx_pub_key_to_extra(tx_extra, rct::rct2pk(enote_ephemeral_pubkey_1)); + cryptonote::add_tx_pub_key_to_extra(tx_extra, rct::rct2pk(enote_ephemeral_pubkey_2)); + + ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( + rct::pkGen(), + 0, + tx_extra, + {}, + { + enote_v5_1, + enote_v5_2 + } + )); + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 2); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 2); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(seraphis_enote_scanning, legacy_non_standard_more_additional_pub_keys_than_enotes) +{ + // ignore additional pub keys beyond number of enotes in tx + + /// setup + + // 1. config + const scanning::ScanMachineConfig refresh_config{ + .reorg_avoidance_increment = 1, + .max_chunk_size_hint = 1, + .max_partialscan_attempts = 0 + }; + + // 2. user keys + legacy_mock_keys legacy_keys; + make_legacy_mock_keys(legacy_keys); + + // 3. user subaddress + rct::key subaddr_spendkey; + rct::key subaddr_viewkey; + cryptonote::subaddress_index subaddr_index; + + gen_legacy_subaddress(legacy_keys.Ks, legacy_keys.k_v, subaddr_spendkey, subaddr_viewkey, subaddr_index); + + std::unordered_map legacy_subaddress_map; + legacy_subaddress_map[subaddr_spendkey] = subaddr_index; + + /// test + + // 1. v5 legacy enotes (subaddress destinations) + MockLedgerContext ledger_context{10000, 10000}; + SpEnoteStore enote_store{0, 10000, 0}; + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + + LegacyEnoteV5 enote_v5_1; //to subaddress destination + const crypto::secret_key enote_ephemeral_privkey_1{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_1{ + rct::scalarmultKey(subaddr_spendkey, rct::sk2rct(enote_ephemeral_privkey_1)) + }; + + LegacyEnoteV5 enote_v5_2; //to subaddress destination + const crypto::secret_key enote_ephemeral_privkey_2{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_2{ + rct::scalarmultKey(subaddr_spendkey, rct::sk2rct(enote_ephemeral_privkey_2)) + }; + + // more additional tx pub keys than enotes + const crypto::secret_key enote_ephemeral_privkey_3{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_3{ + rct::scalarmultKey(subaddr_spendkey, rct::sk2rct(enote_ephemeral_privkey_3)) + }; + + ASSERT_NO_THROW(make_legacy_enote_v5(subaddr_spendkey, + subaddr_viewkey, + 1, //amount + 0, //index in planned mock coinbase tx + enote_ephemeral_privkey_1, + enote_v5_1)); + + ASSERT_NO_THROW(make_legacy_enote_v5(subaddr_spendkey, + subaddr_viewkey, + 1, //amount + 1, //index in planned mock coinbase tx + enote_ephemeral_privkey_2, + enote_v5_2)); + + TxExtra tx_extra_1; + ASSERT_TRUE(try_append_legacy_enote_ephemeral_pubkeys_to_tx_extra( + { + enote_ephemeral_pubkey_1, + enote_ephemeral_pubkey_2, + enote_ephemeral_pubkey_3 + }, + tx_extra_1 + )); + ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( + rct::pkGen(), + 0, + tx_extra_1, + {}, + { + enote_v5_1, + enote_v5_2 + } + )); + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 2); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 2); +} +//------------------------------------------------------------------------------------------------------------------- +TEST(seraphis_enote_scanning, legacy_non_standard_less_additional_pub_keys_than_enotes) +{ + // scan using additionals corresponding to lower indexed enotes, + // if there are fewer additional pub keys than enotes + + /// setup + + // 1. config + const scanning::ScanMachineConfig refresh_config{ + .reorg_avoidance_increment = 1, + .max_chunk_size_hint = 1, + .max_partialscan_attempts = 0 + }; + + // 2. user keys + legacy_mock_keys legacy_keys; + make_legacy_mock_keys(legacy_keys); + + // 3. user subaddress + rct::key subaddr_spendkey; + rct::key subaddr_viewkey; + cryptonote::subaddress_index subaddr_index; + + gen_legacy_subaddress(legacy_keys.Ks, legacy_keys.k_v, subaddr_spendkey, subaddr_viewkey, subaddr_index); + + std::unordered_map legacy_subaddress_map; + legacy_subaddress_map[subaddr_spendkey] = subaddr_index; + + /// test + + // 1. v5 legacy enotes (subaddress destinations) + MockLedgerContext ledger_context{10000, 10000}; + SpEnoteStore enote_store{0, 10000, 0}; + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + + LegacyEnoteV5 enote_v5_1; //to subaddress destination + const crypto::secret_key enote_ephemeral_privkey_1{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_1{ + rct::scalarmultKey(subaddr_spendkey, rct::sk2rct(enote_ephemeral_privkey_1)) + }; + + LegacyEnoteV5 enote_v5_2; //to subaddress destination + const crypto::secret_key enote_ephemeral_privkey_2{make_secret_key()}; + const rct::key enote_ephemeral_pubkey_2{ + rct::scalarmultKey(subaddr_spendkey, rct::sk2rct(enote_ephemeral_privkey_2)) + }; + + // more enotes than additional tx pub keys + LegacyEnoteV5 enote_v5_3; //to subaddress destination + + ASSERT_NO_THROW(make_legacy_enote_v5(subaddr_spendkey, + subaddr_viewkey, + 1, //amount + 0, //index in planned mock coinbase tx + enote_ephemeral_privkey_1, + enote_v5_1)); + + ASSERT_NO_THROW(make_legacy_enote_v5(subaddr_spendkey, + subaddr_viewkey, + 1, //amount + 1, //index in planned mock coinbase tx + enote_ephemeral_privkey_2, + enote_v5_2)); + + ASSERT_NO_THROW(make_legacy_enote_v5(subaddr_spendkey, + subaddr_viewkey, + 1, //amount + 2, //index in planned mock coinbase tx + make_secret_key(), + enote_v5_3)); + + TxExtra tx_extra_1; + ASSERT_TRUE(try_append_legacy_enote_ephemeral_pubkeys_to_tx_extra( + { + enote_ephemeral_pubkey_1, + enote_ephemeral_pubkey_2, + }, + tx_extra_1 + )); + ASSERT_NO_THROW(ledger_context.add_legacy_coinbase( + rct::pkGen(), + 0, + tx_extra_1, + {}, + { + enote_v5_1, + enote_v5_2, + enote_v5_3 + } + )); + + refresh_user_enote_store_legacy_full(legacy_keys.Ks, + legacy_subaddress_map, + legacy_keys.k_s, + legacy_keys.k_v, + refresh_config, + ledger_context, + enote_store); + + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN}, + {SpEnoteSpentStatus::SPENT_ONCHAIN}) == 2); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 0); + ASSERT_TRUE(get_balance(enote_store, {SpEnoteOriginStatus::ONCHAIN, SpEnoteOriginStatus::UNCONFIRMED}, + {SpEnoteSpentStatus::SPENT_ONCHAIN, SpEnoteSpentStatus::SPENT_UNCONFIRMED}) == 2); +} +//------------------------------------------------------------------------------------------------------------------- TEST(seraphis_enote_scanning, legacy_pre_transition_1) { /// setup