delay selfsend proposal ephemeral pubkey dependencies

This commit is contained in:
jeffro256 2025-01-23 01:06:59 -06:00
parent 43178fddc5
commit 81317b36cd
No known key found for this signature in database
GPG Key ID: 6F79797A6E392442
5 changed files with 109 additions and 64 deletions

View File

@ -46,11 +46,11 @@ namespace carrot
//-------------------------------------------------------------------------------------------------------------------
std::optional<AdditionalOutputType> get_additional_output_type(const size_t num_outgoing,
const size_t num_selfsend,
const bool remaining_change,
const bool need_change_output,
const bool have_payment_type_selfsend)
{
const size_t num_outputs = num_outgoing + num_selfsend;
const bool already_completed = num_outputs >= 2 && num_selfsend >= 1 && !remaining_change;
const bool already_completed = num_outputs >= 2 && num_selfsend >= 1 && !need_change_output;
if (num_outputs == 0)
{
ASSERT_MES_AND_THROW("get additional output type: set contains 0 outputs");
@ -65,11 +65,11 @@ std::optional<AdditionalOutputType> get_additional_output_type(const size_t num_
{
return AdditionalOutputType::CHANGE_SHARED;
}
else if (!remaining_change)
else if (!need_change_output)
{
return AdditionalOutputType::DUMMY;
}
else // num_selfsend == 1 && remaining_change
else // num_selfsend == 1 && need_change_output
{
if (have_payment_type_selfsend)
{
@ -95,15 +95,14 @@ std::optional<AdditionalOutputType> get_additional_output_type(const size_t num_
tools::optional_variant<CarrotPaymentProposalV1, CarrotPaymentProposalSelfSendV1> get_additional_output_proposal(
const size_t num_outgoing,
const size_t num_selfsend,
const rct::xmr_amount remaining_change,
const rct::xmr_amount needed_change_amount,
const bool have_payment_type_selfsend,
const crypto::public_key &change_address_spend_pubkey,
const mx25519_pubkey &other_enote_ephemeral_pubkey)
const crypto::public_key &change_address_spend_pubkey)
{
const std::optional<AdditionalOutputType> additional_output_type = get_additional_output_type(
num_outgoing,
num_selfsend,
remaining_change,
needed_change_amount,
have_payment_type_selfsend
);
@ -115,23 +114,23 @@ tools::optional_variant<CarrotPaymentProposalV1, CarrotPaymentProposalSelfSendV1
case AdditionalOutputType::PAYMENT_SHARED:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.amount = needed_change_amount,
.enote_type = CarrotEnoteType::PAYMENT,
.enote_ephemeral_pubkey = other_enote_ephemeral_pubkey
.enote_ephemeral_pubkey = std::nullopt
};
case AdditionalOutputType::CHANGE_SHARED:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.amount = needed_change_amount,
.enote_type = CarrotEnoteType::CHANGE,
.enote_ephemeral_pubkey = other_enote_ephemeral_pubkey
.enote_ephemeral_pubkey = std::nullopt
};
case AdditionalOutputType::CHANGE_UNIQUE:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.amount = needed_change_amount,
.enote_type = CarrotEnoteType::CHANGE,
.enote_ephemeral_pubkey = gen_x25519_pubkey()
.enote_ephemeral_pubkey = std::nullopt
};
case AdditionalOutputType::DUMMY:
return CarrotPaymentProposalV1{
@ -218,11 +217,17 @@ void get_output_enote_proposals(std::vector<CarrotPaymentProposalV1> &&normal_pa
// construct selfsend enotes, preferring internal enotes over special enotes when possible
for (const CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
{
const std::optional<mx25519_pubkey> other_enote_ephemeral_pubkey =
(num_proposals == 2 && output_enote_proposals_out.size())
? output_enote_proposals_out.at(0).enote.enote_ephemeral_pubkey
: std::optional<mx25519_pubkey>{};
if (s_view_balance_dev != nullptr)
{
get_output_proposal_internal_v1(selfsend_payment_proposal,
*s_view_balance_dev,
tx_first_key_image,
other_enote_ephemeral_pubkey,
tools::add_element(output_enote_proposals_out));
}
else if (k_view_dev != nullptr)
@ -231,6 +236,7 @@ void get_output_enote_proposals(std::vector<CarrotPaymentProposalV1> &&normal_pa
*k_view_dev,
account_spend_pubkey,
tx_first_key_image,
other_enote_ephemeral_pubkey,
tools::add_element(output_enote_proposals_out));
}
else // neither k_v nor s_vb device passed

View File

@ -59,33 +59,31 @@ enum class AdditionalOutputType
* brief: get_additional_output_type - get the type of the additional enote needed to finalize an output set
* param: num_outgoing - number of outgoing transfers
* param: num_selfsend - number of selfsend transfers
* param: remaining_change - whether there is any leftover change needed to be included
* param: need_change_output - whether an additional change output needs to be included for balance
* param: have_payment_type_selfsend - true if the enote set has a selfsend enote with enote_type="payment"
* return: AdditionalOutputType if need an additional enote, else std::nullopt
* throw: std::runtime_error if the output set is in a state where it cannot be finalized
*/
std::optional<AdditionalOutputType> get_additional_output_type(const size_t num_outgoing,
const size_t num_selfsend,
const bool remaining_change,
const bool need_change_output,
const bool have_payment_type_selfsend);
/**
* brief: get_additional_output_proposal - get an additional output proposal to complete an output set
* param: num_outgoing - number of outgoing transfers
* param: num_selfsend - number of selfsend transfers
* param: remaining_change - the amount of leftover change needed to be included
* param: needed_change_amount - the amount of leftover change needed to be included
* param: have_payment_type_selfsend - true if the enote set has a selfsend enote with enote_type="payment"
* param: change_address_spend_pubkey - K^j_s of our change address
* param: other_enote_ephemeral_pubkey - D^other_e
* return: an output proposal if need an additional enote, else none
* throw: std::runtime_error if the output set is in a state where it cannot be finalized
*/
tools::optional_variant<CarrotPaymentProposalV1, CarrotPaymentProposalSelfSendV1> get_additional_output_proposal(
const size_t num_outgoing,
const size_t num_selfsend,
const rct::xmr_amount remaining_change,
const rct::xmr_amount needed_change_amount,
const bool have_payment_type_selfsend,
const crypto::public_key &change_address_spend_pubkey,
const mx25519_pubkey &other_enote_ephemeral_pubkey);
const crypto::public_key &change_address_spend_pubkey);
/**
* brief: get_output_enote_proposals - convert a *finalized* set of payment proposals into output enote proposals
* param: normal_payment_proposals -

View File

@ -59,29 +59,56 @@ static auto auto_wiper(T &obj)
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalV1 &proposal,
static crypto::secret_key get_enote_ephemeral_privkey(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))
crypto::secret_key enote_ephemeral_privkey;
make_carrot_enote_ephemeral_privkey(proposal.randomness,
make_carrot_enote_ephemeral_privkey(randomness,
input_context,
proposal.destination.address_spend_pubkey,
proposal.destination.address_view_pubkey,
proposal.destination.payment_id,
destination.address_spend_pubkey,
destination.address_view_pubkey,
destination.payment_id,
enote_ephemeral_privkey);
return enote_ephemeral_privkey;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
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))
const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(randomness,
destination,
input_context)};
mx25519_pubkey enote_ephemeral_pubkey;
if (destination.is_subaddress)
// D_e = d_e ConvertPointE(K^j_s)
make_carrot_enote_ephemeral_pubkey_subaddress(enote_ephemeral_privkey,
destination.address_spend_pubkey,
enote_ephemeral_pubkey);
else
// D_e = d_e B
make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey,
enote_ephemeral_pubkey);
return enote_ephemeral_pubkey;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context,
mx25519_pubkey &enote_ephemeral_pubkey_out,
mx25519_pubkey &s_sender_receiver_unctx_out)
{
// 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);
const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal.randomness,
proposal.destination,
input_context);
// 2. make D_e
enote_ephemeral_pubkey_out = get_enote_ephemeral_pubkey(proposal, input_context);
@ -202,21 +229,7 @@ bool operator<(const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b)
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, K^j_v, pid))
const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(proposal, input_context)};
mx25519_pubkey enote_ephemeral_pubkey;
if (proposal.destination.is_subaddress)
// D_e = d_e ConvertPointE(K^j_s)
make_carrot_enote_ephemeral_pubkey_subaddress(enote_ephemeral_privkey,
proposal.destination.address_spend_pubkey,
enote_ephemeral_pubkey);
else
// D_e = d_e B
make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey,
enote_ephemeral_pubkey);
return enote_ephemeral_pubkey;
return get_enote_ephemeral_pubkey(proposal.randomness, proposal.destination, input_context);
}
//-------------------------------------------------------------------------------------------------------------------
void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal,
@ -326,6 +339,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
const view_incoming_key_device &k_view_dev,
const crypto::public_key &account_spend_pubkey,
const crypto::key_image &tx_first_key_image,
const std::optional<mx25519_pubkey> &other_enote_ephemeral_pubkey,
RCTOutputEnoteProposal &output_enote_out)
{
// 1. sanity checks
@ -336,13 +350,22 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
input_context_t input_context;
make_carrot_input_context(tx_first_key_image, input_context);
// 3. s_sr = k_v D_e
mx25519_pubkey s_sender_receiver_unctx;
CHECK_AND_ASSERT_THROW_MES(k_view_dev.view_key_scalar_mult_x25519(proposal.enote_ephemeral_pubkey,
// 3. D_e
const bool mismatched_enote_ephemeral_pubkeys = proposal.enote_ephemeral_pubkey &&
other_enote_ephemeral_pubkey &&
memcmp(&*proposal.enote_ephemeral_pubkey, &*other_enote_ephemeral_pubkey, sizeof(mx25519_pubkey));
CHECK_AND_ASSERT_THROW_MES(!mismatched_enote_ephemeral_pubkeys,
"get output proposal special v1: mismatched enote ephemeral pubkeys provided");
const mx25519_pubkey enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey.value_or(
other_enote_ephemeral_pubkey.value_or(gen_x25519_pubkey()));
// 4. s_sr = k_v D_e
mx25519_pubkey s_sender_receiver_unctx; auto ecdh_wiper = auto_wiper(s_sender_receiver_unctx);
CHECK_AND_ASSERT_THROW_MES(k_view_dev.view_key_scalar_mult_x25519(enote_ephemeral_pubkey,
s_sender_receiver_unctx),
"get output proposal special v1: HW device failed to perform ECDH with ephemeral pubkey");
// 4. build the output enote address pieces
// 5. build the output enote address pieces
crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver);
encrypted_payment_id_t dummy_encrypted_payment_id;
get_external_output_proposal_parts(s_sender_receiver_unctx,
@ -350,7 +373,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
null_payment_id,
proposal.amount,
proposal.enote_type,
proposal.enote_ephemeral_pubkey,
enote_ephemeral_pubkey,
input_context,
false, // coinbase_amount_commitment
s_sender_receiver,
@ -361,21 +384,21 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
dummy_encrypted_payment_id,
output_enote_out.enote.view_tag);
// 5. make special janus anchor: anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s)
// 6. make special janus anchor: anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s)
janus_anchor_t janus_anchor_special;
k_view_dev.make_janus_anchor_special(proposal.enote_ephemeral_pubkey,
k_view_dev.make_janus_anchor_special(enote_ephemeral_pubkey,
input_context,
output_enote_out.enote.onetime_address,
account_spend_pubkey,
janus_anchor_special);
// 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor
// 7. encrypt special anchor: anchor_enc = anchor XOR m_anchor
output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(janus_anchor_special,
s_sender_receiver,
output_enote_out.enote.onetime_address);
// 7. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
// 8. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote.enote_ephemeral_pubkey = enote_ephemeral_pubkey;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
output_enote_out.amount = proposal.amount;
}
@ -383,6 +406,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal,
const view_balance_secret_device &s_view_balance_dev,
const crypto::key_image &tx_first_key_image,
const std::optional<mx25519_pubkey> &other_enote_ephemeral_pubkey,
RCTOutputEnoteProposal &output_enote_out)
{
// 1. sanity checks
@ -392,20 +416,29 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop
input_context_t input_context;
make_carrot_input_context(tx_first_key_image, input_context);
// 3. s^ctx_sr = H_32(s_vb, D_e, input_context)
// 3. D_e
const bool mismatched_enote_ephemeral_pubkeys = proposal.enote_ephemeral_pubkey &&
other_enote_ephemeral_pubkey &&
memcmp(&*proposal.enote_ephemeral_pubkey, &*other_enote_ephemeral_pubkey, sizeof(mx25519_pubkey));
CHECK_AND_ASSERT_THROW_MES(!mismatched_enote_ephemeral_pubkeys,
"get output proposal internal v1: mismatched enote ephemeral pubkeys provided");
const mx25519_pubkey enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey.value_or(
other_enote_ephemeral_pubkey.value_or(gen_x25519_pubkey()));
// 4. s^ctx_sr = H_32(s_vb, D_e, input_context)
crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver);
s_view_balance_dev.make_internal_sender_receiver_secret(proposal.enote_ephemeral_pubkey,
s_view_balance_dev.make_internal_sender_receiver_secret(enote_ephemeral_pubkey,
input_context,
s_sender_receiver);
// 4. build the output enote address pieces
// 5. build the output enote address pieces
encrypted_payment_id_t dummy_encrypted_payment_id;
get_output_proposal_parts(s_sender_receiver,
proposal.destination_address_spend_pubkey,
null_payment_id,
proposal.amount,
proposal.enote_type,
proposal.enote_ephemeral_pubkey,
enote_ephemeral_pubkey,
input_context,
false, // coinbase_amount_commitment
output_enote_out.amount_blinding_factor,
@ -414,21 +447,21 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop
output_enote_out.enote.amount_enc,
dummy_encrypted_payment_id);
// 5. vt = H_3(s_vb || input_context || Ko)
// 6. vt = H_3(s_vb || input_context || Ko)
s_view_balance_dev.make_internal_view_tag(input_context,
output_enote_out.enote.onetime_address,
output_enote_out.enote.view_tag);
// 6. anchor = given message OR 0s, if not available
// 7. anchor = given message OR 0s, if not available
const janus_anchor_t anchor = proposal.internal_message.value_or(janus_anchor_t{});
// 7. encrypt anchor: anchor_enc = anchor XOR m_anchor
// 8. encrypt anchor: anchor_enc = anchor XOR m_anchor
output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(anchor,
s_sender_receiver,
output_enote_out.enote.onetime_address);
// 8. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
// 9. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote.enote_ephemeral_pubkey = enote_ephemeral_pubkey;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
output_enote_out.amount = proposal.amount;
}

View File

@ -36,6 +36,7 @@
#include "destination.h"
#include "device.h"
#include "ringct/rctTypes.h"
#include "common/variant.h"
//third party headers
@ -75,8 +76,8 @@ struct CarrotPaymentProposalSelfSendV1 final
/// enote_type
CarrotEnoteType enote_type;
/// enote ephemeral pubkey: xr G
mx25519_pubkey enote_ephemeral_pubkey;
/// enote ephemeral pubkey: D_e
std::optional<mx25519_pubkey> enote_ephemeral_pubkey;
/// anchor: arbitrary, pre-encrypted message for _internal_ selfsends
std::optional<janus_anchor_t> internal_message;
};
@ -133,12 +134,14 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
* param: k_view_dev -
* param: account_spend_pubkey -
* param: tx_first_key_image -
* param: other_enote_ephemeral_pubkey -
* outparam: output_enote_out -
*/
void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal,
const view_incoming_key_device &k_view_dev,
const crypto::public_key &account_spend_pubkey,
const crypto::key_image &tx_first_key_image,
const std::optional<mx25519_pubkey> &other_enote_ephemeral_pubkey,
RCTOutputEnoteProposal &output_enote_out);
/**
* brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal)
@ -146,12 +149,13 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
* param: s_view_balance_dev -
* param: account_spend_pubkey -
* param: tx_first_key_image -
* param: other_enote_ephemeral_pubkey -
* outparam: output_enote_out -
* outparam: partial_memo_out -
*/
void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal,
const view_balance_secret_device &s_view_balance_dev,
const crypto::key_image &tx_first_key_image,
const std::optional<mx25519_pubkey> &other_enote_ephemeral_pubkey,
RCTOutputEnoteProposal &output_enote_out);
/**
* brief: gen_jamtis_payment_proposal_v1 - generate a random proposal

View File

@ -543,6 +543,7 @@ TEST(carrot_core, main_address_special_scan_completeness)
keys.k_view_dev,
keys.account_spend_pubkey,
tx_first_key_image,
std::nullopt,
enote_proposal);
ASSERT_EQ(proposal.amount, enote_proposal.amount);
@ -627,6 +628,7 @@ TEST(carrot_core, subaddress_special_scan_completeness)
keys.k_view_dev,
keys.account_spend_pubkey,
tx_first_key_image,
std::nullopt,
enote_proposal);
ASSERT_EQ(proposal.amount, enote_proposal.amount);
@ -716,6 +718,7 @@ TEST(carrot_core, main_address_internal_scan_completeness)
get_output_proposal_internal_v1(proposal,
keys.s_view_balance_dev,
tx_first_key_image,
std::nullopt,
enote_proposal);
ASSERT_EQ(proposal.amount, enote_proposal.amount);
@ -792,6 +795,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness)
get_output_proposal_internal_v1(proposal,
keys.s_view_balance_dev,
tx_first_key_image,
std::nullopt,
enote_proposal);
ASSERT_EQ(proposal.amount, enote_proposal.amount);