diff --git a/src/common/container_helpers.h b/src/common/container_helpers.h index 5824c9c374f..0c1a5cd48b9 100644 --- a/src/common/container_helpers.h +++ b/src/common/container_helpers.h @@ -73,10 +73,12 @@ inline auto compare_func(ComparisonOpT comparison_op_func) } /// test if a container is sorted and unique according to a comparison criteria (defaults to operator<) /// NOTE: ComparisonOpT must establish 'strict weak ordering' https://en.cppreference.com/w/cpp/named_req/Compare +/// NOTE: T::const_iterator must satisfy LegacyForwardIterator https://en.cppreference.com/w/cpp/named_req/ForwardIterator template > bool is_sorted_and_unique(const T &container, ComparisonOpT comparison_op = ComparisonOpT{}) { using ValueT = typename T::value_type; + using ConstIt = typename T::const_iterator; static_assert( std::is_same< bool, @@ -90,17 +92,20 @@ bool is_sorted_and_unique(const T &container, ComparisonOpT comparison_op = Comp "invalid callable - expected callable in form bool(ValueT, ValueT)" ); - if (!std::is_sorted(container.begin(), container.end(), comparison_op)) - return false; - - if (std::adjacent_find(container.begin(), - container.end(), - [comparison_op = std::move(comparison_op)](const ValueT &a, const ValueT &b) -> bool - { - return !comparison_op(a, b) && !comparison_op(b, a); - }) - != container.end()) - return false; + const ConstIt end = container.cend(); + + ConstIt it_a = container.cbegin(); + if (it_a == end) + return true; + + ConstIt it_b = it_a; + it_b++; + + for (; it_b != end; it_a = it_b++) + { + if (!comparison_op(*it_a, *it_b)) + return false; + } return true; } diff --git a/src/seraphis_core/jamtis_payment_proposal.cpp b/src/seraphis_core/jamtis_payment_proposal.cpp index 74b7d862f86..667396d00a2 100644 --- a/src/seraphis_core/jamtis_payment_proposal.cpp +++ b/src/seraphis_core/jamtis_payment_proposal.cpp @@ -99,6 +99,19 @@ static void get_output_proposal_address_parts_v1(const rct::key &q, } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +/// equality operators +bool operator==(const JamtisPaymentProposalV1 a, const JamtisPaymentProposalV1 b) +{ + return a.destination == b.destination && a.amount == b.amount && + a.enote_ephemeral_privkey == b.enote_ephemeral_privkey && a.partial_memo == b.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const JamtisPaymentProposalSelfSendV1 a, const JamtisPaymentProposalSelfSendV1 b) +{ + return a.destination == b.destination && a.amount == b.amount && a.type == b.type && + a.enote_ephemeral_privkey == b.enote_ephemeral_privkey && a.partial_memo == b.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- void get_enote_ephemeral_pubkey(const JamtisPaymentProposalV1 &proposal, crypto::x25519_pubkey &enote_ephemeral_pubkey_out) { diff --git a/src/seraphis_core/jamtis_payment_proposal.h b/src/seraphis_core/jamtis_payment_proposal.h index 6d737a1224f..06ea329aef7 100644 --- a/src/seraphis_core/jamtis_payment_proposal.h +++ b/src/seraphis_core/jamtis_payment_proposal.h @@ -91,6 +91,10 @@ struct JamtisPaymentProposalSelfSendV1 final TxExtra partial_memo; }; +/// equality operators +bool operator==(const JamtisPaymentProposalV1 a, const JamtisPaymentProposalV1 b); +bool operator==(const JamtisPaymentProposalSelfSendV1 a, const JamtisPaymentProposalSelfSendV1 b); + /** * brief: get_enote_ephemeral_pubkey - get the proposal's enote ephemeral pubkey xK_e * param: proposal - diff --git a/src/seraphis_impl/serialization_demo_types.h b/src/seraphis_impl/serialization_demo_types.h index 50dd0db586a..1cd507d476f 100644 --- a/src/seraphis_impl/serialization_demo_types.h +++ b/src/seraphis_impl/serialization_demo_types.h @@ -56,6 +56,11 @@ namespace sp { namespace serialization { +/// serializable jamtis::address_index_t +struct ser_address_index_t final +{ + unsigned char bytes[sizeof(jamtis::address_index_t)]; +}; /// serializable jamtis::address_tag_t struct ser_address_tag_t final @@ -433,10 +438,53 @@ struct ser_JamtisDestinationV1 final END_SERIALIZE() }; +/// serializable JamtisPaymentProposalV1 +struct ser_JamtisPaymentProposalV1 final +{ + /// destination address + ser_JamtisDestinationV1 destination; + /// amount + rct::xmr_amount amount; + /// enote ephemeral private key + crypto::x25519_scalar enote_ephemeral_privkey; + /// memo elements + std::vector partial_memo; + + BEGIN_SERIALIZE() + FIELD(destination) + FIELD(amount) + FIELD(enote_ephemeral_privkey) + FIELD(partial_memo) + END_SERIALIZE() +}; + +/// serializable JamtisPaymentProposalV1 +struct ser_JamtisPaymentProposalSelfSendV1 final +{ + /// destination address + ser_JamtisDestinationV1 destination; + /// amount + rct::xmr_amount amount; + /// selfspend type + unsigned char type; + /// enote ephemeral private key + crypto::x25519_scalar enote_ephemeral_privkey; + /// memo elements + std::vector partial_memo; + + BEGIN_SERIALIZE() + FIELD(destination) + FIELD(amount) + FIELD(type) + FIELD(enote_ephemeral_privkey) + FIELD(partial_memo) + END_SERIALIZE() +}; } //namespace serialization } //namespace sp +BLOB_SERIALIZER(sp::serialization::ser_address_index_t); BLOB_SERIALIZER(sp::serialization::ser_address_tag_t); BLOB_SERIALIZER(sp::serialization::ser_encrypted_address_tag_t); BLOB_SERIALIZER(sp::serialization::ser_encoded_amount_t); diff --git a/src/seraphis_impl/serialization_demo_utils.cpp b/src/seraphis_impl/serialization_demo_utils.cpp index 0e2949ee96b..14c0bee96db 100644 --- a/src/seraphis_impl/serialization_demo_utils.cpp +++ b/src/seraphis_impl/serialization_demo_utils.cpp @@ -37,6 +37,8 @@ #include "seraphis_core/binned_reference_set.h" #include "seraphis_core/discretized_fee.h" #include "seraphis_core/jamtis_destination.h" +#include "seraphis_core/jamtis_payment_proposal.h" +#include "seraphis_core/jamtis_support_types.h" #include "seraphis_crypto/bulletproofs_plus2.h" #include "seraphis_crypto/grootle.h" #include "seraphis_crypto/sp_composition_proof.h" @@ -223,6 +225,23 @@ void make_serializable_grootle_proof(const GrootleProof &grootle, ser_GrootlePro serializable_grootle_out.z = grootle.z; } //------------------------------------------------------------------------------------------------------------------- +void make_serializable_jamtis_payment_proposal_v1(const jamtis::JamtisPaymentProposalV1 &payment, ser_JamtisPaymentProposalV1 &serializable_payment_out) +{ + make_serializable_sp_destination_v1(payment.destination, serializable_payment_out.destination); + serializable_payment_out.amount = payment.amount; + serializable_payment_out.enote_ephemeral_privkey = payment.enote_ephemeral_privkey; + serializable_payment_out.partial_memo = payment.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- +void make_serializable_jamtis_payment_proposal_selfsend_v1(const jamtis::JamtisPaymentProposalSelfSendV1 &payment, ser_JamtisPaymentProposalSelfSendV1 &serializable_payment_out) +{ + make_serializable_sp_destination_v1(payment.destination, serializable_payment_out.destination); + serializable_payment_out.amount = payment.amount; + serializable_payment_out.type = static_cast(payment.type); + serializable_payment_out.enote_ephemeral_privkey = payment.enote_ephemeral_privkey; + serializable_payment_out.partial_memo = payment.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- void make_serializable_sp_composition_proof(const SpCompositionProof &proof, ser_SpCompositionProof &serializable_proof_out) { @@ -435,6 +454,23 @@ void recover_grootle_proof(ser_GrootleProof &serializable_grootle_in, GrootlePro grootle_out.z = serializable_grootle_in.z; } //------------------------------------------------------------------------------------------------------------------- +void recover_jamtis_payment_proposal_v1(const ser_JamtisPaymentProposalV1 &serializable_payment, jamtis::JamtisPaymentProposalV1 &payment_out) +{ + recover_sp_destination_v1(serializable_payment.destination, payment_out.destination); + payment_out.amount = serializable_payment.amount; + payment_out.enote_ephemeral_privkey = serializable_payment.enote_ephemeral_privkey; + payment_out.partial_memo = serializable_payment.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- +void recover_jamtis_payment_proposal_selfsend_v1(const ser_JamtisPaymentProposalSelfSendV1 &serializable_payment, jamtis::JamtisPaymentProposalSelfSendV1 &payment_out) +{ + recover_sp_destination_v1(serializable_payment.destination, payment_out.destination); + payment_out.amount = serializable_payment.amount; + payment_out.type = static_cast(serializable_payment.type); + payment_out.enote_ephemeral_privkey = serializable_payment.enote_ephemeral_privkey; + payment_out.partial_memo = serializable_payment.partial_memo; +} +//------------------------------------------------------------------------------------------------------------------- void recover_sp_composition_proof(const ser_SpCompositionProof &serializable_proof, SpCompositionProof &proof_out) { proof_out.c = serializable_proof.c; @@ -701,5 +737,6 @@ void recover_sp_destination_v1(const ser_JamtisDestinationV1 &serializable_desti sizeof(serializable_destination.addr_tag)); } //------------------------------------------------------------------------------------------------------------------- + } //namespace serialization } //namespace sp diff --git a/src/seraphis_impl/serialization_demo_utils.h b/src/seraphis_impl/serialization_demo_utils.h index 62bb13d5b96..77964ccbc61 100644 --- a/src/seraphis_impl/serialization_demo_utils.h +++ b/src/seraphis_impl/serialization_demo_utils.h @@ -110,6 +110,8 @@ bool try_get_serializable(epee::span serialized, Serializabl void make_serializable_bpp2(const BulletproofPlus2 &bpp2, ser_BulletproofPlus2_PARTIAL &serializable_bpp2_out); void make_serializable_clsag(const rct::clsag &clsag, ser_clsag_PARTIAL &serializable_clsag_out); void make_serializable_grootle_proof(const GrootleProof &grootle, ser_GrootleProof &serializable_grootle_out); +void make_serializable_jamtis_payment_proposal_v1(const jamtis::JamtisPaymentProposalV1 &payment, ser_JamtisPaymentProposalV1 &serializable_payment_out); +void make_serializable_jamtis_payment_proposal_selfsend_v1(const jamtis::JamtisPaymentProposalSelfSendV1 &payment, ser_JamtisPaymentProposalSelfSendV1 &serializable_payment_out); void make_serializable_sp_composition_proof(const SpCompositionProof &proof, ser_SpCompositionProof &serializable_proof_out); void make_serializable_sp_coinbase_enote_core(const SpCoinbaseEnoteCore &enote, @@ -148,6 +150,8 @@ void recover_bpp2(ser_BulletproofPlus2_PARTIAL &serializable_bpp2_in, BulletproofPlus2 &bpp2_out); void recover_clsag(ser_clsag_PARTIAL &serializable_clsag_in, const crypto::key_image &key_image, rct::clsag &clsag_out); void recover_grootle_proof(ser_GrootleProof &serializable_grootle_in, GrootleProof &grootle_out); +void recover_jamtis_payment_proposal_v1(const ser_JamtisPaymentProposalV1 &serializable_payment, jamtis::JamtisPaymentProposalV1 &payment_out); +void recover_jamtis_payment_proposal_selfsend_v1(const ser_JamtisPaymentProposalSelfSendV1 &serializable_payment, jamtis::JamtisPaymentProposalSelfSendV1 &payment_out); void recover_sp_composition_proof(const ser_SpCompositionProof &serializable_proof, SpCompositionProof &proof_out); void recover_sp_coinbase_enote_core(const ser_SpCoinbaseEnoteCore &serializable_enote, SpCoinbaseEnoteCore &enote_out); void recover_sp_enote_core(const ser_SpEnoteCore &serializable_enote, SpEnoteCore &enote_out); diff --git a/src/seraphis_main/enote_record_utils_legacy.cpp b/src/seraphis_main/enote_record_utils_legacy.cpp index dc1e3ad97b8..a037a202f02 100644 --- a/src/seraphis_main/enote_record_utils_legacy.cpp +++ b/src/seraphis_main/enote_record_utils_legacy.cpp @@ -36,6 +36,7 @@ extern "C" { #include "crypto/crypto-ops.h" } +#include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/subaddress_index.h" #include "device/device.hpp" #include "enote_record_types.h" @@ -366,6 +367,197 @@ bool try_get_legacy_basic_enote_record(const LegacyEnoteVariant &enote, return true; } //------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_encoded_amount_v1(const cryptonote::transaction &tx) +{ + return tx.rct_signatures.type == rct::RCTTypeFull || tx.rct_signatures.type == rct::RCTTypeSimple || + tx.rct_signatures.type == rct::RCTTypeBulletproof; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_encoded_amount_v2(const cryptonote::transaction &tx) +{ + return tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || + tx.rct_signatures.type == rct::RCTTypeBulletproofPlus; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_legacy_enote_v1(const cryptonote::transaction &tx, const cryptonote::tx_out &out) +{ + // Plaintext amount, no view tag + return (tx.version == 1 || (tx.version == 2 && cryptonote::is_coinbase(tx))) && + out.target.type() == typeid(cryptonote::txout_to_key); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_legacy_enote_v2(const cryptonote::transaction &tx, const cryptonote::tx_out &out) +{ + // Encrypted amount v1, no view tag + return tx.version == 2 && !cryptonote::is_coinbase(tx) && is_encoded_amount_v1(tx) && + out.target.type() == typeid(cryptonote::txout_to_key); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_legacy_enote_v3(const cryptonote::transaction &tx, const cryptonote::tx_out &out) +{ + // Encrypted amount v2, no view tag + return tx.version == 2 && !cryptonote::is_coinbase(tx) && is_encoded_amount_v2(tx) && + out.target.type() == typeid(cryptonote::txout_to_key); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_legacy_enote_v4(const cryptonote::transaction &tx, const cryptonote::tx_out &out) +{ + // Plaintext amount, view tag + return (tx.version == 1 || (tx.version == 2 && cryptonote::is_coinbase(tx))) && + out.target.type() == typeid(cryptonote::txout_to_tagged_key); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool is_legacy_enote_v5(const cryptonote::transaction &tx, const cryptonote::tx_out &out) +{ + // Encrypted amount v2, view tag + return tx.version == 2 && !cryptonote::is_coinbase(tx) && is_encoded_amount_v2(tx) && + out.target.type() == typeid(cryptonote::txout_to_tagged_key); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_out_to_legacy_enote_v1(const cryptonote::transaction &tx, + const size_t output_index, + sp::LegacyEnoteVariant &enote_out) +{ + if (output_index >= tx.vout.size()) + return false; + if (!is_legacy_enote_v1(tx, tx.vout[output_index])) + return false; + + sp::LegacyEnoteV1 enote_v1; + + /// Ko + crypto::public_key out_pub_key; + cryptonote::get_output_public_key(tx.vout[output_index], out_pub_key); + enote_v1.onetime_address = rct::pk2rct(out_pub_key); + /// a + enote_v1.amount = tx.vout[output_index].amount; + + enote_out = std::move(enote_v1); + return true; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_out_to_legacy_enote_v2(const cryptonote::transaction &tx, + const size_t output_index, + sp::LegacyEnoteVariant &enote_out) +{ + if (output_index >= tx.vout.size()) + return false; + if (!is_legacy_enote_v2(tx, tx.vout[output_index])) + return false; + if (output_index >= tx.rct_signatures.outPk.size() || output_index >= tx.rct_signatures.ecdhInfo.size()) + return false; + + sp::LegacyEnoteV2 enote_v2; + + /// Ko + crypto::public_key out_pub_key; + cryptonote::get_output_public_key(tx.vout[output_index], out_pub_key); + enote_v2.onetime_address = rct::pk2rct(out_pub_key); + /// C + enote_v2.amount_commitment = tx.rct_signatures.outPk[output_index].mask; + /// enc(x) + enote_v2.encoded_amount_blinding_factor = tx.rct_signatures.ecdhInfo[output_index].mask; + /// enc(a) + enote_v2.encoded_amount = tx.rct_signatures.ecdhInfo[output_index].amount; + + enote_out = std::move(enote_v2); + return true; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_out_to_legacy_enote_v3(const cryptonote::transaction &tx, + const size_t output_index, + sp::LegacyEnoteVariant &enote_out) +{ + if (output_index >= tx.vout.size()) + return false; + if (!is_legacy_enote_v3(tx, tx.vout[output_index])) + return false; + if (output_index >= tx.rct_signatures.outPk.size() || output_index >= tx.rct_signatures.ecdhInfo.size()) + return false; + + sp::LegacyEnoteV3 enote_v3; + + /// Ko + crypto::public_key out_pub_key; + cryptonote::get_output_public_key(tx.vout[output_index], out_pub_key); + enote_v3.onetime_address = rct::pk2rct(out_pub_key); + /// C + enote_v3.amount_commitment = tx.rct_signatures.outPk[output_index].mask; + /// enc(a) + constexpr size_t byte_size = sizeof(enote_v3.encoded_amount); + static_assert(byte_size <= sizeof(tx.rct_signatures.ecdhInfo[output_index].amount.bytes)); + memcpy(&enote_v3.encoded_amount, &tx.rct_signatures.ecdhInfo[output_index].amount.bytes, byte_size); + + enote_out = std::move(enote_v3); + return true; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_out_to_legacy_enote_v4(const cryptonote::transaction &tx, + const size_t output_index, + sp::LegacyEnoteVariant &enote_out) +{ + if (output_index >= tx.vout.size()) + return false; + if (!is_legacy_enote_v4(tx, tx.vout[output_index])) + return false; + + sp::LegacyEnoteV4 enote_v4; + + /// Ko + crypto::public_key out_pub_key; + cryptonote::get_output_public_key(tx.vout[output_index], out_pub_key); + enote_v4.onetime_address = rct::pk2rct(out_pub_key); + /// a + enote_v4.amount = tx.vout[output_index].amount; + /// view_tag + enote_v4.view_tag = *cryptonote::get_output_view_tag(tx.vout[output_index]); + + enote_out = std::move(enote_v4); + return true; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_out_to_legacy_enote_v5(const cryptonote::transaction &tx, + const size_t output_index, + LegacyEnoteVariant &enote_out) +{ + if (output_index >= tx.vout.size()) + return false; + if (!is_legacy_enote_v5(tx, tx.vout[output_index])) + return false; + if (output_index >= tx.rct_signatures.outPk.size() || output_index >= tx.rct_signatures.ecdhInfo.size()) + return false; + + sp::LegacyEnoteV5 enote_v5; + + /// Ko + crypto::public_key out_pub_key; + cryptonote::get_output_public_key(tx.vout[output_index], out_pub_key); + enote_v5.onetime_address = rct::pk2rct(out_pub_key); + /// C + enote_v5.amount_commitment = tx.rct_signatures.outPk[output_index].mask; + /// enc(a) + constexpr size_t byte_size = sizeof(enote_v5.encoded_amount); + static_assert(byte_size <= sizeof(tx.rct_signatures.ecdhInfo[output_index].amount.bytes)); + memcpy(&enote_v5.encoded_amount, &tx.rct_signatures.ecdhInfo[output_index].amount.bytes, byte_size); + /// view_tag + enote_v5.view_tag = *cryptonote::get_output_view_tag(tx.vout[output_index]); + + enote_out = std::move(enote_v5); + return true; +} +//------------------------------------------------------------------------------------------------------------------- bool try_get_legacy_basic_enote_record(const LegacyEnoteVariant &enote, const rct::key &enote_ephemeral_pubkey, const std::uint64_t tx_output_index, @@ -554,4 +746,23 @@ void get_legacy_enote_record(const LegacyIntermediateEnoteRecord &intermediate_r get_legacy_enote_record(intermediate_record, key_image, record_out); } //------------------------------------------------------------------------------------------------------------------- +void legacy_outputs_to_enotes(const cryptonote::transaction &tx, std::vector &enotes_out) +{ + enotes_out.clear(); + enotes_out.reserve(tx.vout.size()); + + for (size_t i = 0; i < tx.vout.size(); ++i) + { + enotes_out.emplace_back(); + if (!try_out_to_legacy_enote_v1(tx, i, enotes_out.back()) + && !try_out_to_legacy_enote_v2(tx, i, enotes_out.back()) + && !try_out_to_legacy_enote_v3(tx, i, enotes_out.back()) + && !try_out_to_legacy_enote_v4(tx, i, enotes_out.back()) + && !try_out_to_legacy_enote_v5(tx, i, enotes_out.back())) + { + CHECK_AND_ASSERT_THROW_MES(false, "converting legacy output type to enote type: unknown output type."); + } + } +} +//------------------------------------------------------------------------------------------------------------------- } //namespace sp diff --git a/src/seraphis_main/enote_record_utils_legacy.h b/src/seraphis_main/enote_record_utils_legacy.h index 7899475efa9..5eabb6f2f13 100644 --- a/src/seraphis_main/enote_record_utils_legacy.h +++ b/src/seraphis_main/enote_record_utils_legacy.h @@ -37,6 +37,7 @@ #include "enote_record_types.h" #include "ringct/rctTypes.h" #include "seraphis_core/legacy_enote_types.h" +#include "cryptonote_basic/cryptonote_basic.h" //third party headers @@ -142,5 +143,11 @@ void get_legacy_enote_record(const LegacyIntermediateEnoteRecord &intermediate_r const crypto::secret_key &legacy_spend_privkey, hw::device &hwdev, LegacyEnoteRecord &record_out); +/** +* brief: legacy_outputs_to_enotes - convert legacy tx's "outputs" to Seraphis lib compatible "enotes" +* param: tx - +* outparam: enotes_out - +*/ +void legacy_outputs_to_enotes(const cryptonote::transaction &tx, std::vector &enotes_out); } //namespace sp