carrot_impl<->fcmp++ bridge tests

This commit is contained in:
jeffro256 2025-03-28 04:42:03 -05:00
parent e1d2d9e5cc
commit af360d7bee
No known key found for this signature in database
GPG key ID: 6F79797A6E392442
13 changed files with 1110 additions and 23 deletions

View file

@ -49,7 +49,7 @@ rct::key calculate_amount_commitment(const lazy_amount_commitment_t &lazy_amount
rct::key operator()(const rct::key &C) const { return C; }
rct::key operator()(const std::pair<rct::xmr_amount, rct::key> &op) const
{ return rct::commit(op.first, op.second); }
rct::key operator()(const rct::xmr_amount &a) const { return rct::zeroCommit(a); }
rct::key operator()(const rct::xmr_amount &a) const { return rct::zeroCommitVartime(a); }
};
return lazy_amount_commitment.visit(lazy_amount_commitment_visitor{});

View file

@ -29,6 +29,7 @@
set(carrot_impl_sources
address_device_ram_borrowed.cpp
address_utils_compat.cpp
carrot_tx_builder_inputs.cpp
carrot_tx_builder_utils.cpp
carrot_tx_format_utils.cpp
input_selection.cpp
@ -43,7 +44,8 @@ monero_add_library(carrot_impl
target_link_libraries(carrot_impl
PUBLIC
carrot_core
cryptonote_format_utils_basic
cryptonote_basic
fcmp_pp
PRIVATE
${EXTRA_LIBRARIES})

View file

@ -0,0 +1,654 @@
// 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 "carrot_tx_builder_inputs.h"
//local headers
#include "carrot_core/account_secrets.h"
#include "carrot_core/address_utils.h"
#include "carrot_core/config.h"
#include "carrot_core/enote_utils.h"
#include "carrot_core/scan.h"
#include "crypto/generators.h"
#include "fcmp_pp/prove.h"
#include "misc_log_ex.h"
#include "ringct/rctOps.h"
//third party headers
//standard headers
#include <algorithm>
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "carrot_impl"
namespace carrot
{
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
rct::key load_key(const std::uint8_t bytes[32])
{
rct::key k;
memcpy(k.bytes, bytes, sizeof(k));
return k;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
void store_key(std::uint8_t bytes[32], const rct::key &k)
{
memcpy(bytes, k.bytes, 32);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static FcmpInputCompressed calculate_fcmp_input_for_rerandomizations(const crypto::public_key &onetime_address,
const rct::key &amount_commitment,
const rct::key &r_o,
const rct::key &r_i,
const rct::key &r_r_i,
const rct::key &r_c)
{
return fcmp_pp::calculate_fcmp_input_for_rerandomizations(onetime_address,
rct::rct2pt(amount_commitment),
rct::rct2sk(r_o),
rct::rct2sk(r_i),
rct::rct2sk(r_r_i),
rct::rct2sk(r_c));
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void make_sal_proof_nominal_address_naive(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const crypto::secret_key &address_privkey_g,
const crypto::secret_key &address_privkey_t,
const crypto::secret_key &sender_extension_g,
const crypto::secret_key &sender_extension_t,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
// O = x G + y T
// x = k^{j,g}_addr + k^g_o
crypto::secret_key x;
sc_add(to_bytes(x),
to_bytes(address_privkey_g),
to_bytes(sender_extension_g));
// y = k^{j,t}_addr + k^t_o
crypto::secret_key y;
sc_add(to_bytes(y),
to_bytes(address_privkey_t),
to_bytes(sender_extension_t));
std::tie(sal_proof_out, key_image_out) = fcmp_pp::prove_sal(signable_tx_hash,
x,
y,
rerandomized_output);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void make_sal_proof_nominal_address_carrot_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &address_privkey_g,
const crypto::secret_key &address_privkey_t,
const crypto::public_key &account_spend_pubkey,
const view_balance_secret_device *s_view_balance_dev,
const view_incoming_key_device *k_view_incoming_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
CHECK_AND_ASSERT_THROW_MES(verify_rerandomized_output_basic(rerandomized_output,
opening_hint.source_enote.onetime_address,
opening_hint.source_enote.amount_commitment),
"make sal proof nominal address carrot v1: rerandomized output does not verify");
// We scan scan here as a defensive programming measure against naive-scanner burning bugs,
// malicious-scanner burning bugs, and malicious-scanner subaddress swaps. However, if you want
// a user to confirm other details about the enote they're spending (e.g. amount, payment ID,
// subaddress index, internal message, enote type, TXID), you're going to have to pre-scan this
// enote and implement the checks yourself before calling this function. Hardware wallet
// developers: if you want your users to keep their hard-earned funds, don't skip cold-side
// enote scanning in Carrot enotes! Legacy enotes aren't SAFU from malicious-scanner burning
// anyways since K_o doesn't bind to C_a.
crypto::secret_key sender_extension_g;
crypto::secret_key sender_extension_t;
crypto::public_key address_spend_pubkey;
rct::xmr_amount amount;
crypto::secret_key amount_blinding_factor;
payment_id_t payment_id;
CarrotEnoteType enote_type;
janus_anchor_t internal_message;
// first, try do an internal scan of the enote
bool scanned = false;
if (s_view_balance_dev)
{
scanned = try_scan_carrot_enote_internal(opening_hint.source_enote,
*s_view_balance_dev,
sender_extension_g,
sender_extension_t,
address_spend_pubkey,
amount,
amount_blinding_factor,
enote_type,
internal_message);
payment_id = null_payment_id;
}
else
{
internal_message = janus_anchor_t{};
}
// if that didn't work, try an external scan
if (!scanned && k_view_incoming_dev)
{
scanned = try_ecdh_and_scan_carrot_enote_external(opening_hint.source_enote,
opening_hint.encrypted_payment_id,
*k_view_incoming_dev,
account_spend_pubkey,
sender_extension_g,
sender_extension_t,
address_spend_pubkey,
amount,
amount_blinding_factor,
payment_id,
enote_type);
}
CHECK_AND_ASSERT_THROW_MES(scanned,
"make sal proof nominal address carrot v1: cannot spend enote because of a scan failure");
make_sal_proof_nominal_address_naive(signable_tx_hash,
rerandomized_output,
address_privkey_g,
address_privkey_t,
sender_extension_g,
sender_extension_t,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void make_sal_proof_nominal_address_carrot_coinbase_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotCoinbaseOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &address_privkey_g,
const crypto::secret_key &address_privkey_t,
const crypto::public_key &account_spend_pubkey,
const view_incoming_key_device &k_view_incoming_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
const rct::key coinbase_amount_commitment = rct::zeroCommitVartime(opening_hint.source_enote.amount);
CHECK_AND_ASSERT_THROW_MES(verify_rerandomized_output_basic(rerandomized_output,
opening_hint.source_enote.onetime_address,
coinbase_amount_commitment),
"make sal proof nominal address carrot coinbase v1: rerandomized output does not verify");
// We scan scan here as a defensive programming measure against naive-scanner burning bugs and
// malicious-scanner burning bugs. However, if you want a user to confirm other details about
// the coinbase enote they're spending (e.g. amount, block index), you're going to have to
// pre-scan this enote and implement the checks yourself before calling this function. Hardware
// wallet developers: if you want your users to keep their hard-earned funds, don't skip
// cold-side enote scanning in Carrot enotes! Legacy enotes aren't SAFU from malicious-scanner
// burning anyways since K_o doesn't bind to C_a.
crypto::secret_key sender_extension_g;
crypto::secret_key sender_extension_t;
crypto::public_key address_spend_pubkey;
// first, try do an internal scan of the enote
const bool scanned = try_ecdh_and_scan_carrot_coinbase_enote(opening_hint.source_enote,
k_view_incoming_dev,
account_spend_pubkey,
sender_extension_g,
sender_extension_t);
CHECK_AND_ASSERT_THROW_MES(scanned,
"make sal proof nominal address carrot coinbase v1: cannot spend enote because of a scan failure");
make_sal_proof_nominal_address_naive(signable_tx_hash,
rerandomized_output,
address_privkey_g,
address_privkey_t,
sender_extension_g,
sender_extension_t,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
const crypto::public_key &onetime_address_ref(const OutputOpeningHintVariant &opening_hint)
{
struct onetime_address_ref_visitor: public tools::variant_static_visitor<const crypto::public_key &>
{
const crypto::public_key &operator()(const LegacyOutputOpeningHintV1 &h) const
{ return h.onetime_address; }
const crypto::public_key &operator()(const CarrotOutputOpeningHintV1 &h) const
{ return h.source_enote.onetime_address; }
const crypto::public_key &operator()(const CarrotCoinbaseOutputOpeningHintV1 &h) const
{ return h.source_enote.onetime_address; }
};
return opening_hint.visit(onetime_address_ref_visitor{});
}
//-------------------------------------------------------------------------------------------------------------------
rct::key amount_commitment_ref(const OutputOpeningHintVariant &opening_hint)
{
struct amount_commitment_ref_visitor: public tools::variant_static_visitor<rct::key>
{
rct::key operator()(const LegacyOutputOpeningHintV1 &h) const
{ return rct::commit(h.amount, rct::sk2rct(h.amount_blinding_factor)); }
rct::key operator()(const CarrotOutputOpeningHintV1 &h) const
{ return h.source_enote.amount_commitment; }
rct::key operator()(const CarrotCoinbaseOutputOpeningHintV1 &h) const
{ return rct::zeroCommitVartime(h.source_enote.amount); }
};
return opening_hint.visit(amount_commitment_ref_visitor{});
}
//-------------------------------------------------------------------------------------------------------------------
void make_carrot_rerandomized_outputs_nonrefundable(const std::vector<crypto::public_key> &input_onetime_addresses,
const std::vector<rct::key> &input_amount_commitments,
const std::vector<rct::key> &input_amount_blinding_factors,
const std::vector<rct::key> &output_amount_blinding_factors,
std::vector<FcmpRerandomizedOutputCompressed> &rerandomized_outputs_out)
{
// collect input_amount_commitments as crypto::ec_point
std::vector<crypto::ec_point> input_amount_commitments_pt;
input_amount_commitments_pt.reserve(input_amount_commitments.size());
for (const rct::key &input_amount_commitment : input_amount_commitments)
input_amount_commitments_pt.push_back(rct::rct2pt(input_amount_commitment));
// collect input_amount_blinding_factors as crypto::secret_key
std::vector<crypto::secret_key> input_amount_blinding_factors_sk;
input_amount_blinding_factors_sk.reserve(input_amount_blinding_factors_sk.size());
for (const rct::key &input_amount_blinding_factor : input_amount_blinding_factors)
input_amount_blinding_factors_sk.push_back(rct::rct2sk(input_amount_blinding_factor));
// generate random r_o
std::vector<crypto::secret_key> r_o(input_onetime_addresses.size());
for (size_t i = 0; i < input_onetime_addresses.size(); ++i)
crypto::random32_unbiased(to_bytes(r_o[i]));
// calculate output_amount_blinding_factor_sum = sum(output_amount_blinding_factors)
crypto::secret_key output_amount_blinding_factor_sum;
sc_0(to_bytes(output_amount_blinding_factor_sum));
for (const rct::key &output_amount_blinding_factor : output_amount_blinding_factors)
sc_add(to_bytes(output_amount_blinding_factor_sum),
to_bytes(output_amount_blinding_factor_sum),
output_amount_blinding_factor.bytes);
fcmp_pp::make_balanced_rerandomized_output_set(input_onetime_addresses,
input_amount_commitments_pt,
input_amount_blinding_factors_sk,
r_o,
output_amount_blinding_factor_sum,
rerandomized_outputs_out);
}
//-------------------------------------------------------------------------------------------------------------------
bool verify_rerandomized_output_basic(const FcmpRerandomizedOutputCompressed &rerandomized_output,
const crypto::public_key &onetime_address,
const rct::key &amount_commitment)
{
const FcmpInputCompressed recomputed_input = calculate_fcmp_input_for_rerandomizations(
onetime_address,
amount_commitment,
load_key(rerandomized_output.r_o),
load_key(rerandomized_output.r_i),
load_key(rerandomized_output.r_r_i),
load_key(rerandomized_output.r_c));
return 0 == memcmp(&recomputed_input, &rerandomized_output.input, sizeof(FcmpInputCompressed));
}
//-------------------------------------------------------------------------------------------------------------------
bool verify_openable_rerandomized_output_basic(const CarrotOpenableRerandomizedOutputV1 &openable_rerandomized_output)
{
return verify_rerandomized_output_basic(openable_rerandomized_output.rerandomized_output,
onetime_address_ref(openable_rerandomized_output.opening_hint),
amount_commitment_ref(openable_rerandomized_output.opening_hint));
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_legacy_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const LegacyOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
CHECK_AND_ASSERT_THROW_MES(verify_rerandomized_output_basic(rerandomized_output,
opening_hint.onetime_address,
rct::commit(opening_hint.amount, rct::sk2rct(opening_hint.amount_blinding_factor))),
"make sal proof legacy to legacy v1: rerandomized output does not verify");
// k^{j,}g_addr = k_s + k^j_subext
crypto::secret_key address_privkey_g;
addr_dev.make_legacy_subaddress_extension(opening_hint.subaddr_index.major,
opening_hint.subaddr_index.minor,
address_privkey_g);
sc_add(to_bytes(address_privkey_g),
to_bytes(address_privkey_g),
to_bytes(k_spend));
// note that we pass k_spend as k_generate_image, and leave k_prove_spend as 0
make_sal_proof_nominal_address_naive(signable_tx_hash,
rerandomized_output,
address_privkey_g,
crypto::null_skey,
opening_hint.sender_extension_g,
crypto::null_skey,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_carrot_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
// check that the opening hint tells us to open as a legacy address
const AddressDeriveType derive_type = opening_hint.subaddr_index.derive_type;
CHECK_AND_ASSERT_THROW_MES(derive_type == AddressDeriveType::PreCarrot,
"make sal proof carrot to carrot v1: invalid subaddr derive type: " << static_cast<int>(derive_type));
// k^{j, g}_addr = k_s + k^j_subext
crypto::secret_key address_privkey_g;
addr_dev.make_legacy_subaddress_extension(opening_hint.subaddr_index.index.major,
opening_hint.subaddr_index.index.minor,
address_privkey_g);
sc_add(to_bytes(address_privkey_g),
to_bytes(address_privkey_g),
to_bytes(k_spend));
make_sal_proof_nominal_address_carrot_v1(signable_tx_hash,
rerandomized_output,
opening_hint,
address_privkey_g,
/*address_privkey_t=*/crypto::null_skey,
addr_dev.get_cryptonote_account_spend_pubkey(),
/*s_view_balance_dev=*/nullptr,
&addr_dev,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_carrot_to_carrot_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_balance_secret_device &s_view_balance_dev,
const view_incoming_key_device &k_view_incoming_dev,
const generate_address_secret_device &s_generate_address_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
// check that the opening hint tells us to open as a Carrot address
const AddressDeriveType derive_type = opening_hint.subaddr_index.derive_type;
CHECK_AND_ASSERT_THROW_MES(derive_type == AddressDeriveType::Carrot,
"make sal proof carrot to carrot v1: invalid subaddr derive type: " << static_cast<int>(derive_type));
// K_s = k_gi G + k_ps T
crypto::public_key account_spend_pubkey;
make_carrot_spend_pubkey(k_generate_image, k_prove_spend, account_spend_pubkey);
const std::uint32_t major_index = opening_hint.subaddr_index.index.major;
const std::uint32_t minor_index = opening_hint.subaddr_index.index.minor;
const bool is_subaddress = major_index || minor_index;
crypto::secret_key k_subaddress_scalar;
if (is_subaddress)
{
// s^j_gen = H_32[s_ga](j_major, j_minor)
crypto::secret_key s_address_generator;
s_generate_address_dev.make_index_extension_generator(major_index, minor_index, s_address_generator);
// k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen)
make_carrot_subaddress_scalar(account_spend_pubkey,
s_address_generator,
major_index,
minor_index,
k_subaddress_scalar);
}
else
{
// k^j_subscal = 1
sc_1(to_bytes(k_subaddress_scalar));
}
// k^{j, g}_addr = k_gi * k^j_subscal
crypto::secret_key address_privkey_g;
sc_mul(to_bytes(address_privkey_g), to_bytes(k_generate_image), to_bytes(k_subaddress_scalar));
// k^{j, t}_addr = k_ps * k^j_subscal
crypto::secret_key address_privkey_t;
sc_mul(to_bytes(address_privkey_t), to_bytes(k_prove_spend), to_bytes(k_subaddress_scalar));
make_sal_proof_nominal_address_carrot_v1(signable_tx_hash,
rerandomized_output,
opening_hint,
address_privkey_g,
address_privkey_t,
account_spend_pubkey,
&s_view_balance_dev,
&k_view_incoming_dev,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_carrot_coinbase_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotCoinbaseOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
// check that the opening hint tells us to open as a legacy address
const AddressDeriveType derive_type = opening_hint.derive_type;
CHECK_AND_ASSERT_THROW_MES(derive_type == AddressDeriveType::PreCarrot,
"make sal proof carrot coinbase to legacy v1: invalid subaddr derive type: " << static_cast<int>(derive_type));
make_sal_proof_nominal_address_carrot_coinbase_v1(signable_tx_hash,
rerandomized_output,
opening_hint,
k_spend,
crypto::null_skey,
addr_dev.get_cryptonote_account_spend_pubkey(),
addr_dev,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_carrot_coinbase_to_carrot_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotCoinbaseOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_incoming_key_device &k_view_incoming_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
// check that the opening hint tells us to open as a Carrot address
const AddressDeriveType derive_type = opening_hint.derive_type;
CHECK_AND_ASSERT_THROW_MES(derive_type == AddressDeriveType::Carrot,
"make sal proof carrot coinbase to carrot v1: invalid subaddr derive type: " << static_cast<int>(derive_type));
// K_s = k_gi G + k_ps T
crypto::public_key account_spend_pubkey;
make_carrot_spend_pubkey(k_generate_image, k_prove_spend, account_spend_pubkey);
make_sal_proof_nominal_address_carrot_coinbase_v1(signable_tx_hash,
rerandomized_output,
opening_hint,
k_generate_image,
k_prove_spend,
account_spend_pubkey,
k_view_incoming_dev,
sal_proof_out,
key_image_out);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_any_to_legacy_v1(const crypto::hash &signable_tx_hash,
const CarrotOpenableRerandomizedOutputV1 &openable_rerandomized_output,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
struct make_sal_proof_any_to_legacy_v1_visitor
{
void operator()(const LegacyOutputOpeningHintV1 &hint)
{
make_sal_proof_legacy_to_legacy_v1(signable_tx_hash,
rerandomized_output,
hint,
k_spend,
addr_dev,
sal_proof_out,
key_image_out);
}
void operator()(const CarrotOutputOpeningHintV1 &hint)
{
make_sal_proof_carrot_to_legacy_v1(signable_tx_hash,
rerandomized_output,
hint,
k_spend,
addr_dev,
sal_proof_out,
key_image_out);
}
void operator()(const CarrotCoinbaseOutputOpeningHintV1 &hint)
{
make_sal_proof_carrot_coinbase_to_legacy_v1(signable_tx_hash,
rerandomized_output,
hint,
k_spend,
addr_dev,
sal_proof_out,
key_image_out);
}
const crypto::hash &signable_tx_hash;
const FcmpRerandomizedOutputCompressed &rerandomized_output;
const crypto::secret_key &k_spend;
const cryptonote_hierarchy_address_device &addr_dev;
fcmp_pp::FcmpPpSalProof &sal_proof_out;
crypto::key_image &key_image_out;
};
return openable_rerandomized_output.opening_hint.visit(
make_sal_proof_any_to_legacy_v1_visitor{
signable_tx_hash,
openable_rerandomized_output.rerandomized_output,
k_spend,
addr_dev,
sal_proof_out,
key_image_out
}
);
}
//-------------------------------------------------------------------------------------------------------------------
void make_sal_proof_any_to_carrot_v1(const crypto::hash &signable_tx_hash,
const CarrotOpenableRerandomizedOutputV1 &openable_rerandomized_output,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_balance_secret_device &s_view_balance_dev,
const view_incoming_key_device &k_view_incoming_dev,
const generate_address_secret_device &s_generate_address_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out)
{
struct make_sal_proof_any_to_carrot_v1_visitor
{
void operator()(const LegacyOutputOpeningHintV1 &hint)
{
ASSERT_MES_AND_THROW("make sal proof any to carrot v1: legacy unsupported");
}
void operator()(const CarrotOutputOpeningHintV1 &hint)
{
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_output,
hint,
k_prove_spend,
k_generate_image,
s_view_balance_dev,
k_view_incoming_dev,
s_generate_address_dev,
sal_proof_out,
key_image_out);
}
void operator()(const CarrotCoinbaseOutputOpeningHintV1 &hint)
{
make_sal_proof_carrot_coinbase_to_carrot_v1(signable_tx_hash,
rerandomized_output,
hint,
k_prove_spend,
k_generate_image,
k_view_incoming_dev,
sal_proof_out,
key_image_out);
}
const crypto::hash &signable_tx_hash;
const FcmpRerandomizedOutputCompressed &rerandomized_output;
const crypto::secret_key &k_prove_spend;
const crypto::secret_key &k_generate_image;
const view_balance_secret_device &s_view_balance_dev;
const view_incoming_key_device &k_view_incoming_dev;
const generate_address_secret_device &s_generate_address_dev;
fcmp_pp::FcmpPpSalProof &sal_proof_out;
crypto::key_image &key_image_out;
};
return openable_rerandomized_output.opening_hint.visit(
make_sal_proof_any_to_carrot_v1_visitor{
signable_tx_hash,
openable_rerandomized_output.rerandomized_output,
k_prove_spend,
k_generate_image,
s_view_balance_dev,
k_view_incoming_dev,
s_generate_address_dev,
sal_proof_out,
key_image_out
}
);
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace carrot

View file

@ -0,0 +1,199 @@
// 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.
#pragma once
//local headers
#include "address_device.h"
#include "carrot_tx_builder_types.h"
#include "fcmp_pp/curve_trees.h"
#include "span.h"
//third party headers
//standard headers
//forward declarations
namespace carrot
{
struct LegacyOutputOpeningHintV1
{
// WARNING: Using this opening hint is unsafe and enables for HW devices to
// accidentally burn XMR if an attacker controls the hot wallet and
// can publish a new enote with the same K_o as an existing enote,
// but with a different amount. However, it is unavoidable for
// legacy enotes, since the computation of K_o is not directly nor
// indirectly bound to the amount.
// Informs remote prover (implied to know opening of K^j_s given j) how to open O, C such that:
// O = K^j_s + k_o G
// C = z G + a H
// O
crypto::public_key onetime_address;
// k_o
crypto::secret_key sender_extension_g;
// j (legacy only)
subaddress_index subaddr_index;
// a
rct::xmr_amount amount;
// z
crypto::secret_key amount_blinding_factor;
};
struct CarrotOutputOpeningHintV1
{
// source enote
CarrotEnoteV1 source_enote;
// pid_enc
std::optional<encrypted_payment_id_t> encrypted_payment_id;
// j, derive type
subaddress_index_extended subaddr_index;
};
struct CarrotCoinbaseOutputOpeningHintV1
{
// source enote
CarrotCoinbaseEnoteV1 source_enote;
// no encrypted pids for coinbase transactions
// subaddress index is assumed to be (0, 0) in coinbase transactions
AddressDeriveType derive_type;
};
using OutputOpeningHintVariant = tools::variant<
LegacyOutputOpeningHintV1,
CarrotOutputOpeningHintV1,
CarrotCoinbaseOutputOpeningHintV1
>;
const crypto::public_key &onetime_address_ref(const OutputOpeningHintVariant&);
rct::key amount_commitment_ref(const OutputOpeningHintVariant&);
struct CarrotOpenableRerandomizedOutputV1
{
FcmpRerandomizedOutputCompressed rerandomized_output;
OutputOpeningHintVariant opening_hint;
};
struct CarrotSignableTransactionProposalV1
{
CarrotTransactionProposalV1 tx_proposal;
std::vector<CarrotOpenableRerandomizedOutputV1> inputs;
};
void make_carrot_rerandomized_outputs_nonrefundable(const std::vector<crypto::public_key> &input_onetime_addresses,
const std::vector<rct::key> &input_amount_commitments,
const std::vector<rct::key> &input_amount_blinding_factors,
const std::vector<rct::key> &output_amount_blinding_factors,
std::vector<FcmpRerandomizedOutputCompressed> &rerandomized_outputs_out);
bool verify_rerandomized_output_basic(const FcmpRerandomizedOutputCompressed &rerandomized_output,
const crypto::public_key &onetime_address,
const rct::key &amount_commitment);
bool verify_openable_rerandomized_output_basic(const CarrotOpenableRerandomizedOutputV1&);
// spend legacy enote addressed to legacy address
void make_sal_proof_legacy_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const LegacyOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend carrot enote addressed to legacy address
void make_sal_proof_carrot_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend carrot enote addressed to carrot address
void make_sal_proof_carrot_to_carrot_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_balance_secret_device &s_view_balance_dev,
const view_incoming_key_device &k_view_incoming_dev,
const generate_address_secret_device &s_generate_address_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend carrot coinbase enote addressed to legacy address
void make_sal_proof_carrot_coinbase_to_legacy_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotCoinbaseOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend carrot coinbase enote addressed to carrot address
void make_sal_proof_carrot_coinbase_to_carrot_v1(const crypto::hash &signable_tx_hash,
const FcmpRerandomizedOutputCompressed &rerandomized_output,
const CarrotCoinbaseOutputOpeningHintV1 &opening_hint,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_incoming_key_device &k_view_incoming_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend any enote addressed to legacy address
void make_sal_proof_any_to_legacy_v1(const crypto::hash &signable_tx_hash,
const CarrotOpenableRerandomizedOutputV1 &openable_rerandomized_output,
const crypto::secret_key &k_spend,
const cryptonote_hierarchy_address_device &addr_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
// spend any carrot enote addressed to carrot address
void make_sal_proof_any_to_carrot_v1(const crypto::hash &signable_tx_hash,
const CarrotOpenableRerandomizedOutputV1 &openable_rerandomized_output,
const crypto::secret_key &k_prove_spend,
const crypto::secret_key &k_generate_image,
const view_balance_secret_device &s_view_balance_dev,
const view_incoming_key_device &k_view_incoming_dev,
const generate_address_secret_device &s_generate_address_dev,
fcmp_pp::FcmpPpSalProof &sal_proof_out,
crypto::key_image &key_image_out);
} //namespace carrot

View file

@ -377,4 +377,57 @@ bool try_load_carrot_from_coinbase_transaction_v1(const cryptonote::transaction
return true;
}
//-------------------------------------------------------------------------------------------------------------------
rct::rctSigPrunable store_fcmp_proofs_to_rct_prunable_v1(
std::vector<rct::BulletproofPlus> &&bulletproofs_plus,
const std::vector<FcmpRerandomizedOutputCompressed> &rerandomized_outputs,
const std::vector<fcmp_pp::FcmpPpSalProof> &sal_proofs,
const fcmp_pp::FcmpMembershipProof &membership_proof,
const std::uint64_t fcmp_reference_block,
const std::uint8_t n_tree_layers)
{
const size_t n_inputs = rerandomized_outputs.size();
CHECK_AND_ASSERT_THROW_MES(sal_proofs.size() == n_inputs,
"store fcmp proofs to rct prunable v1: wrong number of sal_proofs");
for (const fcmp_pp::FcmpPpSalProof &sal_proof : sal_proofs)
CHECK_AND_ASSERT_THROW_MES(sal_proof.size() == FCMP_PP_SAL_PROOF_SIZE_V1,
"store fcmp proofs to rct prunable v1: sal proof is incorrect size");
CHECK_AND_ASSERT_THROW_MES(membership_proof.size() == ::fcmp_proof_size(n_inputs, n_tree_layers),
"store fcmp proofs to rct prunable v1: membership proof is incorrect size");
const size_t actual_proof_size = membership_proof.size() + (3 * 32 + FCMP_PP_SAL_PROOF_SIZE_V1) * n_inputs;
CHECK_AND_ASSERT_THROW_MES(actual_proof_size == fcmp_pp::proof_len(n_inputs, n_tree_layers),
"store fcmp proofs to rct prunable v1: bug: bad length calculation");
// extract C~
rct::keyV pseudoOuts(n_inputs);
for (size_t i = 0; i < n_inputs; ++i)
memcpy(pseudoOuts[i].bytes, rerandomized_outputs.at(i).input.C_tilde, sizeof(rct::key));
// build FCMP++ from FCMP and SA/L parts
fcmp_pp::FcmpPpProof proof_bytes;
proof_bytes.reserve(actual_proof_size);
for (size_t i = 0; i < n_inputs; ++i)
{
const FcmpInputCompressed &input = rerandomized_outputs.at(i).input;
const fcmp_pp::FcmpPpSalProof &sal_proof = sal_proofs.at(i);
// append O~, I~, R (C_tilde not included)
proof_bytes.insert(proof_bytes.end(), input.O_tilde, input.C_tilde);
// append SAL proof
proof_bytes.insert(proof_bytes.end(), sal_proof.cbegin(), sal_proof.cend());
}
// append membership proof
proof_bytes.insert(proof_bytes.end(), membership_proof.cbegin(), membership_proof.cend());
CHECK_AND_ASSERT_THROW_MES(proof_bytes.size() == actual_proof_size,
"store fcmp proofs to rct prunable v1: bug: bad proof building");
return rct::rctSigPrunable{
.bulletproofs_plus = std::move(bulletproofs_plus),
.pseudoOuts = std::move(pseudoOuts),
.reference_block = fcmp_reference_block,
.n_tree_layers = n_tree_layers,
.fcmp_pp = std::move(proof_bytes)
};
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace carrot

View file

@ -118,5 +118,22 @@ cryptonote::transaction store_carrot_to_coinbase_transaction_v1(
*/
bool try_load_carrot_from_coinbase_transaction_v1(const cryptonote::transaction &tx,
std::vector<CarrotCoinbaseEnoteV1> &enotes_out);
/**
* brief: store_fcmp_proofs_to_rct_prunable_v1 -
* param: bulletproofs_plus -
* param: rerandomized_outputs -
* param: sal_proofs -
* param: membership_proof -
* param: fcmp_reference_block -
* param: n_tree_layers -
* return: prunable RCT signature data that can be attached to corresponding pruned tx
*/
rct::rctSigPrunable store_fcmp_proofs_to_rct_prunable_v1(
std::vector<rct::BulletproofPlus> &&bulletproofs_plus,
const std::vector<FcmpRerandomizedOutputCompressed> &rerandomized_outputs,
const std::vector<fcmp_pp::FcmpPpSalProof> &sal_proofs,
const fcmp_pp::FcmpMembershipProof &membership_proof,
const std::uint64_t fcmp_reference_block,
const std::uint8_t n_tree_layers);
} //namespace carrot

View file

@ -30,8 +30,8 @@
#include "tx_builder.h"
//local headers
#include "carrot_core/config.h"
#include "carrot_core/device_ram_borrowed.h"
#include "carrot_impl/carrot_tx_builder_inputs.h"
#include "carrot_impl/carrot_tx_builder_utils.h"
#include "carrot_impl/input_selection.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
@ -363,7 +363,7 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
{
CHECK_AND_ASSERT_THROW_MES(!input_key_images.empty(),
"make carrot transaction proposal wallet2 sweep: no key images provided");
CHECK_AND_ASSERT_THROW_MES(n_dests <= carrot::CARROT_MAX_TX_INPUTS,
CHECK_AND_ASSERT_THROW_MES(n_dests < FCMP_PLUS_PLUS_MAX_OUTPUTS,
"make carrot transaction proposal wallet2 sweep: too many destinations");
// Check that the key image is available and isn't spent, and collect amounts
@ -424,5 +424,74 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
return tx_proposal;
}
//-------------------------------------------------------------------------------------------------------------------
carrot::OutputOpeningHintVariant make_sal_opening_hint_from_transfer_details(
const wallet2::transfer_details &td,
const crypto::secret_key &k_view,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses_map,
hw::device &hwdev)
{
//! @TODO: handle Carrot coinbase and non-coinbase enotes
// K_o
const crypto::public_key onetime_address = td.get_public_key();
// R
const crypto::public_key main_tx_pubkey = cryptonote::get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
const std::vector<crypto::public_key> additional_tx_pubkeys =
cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx);
//! @TODO: reject too many additional tx pubkeys??
std::vector<crypto::key_derivation> ecdhs;
ecdhs.reserve(additional_tx_pubkeys.size() + 1);
// 8 * k_v * R
crypto::key_derivation ecdh;
if (hwdev.generate_key_derivation(main_tx_pubkey, k_view, ecdh))
ecdhs.push_back(ecdh);
// 8 * k_v * R
for (const crypto::public_key &additional_tx_pubkey : additional_tx_pubkeys)
if (hwdev.generate_key_derivation(additional_tx_pubkey, k_view, ecdh))
ecdhs.push_back(ecdh);
// Search for (j, K^j_s, k^g_o) s.t. K^j_s = K_o + H_n(8 * k_v * R, output_index)
for (const crypto::key_derivation &ecdh : ecdhs)
{
// K^j_s' = K_o - k^g_o G
crypto::public_key nominal_address_spend_pubkey;
if (!hwdev.derive_subaddress_public_key(onetime_address,
ecdh,
td.m_internal_output_index,
nominal_address_spend_pubkey))
continue;
// Know about K^j_s?
const auto subaddr_it = subaddresses_map.find(nominal_address_spend_pubkey);
if (subaddr_it == subaddresses_map.cend())
continue;
// k^g_o = H_n(8 * k_v * R, output_index)
//! @TODO: find out if any mainline HWs "conceal" the derived scalar
crypto::secret_key sender_extension_g;
if (!hwdev.derivation_to_scalar(ecdh, td.m_internal_output_index, sender_extension_g))
continue;
// j
const carrot::subaddress_index subaddr_index = {subaddr_it->second.major, subaddr_it->second.minor};
return carrot::LegacyOutputOpeningHintV1{
.onetime_address = onetime_address,
.sender_extension_g = sender_extension_g,
.subaddr_index = subaddr_index,
.amount = td.amount(),
.amount_blinding_factor = rct::rct2sk(td.m_mask)
};
}
ASSERT_MES_AND_THROW("make sal opening hint from transfer details: cannot find subaddress and sender extension "
"for given transfer info");
}
//-------------------------------------------------------------------------------------------------------------------
} //namespace wallet
} //namespace tools

View file

@ -29,7 +29,7 @@
#pragma once
//local headers
#include "carrot_impl/carrot_tx_builder_types.h"
#include "carrot_impl/carrot_tx_builder_inputs.h"
#include "wallet2.h"
//third party headers
@ -94,5 +94,11 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
const std::vector<uint8_t>& extra,
const std::uint64_t top_block_index,
const cryptonote::account_base &acb);
carrot::OutputOpeningHintVariant make_sal_opening_hint_from_transfer_details(
const wallet2::transfer_details &td,
const crypto::secret_key &k_view,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses_map,
hw::device &hwdev);
} //namespace wallet
} //namespace tools

View file

@ -39,10 +39,12 @@ set(unit_tests_sources
bulletproofs_plus.cpp
canonical_amounts.cpp
carrot_core.cpp
carrot_fcmp.cpp
carrot_impl.cpp
carrot_legacy.cpp
carrot_mock_helpers.cpp
carrot_transcript_fixed.cpp
carrot_tx_builder.cpp
chacha.cpp
checkpoints.cpp
command_line.cpp

View file

@ -130,7 +130,6 @@ static const CarrotOutputContextsAndKeys generate_random_carrot_outputs(
case 3: // special enote main address
get_output_proposal_special_v1(selfsend_payment_proposal.proposal,
keys.k_view_incoming_dev,
keys.cryptonote_address().address_spend_pubkey,
mock::gen_key_image(),
std::nullopt,
rct_output_enote_proposal);
@ -141,7 +140,6 @@ static const CarrotOutputContextsAndKeys generate_random_carrot_outputs(
= keys.subaddress(selfsend_payment_proposal.subaddr_index).address_spend_pubkey;
get_output_proposal_special_v1(selfsend_payment_proposal.proposal,
keys.k_view_incoming_dev,
keys.cryptonote_address().address_spend_pubkey,
mock::gen_key_image(),
std::nullopt,
rct_output_enote_proposal);
@ -378,7 +376,6 @@ TEST(carrot_fcmp, receive_scan_spend_and_verify_serialized_carrot_tx)
tx_proposal.dummy_encrypted_payment_id,
&alice.s_view_balance_dev,
&alice.k_view_incoming_dev,
alice.carrot_account_spend_pubkey,
tx_proposal.key_images_sorted.at(0),
output_enote_proposals,
encrypted_payment_id);

View file

@ -393,7 +393,6 @@ TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_subaddr_special)
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);
@ -481,7 +480,6 @@ TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_mainaddr_special)
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);
@ -740,7 +738,6 @@ TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_mainaddr_special)
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
keys.carrot_account_spend_pubkey,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);
@ -830,7 +827,6 @@ TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_subaddr_special)
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
keys.carrot_account_spend_pubkey,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);

View file

@ -180,7 +180,7 @@ static cryptonote::tx_source_entry gen_tx_source_entry_fake_members(
continue;
used_indices.insert(global_output_index);
const rct::ctkey output_pair{rct::pkGen(),
is_rct ? rct::pkGen() : rct::zeroCommit(in.amount)};
is_rct ? rct::pkGen() : rct::zeroCommitVartime(in.amount)};
res.outputs.push_back({global_output_index, output_pair});
}
// sort by index
@ -311,6 +311,7 @@ static cryptonote::transaction construct_pre_carrot_tx_with_fake_inputs(
cryptonote::transaction tx;
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
fcmp_pp::ProofParams dummy_fcmp_params;
const bool r = cryptonote::construct_tx_and_get_tx_key(
sender_account_keys,
subaddresses,
@ -321,6 +322,7 @@ static cryptonote::transaction construct_pre_carrot_tx_with_fake_inputs(
tx,
tx_key,
additional_tx_keys,
dummy_fcmp_params,
rct,
rct_config,
use_view_tags);
@ -533,13 +535,12 @@ public:
w.m_blockchain.push_back(m_parsed_blocks.front().hash);
w.m_blockchain.trim(m_start_block_index);
//! TODO: uncomment for FCMP++ integration
//w.m_tree_cache.clear();
//w.m_tree_cache.init(m_start_block_index,
// m_parsed_blocks.front().hash,
// /*n_leaf_tuples=*/0,
// /*last_path=*/{},
// /*locked_outputs=*/{});
w.m_tree_cache.clear();
w.m_tree_cache.init(m_start_block_index,
m_parsed_blocks.front().hash,
/*n_leaf_tuples=*/0,
/*last_path=*/{},
/*locked_outputs=*/{});
}
uint8_t hf_version() const

View file

@ -30,10 +30,12 @@
#include "gtest/gtest.h"
#include "carrot_core/config.h"
#include "carrot_impl/carrot_tx_builder_utils.h"
#include "common/container_helpers.h"
#include "ringct/rctOps.h"
#include "wallet/tx_builder.h"
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
static tools::wallet2::transfer_details gen_transfer_details()
{
return tools::wallet2::transfer_details{
@ -59,13 +61,15 @@ static tools::wallet2::transfer_details gen_transfer_details()
.m_uses = {},
};
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
static bool compare_transfer_to_selected_input(const tools::wallet2::transfer_details &td,
const carrot::CarrotSelectedInput &input)
{
return td.m_amount == input.amount && td.m_key_image == input.key_image;
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
TEST(wallet_tx_builder, input_selection_basic)
{
std::map<std::size_t, rct::xmr_amount> fee_by_input_count;
@ -135,3 +139,90 @@ TEST(wallet_tx_builder, input_selection_basic)
}
ASSERT_EQ(selected_transfer_indices.size(), matched_transfer_indices.size());
}
//----------------------------------------------------------------------------------------------------------------------
TEST(wallet_tx_builder, make_carrot_transaction_proposal_wallet2_transfer_1)
{
cryptonote::account_base alice;
alice.generate();
cryptonote::account_base bob;
bob.generate();
const tools::wallet2::transfer_container transfers{
gen_transfer_details(),
gen_transfer_details()};
const rct::xmr_amount out_amount = rct::randXmrAmount(transfers.front().amount() / 2);
const std::vector<cryptonote::tx_destination_entry> dsts{
cryptonote::tx_destination_entry(out_amount, bob.get_keys().m_account_address, false)
};
const uint64_t top_block_index = std::max(transfers.front().m_block_height, transfers.back().m_block_height)
+ CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE;
const carrot::CarrotTransactionProposalV1 tx_proposal = tools::wallet::make_carrot_transaction_proposal_wallet2_transfer(
transfers,
/*subaddress_map=*/{},
dsts,
/*fee_per_weight=*/1,
/*extra=*/{},
/*subaddr_account=*/0,
/*subaddr_indices=*/{},
/*ignore_above=*/MONEY_SUPPLY,
/*ignore_below=*/0,
top_block_index,
alice);
std::vector<crypto::key_image> expected_key_images{
transfers.front().m_key_image,
transfers.back().m_key_image};
std::sort(expected_key_images.begin(),
expected_key_images.end(),
&carrot::compare_input_key_images);
// Assert basic length facts about tx proposal
ASSERT_EQ(2, tx_proposal.key_images_sorted.size()); // we always try 2 when available
EXPECT_EQ(expected_key_images, tx_proposal.key_images_sorted);
ASSERT_EQ(1, tx_proposal.normal_payment_proposals.size());
ASSERT_EQ(1, tx_proposal.selfsend_payment_proposals.size());
EXPECT_EQ(0, tx_proposal.extra.size());
// Assert amounts
EXPECT_EQ(out_amount, tx_proposal.normal_payment_proposals.front().amount);
EXPECT_EQ(out_amount + tx_proposal.selfsend_payment_proposals.front().proposal.amount + tx_proposal.fee,
transfers.front().amount() + transfers.back().amount());
}
//----------------------------------------------------------------------------------------------------------------------
TEST(wallet_tx_builder, make_carrot_transaction_proposal_wallet2_sweep_1)
{
cryptonote::account_base alice;
alice.generate();
cryptonote::account_base bob;
bob.generate();
const tools::wallet2::transfer_container transfers{gen_transfer_details()};
const carrot::CarrotTransactionProposalV1 tx_proposal = tools::wallet::make_carrot_transaction_proposal_wallet2_sweep(
transfers,
/*subaddress_map=*/{},
{transfers.front().m_key_image},
bob.get_keys().m_account_address,
/*is_subaddress=*/false,
/*n_dests=*/1,
/*fee_per_weight=*/1,
/*extra=*/{},
transfers.front().m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
alice);
// Assert basic length facts about tx proposal
ASSERT_EQ(1, tx_proposal.key_images_sorted.size());
EXPECT_EQ(transfers.front().m_key_image, tx_proposal.key_images_sorted.front());
ASSERT_EQ(1, tx_proposal.normal_payment_proposals.size());
ASSERT_EQ(1, tx_proposal.selfsend_payment_proposals.size());
EXPECT_EQ(0, tx_proposal.extra.size());
// Assert amounts
EXPECT_EQ(0, tx_proposal.selfsend_payment_proposals.front().proposal.amount);
EXPECT_EQ(transfers.front().amount(), tx_proposal.fee + tx_proposal.normal_payment_proposals.front().amount);
}
//----------------------------------------------------------------------------------------------------------------------