carrot_core: rebind K^j_v in d_e

This commit is contained in:
jeffro256
2026-09-18 21:43:24 -05:00
parent f978eab613
commit d4cfc04690
18 changed files with 263 additions and 166 deletions
+6 -3
View File
@@ -177,12 +177,13 @@ static void make_carrot_sender_extension_pubkey(const crypto::hash &s_sender_rec
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)
{
// d_e = (H_64(anchor_norm, input_context, K^j_s, pid)) mod l
// d_e = (H_64(anchor_norm, input_context, K^j_s, K^j_v, pid)) mod l
const auto transcript = make_fixed_transcript<CARROT_DOMAIN_SEP_EPHEMERAL_PRIVKEY>(
anchor_norm, input_context, address_spend_pubkey, payment_id);
anchor_norm, input_context, address_spend_pubkey, address_view_pubkey, payment_id);
derive_scalar(transcript.data(), transcript.size(), nullptr, &enote_ephemeral_privkey_out);
}
//-------------------------------------------------------------------------------------------------------------------
@@ -662,15 +663,17 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx,
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, pid)
// d_e' = H_n(anchor_norm, input_context, K^j_s, K^j_v, 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);
+5 -1
View File
@@ -46,16 +46,18 @@ namespace carrot
/**
* @brief Derive enote ephemeral privkey d_e for Carrot enotes
* d_e = H_n(anchor_norm, input_context, K^j_s, pid)
* d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
* @param anchor_norm normal Janus anchor
* @param input_context input_context
* @param address_spend_pubkey K^j_s
* @param address_view_pubkey K^j_v
* @param payment_id pid
* @param[out] 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);
/**
@@ -422,6 +424,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx,
* @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
@@ -430,6 +433,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver_ctx,
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);
+4 -3
View File
@@ -54,7 +54,7 @@ static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &propos
mx25519_pubkey &enote_ephemeral_pubkey_out,
mx25519_pubkey &s_sender_receiver_out)
{
// 1. d_e = H_n(anchor_norm, input_context, K^j_s, pid)
// 1. d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal, input_context);
// 2. make D_e
@@ -183,11 +183,12 @@ bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentPro
crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context)
{
// d_e = H_n(anchor_norm, input_context, K^j_s, pid)
// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
crypto::secret_key enote_ephemeral_privkey;
make_carrot_enote_ephemeral_privkey(proposal.randomness,
input_context,
proposal.destination.address_spend_pubkey,
proposal.destination.address_view_pubkey,
proposal.destination.payment_id,
enote_ephemeral_privkey);
@@ -230,7 +231,7 @@ crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalSelfSe
mx25519_pubkey get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context)
{
// d_e = H_n(anchor_norm, input_context, K^j_s, pid)
// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(proposal, input_context)};
// D_e = d_e * ...
+65 -57
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2025, The Monero Project
// Copyright (c) 2025-2026, The Monero Project
//
// All rights reserved.
//
@@ -32,6 +32,7 @@
#include "scan.h"
//local headers
#include "crypto/generators.h"
#include "destination.h"
#include "enote_utils.h"
#include "scan_unsafe.h"
@@ -40,6 +41,9 @@
//standard headers
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "carrot.scan"
namespace carrot
{
@@ -55,15 +59,31 @@ static bool is_main_address_spend_pubkey(const crypto::public_key &address_spend
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static bool recover_address_view_pubkey(const crypto::public_key &address_spend_pubkey,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const view_incoming_key_device &k_view_dev,
crypto::public_key &address_view_pubkey_out,
bool &is_subaddress_out)
{
// K^j_v = k_v K_base, where:
// [subaddress] K_base = K^j_s
// [main address] K_base = G
is_subaddress_out = !is_main_address_spend_pubkey(address_spend_pubkey, main_address_spend_pubkeys);
address_view_pubkey_out = is_subaddress_out ? address_spend_pubkey : crypto::get_G();
return k_view_dev.view_key_scalar_mult_ed25519(address_view_pubkey_out, address_view_pubkey_out);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static crypto::secret_key get_enote_ephemeral_privkey_sender(const janus_anchor_t &anchor_norm,
const CarrotDestinationV1 &destination,
const input_context_t &input_context)
{
// d_e = H_n(anchor_norm, input_context, K^j_s, pid)
// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
crypto::secret_key enote_ephemeral_privkey;
make_carrot_enote_ephemeral_privkey(anchor_norm,
input_context,
destination.address_spend_pubkey,
destination.address_view_pubkey,
destination.payment_id,
enote_ephemeral_privkey);
return enote_ephemeral_privkey;
@@ -73,6 +93,7 @@ static bool try_scan_carrot_coinbase_enote_checked(
const CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
crypto::secret_key &sender_extension_g_out,
crypto::secret_key &sender_extension_t_out,
crypto::public_key &address_spend_pubkey_out)
@@ -91,49 +112,12 @@ static bool try_scan_carrot_coinbase_enote_checked(
return verify_carrot_normal_janus_protection(nominal_janus_anchor,
make_carrot_input_context_coinbase(enote.block_index),
address_spend_pubkey_out,
main_address_view_pubkey,
/*is_subaddress=*/false,
null_payment_id,
enote.enote_ephemeral_pubkey);
}
//-------------------------------------------------------------------------------------------------------------------
static bool try_scan_carrot_enote_external_normal_checked(const CarrotEnoteV1 &enote,
const std::optional<encrypted_payment_id_t> &encrypted_payment_id,
const mx25519_pubkey &s_sender_receiver,
const epee::span<const crypto::public_key> 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,
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)
{
if (!try_scan_carrot_enote_external_no_janus(enote,
encrypted_payment_id,
s_sender_receiver,
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;
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);
return true;
}
//-------------------------------------------------------------------------------------------------------------------
bool try_make_carrot_shared_key_receiver(
const view_incoming_key_device &k_view_dev,
const mx25519_pubkey &enote_ephemeral_pubkey,
@@ -178,6 +162,7 @@ bool try_scan_carrot_coinbase_enote_sender(
if (!try_scan_carrot_coinbase_enote_checked(enote,
s_sender_receiver,
{&destination.address_spend_pubkey, 1},
destination.address_view_pubkey,
sender_extension_g_out,
sender_extension_t_out,
dummy_main_address_spend_pubkey))
@@ -191,6 +176,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
const CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
crypto::secret_key &sender_extension_g_out,
crypto::secret_key &sender_extension_t_out,
crypto::public_key &main_address_spend_pubkey_out)
@@ -198,6 +184,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
return try_scan_carrot_coinbase_enote_checked(enote,
s_sender_receiver,
main_address_spend_pubkeys,
main_address_view_pubkey,
sender_extension_g_out,
sender_extension_t_out,
main_address_spend_pubkey_out);
@@ -207,6 +194,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
const CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const crypto::public_key &main_address_spend_pubkey,
const crypto::public_key &main_address_view_pubkey,
crypto::secret_key &sender_extension_g_out,
crypto::secret_key &sender_extension_t_out)
{
@@ -215,6 +203,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
enote,
s_sender_receiver,
{&main_address_spend_pubkey, 1},
main_address_view_pubkey,
sender_extension_g_out,
sender_extension_t_out,
dummy_main_address_spend_pubkey);
@@ -290,31 +279,34 @@ bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote,
{
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,
janus_anchor_t recovered_janus_anchor;
if (!try_scan_carrot_enote_external_no_janus(enote,
encrypted_payment_id,
s_sender_receiver,
{&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))
enote_type_out,
recovered_janus_anchor))
return false;
else if (!verified_normal_janus)
if (recovered_address_spend_pubkey != destination.address_spend_pubkey)
return false;
else if (recovered_address_spend_pubkey != destination.address_spend_pubkey)
if (!verify_carrot_normal_janus_protection(
make_carrot_input_context(enote.tx_first_key_image),
destination.address_spend_pubkey,
destination.address_view_pubkey,
destination.is_subaddress,
enote.enote_ephemeral_pubkey,
recovered_janus_anchor,
recovered_payment_id))
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;
}
@@ -333,11 +325,9 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote,
CarrotEnoteType &enote_type_out)
{
janus_anchor_t nominal_janus_anchor;
bool verified_normal_janus = false;
if (!try_scan_carrot_enote_external_normal_checked(enote,
if (!try_scan_carrot_enote_external_no_janus(enote,
encrypted_payment_id,
s_sender_receiver,
main_address_spend_pubkeys,
sender_extension_g_out,
sender_extension_t_out,
address_spend_pubkey_out,
@@ -345,10 +335,28 @@ bool try_scan_carrot_enote_external_receiver(const CarrotEnoteV1 &enote,
amount_blinding_factor_out,
payment_id_out,
enote_type_out,
nominal_janus_anchor,
verified_normal_janus))
nominal_janus_anchor))
return false;
// K^j_v from K^j_s
crypto::public_key address_view_pubkey;
bool is_subaddress{};
if (!recover_address_view_pubkey(address_spend_pubkey_out,
main_address_spend_pubkeys,
k_view_dev,
address_view_pubkey,
is_subaddress))
return false;
const bool verified_normal_janus = verify_carrot_normal_janus_protection(
make_carrot_input_context(enote.tx_first_key_image),
address_spend_pubkey_out,
address_view_pubkey,
is_subaddress,
enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
payment_id_out);
if (!verified_normal_janus && !verify_carrot_special_janus_protection(enote.tx_first_key_image,
enote.enote_ephemeral_pubkey,
enote.onetime_address,
+3
View File
@@ -71,6 +71,7 @@ bool try_make_carrot_shared_key_receiver(
* @param enote_ephemeral_privkey d_e
* @param s_sender_receiver s_sr
* @param main_address_spend_pubkeys {K^0_s, ...}
* @param main_address_view_pubkey K^0_v
* @param[out] sender_extension_g_out k^g_o
* @param[out] sender_extension_t_out k^t_o
* @param[out] main_address_spend_pubkey_out K^0_s, which will be one of main_address_spend_pubkeys
@@ -92,6 +93,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
const CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
crypto::secret_key &sender_extension_g_out,
crypto::secret_key &sender_extension_t_out,
crypto::public_key &main_address_spend_pubkey_out);
@@ -99,6 +101,7 @@ bool try_scan_carrot_coinbase_enote_receiver(
const CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const crypto::public_key &main_address_spend_pubkey,
const crypto::public_key &main_address_view_pubkey,
crypto::secret_key &sender_extension_g_out,
crypto::secret_key &sender_extension_t_out);
/**
+4 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2025, The Monero Project
// Copyright (c) 2025-2026, The Monero Project
//
// All rights reserved.
//
@@ -226,6 +226,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote,
//-------------------------------------------------------------------------------------------------------------------
bool verify_carrot_normal_janus_protection(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 mx25519_pubkey &enote_ephemeral_pubkey,
const janus_anchor_t &nominal_janus_anchor,
@@ -235,6 +236,7 @@ bool verify_carrot_normal_janus_protection(const input_context_t &input_context,
if (verify_carrot_normal_janus_protection(nominal_janus_anchor,
input_context,
nominal_address_spend_pubkey,
nominal_address_view_pubkey,
is_subaddress,
nominal_payment_id_inout,
enote_ephemeral_pubkey))
@@ -245,6 +247,7 @@ bool verify_carrot_normal_janus_protection(const input_context_t &input_context,
return verify_carrot_normal_janus_protection(nominal_janus_anchor,
input_context,
nominal_address_spend_pubkey,
nominal_address_view_pubkey,
is_subaddress,
nominal_payment_id_inout,
enote_ephemeral_pubkey);
+2
View File
@@ -126,6 +126,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote,
* @brief 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 nominal_address_view_pubkey K^j_v'
* @param is_subaddress true iff K^j_s' corresponds to a subaddress
* @param enote_ephemeral_pubkey D_e
* @param nominal_janus_anchor anchor'
@@ -134,6 +135,7 @@ bool try_scan_carrot_enote_internal_burnt(const CarrotEnoteV1 &enote,
*/
bool verify_carrot_normal_janus_protection(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 mx25519_pubkey &enote_ephemeral_pubkey,
const janus_anchor_t &nominal_janus_anchor,
+16 -8
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2025, The Monero Project
// Copyright (c) 2025-2026, The Monero Project
//
// All rights reserved.
//
@@ -108,7 +108,9 @@ void cryptonote_hierarchy_address_device::get_address_spend_pubkey(const subaddr
// decompress K_S
ge_p3 account_spend_pubkey_p3;
ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_cryptonote_account_spend_pubkey)); // discard result
[[maybe_unused]] int r = 1;
r = ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_cryptonote_account_spend_pubkey));
assert(0 == r);
ge_cached account_spend_pubkey_cached;
ge_p3_to_cached(&account_spend_pubkey_cached, &account_spend_pubkey_p3);
@@ -205,11 +207,13 @@ void cryptonote_hierarchy_address_device::assert_derive_type(const subaddress_in
carrot_hierarchy_address_device::carrot_hierarchy_address_device(
std::shared_ptr<generate_address_secret_device> s_generate_address_dev,
const crypto::public_key &carrot_account_spend_pubkey,
const crypto::public_key &carrot_account_view_pubkey)
const crypto::public_key &carrot_account_view_pubkey,
const crypto::public_key &carrot_main_address_view_pubkey)
:
m_s_generate_address_dev(std::move(s_generate_address_dev)),
m_carrot_account_spend_pubkey(carrot_account_spend_pubkey),
m_carrot_account_view_pubkey(carrot_account_view_pubkey)
m_carrot_account_view_pubkey(carrot_account_view_pubkey),
m_carrot_main_address_view_pubkey(carrot_main_address_view_pubkey)
{
assert(this->m_s_generate_address_dev);
}
@@ -229,7 +233,9 @@ void carrot_hierarchy_address_device::get_address_spend_pubkey(const subaddress_
// decompress K_s
ge_p3 account_spend_pubkey_p3;
ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_carrot_account_spend_pubkey));
[[maybe_unused]] int r = 1;
r = ge_frombytes_vartime(&account_spend_pubkey_p3, to_bytes(this->m_carrot_account_spend_pubkey));
assert(0 == r);
// K^j_s = k^j_subscal K_s
ge_p2 tmp_p2;
@@ -246,7 +252,7 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index
if (!subaddr_index.index.is_subaddress())
{
address_spend_pubkey_out = this->m_carrot_account_spend_pubkey;
address_view_pubkey_out = this->m_carrot_account_view_pubkey;
address_view_pubkey_out = this->m_carrot_main_address_view_pubkey;
return;
}
@@ -254,7 +260,8 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index
// decompress K_s
ge_p3 tmp_p3;
ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_spend_pubkey));
[[maybe_unused]] int r = ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_spend_pubkey));
assert(0 == r);
// K^j_s = k^j_subscal K_s
ge_p2 tmp_p2;
@@ -262,7 +269,8 @@ void carrot_hierarchy_address_device::get_address_pubkeys(const subaddress_index
ge_tobytes(to_bytes(address_spend_pubkey_out), &tmp_p2);
// decompress K_v
ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_view_pubkey));
r = ge_frombytes_vartime(&tmp_p3, to_bytes(this->m_carrot_account_view_pubkey));
assert(0 == r);
// K^j_v = k^j_subscal K_v
ge_scalarmult(&tmp_p2, to_bytes(subaddress_scalar), &tmp_p3);
+3 -1
View File
@@ -117,7 +117,8 @@ public:
carrot_hierarchy_address_device(
std::shared_ptr<generate_address_secret_device> s_generate_address_dev,
const crypto::public_key &carrot_account_spend_pubkey,
const crypto::public_key &carrot_account_view_pubkey);
const crypto::public_key &carrot_account_view_pubkey,
const crypto::public_key &carrot_main_address_view_pubkey);
//address_device
void get_address_spend_pubkey(const subaddress_index_extended &subaddr_index,
@@ -138,6 +139,7 @@ private:
std::shared_ptr<generate_address_secret_device> m_s_generate_address_dev;
crypto::public_key m_carrot_account_spend_pubkey;
crypto::public_key m_carrot_account_view_pubkey;
crypto::public_key m_carrot_main_address_view_pubkey;
//member functions
crypto::secret_key get_subaddress_scalar(const subaddress_index &subaddr_index) const;
+4
View File
@@ -253,9 +253,13 @@ static bool try_scan_opening_hint(const OutputOpeningHintVariant &opening_hint,
s_sender_receiver))
{
crypto::public_key dummy_address_spend_pubkey;
crypto::public_key main_address_view_pubkey;
addr_dev.get_address_pubkeys({}, dummy_address_spend_pubkey, main_address_view_pubkey);
return try_scan_carrot_coinbase_enote_receiver(hint.source_enote,
s_sender_receiver,
main_address_spend_pubkeys,
main_address_view_pubkey,
sender_extension_g_out,
sender_extension_t_out,
dummy_address_spend_pubkey);
+1 -1
View File
@@ -209,7 +209,7 @@ void get_enote_ephemeral_privkeys_from_proposal_v1(
}
else
{
// d_e = H_n(anchor_norm, input_context, K^j_s, pid)
// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)
enote_ephemeral_privkey = get_enote_ephemeral_privkey(
normal_payment_proposals.at(payment_proposal_idx.second),
make_carrot_input_context(tx_first_key_image));
+3 -1
View File
@@ -493,7 +493,8 @@ exported_carrot_transfer_details export_cold_carrot_output(const wallet2_basic::
// 10. K^j_s, enote_type
crypto::public_key address_spend_pubkey;
addr_dev.get_address_spend_pubkey({etd.subaddr_index}, address_spend_pubkey);
crypto::public_key address_view_pubkey;
addr_dev.get_address_pubkeys({etd.subaddr_index}, address_spend_pubkey, address_view_pubkey);
crypto::secret_key amount_blinding_factor;
etd.flags.m_enote_type_change = carrot::try_recompute_carrot_amount_commitment(s_sender_receiver_ctx,
td.amount(), address_spend_pubkey, carrot::CarrotEnoteType::CHANGE,
@@ -513,6 +514,7 @@ exported_carrot_transfer_details export_cold_carrot_output(const wallet2_basic::
CHECK_AND_ASSERT_THROW_MES(
carrot::verify_carrot_normal_janus_protection(input_context,
address_spend_pubkey,
address_view_pubkey,
is_subaddress,
enote_ephemeral_pubkey,
etd.janus_anchor,
+20 -3
View File
@@ -286,6 +286,7 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
const carrot::CarrotCoinbaseEnoteV1 &enote,
const mx25519_pubkey &s_sender_receiver,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map)
{
enote_view_incoming_scan_info_t res;
@@ -293,6 +294,7 @@ static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_carrot_
if (!carrot::try_scan_carrot_coinbase_enote_receiver(enote,
s_sender_receiver,
main_address_spend_pubkeys,
main_address_view_pubkey,
res.sender_extension_g,
res.sender_extension_t,
res.address_spend_pubkey))
@@ -482,7 +484,7 @@ bool try_load_pre_carrot_enote_from_transaction_prefix(const cryptonote::transac
return true;
}
//-------------------------------------------------------------------------------------------------------------------
std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
const MoneroEnoteVariant &enote,
const std::size_t local_output_index,
const cryptonote::blobdata &tx_extra_nonce,
@@ -491,6 +493,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
const cryptonote::account_public_address &address,
const carrot::view_incoming_key_device *k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map)
{
CHECK_AND_ASSERT_MES(!main_derivations.empty() || !additional_derivations.empty(),
@@ -539,6 +542,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
scan_as_sender
? epee::span<const crypto::public_key>(&address.m_spend_public_key, 1)
: main_address_spend_pubkeys,
scan_as_sender ? address.m_view_public_key : main_address_view_pubkey,
subaddress_map);
}
@@ -576,6 +580,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
const cryptonote::account_public_address &address;
const carrot::view_incoming_key_device *k_view_incoming_dev;
const epee::span<const crypto::public_key> main_address_spend_pubkeys;
const crypto::public_key &main_address_view_pubkey;
const carrot::subaddress_map &subaddress_map;
};
@@ -588,12 +593,13 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
address,
k_view_incoming_dev,
main_address_spend_pubkeys,
main_address_view_pubkey,
subaddress_map
},
enote);
}
//-------------------------------------------------------------------------------------------------------------------
std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
static std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
const cryptonote::transaction &tx,
const std::size_t local_output_index,
const epee::span<const crypto::public_key> main_tx_ephemeral_pubkeys,
@@ -604,6 +610,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
const cryptonote::account_public_address &address,
const carrot::view_incoming_key_device *k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map)
{
MoneroEnoteVariant enote;
@@ -661,6 +668,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote(
address,
k_view_incoming_dev,
main_address_spend_pubkeys,
main_address_view_pubkey,
subaddress_map);
}
//-------------------------------------------------------------------------------------------------------------------
@@ -671,6 +679,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote_from_pre
const std::size_t local_output_index,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map)
{
const bool is_carrot = carrot::is_carrot_transaction_v1(tx_prefix);
@@ -722,6 +731,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote_from_pre
/*address=*/{},
&k_view_incoming_dev,
main_address_spend_pubkeys,
main_address_view_pubkey,
subaddress_map);
}
//-------------------------------------------------------------------------------------------------------------------
@@ -734,6 +744,7 @@ void view_incoming_scan_transaction(
const epee::span<const crypto::key_derivation> additional_derivations,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out)
{
@@ -755,6 +766,7 @@ void view_incoming_scan_transaction(
/*address=*/{},
&k_view_incoming_dev,
main_address_spend_pubkeys,
main_address_view_pubkey,
subaddress_map);
}
}
@@ -763,6 +775,7 @@ void view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out)
{
@@ -805,6 +818,7 @@ void view_incoming_scan_transaction(
epee::to_span(additional_derivations),
k_view_incoming_dev,
main_address_spend_pubkeys,
main_address_view_pubkey,
subaddress_map,
enote_scan_infos_out);
}
@@ -816,10 +830,12 @@ std::vector<std::optional<enote_view_incoming_scan_info_t>> view_incoming_scan_t
const carrot::subaddress_map &subaddress_map)
{
crypto::public_key main_address_spend_pubkeys[2];
crypto::public_key main_address_view_pubkey;
addr_dev.get_address_pubkeys({}, main_address_spend_pubkeys[0], main_address_view_pubkey);
std::vector<std::optional<enote_view_incoming_scan_info_t>> res(tx.vout.size());
view_incoming_scan_transaction(tx, k_view_incoming_dev,
carrot::get_all_main_address_spend_pubkeys_span(addr_dev, main_address_spend_pubkeys),
subaddress_map, epee::to_mut_span(res));
main_address_view_pubkey, subaddress_map, epee::to_mut_span(res));
return res;
}
//-------------------------------------------------------------------------------------------------------------------
@@ -859,6 +875,7 @@ std::vector<std::optional<enote_view_incoming_scan_info_t>> view_incoming_scan_t
address,
/*k_view_incoming_dev=*/nullptr,
/*main_address_spend_pubkeys=*/{},
address.m_view_public_key,
carrot::subaddress_map_legacy{{{address.m_spend_public_key, {}}}});
}
+5
View File
@@ -103,6 +103,7 @@ using MoneroEnoteVariant = std::variant<PreCarrotEnote,
* @param local_output_index i
* @param k_view_incoming_dev k_v
* @param main_address_spend_pubkeys [K_s, ...]
* @param main_address_view_pubkey K^0_v
* @param subaddress_map -
* @return std::nullopt if scan failed, otherwise enote scan info on success
*/
@@ -113,6 +114,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote_from_pre
const std::size_t local_output_index,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map);
/**
@@ -125,6 +127,7 @@ std::optional<enote_view_incoming_scan_info_t> view_incoming_scan_enote_from_pre
* @param additional_derivations sender-receiver ECDHs against additional ephemeral pubkeys
* @param k_view_incoming_dev device for k_v
* @param main_address_spend_pubkeys [K_s, ...]
* @param main_address_view_pubkey K^0_v
* @param subaddress_map -
* @param[out] enote_scan_infos_out - mutable span to enote scan infos of size N, where N is number of `tx`'s outputs
*/
@@ -137,12 +140,14 @@ void view_incoming_scan_transaction(
const epee::span<const crypto::key_derivation> additional_derivations,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out);
void view_incoming_scan_transaction(
const cryptonote::transaction &tx,
const carrot::view_incoming_key_device &k_view_incoming_dev,
const epee::span<const crypto::public_key> main_address_spend_pubkeys,
const crypto::public_key &main_address_view_pubkey,
const carrot::subaddress_map &subaddress_map,
const epee::span<std::optional<enote_view_incoming_scan_info_t>> enote_scan_infos_out);
std::vector<std::optional<enote_view_incoming_scan_info_t>> view_incoming_scan_transaction(
+2
View File
@@ -3659,6 +3659,7 @@ void wallet2::process_parsed_blocks(const uint64_t start_height, const uint64_t
wallet::view_incoming_scan_transaction(tx,
*k_view_incoming_dev,
{&m_account.get_keys().m_account_address.m_spend_public_key, 1}, //! @TODO: Carrot
m_account.get_keys().m_account_address.m_view_public_key,
carrot::subaddress_map_legacy{this->m_subaddresses},
{&enote_scan_infos[0] + tx_output_idx, tx.vout.size()});
@@ -13560,6 +13561,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
td.m_internal_output_index,
*k_view_incoming_dev,
{&m_account.get_keys().m_account_address.m_spend_public_key, 1}, //! @TODO: Carrot
m_account.get_keys().m_account_address.m_view_public_key,
carrot::subaddress_map_legacy{m_subaddresses});
const size_t main_tx_pubkey_index = enote_scan_info ? enote_scan_info->main_tx_pubkey_index : 0;
+16 -15
View File
@@ -117,24 +117,24 @@ static const hex_value_t<crypto::public_key> subaddress_spend_pubkey("97d227d0ff
static const hex_value_t<crypto::public_key> subaddress_view_pubkey("99a684cd429d88815cb1f90b794522b32812388a9f35120cfc08c7435f7bd51f");
static const hex_value_t<carrot::janus_anchor_t> anchor_norm("caee1381775487a0982557f0d2680b55");
static const hex_value_t<carrot::janus_anchor_t> anchor_special("dea14ab8268ba491238f1c554ab60d83");
static const hex_value_t<carrot::janus_anchor_t> anchor_special("ed214f5093ad9c5da37765459ac45837");
static const hex_value_t<carrot::input_context_t> input_context("9423f74f3e869dc8427d8b35bb24c917480409c3f4750bff3c742f8e4d5af7bef7");
static const hex_value_t<carrot::payment_id_t> payment_id("4321734f56621440");
static const hex_value_t<crypto::secret_key> enote_ephemeral_privkey("6bd72042c79d9532a3b90b3689ee53c22725a11169ac2d251337bc4a69b2340d");
static const hex_value_t<mx25519_pubkey> enote_ephemeral_pubkey_cryptonote("65b42ef1ed3bd2ab3e6e86d17a52d832bcb6c820a8987306bedd9f6453693869");
static const hex_value_t<mx25519_pubkey> enote_ephemeral_pubkey_subaddress("d8e787047bb21d7dd348524741c78f311f549554b6dffd71c86ecc4a98a15720");
static const hex_value_t<mx25519_pubkey> s_sender_receiver("513ee79c0c8d76fdd95665a36d607b618e2f76a4806cfdba340fafe64b7f805f");
static const hex_value_t<crypto::hash> s_sender_receiver_ctx("6d4288869ce44ed5c38d4016b33083a1a0200daa2d8afc16625702d2108b62ae");
static const hex_value_t<crypto::secret_key> enote_ephemeral_privkey("ae5802c6757d77bd7032e071f72fc99c31a01b9f1bc3af2671c482c8e458140e");
static const hex_value_t<mx25519_pubkey> enote_ephemeral_pubkey_cryptonote("f2296f8bd12d705b63d4d827011dd2b2834f2c3396e3546969110173fd14c11a");
static const hex_value_t<mx25519_pubkey> enote_ephemeral_pubkey_subaddress("09b26146c8e458ea0822958af12d5f99de17600849388d9f85eabda24934c346");
static const hex_value_t<mx25519_pubkey> s_sender_receiver("1ab57d1f23d12d3f67bb52587dd0c74123cbba6e1c4fd10d0a93067caff19647");
static const hex_value_t<crypto::hash> s_sender_receiver_ctx("911a15979c3d448aca2b81cd91d16ba2bf2ca4fa4f6dd16ad596c6304eb52227");
static const rct::xmr_amount amount = 67000000000000;
static const hex_value_t<crypto::secret_key> amount_blinding_factor_payment("2943f1f7cdabfcfef4803fe6a36df414065e912089ebcee5dbdad32a9685060a");
static const hex_value_t<crypto::secret_key> amount_blinding_factor_change("b9787d10298d13e34cf21513bce84dd1b13ae8aad6ab79794ac06a4c936e2b09");
static const hex_value_t<carrot::amount_commitment_t> amount_commitment("95f818f40a41665950d90db8f790cd8a135403624bac284d1059aeb29652fafe");
static const hex_value_t<crypto::public_key> onetime_address_coinbase("eb6d42a2cabe4e71a0d61172ed8b528b62c0ab398cde9373dab35cc774ba7c05");
static const hex_value_t<crypto::public_key> onetime_address("5e8f18d1dd3aba72d6ea2c7cfb217573ff4baa878300bbf97b4e32c5c566050d");
static const hex_value_t<carrot::view_tag_t> view_tag("98ad1e");
static const hex_value_t<carrot::encrypted_janus_anchor_t> anchor_encryption_mask("8d769d8417759007792f824e83115408");
static const hex_value_t<carrot::encrypted_amount_t> amount_encryption_mask("ee875c495435d1c3");
static const hex_value_t<carrot::encrypted_payment_id_t> payment_id_encryption_mask("736350182e1e0840");
static const hex_value_t<crypto::secret_key> amount_blinding_factor_payment("dd8f4252476020ddab59df4ec0ca506e6d25618bfe465e13302276398b23550f");
static const hex_value_t<crypto::secret_key> amount_blinding_factor_change("dba0496c81efa9e25c55a65682024e6e42f1893d3ba57026d9a96468ea5d680b");
static const hex_value_t<carrot::amount_commitment_t> amount_commitment("a44cacd33bdacb1c86262987518885a6c22b569ffc89be13e730d5d175f2dede");
static const hex_value_t<crypto::public_key> onetime_address_coinbase("4f01c472b41a0d85f3c9bc88ad38b7fd47a24029508f84aea84f55d221a48d29");
static const hex_value_t<crypto::public_key> onetime_address("cd9610e64abdae97e2f8d8580550dbdd0ed543c99740c9c90f9f2cc854841bfa");
static const hex_value_t<carrot::view_tag_t> view_tag("13191c");
static const hex_value_t<carrot::encrypted_janus_anchor_t> anchor_encryption_mask("ed6dac7330268e639619551f662b19b5");
static const hex_value_t<carrot::encrypted_amount_t> amount_encryption_mask("f5ff32293fa31ed6");
static const hex_value_t<carrot::encrypted_payment_id_t> payment_id_encryption_mask("59220b6582addd7d");
} //anonymous namespace
//---------------------------------------------------------------------------------------------------------------------
@@ -247,6 +247,7 @@ TEST(carrot_convergence, make_carrot_enote_ephemeral_privkey)
make_carrot_enote_ephemeral_privkey(anchor_norm.value,
input_context.value,
subaddress_spend_pubkey.value,
subaddress_view_pubkey.value,
payment_id.value,
enote_ephemeral_privkey_rc);
EXPECT_TRUE(enote_ephemeral_privkey.matches(enote_ephemeral_privkey_rc));
+101 -71
View File
@@ -49,6 +49,29 @@ using namespace carrot::mock::people;
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "unit_tests.carrot"
namespace
{
static bool verify_carrot_normal_janus_protection_to_mock_keys(const input_context_t &input_context,
const crypto::public_key &nominal_address_spend_pubkey,
const mock::mock_carrot_and_legacy_keys &keys,
const mx25519_pubkey &enote_ephemeral_pubkey,
const janus_anchor_t &nominal_janus_anchor,
const payment_id_t &nominal_payment_id)
{
const subaddress_index_extended subaddr_index = keys.subaddress_map.get_index_for_address_spend_pubkey(
nominal_address_spend_pubkey).value();
const CarrotDestinationV1 address = keys.subaddress(subaddr_index);
CHECK_AND_ASSERT_THROW_MES(address.address_spend_pubkey == nominal_address_spend_pubkey,
"Bad subaddress index lookup: mismatched address spend pubkeys");
return verify_carrot_normal_janus_protection(nominal_janus_anchor,
input_context,
nominal_address_spend_pubkey,
address.address_view_pubkey,
subaddr_index.index.is_subaddress(),
nominal_payment_id,
enote_ephemeral_pubkey);
}
} //anonymous namespace
//---------------------------------------------------------------------------------------------------------------------
TEST(carrot_core, ECDH_cryptonote_completeness)
{
@@ -680,6 +703,7 @@ TEST(carrot_core, main_address_coinbase_scan_completeness)
const bool scan_success = try_scan_carrot_coinbase_enote_receiver(enote,
s_sender_receiver,
keys.carrot_account_spend_pubkey,
keys.cryptonote_address().address_view_pubkey,
recovered_sender_extension_g,
recovered_sender_extension_t);
@@ -906,15 +930,15 @@ TEST(carrot_core, get_enote_output_proposals_external_ss_sub2integ_completeness)
struct JanusAttackProposalV1
{
/// The address provided in the normal proposal is used to scale the enote ephemeral pubkey and derive s_sr.
/// The `readjusted_opening_subaddress_spend_pubkey` is what is added to the sender extensions to derive K_o.
/// If `use_readjusted_spend_pubkey_in_ephemeral_privkey_hash=true`, then d_e is calculated as
/// d_e = H_n(anchor_norm, input_context, K^i_s, pid)), else it is normally calculated as
/// d_e = H_n(anchor_norm, input_context, K^j_s, pid)). It shouldn't make a difference to the attack either
/// The `readjusted_opening_address` is what is added to the sender extensions to derive K_o.
/// If `use_readjusted_address_in_ephemeral_privkey_hash=true`, then d_e is calculated as
/// d_e = H_n(anchor_norm, input_context, K^i_s, K^i_v, pid)), else it is normally calculated as
/// d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)). It shouldn't make a difference to the attack either
/// way, but we will test that below.
carrot::CarrotPaymentProposalV1 normal; // contains address (is_subaddress, K^j_s, K^j_v, pid)
crypto::public_key readjusted_opening_subaddress_spend_pubkey; // K^i_s
bool use_readjusted_spend_pubkey_in_ephemeral_privkey_hash;
carrot::CarrotDestinationV1 readjusted_opening_address; // (K^i_s, K^i_v)
bool use_readjusted_address_in_ephemeral_privkey_hash;
};
//----------------------------------------------------------------------------------------------------------------------
static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &proposal,
@@ -932,16 +956,17 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro
const input_context_t input_context = make_carrot_input_context(tx_first_key_image);
// 3. decide if K^x_s = K^j_s XOR K^x_s = K^i_s
const crypto::public_key &spend_pubkey_used_in_ephemeral_privkey_hash =
proposal.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash
? proposal.readjusted_opening_subaddress_spend_pubkey
: destination.address_spend_pubkey;
const carrot::CarrotDestinationV1 &destination_used_in_ephemeral_privkey_hash =
proposal.use_readjusted_address_in_ephemeral_privkey_hash
? proposal.readjusted_opening_address
: destination;
// 4. d_e = H_n(anchor_norm, input_context, K^x_s, pid)
// 4. d_e = H_n(anchor_norm, input_context, K^x_s, K^x_v, pid)
crypto::secret_key enote_ephemeral_privkey;
carrot::make_carrot_enote_ephemeral_privkey(proposal.normal.randomness,
input_context,
spend_pubkey_used_in_ephemeral_privkey_hash,
destination_used_in_ephemeral_privkey_hash.address_spend_pubkey,
destination_used_in_ephemeral_privkey_hash.address_view_pubkey,
destination.payment_id,
enote_ephemeral_privkey);
@@ -968,7 +993,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro
// 8. k_a = H_n(s^ctx_sr, a, K^i_s, enote_type)
make_carrot_amount_blinding_factor(s_sender_receiver_ctx,
proposal.normal.amount,
proposal.readjusted_opening_subaddress_spend_pubkey,
proposal.readjusted_opening_address.address_spend_pubkey,
carrot::CarrotEnoteType::PAYMENT,
output_enote_out.amount_blinding_factor);
@@ -978,7 +1003,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro
// 10. Ko = K^i_s + K^o_ext = K^i_s + (k^o_g G + k^o_t T)
CHECK_AND_ASSERT_THROW_MES(try_make_carrot_onetime_address(
proposal.readjusted_opening_subaddress_spend_pubkey,
proposal.readjusted_opening_address.address_spend_pubkey,
s_sender_receiver_ctx,
output_enote_out.enote.amount_commitment,
output_enote_out.enote.onetime_address),
@@ -1009,7 +1034,7 @@ static void get_output_proposal_janus_attack_v1(const JanusAttackProposalV1 &pro
output_enote_out.amount = proposal.normal.amount;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
// Notice steps 8 & 10 specifically for where `readjusted_opening_subaddress_spend_pubkey` is
// Notice steps 8 & 10 specifically for where `readjusted_opening_address` is
// substituted instead of the actual address spend pubkey used for deriving D_e and s_sr.
}
//----------------------------------------------------------------------------------------------------------------------
@@ -1028,8 +1053,8 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_subaddr.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false,
.readjusted_opening_address = bob_subaddr,
.use_readjusted_address_in_ephemeral_privkey_hash = false,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1062,7 +1087,7 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1071,10 +1096,10 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_NOT_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1118,8 +1143,8 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_subaddr.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true,
.readjusted_opening_address = bob_subaddr,
.use_readjusted_address_in_ephemeral_privkey_hash = true,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1152,7 +1177,7 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1161,10 +1186,10 @@ TEST(carrot_core, janus_protection_non_coinbase_main_sub_readjust_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1208,8 +1233,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false,
.readjusted_opening_address = bob_main,
.use_readjusted_address_in_ephemeral_privkey_hash = false,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1242,7 +1267,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1251,10 +1276,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_NOT_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1298,8 +1323,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true,
.readjusted_opening_address = bob_main,
.use_readjusted_address_in_ephemeral_privkey_hash = true,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1332,7 +1357,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1341,10 +1366,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_main_readjust_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1388,8 +1413,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_subaddr2.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false,
.readjusted_opening_address = bob_subaddr2,
.use_readjusted_address_in_ephemeral_privkey_hash = false,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1422,7 +1447,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1431,10 +1456,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_NOT_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1478,8 +1503,8 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_subaddr2.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true,
.readjusted_opening_address = bob_subaddr2,
.use_readjusted_address_in_ephemeral_privkey_hash = true,
};
carrot::RCTOutputEnoteProposal output_enote;
@@ -1512,7 +1537,7 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e)
recovered_enote_type,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(amount, recovered_amount);
ASSERT_EQ(carrot::CarrotEnoteType::PAYMENT, recovered_enote_type);
ASSERT_EQ(output_enote.enote.amount_commitment, commit_carrot_amount(recovered_amount, recovered_amount_blinding_factor));
@@ -1521,10 +1546,10 @@ TEST(carrot_core, janus_protection_non_coinbase_sub_sub_readjust_in_d_e)
sender_extension_t,
output_enote.enote.onetime_address));
EXPECT_FALSE(verify_carrot_normal_janus_protection(
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
carrot::make_carrot_input_context(tx_first_key_image),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
bob,
output_enote.enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
nominal_payment_id));
@@ -1569,16 +1594,17 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos
const input_context_t input_context = make_carrot_input_context_coinbase(block_index);
// 3. decide if K^x_s = K^j_s XOR K^x_s = K^i_s
const crypto::public_key &spend_pubkey_used_in_ephemeral_privkey_hash =
proposal.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash
? proposal.readjusted_opening_subaddress_spend_pubkey
: destination.address_spend_pubkey;
const carrot::CarrotDestinationV1 &destination_used_in_ephemeral_privkey_hash =
proposal.use_readjusted_address_in_ephemeral_privkey_hash
? proposal.readjusted_opening_address
: destination;
// 4. d_e = H_n(anchor_norm, input_context, K^x_s, pid)
crypto::secret_key enote_ephemeral_privkey;
carrot::make_carrot_enote_ephemeral_privkey(proposal.normal.randomness,
input_context,
spend_pubkey_used_in_ephemeral_privkey_hash,
destination_used_in_ephemeral_privkey_hash.address_spend_pubkey,
destination_used_in_ephemeral_privkey_hash.address_view_pubkey,
destination.payment_id,
enote_ephemeral_privkey);
@@ -1604,7 +1630,7 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos
// 8. Ko = K^i_s + K^o_ext = K^i_s + (k^o_g G + k^o_t T)
CHECK_AND_ASSERT_THROW_MES(try_make_carrot_onetime_address_coinbase(
proposal.readjusted_opening_subaddress_spend_pubkey,
proposal.readjusted_opening_address.address_spend_pubkey,
s_sender_receiver_ctx,
proposal.normal.amount,
output_enote_out.onetime_address),
@@ -1625,7 +1651,7 @@ static void get_coinbase_output_proposal_janus_attack_v1(const JanusAttackPropos
output_enote_out.amount = proposal.normal.amount;
output_enote_out.block_index = block_index;
// Notice step 8 specifically for where `readjusted_opening_subaddress_spend_pubkey` is
// Notice step 8 specifically for where `readjusted_opening_address` is
// substituted instead of the actual address spend pubkey used for deriving D_e and s_sr.
}
//----------------------------------------------------------------------------------------------------------------------
@@ -1644,8 +1670,8 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_NOT_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false,
.readjusted_opening_address = bob_main,
.use_readjusted_address_in_ephemeral_privkey_hash = false,
};
carrot::CarrotCoinbaseEnoteV1 output_enote;
@@ -1669,22 +1695,24 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_NOT_in_d_e)
nominal_address_spend_pubkey,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_TRUE(bob.can_open_fcmp_onetime_address(nominal_address_spend_pubkey,
sender_extension_g,
sender_extension_t,
output_enote.onetime_address));
EXPECT_FALSE(carrot::verify_carrot_normal_janus_protection(nominal_janus_anchor,
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(
make_carrot_input_context_coinbase(block_index),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
null_payment_id,
output_enote.enote_ephemeral_pubkey));
bob,
output_enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
null_payment_id));
EXPECT_FALSE(try_scan_carrot_coinbase_enote_receiver(output_enote,
s_sender_receiver,
{&bob.carrot_account_spend_pubkey, 1},
bob.cryptonote_address().address_view_pubkey,
sender_extension_g,
sender_extension_t,
nominal_address_spend_pubkey));
@@ -1711,8 +1739,8 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e)
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true,
.readjusted_opening_address = bob_main,
.use_readjusted_address_in_ephemeral_privkey_hash = true,
};
carrot::CarrotCoinbaseEnoteV1 output_enote;
@@ -1736,22 +1764,23 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e)
nominal_address_spend_pubkey,
nominal_janus_anchor);
ASSERT_TRUE(scanned);
ASSERT_EQ(proposal.readjusted_opening_subaddress_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_EQ(proposal.readjusted_opening_address.address_spend_pubkey, nominal_address_spend_pubkey);
ASSERT_TRUE(bob.can_open_fcmp_onetime_address(nominal_address_spend_pubkey,
sender_extension_g,
sender_extension_t,
output_enote.onetime_address));
EXPECT_FALSE(carrot::verify_carrot_normal_janus_protection(nominal_janus_anchor,
make_carrot_input_context_coinbase(block_index),
EXPECT_FALSE(verify_carrot_normal_janus_protection_to_mock_keys(make_carrot_input_context_coinbase(block_index),
nominal_address_spend_pubkey,
bob.cryptonote_address().address_spend_pubkey != nominal_address_spend_pubkey,
null_payment_id,
output_enote.enote_ephemeral_pubkey));
bob,
output_enote.enote_ephemeral_pubkey,
nominal_janus_anchor,
null_payment_id));
EXPECT_FALSE(try_scan_carrot_coinbase_enote_receiver(output_enote,
s_sender_receiver,
{&bob.carrot_account_spend_pubkey, 1},
bob.cryptonote_address().address_view_pubkey,
sender_extension_g,
sender_extension_t,
nominal_address_spend_pubkey));
@@ -1763,7 +1792,7 @@ TEST(carrot_core, janus_protection_coinbase_main_sub_readjust_in_d_e)
sender_extension_t));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_core, janus_protection_use_readjusted_spend_pubkey_in_ephemeral_privkey_hash)
TEST(carrot_core, janus_protection_use_readjusted_address_in_ephemeral_privkey_hash)
{
const std::uint64_t block_index = mock::gen_block_index();
@@ -1778,12 +1807,12 @@ TEST(carrot_core, janus_protection_use_readjusted_spend_pubkey_in_ephemeral_priv
.amount = amount,
.randomness = carrot::gen_janus_anchor()
},
.readjusted_opening_subaddress_spend_pubkey = bob_main.address_spend_pubkey,
.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = false,
.readjusted_opening_address = bob_main,
.use_readjusted_address_in_ephemeral_privkey_hash = false,
};
JanusAttackProposalV1 proposal2 = proposal1;
proposal2.use_readjusted_spend_pubkey_in_ephemeral_privkey_hash = true;
proposal2.use_readjusted_address_in_ephemeral_privkey_hash = true;
carrot::CarrotCoinbaseEnoteV1 output_enote1;
get_coinbase_output_proposal_janus_attack_v1(proposal1, block_index, output_enote1);
@@ -2301,6 +2330,7 @@ TEST(carrot_core, pq_turnstile_completeness_coinbase)
const bool scan_success = try_scan_carrot_coinbase_enote_receiver(output_enote,
s_sender_receiver,
{&bob.carrot_account_spend_pubkey, 1},
bob.cryptonote_address().address_view_pubkey,
recovered_sender_extension_g,
recovered_sender_extension_t,
recovered_address_spend_pubkey);
+3 -1
View File
@@ -64,7 +64,8 @@ static void reassign_non_borrowed_devices(carrot::mock::mock_carrot_and_legacy_k
std::make_shared<carrot::carrot_hierarchy_address_device>(
std::make_shared<carrot::generate_address_secret_ram_borrowed_device>(keys.s_generate_address_dev),
keys.carrot_account_spend_pubkey,
keys.carrot_account_view_pubkey),
keys.carrot_account_view_pubkey,
keys.legacy_acb.get_keys().m_account_address.m_view_public_key),
std::make_shared<carrot::cryptonote_hierarchy_address_device>(
k_view_incoming_dev,
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key)));
@@ -570,6 +571,7 @@ void mock_scan_coinbase_enote_set(const std::vector<CarrotCoinbaseEnoteV1> &coin
if (try_scan_carrot_coinbase_enote_receiver(enote,
s_sender_receiver,
{main_address_spend_pubkeys, 2},
keys.cryptonote_address().address_view_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
scan_result.address_spend_pubkey))