From 2194b853d5b4cb5f48cbd315649fa65bb7618e2b Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Wed, 16 Apr 2025 17:16:18 -0500 Subject: [PATCH] carrot_core: unbind K^j_v in d_e and overhaul scanning functions --- src/carrot_core/CMakeLists.txt | 3 +- src/carrot_core/enote_utils.cpp | 62 ++- src/carrot_core/enote_utils.h | 31 +- src/carrot_core/output_set_finalization.cpp | 6 +- src/carrot_core/payment_proposal.cpp | 17 +- src/carrot_core/scan.cpp | 569 ++++++++------------ src/carrot_core/scan.h | 202 +++---- src/carrot_core/scan_unsafe.cpp | 287 ++++++++++ src/carrot_core/scan_unsafe.h | 155 ++++++ tests/performance_tests/view_scan.h | 8 +- tests/unit_tests/carrot_core.cpp | 38 +- tests/unit_tests/carrot_legacy.cpp | 7 +- tests/unit_tests/carrot_mock_helpers.cpp | 41 +- 13 files changed, 863 insertions(+), 563 deletions(-) create mode 100644 src/carrot_core/scan_unsafe.cpp create mode 100644 src/carrot_core/scan_unsafe.h diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt index 704c29f7b..e4e145c60 100644 --- a/src/carrot_core/CMakeLists.txt +++ b/src/carrot_core/CMakeLists.txt @@ -38,7 +38,8 @@ set(carrot_core_sources lazy_amount_commitment.cpp output_set_finalization.cpp payment_proposal.cpp - scan.cpp) + scan.cpp + scan_unsafe.cpp) monero_find_all_headers(carrot_core_headers, "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index e35910fb3..47df16df4 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -92,20 +92,19 @@ static rct::xmr_amount dec_amount(const encrypted_amount_t &encrypted_amount, co void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, const input_context_t &input_context, const crypto::public_key &address_spend_pubkey, - const crypto::public_key &address_view_pubkey, const payment_id_t payment_id, crypto::secret_key &enote_ephemeral_privkey_out) { - // k_e = (H_64(anchor_norm, input_context, K^j_s, K^j_v, pid)) mod l + // k_e = (H_64(anchor_norm, input_context, K^j_s, pid)) mod l const auto transcript = sp::make_fixed_transcript( - anchor_norm, input_context, address_spend_pubkey, address_view_pubkey, payment_id); + anchor_norm, input_context, address_spend_pubkey, payment_id); derive_scalar(transcript.data(), transcript.size(), nullptr, &enote_ephemeral_privkey_out); } //------------------------------------------------------------------------------------------------------------------- void make_carrot_enote_ephemeral_pubkey_cryptonote(const crypto::secret_key &enote_ephemeral_privkey, mx25519_pubkey &enote_ephemeral_pubkey_out) { - // D_e = d_e G + // D_e = d_e B mx25519_scmul_base(get_mx25519_impl(), &enote_ephemeral_pubkey_out, reinterpret_cast(&enote_ephemeral_privkey)); @@ -127,6 +126,25 @@ void make_carrot_enote_ephemeral_pubkey_subaddress(const crypto::secret_key &eno ge_p3_to_x25519(enote_ephemeral_pubkey_out.data, &D_e_in_ed25519); } //------------------------------------------------------------------------------------------------------------------- +void make_carrot_enote_ephemeral_pubkey(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_spend_pubkey, + const bool is_subaddress, + mx25519_pubkey &enote_ephemeral_pubkey_out) +{ + if (is_subaddress) + { + // D_e = d_e ConvertPointE(K^j_s) + make_carrot_enote_ephemeral_pubkey_subaddress(enote_ephemeral_privkey, + address_spend_pubkey, + enote_ephemeral_pubkey_out); + } + else // !is_subaddress + { + // D_e = d_e B + make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey, enote_ephemeral_pubkey_out); + } +} +//------------------------------------------------------------------------------------------------------------------- bool make_carrot_uncontextualized_shared_key_receiver(const crypto::secret_key &k_view, const mx25519_pubkey &enote_ephemeral_pubkey, mx25519_pubkey &s_sender_receiver_unctx_out) @@ -172,19 +190,22 @@ void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], derive_bytes_3(transcript.data(), transcript.size(), s_sender_receiver_unctx, &view_tag_out); } //------------------------------------------------------------------------------------------------------------------- -void make_carrot_input_context_coinbase(const std::uint64_t block_index, input_context_t &input_context_out) +input_context_t make_carrot_input_context_coinbase(const std::uint64_t block_index) { // input_context = "C" || IntToBytes256(block_index) - memset(input_context_out.bytes, 0, sizeof(input_context_t)); - input_context_out.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_COINBASE; - memcpy_swap64le(input_context_out.bytes + 1, &block_index, 1); + input_context_t input_context{}; + input_context.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_COINBASE; + memcpy_swap64le(input_context.bytes + 1, &block_index, 1); + return input_context; } //------------------------------------------------------------------------------------------------------------------- -void make_carrot_input_context(const crypto::key_image &first_rct_key_image, input_context_t &input_context_out) +input_context_t make_carrot_input_context(const crypto::key_image &first_rct_key_image) { // input_context = "R" || KI_1 - input_context_out.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_RINGCT; - memcpy(input_context_out.bytes + 1, first_rct_key_image.data, sizeof(crypto::key_image)); + input_context_t input_context{}; + input_context.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_RINGCT; + memcpy(input_context.bytes + 1, first_rct_key_image.data, sizeof(crypto::key_image)); + return input_context; } //------------------------------------------------------------------------------------------------------------------- void make_carrot_sender_receiver_secret(const unsigned char s_sender_receiver_unctx[32], @@ -464,32 +485,27 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, return false; } //------------------------------------------------------------------------------------------------------------------- -bool verify_carrot_external_janus_protection(const janus_anchor_t &nominal_anchor, +bool verify_carrot_normal_janus_protection(const janus_anchor_t &nominal_anchor, const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, - const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const payment_id_t nominal_payment_id, const mx25519_pubkey &enote_ephemeral_pubkey) { - // d_e' = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + // d_e' = H_n(anchor_norm, input_context, K^j_s, pid)) crypto::secret_key nominal_enote_ephemeral_privkey; make_carrot_enote_ephemeral_privkey(nominal_anchor, input_context, nominal_address_spend_pubkey, - nominal_address_view_pubkey, nominal_payment_id, nominal_enote_ephemeral_privkey); - + // recompute D_e' for d_e' and address type mx25519_pubkey nominal_enote_ephemeral_pubkey; - if (is_subaddress) - make_carrot_enote_ephemeral_pubkey_subaddress(nominal_enote_ephemeral_privkey, - nominal_address_spend_pubkey, - nominal_enote_ephemeral_pubkey); - else // cryptonote address - make_carrot_enote_ephemeral_pubkey_cryptonote(nominal_enote_ephemeral_privkey, - nominal_enote_ephemeral_pubkey); + make_carrot_enote_ephemeral_pubkey(nominal_enote_ephemeral_privkey, + nominal_address_spend_pubkey, + is_subaddress, + nominal_enote_ephemeral_pubkey); // D_e' ?= D_e return 0 == memcmp(&nominal_enote_ephemeral_pubkey, &enote_ephemeral_pubkey, sizeof(mx25519_pubkey)); diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h index f92c83055..c4a68a38b 100644 --- a/src/carrot_core/enote_utils.h +++ b/src/carrot_core/enote_utils.h @@ -47,18 +47,16 @@ namespace carrot /** * brief: make_carrot_enote_ephemeral_privkey - enote ephemeral privkey k_e for Carrot enotes - * d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + * d_e = H_n(anchor_norm, input_context, K^j_s, pid)) * param: anchor_norm - anchor_norm * param: input_context - input_context * param: address_spend_pubkey - K^j_s - * param: address_view_pubkey - K^j_v * param: payment_id - pid * outparam: enote_ephemeral_privkey_out - k_e */ void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, const input_context_t &input_context, const crypto::public_key &address_spend_pubkey, - const crypto::public_key &address_view_pubkey, const payment_id_t payment_id, crypto::secret_key &enote_ephemeral_privkey_out); /** @@ -79,6 +77,19 @@ void make_carrot_enote_ephemeral_pubkey_cryptonote(const crypto::secret_key &eno void make_carrot_enote_ephemeral_pubkey_subaddress(const crypto::secret_key &enote_ephemeral_privkey, const crypto::public_key &address_spend_pubkey, mx25519_pubkey &enote_ephemeral_pubkey_out); +/** + * brief: make_carrot_enote_ephemeral_pubkey - make enote ephemeral pubkey D_e for either either address type + * [is_subaddress]: D_e = d_e ConvertPointE(K^j_s) + * [!is_subaddress]: D_e = d_e B + * param: enote_ephemeral_privkey - d_e + * param: address_spend_pubkey - K^j_s + * param: is_subaddress - + * outparam: enote_ephemeral_pubkey_out - D_e + */ +void make_carrot_enote_ephemeral_pubkey(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_spend_pubkey, + const bool is_subaddress, + mx25519_pubkey &enote_ephemeral_pubkey_out); /** * brief: make_carrot_uncontextualized_shared_key_receiver - perform the receiver-side ECDH exchange for Carrot enotes * s_sr = k_v D_e @@ -117,16 +128,16 @@ void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], * brief: make_carrot_input_context_coinbase - input context for a sender-receiver secret (coinbase txs) * input_context = "C" || IntToBytes256(block_index) * param: block_index - block index of the coinbase tx -* outparam: input_context_out - "C" || IntToBytes256(block_index) +* return: input_context */ -void make_carrot_input_context_coinbase(const std::uint64_t block_index, input_context_t &input_context_out); +input_context_t make_carrot_input_context_coinbase(const std::uint64_t block_index); /** * brief: make_carrot_input_context - input context for a sender-receiver secret (standard RingCT txs) * input_context = "R" || KI_1 * param: first_rct_key_image - KI_1, the first spent RingCT key image in a tx -* outparam: input_context_out - "S" || KI_1 +* return: input_context */ -void make_carrot_input_context(const crypto::key_image &first_rct_key_image, input_context_t &input_context_out); +input_context_t make_carrot_input_context(const crypto::key_image &first_rct_key_image); /** * brief: make_carrot_sender_receiver_secret - contextualized sender-receiver secret s^ctx_sr * s^ctx_sr = H_32(s_sr, D_e, input_context) @@ -370,20 +381,18 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, rct::xmr_amount &amount_out, crypto::secret_key &amount_blinding_factor_out); /** - * brief: verify_carrot_external_janus_protection - check normal external enote is Janus safe (i.e. can recompute D_e) + * brief: verify_carrot_normal_janus_protection - check normal external enote is Janus safe (i.e. can recompute D_e) * param: nominal_anchor - anchor' * param: input_context - * param: nominal_address_spend_pubkey - K^j_s' - * param: nominal_address_view_pubkey - K^j_v' * param: is_subaddress - * param: nominal_payment_id - pid' * param: enote_ephemeral_pubkey - D_e * return: true if this normal external enote is safe from Janus attacks */ -bool verify_carrot_external_janus_protection(const janus_anchor_t &nominal_anchor, +bool verify_carrot_normal_janus_protection(const janus_anchor_t &nominal_anchor, const input_context_t &input_context, const crypto::public_key &nominal_address_spend_pubkey, - const crypto::public_key &nominal_address_view_pubkey, const bool is_subaddress, const payment_id_t nominal_payment_id, const mx25519_pubkey &enote_ephemeral_pubkey); diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp index e99a09b7a..1aef870a1 100644 --- a/src/carrot_core/output_set_finalization.cpp +++ b/src/carrot_core/output_set_finalization.cpp @@ -194,8 +194,7 @@ void get_output_enote_proposals(const std::vector &norm "get output enote proposals: normal payment proposals contain duplicate anchor_norm AKA randomness"); // input_context = "R" || KI_1 - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context= make_carrot_input_context(tx_first_key_image); // construct normal enotes output_enote_proposals_out.reserve(num_proposals); @@ -329,8 +328,7 @@ void get_coinbase_output_enotes(const std::vector &norm "get coinbase output enotes: normal payment proposals contain duplicate anchor_norm AKA randomness"); // input_context = "C" || IntToBytes256(block_index) - input_context_t input_context; - make_carrot_input_context_coinbase(block_index, input_context); + const input_context_t input_context = make_carrot_input_context_coinbase(block_index); // construct normal enotes output_coinbase_enotes_out.reserve(num_proposals); diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index f2c23a1c5..d3f72d152 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -63,12 +63,11 @@ static crypto::secret_key get_enote_ephemeral_privkey(const janus_anchor_t rando const CarrotDestinationV1 &destination, const input_context_t &input_context) { - // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + // d_e = H_n(anchor_norm, input_context, K^j_s, pid)) crypto::secret_key enote_ephemeral_privkey; make_carrot_enote_ephemeral_privkey(randomness, input_context, destination.address_spend_pubkey, - destination.address_view_pubkey, destination.payment_id, enote_ephemeral_privkey); @@ -80,7 +79,7 @@ static mx25519_pubkey get_enote_ephemeral_pubkey(const janus_anchor_t randomness const CarrotDestinationV1 &destination, const input_context_t &input_context) { - // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + // d_e = H_n(anchor_norm, input_context, K^j_s, pid)) const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(randomness, destination, input_context)}; @@ -240,8 +239,7 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, "get coinbase output proposal v1: integrated addresses aren't allowed as destinations of coinbase outputs"); // 2. coinbase input context - input_context_t input_context; - make_carrot_input_context_coinbase(block_index, input_context); + const input_context_t input_context= make_carrot_input_context_coinbase(block_index); // 3. make D_e and do external ECDH mx25519_pubkey s_sender_receiver_unctx; auto dhe_wiper = auto_wiper(s_sender_receiver_unctx); @@ -292,8 +290,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, "jamtis payment proposal: invalid randomness for janus anchor (zero)."); // 2. input context: input_context = "R" || KI_1 - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // 3. make D_e and do external ECDH mx25519_pubkey s_sender_receiver_unctx; auto dhe_wiper = auto_wiper(s_sender_receiver_unctx); @@ -341,8 +338,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo "get output proposal special v1: internal messages are only for internal selfsends, not special selfsends"); // 2. input context: input_context = "R" || KI_1 - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // 3. D_e const bool missing_enote_ephemeral_pubkeys = !proposal.enote_ephemeral_pubkey && !other_enote_ephemeral_pubkey; @@ -409,8 +405,7 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop // @TODO // 2. input_context = "R" || KI_1 - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // 3. D_e const bool missing_enote_ephemeral_pubkeys = !proposal.enote_ephemeral_pubkey && !other_enote_ephemeral_pubkey; diff --git a/src/carrot_core/scan.cpp b/src/carrot_core/scan.cpp index 1bdf7e275..c12b48dea 100644 --- a/src/carrot_core/scan.cpp +++ b/src/carrot_core/scan.cpp @@ -32,10 +32,10 @@ #include "scan.h" //local headers -#include "crypto/generators.h" +#include "destination.h" #include "enote_utils.h" -#include "lazy_amount_commitment.h" #include "ringct/rctOps.h" +#include "scan_unsafe.h" //third party headers @@ -56,153 +56,85 @@ static bool is_main_address_spend_pubkey(const crypto::public_key &address_spend } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- -static void scan_carrot_dest_info(const crypto::public_key &onetime_address, - const rct::key &amount_commitment, - const encrypted_janus_anchor_t &encrypted_janus_anchor, - const std::optional &encrypted_payment_id, - const crypto::hash &s_sender_receiver, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - payment_id_t &payment_id_out, - janus_anchor_t &nominal_janus_anchor_out) +static crypto::secret_key make_enote_ephemeral_privkey_sender(const janus_anchor_t &anchor_norm, + const CarrotDestinationV1 &destination, + const input_context_t &input_context) { - // k^o_g = H_n("..g..", s^ctx_sr, C_a) - make_carrot_onetime_address_extension_g(s_sender_receiver, - amount_commitment, - sender_extension_g_out); - - // k^o_t = H_n("..t..", s^ctx_sr, C_a) - make_carrot_onetime_address_extension_t(s_sender_receiver, - amount_commitment, - sender_extension_t_out); - - // K^j_s = Ko - K^o_ext = Ko - (k^o_g G + k^o_t T) - recover_address_spend_pubkey(onetime_address, - s_sender_receiver, - amount_commitment, - address_spend_pubkey_out); - - // pid = pid_enc XOR m_pid, if applicable - if (encrypted_payment_id) - payment_id_out = decrypt_legacy_payment_id(*encrypted_payment_id, s_sender_receiver, onetime_address); - else - payment_id_out = null_payment_id; - - // anchor = anchor_enc XOR m_anchor - nominal_janus_anchor_out = decrypt_carrot_anchor(encrypted_janus_anchor, - s_sender_receiver, - onetime_address); + // d_e = H_n(anchor_norm, input_context, K^j_s, pid)) + crypto::secret_key enote_ephemeral_privkey; + make_carrot_enote_ephemeral_privkey(anchor_norm, + input_context, + destination.address_spend_pubkey, + destination.payment_id, + enote_ephemeral_privkey); + return enote_ephemeral_privkey; } //------------------------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------------------------- -static bool try_scan_carrot_external_noamount(const crypto::public_key &onetime_address, - const lazy_amount_commitment_t &lazy_amount_commitment, - const encrypted_janus_anchor_t &encrypted_janus_anchor, - const view_tag_t view_tag, - const mx25519_pubkey &enote_ephemeral_pubkey, - const std::optional &encrypted_payment_id, - const input_context_t &input_context, +static bool try_scan_carrot_coinbase_enote_checked( + const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const epee::span main_address_spend_pubkeys, - crypto::hash &s_sender_receiver_out, + const epee::span main_addresss_spend_pubkeys, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - payment_id_t &payment_id_out) + crypto::public_key &address_spend_pubkey_out) { - // if vt' != vt, then FAIL - if (!test_carrot_view_tag(s_sender_receiver_unctx.data, input_context, onetime_address, view_tag)) + // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, anchor + janus_anchor_t nominal_janus_anchor; + if (!try_scan_carrot_coinbase_enote_no_janus(enote, + s_sender_receiver_unctx, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + nominal_janus_anchor)) return false; - // s^ctx_sr = H_32(s_sr, D_e, input_context) - make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, - enote_ephemeral_pubkey, - input_context, - s_sender_receiver_out); + if (!is_main_address_spend_pubkey(address_spend_pubkey_out, main_addresss_spend_pubkeys)) + return false; - // get C_a - const rct::key amount_commitment = calculate_amount_commitment(lazy_amount_commitment); - - // k^g_o, k^t_o, K^j_s', pid', anchor' - janus_anchor_t nominal_janus_anchor; - scan_carrot_dest_info(onetime_address, - amount_commitment, - encrypted_janus_anchor, - encrypted_payment_id, - s_sender_receiver_out, - sender_extension_g_out, - sender_extension_t_out, + return verify_carrot_normal_janus_protection(nominal_janus_anchor, + make_carrot_input_context_coinbase(enote.block_index), address_spend_pubkey_out, - payment_id_out, - nominal_janus_anchor); - - return verify_carrot_janus_protection(input_context, - onetime_address, - k_view_dev, - address_spend_pubkey_out, - is_main_address_spend_pubkey(address_spend_pubkey_out, main_address_spend_pubkeys), - enote_ephemeral_pubkey, - nominal_janus_anchor, - payment_id_out); + /*is_subaddress=*/false, + null_payment_id, + enote.enote_ephemeral_pubkey); } //------------------------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------------------------- -bool verify_carrot_janus_protection(const input_context_t &input_context, - const crypto::public_key &onetime_address, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &nominal_address_spend_pubkey, - const bool is_main_address_spend_pubkey, - const mx25519_pubkey &enote_ephemeral_pubkey, - const janus_anchor_t &nominal_anchor, - payment_id_t &nominal_payment_id_inout) +static bool try_scan_carrot_enote_external_normal_checked(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const mx25519_pubkey &s_sender_receiver_unctx, + const epee::span main_address_spend_pubkeys, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &payment_id_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &nominal_janus_anchor_out, + bool &verified_normal_janus) { - // make K^j_v' - crypto::public_key nominal_address_view_pubkey; - if (is_main_address_spend_pubkey) - { - // K^j_v' = k_v G - if (!k_view_dev.view_key_scalar_mult_ed25519(crypto::get_G(), nominal_address_view_pubkey)) - return false; - } - else // subaddress - { - // K^j_v' = k_v K^j_s' - if (!k_view_dev.view_key_scalar_mult_ed25519(nominal_address_spend_pubkey, nominal_address_view_pubkey)) - return false; - } + if (!try_scan_carrot_enote_external_no_janus(enote, + encrypted_payment_id, + s_sender_receiver_unctx, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + amount_out, + amount_blinding_factor_out, + payment_id_out, + enote_type_out, + nominal_janus_anchor_out)) + return false; - // if can recompute D_e with pid', then PASS - if (verify_carrot_external_janus_protection(nominal_anchor, - input_context, - nominal_address_spend_pubkey, - nominal_address_view_pubkey, - !is_main_address_spend_pubkey, - nominal_payment_id_inout, - enote_ephemeral_pubkey)) - return true; + verified_normal_janus = verify_carrot_normal_janus_protection( + make_carrot_input_context(enote.tx_first_key_image), + address_spend_pubkey_out, + !is_main_address_spend_pubkey(address_spend_pubkey_out, main_address_spend_pubkeys), + enote.enote_ephemeral_pubkey, + nominal_janus_anchor_out, + payment_id_out); - // if can recompute D_e with null pid, then PASS - nominal_payment_id_inout = null_payment_id; - if (verify_carrot_external_janus_protection(nominal_anchor, - input_context, - nominal_address_spend_pubkey, - nominal_address_view_pubkey, - !is_main_address_spend_pubkey, - null_payment_id, - enote_ephemeral_pubkey)) - return true; - - // anchor_sp = H_16(D_e, input_context, Ko, k_v) - janus_anchor_t expected_special_anchor; - k_view_dev.make_janus_anchor_special(enote_ephemeral_pubkey, - input_context, - onetime_address, - expected_special_anchor); - - // attempt special janus check: anchor_sp ?= anchor' - return expected_special_anchor == nominal_anchor; + return true; } //------------------------------------------------------------------------------------------------------------------- bool make_carrot_uncontextualized_shared_key_receiver( @@ -213,129 +145,186 @@ bool make_carrot_uncontextualized_shared_key_receiver( return k_view_dev.view_key_scalar_mult_x25519(enote_ephemeral_pubkey, s_sender_receiver_unctx_out); } //------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_coinbase_enote( +bool try_scan_carrot_coinbase_enote_sender( const CarrotCoinbaseEnoteV1 &enote, - const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, + const CarrotDestinationV1 &destination, + const janus_anchor_t &anchor_norm, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out) { - return try_scan_carrot_coinbase_enote( - enote, - s_sender_receiver_unctx, - k_view_dev, - {&main_address_spend_pubkey, 1}, + const crypto::secret_key enote_ephemeral_privkey = make_enote_ephemeral_privkey_sender(anchor_norm, + destination, + make_carrot_input_context_coinbase(enote.block_index)); + + return try_scan_carrot_coinbase_enote_sender(enote, + destination, + enote_ephemeral_privkey, sender_extension_g_out, sender_extension_t_out); } //------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_coinbase_enote( +bool try_scan_carrot_coinbase_enote_sender( const CarrotCoinbaseEnoteV1 &enote, - const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const epee::span main_address_spend_pubkeys, + const CarrotDestinationV1 &destination, + const crypto::secret_key &enote_ephemeral_privkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out) { - // input_context - input_context_t input_context; - make_carrot_input_context_coinbase(enote.block_index, input_context); + // s_sr = d_e ConvertPointE(K^j_v) + mx25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_sender(enote_ephemeral_privkey, + destination.address_view_pubkey, + s_sender_receiver_unctx); - // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, and Janus verification - crypto::public_key nominal_address_spend_pubkey; - crypto::hash dummy_s_sender_receiver; - payment_id_t dummy_payment_id; - if (!try_scan_carrot_external_noamount(enote.onetime_address, - enote.amount, - enote.anchor_enc, - enote.view_tag, - enote.enote_ephemeral_pubkey, - std::nullopt, - input_context, + crypto::public_key dummy_main_address_spend_pubkey; + if (!try_scan_carrot_coinbase_enote_checked(enote, s_sender_receiver_unctx, - k_view_dev, - main_address_spend_pubkeys, - dummy_s_sender_receiver, + {&destination.address_spend_pubkey, 1}, sender_extension_g_out, sender_extension_t_out, - nominal_address_spend_pubkey, - dummy_payment_id)) + dummy_main_address_spend_pubkey)) return false; - // if K^j_s' != K_s, then FAIL - // - We have no "hard target" in the amount commitment, so if we want deterministic enote - // scanning without a subaddress table, we reject all non-main addresses in coinbase enotes - return is_main_address_spend_pubkey(nominal_address_spend_pubkey, main_address_spend_pubkeys); + // this should've already been checked, but just for good measure... + return dummy_main_address_spend_pubkey == destination.address_spend_pubkey; } //------------------------------------------------------------------------------------------------------------------- -bool try_ecdh_and_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out) -{ - return try_ecdh_and_scan_carrot_coinbase_enote(enote, - k_view_dev, - {&main_address_spend_pubkey, 1}, - sender_extension_g_out, - sender_extension_t_out); -} -//------------------------------------------------------------------------------------------------------------------- -bool try_ecdh_and_scan_carrot_coinbase_enote( +bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, - const view_incoming_key_device &k_view_dev, + const mx25519_pubkey &s_sender_receiver_unctx, const epee::span main_address_spend_pubkeys, crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out) + crypto::secret_key &sender_extension_t_out, + crypto::public_key &main_address_spend_pubkey_out) { - // s_sr = k_v D_e - mx25519_pubkey s_sender_receiver_unctx; - if (!make_carrot_uncontextualized_shared_key_receiver(k_view_dev, - enote.enote_ephemeral_pubkey, - s_sender_receiver_unctx)) - return false; - - return try_scan_carrot_coinbase_enote(enote, + return try_scan_carrot_coinbase_enote_checked(enote, s_sender_receiver_unctx, - k_view_dev, main_address_spend_pubkeys, sender_extension_g_out, - sender_extension_t_out); + sender_extension_t_out, + main_address_spend_pubkey_out); } //------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, +bool try_scan_carrot_coinbase_enote_receiver( + const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, const crypto::public_key &main_address_spend_pubkey, crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out) + crypto::secret_key &sender_extension_t_out) { - return try_scan_carrot_enote_external(enote, - encrypted_payment_id, + crypto::public_key dummy_main_address_spend_pubkey; + return try_scan_carrot_coinbase_enote_receiver( + enote, s_sender_receiver_unctx, - k_view_dev, {&main_address_spend_pubkey, 1}, sender_extension_g_out, sender_extension_t_out, - address_spend_pubkey_out, - amount_out, - amount_blinding_factor_out, - payment_id_out, - enote_type_out); + dummy_main_address_spend_pubkey); } //------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const janus_anchor_t &anchor_norm, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid) +{ + const crypto::secret_key enote_ephemeral_privkey = make_enote_ephemeral_privkey_sender(anchor_norm, + destination, + make_carrot_input_context(enote.tx_first_key_image)); + + return try_scan_carrot_enote_external_sender(enote, + encrypted_payment_id, + destination, + enote_ephemeral_privkey, + sender_extension_g_out, + sender_extension_t_out, + amount_out, + amount_blinding_factor_out, + enote_type_out, + check_pid); +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const crypto::secret_key &enote_ephemeral_privkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid) +{ + // s_sr = d_e ConvertPointE(K^j_v) + mx25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_sender(enote_ephemeral_privkey, + destination.address_view_pubkey, + s_sender_receiver_unctx); + + return try_scan_carrot_enote_external_sender(enote, + encrypted_payment_id, + destination, + s_sender_receiver_unctx, + sender_extension_g_out, + sender_extension_t_out, + amount_out, + amount_blinding_factor_out, + enote_type_out, + check_pid); +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid) +{ + crypto::public_key recovered_address_spend_pubkey; + payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + janus_anchor_t dummy_janus_anchor; + bool verified_normal_janus = false; + if (!try_scan_carrot_enote_external_normal_checked(enote, + encrypted_payment_id, + s_sender_receiver_unctx, + {&destination.address_spend_pubkey, 1}, + sender_extension_g_out, + sender_extension_t_out, + recovered_address_spend_pubkey, + amount_out, + amount_blinding_factor_out, + recovered_payment_id, + recovered_enote_type, + dummy_janus_anchor, + verified_normal_janus)) + return false; + else if (!verified_normal_janus) + return false; + else if (recovered_address_spend_pubkey != destination.address_spend_pubkey) + return false; + else if (check_pid && recovered_payment_id != destination.payment_id) + return false; + else if (recovered_enote_type != CarrotEnoteType::PAYMENT) + return false; + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, const std::optional &encrypted_payment_id, const mx25519_pubkey &s_sender_receiver_unctx, + const epee::span main_address_spend_pubkeys, const view_incoming_key_device &k_view_dev, - const epee::span &main_address_spend_pubkeys, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &address_spend_pubkey_out, @@ -344,99 +333,34 @@ bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, payment_id_t &payment_id_out, CarrotEnoteType &enote_type_out) { - // input_context - input_context_t input_context; - make_carrot_input_context(enote.tx_first_key_image, input_context); - - // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, and Janus verification - crypto::hash s_sender_receiver; - if (!try_scan_carrot_external_noamount(enote.onetime_address, - enote.amount_commitment, - enote.anchor_enc, - enote.view_tag, - enote.enote_ephemeral_pubkey, + janus_anchor_t nominal_janus_anchor; + bool verified_normal_janus = false; + if (!try_scan_carrot_enote_external_normal_checked(enote, encrypted_payment_id, - input_context, s_sender_receiver_unctx, - k_view_dev, main_address_spend_pubkeys, - s_sender_receiver, sender_extension_g_out, sender_extension_t_out, address_spend_pubkey_out, - payment_id_out)) + amount_out, + amount_blinding_factor_out, + payment_id_out, + enote_type_out, + nominal_janus_anchor, + verified_normal_janus)) return false; - // enote_type, a, z - return try_get_carrot_amount(s_sender_receiver, - enote.amount_enc, - enote.onetime_address, - address_spend_pubkey_out, - enote.amount_commitment, - enote_type_out, - amount_out, - amount_blinding_factor_out); -} -//------------------------------------------------------------------------------------------------------------------- -bool try_ecdh_and_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out) -{ - return try_ecdh_and_scan_carrot_enote_external(enote, - encrypted_payment_id, - k_view_dev, - {&main_address_spend_pubkey, 1}, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - amount_out, - amount_blinding_factor_out, - payment_id_out, - enote_type_out); -} -//------------------------------------------------------------------------------------------------------------------- -bool try_ecdh_and_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const view_incoming_key_device &k_view_dev, - const epee::span &main_address_spend_pubkeys, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out) -{ - // s_sr = k_v D_e - mx25519_pubkey s_sender_receiver_unctx; - if (!make_carrot_uncontextualized_shared_key_receiver(k_view_dev, + if (!verified_normal_janus && !verify_carrot_special_janus_protection(enote.tx_first_key_image, enote.enote_ephemeral_pubkey, - s_sender_receiver_unctx)) + enote.onetime_address, + k_view_dev, + nominal_janus_anchor)) return false; - return try_scan_carrot_enote_external(enote, - encrypted_payment_id, - s_sender_receiver_unctx, - k_view_dev, - main_address_spend_pubkeys, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - amount_out, - amount_blinding_factor_out, - payment_id_out, - enote_type_out); + return true; } //------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, +bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote, const view_balance_secret_device &s_view_balance_dev, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, @@ -447,8 +371,7 @@ bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, janus_anchor_t &internal_message_out) { // input_context - input_context_t input_context; - make_carrot_input_context(enote.tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(enote.tx_first_key_image); // vt = H_3(s_sr || input_context || Ko) view_tag_t nominal_view_tag; @@ -464,63 +387,17 @@ bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, input_context, s_sender_receiver); - // k^g_o, k^t_o, K^j_s', pid', anchor' - payment_id_t dummy_payment_id; - scan_carrot_dest_info(enote.onetime_address, - enote.amount_commitment, - enote.anchor_enc, - std::nullopt, + return try_scan_carrot_enote_internal_burnt(enote, s_sender_receiver, sender_extension_g_out, sender_extension_t_out, address_spend_pubkey_out, - dummy_payment_id, + amount_out, + amount_blinding_factor_out, + enote_type_out, internal_message_out); // janus protection checks are not needed for internal scans - - // enote_type, a, z - return try_get_carrot_amount(s_sender_receiver, - enote.amount_enc, - enote.onetime_address, - address_spend_pubkey_out, - enote.amount_commitment, - enote_type_out, - amount_out, - amount_blinding_factor_out); -} -//------------------------------------------------------------------------------------------------------------------- -bool try_scan_carrot_enote_external_destination_only(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - payment_id_t &payment_id_out) -{ - // input_context - input_context_t input_context; - make_carrot_input_context(enote.tx_first_key_image, input_context); - - // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, and Janus verification - crypto::hash s_sender_receiver; - return try_scan_carrot_external_noamount(enote.onetime_address, - enote.amount_commitment, - enote.anchor_enc, - enote.view_tag, - enote.enote_ephemeral_pubkey, - encrypted_payment_id, - input_context, - s_sender_receiver_unctx, - k_view_dev, - {&main_address_spend_pubkey, 1}, - s_sender_receiver, - sender_extension_g_out, - sender_extension_t_out, - address_spend_pubkey_out, - payment_id_out); } //------------------------------------------------------------------------------------------------------------------- } //namespace carrot diff --git a/src/carrot_core/scan.h b/src/carrot_core/scan.h index ab2c69cf4..7de62e007 100644 --- a/src/carrot_core/scan.h +++ b/src/carrot_core/scan.h @@ -46,29 +46,11 @@ #include //forward declarations +namespace carrot { struct CarrotDestinationV1; } namespace carrot { -/** - * brief: verify_carrot_janus_protection - verify that scanned enote is not attempting a Janus attack, and check pid - * param: input_context - - * param: onetime_address - K_o - * param: k_view_dev - - * param: nominal_address_spend_pubkey - K^j_s' - * param: is_main_address_spend_pubkey - true iff K^j_s' is the spend pubkey of one of our main addresses - * param: enote_ephemeral_pubkey - D_e - * param: nominal_anchor - anchor' - * outparam: nominal_payment_id_inout - takes pid', sets to nullpid if not associated to this enote - */ -bool verify_carrot_janus_protection(const input_context_t &input_context, - const crypto::public_key &onetime_address, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &nominal_address_spend_pubkey, - const bool is_main_address_spend_pubkey, - const mx25519_pubkey &enote_ephemeral_pubkey, - const janus_anchor_t &nominal_anchor, - payment_id_t &nominal_payment_id_inout); /** * brief: make_carrot_uncontextualized_shared_key_receiver - perform the receiver-side ECDH exchange for Carrot enotes * s_sr = k_v D_e @@ -82,97 +64,99 @@ bool make_carrot_uncontextualized_shared_key_receiver( const mx25519_pubkey &enote_ephemeral_pubkey, mx25519_pubkey &s_sender_receiver_unctx_out); /** - * brief: try_scan_carrot_coinbase_enote - attempt full scan process on coinbase enote + * brief: try_scan_carrot_coinbase_enote_[sender/receiver] - attempt scan process on coinbase enote * param: enote - - * param: k_view_dev - - * param: main_address_spend_pubkey - K^0_s + * param: destination - (is_subaddress, K^j_s, K^j_v, pid) + * param: anchor_norm - anchor_norm + * param: enote_ephemeral_privkey - d_e + * param: s_sender_receiver_unctx - s_sr * param: main_address_spend_pubkeys - {K^0_s, ...} - * param: sender_extension_g_out - k^g_o - * param: sender_extension_g_out - k^t_o + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: main_address_spend_pubkey_out - K^0_s, which will be one of main_address_spend_pubkeys * return: true iff the scan process succeeded */ -bool try_scan_carrot_coinbase_enote( +bool try_scan_carrot_coinbase_enote_sender( + const CarrotCoinbaseEnoteV1 &enote, + const CarrotDestinationV1 &destination, + const janus_anchor_t &anchor_norm, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out); +bool try_scan_carrot_coinbase_enote_sender( + const CarrotCoinbaseEnoteV1 &enote, + const CarrotDestinationV1 &destination, + const crypto::secret_key &enote_ephemeral_privkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out); +bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, + const epee::span main_address_spend_pubkeys, crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out); -bool try_scan_carrot_coinbase_enote( + crypto::secret_key &sender_extension_t_out, + crypto::public_key &main_address_spend_pubkey_out); +bool try_scan_carrot_coinbase_enote_receiver( const CarrotCoinbaseEnoteV1 &enote, const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const epee::span main_address_spend_pubkeys, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out); -bool try_ecdh_and_scan_carrot_coinbase_enote( - const CarrotCoinbaseEnoteV1 &enote, - const view_incoming_key_device &k_view_dev, const crypto::public_key &main_address_spend_pubkey, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out); -bool try_ecdh_and_scan_carrot_coinbase_enote( - const CarrotCoinbaseEnoteV1 &enote, - const view_incoming_key_device &k_view_dev, - const epee::span main_address_spend_pubkeys, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out); /** - * brief: try_scan_carrot_enote_external - attempt full scan process on external enote + * brief: try_scan_carrot_enote_external_[sender/receiver] - attempt scan process on external enote * param: enote - * param: encrypted_payment_id - pid_enc + * param: destination - (is_subaddress, K^j_s, K^j_v, pid) + * param: anchor_norm - anchor_norm + * param: enote_ephemeral_privkey - d_e * param: s_sender_receiver_unctx - s_sr - * param: k_view_dev - - * param: main_address_spend_pubkey - K^0_s * param: main_address_spend_pubkeys - {K^0_s, ...} - * param: sender_extension_g_out - k^g_o - * param: sender_extension_g_out - k^t_o - * param: address_spend_pubkey_out - K^j_s - * param: amount_out - a - * param: amount_blinding_factor_out - k_a - * param: payment_id_out - pid - * param: enote_type_out - enote_type + * param: k_view_dev - + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: address_spend_pubkey_out - K^j_s + * outparam: amount_out - a + * outparam: amount_blinding_factor_out - k_a + * outparam: payment_id_out - pid + * outparam: enote_type_out - enote_type * return: true iff the scan process succeeded + * + * warning: the _sender overloads cannot check for Janus protection on special enotes */ -bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const janus_anchor_t &anchor_norm, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid = true); +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const crypto::secret_key &enote_ephemeral_privkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid = true); +bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const CarrotDestinationV1 &destination, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + const bool check_pid = true); +bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote, const std::optional &encrypted_payment_id, const mx25519_pubkey &s_sender_receiver_unctx, + const epee::span main_address_spend_pubkeys, const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out); -bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const epee::span &main_address_spend_pubkeys, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out); -bool try_ecdh_and_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out, - payment_id_t &payment_id_out, - CarrotEnoteType &enote_type_out); -bool try_ecdh_and_scan_carrot_enote_external(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const view_incoming_key_device &k_view_dev, - const epee::span &main_address_spend_pubkeys, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, crypto::public_key &address_spend_pubkey_out, @@ -181,19 +165,19 @@ bool try_ecdh_and_scan_carrot_enote_external(const CarrotEnoteV1 &enote, payment_id_t &payment_id_out, CarrotEnoteType &enote_type_out); /** - * brief: try_scan_carrot_enote_internal - attempt full scan process on internal enote + * brief: try_scan_carrot_enote_internal_receiver - attempt scan process on internal enote * param: enote - * param: s_view_balance_dev - - * param: sender_extension_g_out - k^g_o - * param: sender_extension_g_out - k^t_o - * param: address_spend_pubkey_out - K^j_s - * param: amount_out - a - * param: amount_blinding_factor_out - k_a - * param: enote_type_out - enote_type - * param: internal_message_out - anchor' + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: address_spend_pubkey_out - K^j_s + * outparam: amount_out - a + * outparam: amount_blinding_factor_out - k_a + * outparam: enote_type_out - enote_type + * outparam: internal_message_out - anchor' * return: true iff the scan process succeeded */ -bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, +bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote, const view_balance_secret_device &s_view_balance_dev, crypto::secret_key &sender_extension_g_out, crypto::secret_key &sender_extension_t_out, @@ -202,28 +186,6 @@ bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, crypto::secret_key &amount_blinding_factor_out, CarrotEnoteType &enote_type_out, janus_anchor_t &internal_message_out); -/** - * brief: try_scan_carrot_enote_external_destination_only - attempt external scan process, w/o decrypting - * amount or recomputing the amount commitment - * param: enote - - * param: encrypted_payment_id - pid_enc - * param: s_sender_receiver_unctx - s_sr - * param: k_view_dev - - * param: main_address_spend_pubkey - K^0_s - * param: sender_extension_g_out - k^g_o - * param: sender_extension_g_out - k^t_o - * param: address_spend_pubkey_out - K^j_s - * param: payment_id_out - pid - * return: true iff the scan process (without amount commitment recomputation) succeeded - */ -bool try_scan_carrot_enote_external_destination_only(const CarrotEnoteV1 &enote, - const std::optional &encrypted_payment_id, - const mx25519_pubkey &s_sender_receiver_unctx, - const view_incoming_key_device &k_view_dev, - const crypto::public_key &main_address_spend_pubkey, - crypto::secret_key &sender_extension_g_out, - crypto::secret_key &sender_extension_t_out, - crypto::public_key &address_spend_pubkey_out, - payment_id_t &payment_id_out); +//! @TODO: try_scan_carrot_enote_internal_sender(): can't validate burning w/o passing s_sr = s_vb } //namespace carrot diff --git a/src/carrot_core/scan_unsafe.cpp b/src/carrot_core/scan_unsafe.cpp new file mode 100644 index 000000000..95a1284ec --- /dev/null +++ b/src/carrot_core/scan_unsafe.cpp @@ -0,0 +1,287 @@ +// Copyright (c) 2025, 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. + +// Utilities for scanning carrot enotes + +//paired header +#include "scan_unsafe.h" + +//local headers +#include "enote_utils.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static void scan_carrot_dest_info(const crypto::public_key &onetime_address, + const rct::key &amount_commitment, + const encrypted_janus_anchor_t &encrypted_janus_anchor, + const std::optional &encrypted_payment_id, + const crypto::hash &s_sender_receiver, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + payment_id_t &nominal_payment_id_out, + janus_anchor_t &nominal_janus_anchor_out) +{ + // k^o_g = H_n("..g..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_g(s_sender_receiver, + amount_commitment, + sender_extension_g_out); + + // k^o_t = H_n("..t..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_t(s_sender_receiver, + amount_commitment, + sender_extension_t_out); + + // K^j_s = Ko - K^o_ext = Ko - (k^o_g G + k^o_t T) + recover_address_spend_pubkey(onetime_address, + s_sender_receiver, + amount_commitment, + address_spend_pubkey_out); + + // pid = pid_enc XOR m_pid, if applicable + if (encrypted_payment_id) + nominal_payment_id_out = decrypt_legacy_payment_id(*encrypted_payment_id, s_sender_receiver, onetime_address); + else + nominal_payment_id_out = null_payment_id; + + // anchor = anchor_enc XOR m_anchor + nominal_janus_anchor_out = decrypt_carrot_anchor(encrypted_janus_anchor, + s_sender_receiver, + onetime_address); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_scan_carrot_external_noamount(const crypto::public_key &onetime_address, + const lazy_amount_commitment_t &lazy_amount_commitment, + const encrypted_janus_anchor_t &encrypted_janus_anchor, + const view_tag_t view_tag, + const mx25519_pubkey &enote_ephemeral_pubkey, + const std::optional &encrypted_payment_id, + const input_context_t &input_context, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::hash &s_sender_receiver_out, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + payment_id_t &nominal_payment_id_out, + janus_anchor_t &janus_anchor_out) +{ + // if vt' != vt, then FAIL + if (!test_carrot_view_tag(s_sender_receiver_unctx.data, input_context, onetime_address, view_tag)) + return false; + + // s^ctx_sr = H_32(s_sr, D_e, input_context) + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, + enote_ephemeral_pubkey, + input_context, + s_sender_receiver_out); + + // get C_a + const rct::key amount_commitment = calculate_amount_commitment(lazy_amount_commitment); + + // k^g_o, k^t_o, K^j_s', pid', anchor' + scan_carrot_dest_info(onetime_address, + amount_commitment, + encrypted_janus_anchor, + encrypted_payment_id, + s_sender_receiver_out, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + nominal_payment_id_out, + janus_anchor_out); + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_coinbase_enote_no_janus( + const CarrotCoinbaseEnoteV1 &enote, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &nominal_address_spend_pubkey_out, + janus_anchor_t &nominal_janus_anchor_out) +{ + // input_context + const input_context_t input_context = make_carrot_input_context_coinbase(enote.block_index); + + // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, anchor + crypto::hash dummy_s_sender_receiver; + payment_id_t dummy_payment_id; + if (!try_scan_carrot_external_noamount(enote.onetime_address, + enote.amount, + enote.anchor_enc, + enote.view_tag, + enote.enote_ephemeral_pubkey, + std::nullopt, + input_context, + s_sender_receiver_unctx, + dummy_s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + nominal_address_spend_pubkey_out, + dummy_payment_id, + nominal_janus_anchor_out)) + return false; + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_external_no_janus(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &nominal_payment_id_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &nominal_janus_anchor_out) +{ + // input_context + const input_context_t input_context = make_carrot_input_context(enote.tx_first_key_image); + + // s^ctx_sr, k^g_o, k^g_t, K^j_s, pid, and Janus verification + crypto::hash s_sender_receiver; + if (!try_scan_carrot_external_noamount(enote.onetime_address, + enote.amount_commitment, + enote.anchor_enc, + enote.view_tag, + enote.enote_ephemeral_pubkey, + encrypted_payment_id, + input_context, + s_sender_receiver_unctx, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + nominal_payment_id_out, + nominal_janus_anchor_out)) + return false; + + // enote_type, a, z + return try_get_carrot_amount(s_sender_receiver, + enote.amount_enc, + enote.onetime_address, + address_spend_pubkey_out, + enote.amount_commitment, + enote_type_out, + amount_out, + amount_blinding_factor_out); +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote, + const crypto::hash &s_sender_receiver, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &internal_message_out) +{ + // k^g_o, k^t_o, K^j_s', pid', anchor' + payment_id_t dummy_payment_id; + scan_carrot_dest_info(enote.onetime_address, + enote.amount_commitment, + enote.anchor_enc, + std::nullopt, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + dummy_payment_id, + internal_message_out); + + // enote_type, a, z + return try_get_carrot_amount(s_sender_receiver, + enote.amount_enc, + enote.onetime_address, + address_spend_pubkey_out, + enote.amount_commitment, + enote_type_out, + amount_out, + amount_blinding_factor_out); +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_carrot_normal_janus_protection(const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const bool is_subaddress, + const mx25519_pubkey &enote_ephemeral_pubkey, + const janus_anchor_t &nominal_janus_anchor, + payment_id_t &nominal_payment_id_inout) +{ + // if can recompute D_e with pid', then PASS + if (verify_carrot_normal_janus_protection(nominal_janus_anchor, + input_context, + nominal_address_spend_pubkey, + is_subaddress, + nominal_payment_id_inout, + enote_ephemeral_pubkey)) + return true; + + // if can recompute D_e with null pid, then PASS + nominal_payment_id_inout = null_payment_id; + return verify_carrot_normal_janus_protection(nominal_janus_anchor, + input_context, + nominal_address_spend_pubkey, + is_subaddress, + nominal_payment_id_inout, + enote_ephemeral_pubkey); +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_carrot_special_janus_protection(const crypto::key_image &tx_first_key_image, + const mx25519_pubkey &enote_ephemeral_pubkey, + const crypto::public_key &onetime_address, + const view_incoming_key_device &k_view_dev, + const janus_anchor_t &nominal_janus_anchor) +{ + // input_context = "R" || KI_1 + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); + + // anchor_sp = H_16(D_e, input_context, Ko, k_v) + janus_anchor_t expected_special_anchor; + k_view_dev.make_janus_anchor_special(enote_ephemeral_pubkey, + input_context, + onetime_address, + expected_special_anchor); + + // attempt special janus check: anchor_sp ?= anchor' + return expected_special_anchor == nominal_janus_anchor; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/scan_unsafe.h b/src/carrot_core/scan_unsafe.h new file mode 100644 index 000000000..9e4744b1b --- /dev/null +++ b/src/carrot_core/scan_unsafe.h @@ -0,0 +1,155 @@ +// Copyright (c) 2025, 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. + +//! @file Low-level helper utilities for scanning carrot enotes + +/** + * These functions should not be used directly unless you know what you're doing. Using these + * functions incorrectly can result in Janus attacks, incorrect PIDs, and view tag skips. For + * safer high-level scanning functions, see the scan.h header. + */ + +#pragma once + +//local headers +#include "carrot_enote_types.h" +#include "device.h" +#include "lazy_amount_commitment.h" +#include "span.h" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace carrot +{ +/** + * brief: try_scan_carrot_coinbase_enote_no_janus - attempt scan process on coinbase enote w/o Janus protection + * param: enote - + * param: s_sender_receiver_unctx - s_sr + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: nominal_address_spend_pubkey - K^j_s' + * outparam: nominal_janus_anchor_out - anchor' + * return: true iff the scan process (w/o Janus checks) succeeded + */ +bool try_scan_carrot_coinbase_enote_no_janus( + const CarrotCoinbaseEnoteV1 &enote, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &nominal_address_spend_pubkey_out, + janus_anchor_t &nominal_janus_anchor_out); +/** + * brief: try_scan_carrot_enote_external_no_janus - attempt scan process on external enote, + * w/o Janus protection nor correct PID + * param: enote - + * param: encrypted_payment_id - pid_enc + * param: s_sender_receiver_unctx - s_sr + * param: k_view_dev - + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: address_spend_pubkey_out - K^j_s + * outparam: amount_out - a + * outparam: amount_blinding_factor_out - k_a + * outparam: payment_id_out - pid + * outparam: enote_type_out - enote_type + * outparam: nominal_janus_anchor_out - anchor' + * return: true iff the scan process (w/o Janus or PID checks) succeeded + */ +bool try_scan_carrot_enote_external_no_janus(const CarrotEnoteV1 &enote, + const std::optional &encrypted_payment_id, + const mx25519_pubkey &s_sender_receiver_unctx, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &nominal_payment_id_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &nominal_janus_anchor_out); +/** + * brief: try_scan_carrot_enote_internal_burnt - attempt scan process on internal enote w/o + * regular burning bug check or view tag check + * param: enote - + * param: s_sender_receiver - s^ctx_sr + * outparam: sender_extension_g_out - k^g_o + * outparam: sender_extension_g_out - k^t_o + * outparam: address_spend_pubkey_out - K^j_s + * outparam: amount_out - a + * outparam: amount_blinding_factor_out - k_a + * outparam: enote_type_out - enote_type + * outparam: internal_message_out - anchor' + * return: true iff the scan process (w/o view tag check) succeeded + */ +bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote, + const crypto::hash &s_sender_receiver, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &internal_message_out); +/** + * brief: verify_carrot_normal_janus_protection - verify that scanned normal enote is not attempting a + * Janus attack, and check pid + * param: input_context - input_context + * param: nominal_address_spend_pubkey - K^j_s' + * param: is_subaddress - true iff K^j_s' corresponds to a subaddress + * param: enote_ephemeral_pubkey - D_e + * param: nominal_janus_anchor - anchor' + * outparam: nominal_payment_id_inout - takes pid', sets to nullpid if not associated to this enote + * return: true iff it is computationally intractable for a sender to succeed at a Janus attack + */ +bool verify_carrot_normal_janus_protection(const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const bool is_subaddress, + const mx25519_pubkey &enote_ephemeral_pubkey, + const janus_anchor_t &nominal_janus_anchor, + payment_id_t &nominal_payment_id_inout); +/** + * brief: verify_carrot_special_janus_protection - verify that scanned special enote is not attempting a Janus attack + * param: tx_first_key_image - KI_1 + * param: enote_ephemeral_pubkey - D_e + * param: onetime_address - K_o + * param: k_view_dev - + * param: nominal_janus_anchor - anchor' + * return: true iff it is computationally intractable for a sender to succeed at a Janus attack + */ +bool verify_carrot_special_janus_protection(const crypto::key_image &tx_first_key_image, + const mx25519_pubkey &enote_ephemeral_pubkey, + const crypto::public_key &onetime_address, + const view_incoming_key_device &k_view_dev, + const janus_anchor_t &nominal_janus_anchor); + +} //namespace carrot diff --git a/tests/performance_tests/view_scan.h b/tests/performance_tests/view_scan.h index 9f0cb6437..d5bf2792b 100644 --- a/tests/performance_tests/view_scan.h +++ b/tests/performance_tests/view_scan.h @@ -236,11 +236,11 @@ public: rct::xmr_amount recovered_amount; carrot::payment_id_t recovered_payment_id; carrot::CarrotEnoteType recovered_enote_type; - if (!carrot::try_scan_carrot_enote_external(m_enote, + if (!carrot::try_scan_carrot_enote_external_receiver(m_enote, m_encrypted_payment_id, s_sender_receiver_unctx, + {&m_account_spend_pubkey, 1}, m_k_view_dev, - m_account_spend_pubkey, _1, _2, recovered_address_spend_pubkey, @@ -280,11 +280,11 @@ public: rct::xmr_amount recovered_amount; carrot::payment_id_t recovered_payment_id; carrot::CarrotEnoteType recovered_enote_type; - const bool scan_success = carrot::try_scan_carrot_enote_external(m_enote, + const bool scan_success = carrot::try_scan_carrot_enote_external_receiver(m_enote, m_encrypted_payment_id, s_sender_receiver_unctx, + {&m_account_spend_pubkey, 1}, m_k_view_dev, - m_account_spend_pubkey, _1, _2, recovered_address_spend_pubkey, diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 2f61065f7..c647cbc38 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -132,11 +132,11 @@ TEST(carrot_core, main_address_normal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; encrypted_payment_id_t recovered_payment_id; CarrotEnoteType recovered_enote_type; - const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_external_receiver(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, + {&keys.carrot_account_spend_pubkey, 1}, keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -202,11 +202,11 @@ TEST(carrot_core, subaddress_normal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; encrypted_payment_id_t recovered_payment_id; CarrotEnoteType recovered_enote_type; - const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_external_receiver(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, + {&keys.carrot_account_spend_pubkey, 1}, keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -269,11 +269,11 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; encrypted_payment_id_t recovered_payment_id; CarrotEnoteType recovered_enote_type; - const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_external_receiver(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, + {&keys.carrot_account_spend_pubkey, 1}, keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -340,11 +340,11 @@ TEST(carrot_core, main_address_special_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; encrypted_payment_id_t recovered_payment_id; CarrotEnoteType recovered_enote_type; - const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_external_receiver(enote_proposal.enote, std::nullopt, s_sender_receiver_unctx, + {&keys.carrot_account_spend_pubkey, 1}, keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -417,11 +417,11 @@ TEST(carrot_core, subaddress_special_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; encrypted_payment_id_t recovered_payment_id; CarrotEnoteType recovered_enote_type; - const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_external_receiver(enote_proposal.enote, std::nullopt, s_sender_receiver_unctx, + {&keys.carrot_account_spend_pubkey, 1}, keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -487,7 +487,7 @@ TEST(carrot_core, main_address_internal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; CarrotEnoteType recovered_enote_type; janus_anchor_t recovered_internal_message; - const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_internal_receiver(enote_proposal.enote, keys.s_view_balance_dev, recovered_sender_extension_g, recovered_sender_extension_t, @@ -557,7 +557,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; CarrotEnoteType recovered_enote_type; janus_anchor_t recovered_internal_message; - const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote, + const bool scan_success = try_scan_carrot_enote_internal_receiver(enote_proposal.enote, keys.s_view_balance_dev, recovered_sender_extension_g, recovered_sender_extension_t, @@ -606,14 +606,19 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) ASSERT_EQ(proposal.amount, enote.amount); + mx25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view_incoming_dev, + enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + crypto::secret_key recovered_sender_extension_g; crypto::secret_key recovered_sender_extension_t; - const bool scan_success = try_ecdh_and_scan_carrot_coinbase_enote(enote, - keys.k_view_incoming_dev, + const bool scan_success = try_scan_carrot_coinbase_enote_receiver(enote, + s_sender_receiver_unctx, keys.carrot_account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t); - + ASSERT_TRUE(scan_success); // check spendability @@ -677,8 +682,7 @@ static void subtest_2out_transfer_get_output_enote_proposals_completeness(const // generate input context const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // outgoing payment proposal to bob const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ diff --git a/tests/unit_tests/carrot_legacy.cpp b/tests/unit_tests/carrot_legacy.cpp index 109bda885..4ba99739d 100644 --- a/tests/unit_tests/carrot_legacy.cpp +++ b/tests/unit_tests/carrot_legacy.cpp @@ -110,11 +110,11 @@ static void unittest_legacy_scan_enote_set(const std::vector &eno s_sr); unittest_legacy_scan_result_t scan_result{}; - const bool r = try_scan_carrot_enote_external(enote, + const bool r = try_scan_carrot_enote_external_receiver(enote, encrypted_payment_id, s_sr, + {&acb.get_keys().m_account_address.m_spend_public_key, 1}, view_incoming_key_ram_borrowed_device(acb.get_keys().m_view_secret_key), - acb.get_keys().m_account_address.m_spend_public_key, scan_result.sender_extension_g, scan_result.sender_extension_t, scan_result.address_spend_pubkey, @@ -164,8 +164,7 @@ static void subtest_legacy_2out_transfer_get_output_enote_proposals_completeness // generate input context const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - input_context_t input_context; - make_carrot_input_context(tx_first_key_image, input_context); + const input_context_t input_context = make_carrot_input_context(tx_first_key_image); // outgoing payment proposal to bob const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ diff --git a/tests/unit_tests/carrot_mock_helpers.cpp b/tests/unit_tests/carrot_mock_helpers.cpp index 9f408a71c..64b41ff1e 100644 --- a/tests/unit_tests/carrot_mock_helpers.cpp +++ b/tests/unit_tests/carrot_mock_helpers.cpp @@ -305,7 +305,7 @@ void mock_scan_enote_set(const std::vector &enotes, { res.clear(); - // We support receives to both the new and odl K^0_s + // We support receives to both the new and old K^0_s const crypto::public_key main_address_spend_pubkeys[2] = { keys.carrot_account_spend_pubkey, keys.legacy_acb.get_keys().m_account_address.m_spend_public_key @@ -324,11 +324,11 @@ void mock_scan_enote_set(const std::vector &enotes, mock_scan_result_t scan_result{}; scan_result.output_index = output_index; - if (try_scan_carrot_enote_external(enote, + if (try_scan_carrot_enote_external_receiver(enote, encrypted_payment_id, s_sr, - keys.k_view_incoming_dev, {main_address_spend_pubkeys, 2}, + keys.k_view_incoming_dev, scan_result.sender_extension_g, scan_result.sender_extension_t, scan_result.address_spend_pubkey, @@ -348,7 +348,7 @@ void mock_scan_enote_set(const std::vector &enotes, const CarrotEnoteV1 &enote = enotes.at(output_index); mock_scan_result_t scan_result{}; - const bool r = try_scan_carrot_enote_internal(enote, + const bool r = try_scan_carrot_enote_internal_receiver(enote, keys.s_view_balance_dev, scan_result.sender_extension_g, scan_result.sender_extension_t, @@ -372,6 +372,12 @@ void mock_scan_coinbase_enote_set(const std::vector &coin { res.clear(); + // We support receives to both the new and old K^0_s + const crypto::public_key main_address_spend_pubkeys[2] = { + keys.carrot_account_spend_pubkey, + keys.legacy_acb.get_keys().m_account_address.m_spend_public_key + }; + for (size_t output_index = 0; output_index < coinbase_enotes.size(); ++output_index) { const CarrotCoinbaseEnoteV1 &enote = coinbase_enotes.at(output_index); @@ -384,27 +390,18 @@ void mock_scan_coinbase_enote_set(const std::vector &coin scan_result.enote_type = CarrotEnoteType::PAYMENT; scan_result.internal_message = janus_anchor_t{}; - if (try_ecdh_and_scan_carrot_coinbase_enote(enote, - keys.k_view_incoming_dev, - keys.carrot_account_spend_pubkey, - scan_result.sender_extension_g, - scan_result.sender_extension_t)) - { - scan_result.address_spend_pubkey = keys.carrot_account_spend_pubkey; - res.push_back(scan_result); - continue; - } + mx25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view_incoming_dev, + enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); - // We do a double external scan here to check anchor_sp against both the legacy K_s - // and new K_s. This is inefficient and less secure against Janus attacks, but it allows - // completeness for now when we use hybrid key structures - if (try_ecdh_and_scan_carrot_coinbase_enote(coinbase_enotes.at(output_index), - keys.k_view_incoming_dev, - keys.legacy_acb.get_keys().m_account_address.m_spend_public_key, + if (try_scan_carrot_coinbase_enote_receiver(enote, + s_sender_receiver_unctx, + {main_address_spend_pubkeys, 2}, scan_result.sender_extension_g, - scan_result.sender_extension_t)) + scan_result.sender_extension_t, + scan_result.address_spend_pubkey)) { - scan_result.address_spend_pubkey = keys.legacy_acb.get_keys().m_account_address.m_spend_public_key; res.push_back(scan_result); continue; }