draft output set finalization code

This commit is contained in:
jeffro256 2024-12-02 10:27:41 -06:00
parent 1604edf301
commit 965a5d88c9
No known key found for this signature in database
GPG Key ID: 6F79797A6E392442
7 changed files with 471 additions and 161 deletions

View File

@ -35,6 +35,7 @@ set(carrot_core_sources
device_ram_borrowed.cpp
enote_utils.cpp
hash_functions.cpp
output_set_finalization.cpp
payment_proposal.cpp)
monero_find_all_headers(carrot_core_headers, "${CMAKE_CURRENT_SOURCE_DIR}")

View File

@ -66,4 +66,7 @@ static constexpr const unsigned char CARROT_DOMAIN_SEP_GENERATE_ADDRESS_SECRET[]
static constexpr const unsigned char CARROT_DOMAIN_SEP_ADDRESS_INDEX_GEN[] = "Carrot address index generator";
static constexpr const unsigned char CARROT_DOMAIN_SEP_SUBADDRESS_SCALAR[] = "Carrot subaddress scalar";
// Carrot misc constants
static constexpr const unsigned int CARROT_MIN_TX_OUTPUTS = 2;
static constexpr const unsigned int CARROT_MAX_TX_OUTPUTS = 16;
} //namespace carrot

View File

@ -0,0 +1,228 @@
// Copyright (c) 2024, 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.
//paired header
#include "output_set_finalization.h"
//local headers
#include "common/container_helpers.h"
#include "enote_utils.h"
#include "misc_log_ex.h"
//third party headers
//standard headers
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "carrot"
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 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;
if (num_outputs == 0)
{
ASSERT_MES_AND_THROW("get additional output type: set contains 0 outputs");
}
else if (already_completed)
{
return std::nullopt;
}
else if (num_outputs == 1)
{
if (num_selfsend == 0)
{
return AdditionalOutputType::CHANGE_SHARED;
}
else if (!remaining_change)
{
return AdditionalOutputType::DUMMY;
}
else // num_selfsend == 1 && remaining_change
{
if (have_payment_type_selfsend)
{
return AdditionalOutputType::CHANGE_SHARED;
}
else
{
return AdditionalOutputType::PAYMENT_SHARED;
}
}
}
else if (num_outputs < CARROT_MAX_TX_OUTPUTS)
{
return AdditionalOutputType::CHANGE_UNIQUE;
}
else // num_outputs >= CARROT_MAX_TX_OUTPUTS
{
ASSERT_MES_AND_THROW("get additional output type: "
"set needs finalization but already contains too many outputs");
}
}
//-------------------------------------------------------------------------------------------------------------------
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 bool have_payment_type_selfsend,
const crypto::public_key &change_address_spend_pubkey,
const crypto::x25519_pubkey &other_enote_ephemeral_pubkey)
{
const std::optional<AdditionalOutputType> additional_output_type = get_additional_output_type(
num_outgoing,
num_selfsend,
remaining_change,
have_payment_type_selfsend
);
if (!additional_output_type)
return {};
switch (*additional_output_type)
{
case AdditionalOutputType::PAYMENT_SHARED:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.enote_type = CarrotEnoteType::PAYMENT,
.enote_ephemeral_pubkey = other_enote_ephemeral_pubkey
};
case AdditionalOutputType::CHANGE_SHARED:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.enote_type = CarrotEnoteType::CHANGE,
.enote_ephemeral_pubkey = other_enote_ephemeral_pubkey
};
case AdditionalOutputType::CHANGE_UNIQUE:
return CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = remaining_change,
.enote_type = CarrotEnoteType::CHANGE,
.enote_ephemeral_pubkey = crypto::x25519_pubkey_gen()
};
case AdditionalOutputType::DUMMY:
return CarrotPaymentProposalV1{
.destination = gen_carrot_main_address_v1(),
.amount = 0,
.randomness = gen_janus_anchor()
};
}
ASSERT_MES_AND_THROW("get additional output proposal: unrecognized additional output type");
}
//-------------------------------------------------------------------------------------------------------------------
void get_output_enote_proposals(std::vector<CarrotPaymentProposalV1> &&normal_payment_proposals,
std::vector<CarrotPaymentProposalSelfSendV1> &&selfsend_payment_proposals,
const view_balance_secret_device &s_view_balance_dev,
const crypto::key_image &tx_first_key_image,
std::vector<RCTOutputEnoteProposal> &output_enote_proposals_out,
encrypted_payment_id_t &encrypted_payment_id_out)
{
output_enote_proposals_out.clear();
encrypted_payment_id_out = null_payment_id;
// assert payment proposals numbers
const size_t num_proposals = normal_payment_proposals.size() + selfsend_payment_proposals.size();
CHECK_AND_ASSERT_THROW_MES(num_proposals >= CARROT_MIN_TX_OUTPUTS,
"get output enote proposals: too few payment proposals");
CHECK_AND_ASSERT_THROW_MES(num_proposals <= CARROT_MAX_TX_OUTPUTS,
"get output enote proposals: too many payment proposals");
CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposals.size(),
"get output enote proposals: no selfsend payment proposal");
// assert there is a max of 1 integrated address payment proposals
size_t num_integrated = 0;
for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals)
if (normal_payment_proposal.destination.payment_id != null_payment_id)
++num_integrated;
CHECK_AND_ASSERT_THROW_MES(num_integrated <= 1,
"get output enote proposals: only one integrated address is allowed per tx output set");
// input_context = "R" || KI_1
input_context_t input_context;
make_carrot_input_context(tx_first_key_image, input_context);
// construct normal enotes
output_enote_proposals_out.reserve(num_proposals);
for (size_t i = 0; i < normal_payment_proposals.size(); ++i)
{
encrypted_payment_id_t encrypted_payment_id;
get_output_proposal_normal_v1(normal_payment_proposals[i],
tx_first_key_image,
tools::add_element(output_enote_proposals_out),
encrypted_payment_id);
// set pid to the first payment proposal or only integrated proposal
const bool is_first = i == 0;
const bool is_integrated = normal_payment_proposals[i].destination.payment_id != null_payment_id;
if (is_first || is_integrated)
encrypted_payment_id_out = encrypted_payment_id;
}
// in the case that the pid is ambiguous, set it to random bytes
const bool ambiguous_pid_destination = num_integrated == 0 && normal_payment_proposals.size() > 1;
if (ambiguous_pid_destination)
encrypted_payment_id_out = gen_payment_id();
// construct selfsend enotes
for (const CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals)
{
get_output_proposal_internal_v1(selfsend_payment_proposal,
s_view_balance_dev,
tx_first_key_image,
tools::add_element(output_enote_proposals_out));
}
// sort enotes by D_e and assert uniqueness properties of D_e
const auto sort_by_ephemeral_pubkey = [](const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b) -> bool
{
return memcmp(&a.enote.enote_ephemeral_pubkey,
&b.enote.enote_ephemeral_pubkey,
sizeof(crypto::x25519_pubkey)) < 0;
};
std::sort(output_enote_proposals_out.begin(), output_enote_proposals_out.end(), sort_by_ephemeral_pubkey);
const bool has_unique_ephemeral_pubkeys = tools::is_sorted_and_unique(output_enote_proposals_out,
sort_by_ephemeral_pubkey);
CHECK_AND_ASSERT_THROW_MES(!(num_proposals == 2 && has_unique_ephemeral_pubkeys),
"get output enote proposals: a 2-out set needs to share an ephemeral pubkey, but this 2-out set doesn't");
CHECK_AND_ASSERT_THROW_MES(!(num_proposals != 2 && !has_unique_ephemeral_pubkeys),
"get output enote proposals: this >2-out set contains duplicate enote ephemeral pubkeys");
// sort enotes by Ko
std::sort(output_enote_proposals_out.begin(), output_enote_proposals_out.end());
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace carrot

View File

@ -0,0 +1,99 @@
// Copyright (c) 2024, 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 Utilities for constructing output proposal sets that adhere to Carrot rules
#pragma once
//local headers
#include "carrot_enote_types.h"
#include "common/variant.h"
#include "config.h"
#include "payment_proposal.h"
#include "ringct/rctTypes.h"
//third party headers
//standard headers
#include <optional>
//forward declarations
namespace carrot
{
enum class AdditionalOutputType
{
PAYMENT_SHARED, // selfsend proposal with enote_type="payment" with a shared D_e
CHANGE_SHARED, // selfsend proposal with enote_type="change" with a shared D_e
CHANGE_UNIQUE, // selfsend proposal with enote_type="change" with a unique D_e
DUMMY // outgoing proposal to a random address
};
/**
* 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: 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 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: 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 bool have_payment_type_selfsend,
const crypto::public_key &change_address_spend_pubkey,
const crypto::x25519_pubkey &other_enote_ephemeral_pubkey);
/**
* brief: get_output_enote_proposals - convert payment proposals into output enote proposals
*/
void get_output_enote_proposals(std::vector<CarrotPaymentProposalV1> &&normal_payment_proposals,
std::vector<CarrotPaymentProposalSelfSendV1> &&selfsend_payment_proposals,
const view_balance_secret_device &s_view_balance_dev,
const crypto::key_image &tx_first_key_image,
std::vector<RCTOutputEnoteProposal> &output_enote_proposals_out,
encrypted_payment_id_t &encrypted_payment_id_out);
} //namespace carrot

View File

@ -84,7 +84,7 @@ static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &propos
const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal, input_context);
// 2. make D_e
get_enote_ephemeral_pubkey(proposal, input_context, enote_ephemeral_pubkey_out);
enote_ephemeral_pubkey_out = get_enote_ephemeral_pubkey(proposal, input_context);
// 3. s_sr = 8 d_e ConvertPointE(K^j_v)
make_carrot_uncontextualized_shared_key_sender(enote_ephemeral_privkey,
@ -191,22 +191,29 @@ bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentPro
a.enote_ephemeral_pubkey == b.enote_ephemeral_pubkey;
}
//-------------------------------------------------------------------------------------------------------------------
void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context,
crypto::x25519_pubkey &enote_ephemeral_pubkey_out)
bool operator<(const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b)
{
return memcmp(&a.enote.onetime_address, &b.enote.onetime_address, sizeof(crypto::public_key)) < 0;
}
//-------------------------------------------------------------------------------------------------------------------
crypto::x25519_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)};
crypto::x25519_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_out);
enote_ephemeral_pubkey);
else
// D_e = d_e B
make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey,
enote_ephemeral_pubkey_out);
enote_ephemeral_pubkey);
return enote_ephemeral_pubkey;
}
//-------------------------------------------------------------------------------------------------------------------
void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal,
@ -266,10 +273,8 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal,
//-------------------------------------------------------------------------------------------------------------------
void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
const crypto::key_image &tx_first_key_image,
CarrotEnoteV1 &output_enote_out,
encrypted_payment_id_t &encrypted_payment_id_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out)
RCTOutputEnoteProposal &output_enote_out,
encrypted_payment_id_t &encrypted_payment_id_out)
{
// 1. sanity checks
CHECK_AND_ASSERT_THROW_MES(proposal.randomness != null_anchor,
@ -283,7 +288,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
crypto::x25519_pubkey s_sender_receiver_unctx; auto dhe_wiper = auto_wiper(s_sender_receiver_unctx);
get_normal_proposal_ecdh_parts(proposal,
input_context,
output_enote_out.enote_ephemeral_pubkey,
output_enote_out.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
// 4. build the output enote address pieces
@ -293,34 +298,32 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
proposal.destination.payment_id,
proposal.amount,
CarrotEnoteType::PAYMENT,
output_enote_out.enote_ephemeral_pubkey,
output_enote_out.enote.enote_ephemeral_pubkey,
input_context,
false, // coinbase_amount_commitment
s_sender_receiver,
amount_blinding_factor_out,
output_enote_out.amount_commitment,
output_enote_out.onetime_address,
output_enote_out.amount_enc,
output_enote_out.amount_blinding_factor,
output_enote_out.enote.amount_commitment,
output_enote_out.enote.onetime_address,
output_enote_out.enote.amount_enc,
encrypted_payment_id_out,
output_enote_out.view_tag);
output_enote_out.enote.view_tag);
// 5. anchor_enc = anchor XOR m_anchor
output_enote_out.anchor_enc = encrypt_carrot_anchor(proposal.randomness,
output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(proposal.randomness,
s_sender_receiver,
output_enote_out.onetime_address);
output_enote_out.enote.onetime_address);
// 6. save the amount and first key image
amount_out = proposal.amount;
output_enote_out.tx_first_key_image = tx_first_key_image;
output_enote_out.amount = proposal.amount;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
}
//-------------------------------------------------------------------------------------------------------------------
void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal,
const view_incoming_key_device &k_view_dev,
const crypto::public_key &primary_address_spend_pubkey,
const crypto::key_image &tx_first_key_image,
CarrotEnoteV1 &output_enote_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out)
RCTOutputEnoteProposal &output_enote_out)
{
// 1. sanity checks
// @TODO
@ -347,38 +350,36 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
input_context,
false, // coinbase_amount_commitment
s_sender_receiver,
amount_blinding_factor_out,
output_enote_out.amount_commitment,
output_enote_out.onetime_address,
output_enote_out.amount_enc,
output_enote_out.amount_blinding_factor,
output_enote_out.enote.amount_commitment,
output_enote_out.enote.onetime_address,
output_enote_out.enote.amount_enc,
dummy_encrypted_payment_id,
output_enote_out.view_tag);
output_enote_out.enote.view_tag);
// 5. 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,
input_context,
output_enote_out.onetime_address,
output_enote_out.enote.onetime_address,
primary_address_spend_pubkey,
janus_anchor_special);
// 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor
output_enote_out.anchor_enc = encrypt_carrot_anchor(janus_anchor_special,
output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(janus_anchor_special,
s_sender_receiver,
output_enote_out.onetime_address);
output_enote_out.enote.onetime_address);
// 7. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
output_enote_out.tx_first_key_image = tx_first_key_image;
amount_out = proposal.amount;
output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
output_enote_out.amount = proposal.amount;
}
//-------------------------------------------------------------------------------------------------------------------
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,
CarrotEnoteV1 &output_enote_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out)
RCTOutputEnoteProposal &output_enote_out)
{
// 1. sanity checks
// @TODO
@ -403,24 +404,24 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop
proposal.enote_ephemeral_pubkey,
input_context,
false, // coinbase_amount_commitment
amount_blinding_factor_out,
output_enote_out.amount_commitment,
output_enote_out.onetime_address,
output_enote_out.amount_enc,
output_enote_out.amount_blinding_factor,
output_enote_out.enote.amount_commitment,
output_enote_out.enote.onetime_address,
output_enote_out.enote.amount_enc,
dummy_encrypted_payment_id);
// 5. vt = H_3(s_vb || input_context || Ko)
s_view_balance_dev.make_internal_view_tag(input_context,
output_enote_out.onetime_address,
output_enote_out.view_tag);
output_enote_out.enote.onetime_address,
output_enote_out.enote.view_tag);
// 4. generate random encrypted anchor
output_enote_out.anchor_enc = gen_janus_anchor();
output_enote_out.enote.anchor_enc = gen_janus_anchor();
// 5. save the enote ephemeral pubkey, first tx key image, and amount
output_enote_out.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
output_enote_out.tx_first_key_image = tx_first_key_image;
amount_out = proposal.amount;
output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey;
output_enote_out.enote.tx_first_key_image = tx_first_key_image;
output_enote_out.amount = proposal.amount;
}
//-------------------------------------------------------------------------------------------------------------------
CarrotPaymentProposalV1 gen_carrot_payment_proposal_v1(const bool is_subaddress,

View File

@ -79,20 +79,31 @@ struct CarrotPaymentProposalSelfSendV1 final
crypto::x25519_pubkey enote_ephemeral_pubkey;
};
struct RCTOutputEnoteProposal
{
CarrotEnoteV1 enote;
// we need this opening information to make amount range proofs
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
};
/// equality operators
bool operator==(const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1 &b);
/// equality operators
bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentProposalSelfSendV1 &b);
/// comparison operators
bool operator<(const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b);
/**
* brief: get_enote_ephemeral_pubkey - get the proposal's enote ephemeral pubkey D_e
* param: proposal -
* param: input_context -
* outparam: enote_ephemeral_pubkey_out -
* return: D_e
*/
void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context,
crypto::x25519_pubkey &enote_ephemeral_pubkey_out);
crypto::x25519_pubkey get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal,
const input_context_t &input_context);
/**
* brief: get_coinbase_output_proposal_v1 - convert the carrot proposal to a coinbase output proposal
* param: proposal -
@ -109,15 +120,11 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal,
* param: tx_first_key_image -
* outparam: output_enote_out -
* outparam: encrypted_payment_id_out - pid_enc
* outparam: amount_out - used to open commitment C_a
* outparam: amount_blinding_factor_out - used to open commitment C_a
*/
void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
const crypto::key_image &tx_first_key_image,
CarrotEnoteV1 &output_enote_out,
encrypted_payment_id_t &encrypted_payment_id_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out);
RCTOutputEnoteProposal &output_enote_out,
encrypted_payment_id_t &encrypted_payment_id_out);
/**
* brief: get_output_proposal_v1 - convert the carrot proposal to an output proposal (external selfsend)
* param: proposal -
@ -125,16 +132,12 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal,
* param: primary_address_spend_pubkey -
* param: tx_first_key_image -
* outparam: output_enote_out -
* outparam: amount_out - used to open commitment C_a
* outparam: amount_blinding_factor_out - used to open commitment C_a
*/
void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal,
const view_incoming_key_device &k_view_dev,
const crypto::public_key &primary_address_spend_pubkey,
const crypto::key_image &tx_first_key_image,
CarrotEnoteV1 &output_enote_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out);
RCTOutputEnoteProposal &output_enote_out);
/**
* brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal)
* param: proposal -
@ -143,15 +146,11 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo
* param: tx_first_key_image -
* outparam: output_enote_out -
* outparam: partial_memo_out -
* outparam: amount_out - used to open commitment C_a
* outparam: amount_blinding_factor_out - used to open commitment C_a
*/
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,
CarrotEnoteV1 &output_enote_out,
rct::xmr_amount &amount_out,
crypto::secret_key &amount_blinding_factor_out);
RCTOutputEnoteProposal &output_enote_out);
/**
* brief: gen_jamtis_payment_proposal_v1 - generate a random proposal
* param: is_subaddress - whether to generate a proposal to subaddress

View File

@ -161,23 +161,20 @@ TEST(carrot_core, main_address_normal_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
RCTOutputEnoteProposal enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
get_output_proposal_normal_v1(proposal,
tx_first_key_image,
enote,
encrypted_payment_id,
amount,
amount_blinding_factor);
enote_proposal,
encrypted_payment_id);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::x25519_pubkey s_sender_receiver_unctx;
make_carrot_uncontextualized_shared_key_receiver(keys.k_view,
enote.enote_ephemeral_pubkey,
enote_proposal.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
crypto::secret_key recovered_sender_extension_g;
@ -187,7 +184,7 @@ 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,
const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote,
encrypted_payment_id,
s_sender_receiver_unctx,
keys.k_view_dev,
@ -204,8 +201,8 @@ TEST(carrot_core, main_address_normal_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(null_payment_id, recovered_payment_id);
EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type);
@ -215,7 +212,7 @@ TEST(carrot_core, main_address_normal_scan_completeness)
rct::rct2sk(rct::I),
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_core, subaddress_normal_scan_completeness)
@ -241,23 +238,20 @@ TEST(carrot_core, subaddress_normal_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
RCTOutputEnoteProposal enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
get_output_proposal_normal_v1(proposal,
tx_first_key_image,
enote,
encrypted_payment_id,
amount,
amount_blinding_factor);
enote_proposal,
encrypted_payment_id);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::x25519_pubkey s_sender_receiver_unctx;
make_carrot_uncontextualized_shared_key_receiver(keys.k_view,
enote.enote_ephemeral_pubkey,
enote_proposal.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
crypto::secret_key recovered_sender_extension_g;
@ -267,7 +261,7 @@ 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,
const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote,
encrypted_payment_id,
s_sender_receiver_unctx,
keys.k_view_dev,
@ -284,8 +278,8 @@ TEST(carrot_core, subaddress_normal_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(null_payment_id, recovered_payment_id);
EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type);
@ -308,7 +302,7 @@ TEST(carrot_core, subaddress_normal_scan_completeness)
subaddr_scalar,
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_core, integrated_address_normal_scan_completeness)
@ -329,23 +323,20 @@ TEST(carrot_core, integrated_address_normal_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
RCTOutputEnoteProposal enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
get_output_proposal_normal_v1(proposal,
tx_first_key_image,
enote,
encrypted_payment_id,
amount,
amount_blinding_factor);
enote_proposal,
encrypted_payment_id);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::x25519_pubkey s_sender_receiver_unctx;
make_carrot_uncontextualized_shared_key_receiver(keys.k_view,
enote.enote_ephemeral_pubkey,
enote_proposal.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
crypto::secret_key recovered_sender_extension_g;
@ -355,7 +346,7 @@ 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,
const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote,
encrypted_payment_id,
s_sender_receiver_unctx,
keys.k_view_dev,
@ -372,8 +363,8 @@ TEST(carrot_core, integrated_address_normal_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(integrated_address.payment_id, recovered_payment_id);
EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type);
@ -383,7 +374,7 @@ TEST(carrot_core, integrated_address_normal_scan_completeness)
rct::rct2sk(rct::I),
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_core, main_address_special_scan_completeness)
@ -407,23 +398,20 @@ TEST(carrot_core, main_address_special_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
RCTOutputEnoteProposal enote_proposal;
get_output_proposal_special_v1(proposal,
keys.k_view_dev,
keys.account_spend_pubkey,
tx_first_key_image,
enote,
amount,
amount_blinding_factor);
enote_proposal);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::x25519_pubkey s_sender_receiver_unctx;
make_carrot_uncontextualized_shared_key_receiver(keys.k_view,
enote.enote_ephemeral_pubkey,
enote_proposal.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
crypto::secret_key recovered_sender_extension_g;
@ -433,7 +421,7 @@ 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,
const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote,
std::nullopt,
s_sender_receiver_unctx,
keys.k_view_dev,
@ -450,8 +438,8 @@ TEST(carrot_core, main_address_special_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(null_payment_id, recovered_payment_id);
EXPECT_EQ(enote_type, recovered_enote_type);
@ -461,7 +449,7 @@ TEST(carrot_core, main_address_special_scan_completeness)
rct::rct2sk(rct::I),
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
}
//----------------------------------------------------------------------------------------------------------------------
@ -494,23 +482,20 @@ TEST(carrot_core, subaddress_special_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
RCTOutputEnoteProposal enote_proposal;
get_output_proposal_special_v1(proposal,
keys.k_view_dev,
keys.account_spend_pubkey,
tx_first_key_image,
enote,
amount,
amount_blinding_factor);
enote_proposal);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::x25519_pubkey s_sender_receiver_unctx;
make_carrot_uncontextualized_shared_key_receiver(keys.k_view,
enote.enote_ephemeral_pubkey,
enote_proposal.enote.enote_ephemeral_pubkey,
s_sender_receiver_unctx);
crypto::secret_key recovered_sender_extension_g;
@ -520,7 +505,7 @@ 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,
const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote,
std::nullopt,
s_sender_receiver_unctx,
keys.k_view_dev,
@ -537,8 +522,8 @@ TEST(carrot_core, subaddress_special_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(null_payment_id, recovered_payment_id);
EXPECT_EQ(enote_type, recovered_enote_type);
@ -555,13 +540,13 @@ TEST(carrot_core, subaddress_special_scan_completeness)
j_major,
j_minor,
subaddr_scalar);
EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend,
keys.k_generate_image,
subaddr_scalar,
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
}
//----------------------------------------------------------------------------------------------------------------------
@ -586,18 +571,15 @@ TEST(carrot_core, main_address_internal_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
RCTOutputEnoteProposal enote_proposal;
get_output_proposal_internal_v1(proposal,
keys.s_view_balance_dev,
tx_first_key_image,
enote,
amount,
amount_blinding_factor);
enote_proposal);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::secret_key recovered_sender_extension_g;
crypto::secret_key recovered_sender_extension_t;
@ -605,7 +587,7 @@ TEST(carrot_core, main_address_internal_scan_completeness)
rct::xmr_amount recovered_amount;
crypto::secret_key recovered_amount_blinding_factor;
CarrotEnoteType recovered_enote_type;
const bool scan_success = try_scan_carrot_enote_internal(enote,
const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote,
keys.s_view_balance_dev,
recovered_sender_extension_g,
recovered_sender_extension_t,
@ -618,8 +600,8 @@ TEST(carrot_core, main_address_internal_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(enote_type, recovered_enote_type);
// check spendability
@ -628,7 +610,7 @@ TEST(carrot_core, main_address_internal_scan_completeness)
rct::rct2sk(rct::I),
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
}
//----------------------------------------------------------------------------------------------------------------------
@ -661,18 +643,15 @@ TEST(carrot_core, subaddress_internal_scan_completeness)
const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen());
CarrotEnoteV1 enote;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
RCTOutputEnoteProposal enote_proposal;
get_output_proposal_internal_v1(proposal,
keys.s_view_balance_dev,
tx_first_key_image,
enote,
amount,
amount_blinding_factor);
enote_proposal);
ASSERT_EQ(proposal.amount, amount);
ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor)));
ASSERT_EQ(proposal.amount, enote_proposal.amount);
const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor));
ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment);
crypto::secret_key recovered_sender_extension_g;
crypto::secret_key recovered_sender_extension_t;
@ -680,7 +659,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness)
rct::xmr_amount recovered_amount;
crypto::secret_key recovered_amount_blinding_factor;
CarrotEnoteType recovered_enote_type;
const bool scan_success = try_scan_carrot_enote_internal(enote,
const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote,
keys.s_view_balance_dev,
recovered_sender_extension_g,
recovered_sender_extension_t,
@ -693,8 +672,8 @@ TEST(carrot_core, subaddress_internal_scan_completeness)
// check recovered data
EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey);
EXPECT_EQ(amount, recovered_amount);
EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(proposal.amount, recovered_amount);
EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor);
EXPECT_EQ(enote_type, recovered_enote_type);
// check spendability
@ -716,7 +695,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness)
subaddr_scalar,
recovered_sender_extension_g,
recovered_sender_extension_t,
enote.onetime_address));
enote_proposal.enote.onetime_address));
}
}
//----------------------------------------------------------------------------------------------------------------------