Files
Peya/tests/unit_tests/carrot_tx_builder.cpp
2025-05-06 15:38:34 +03:00

1233 lines
45 KiB
C++

// Copyright (c) 2025, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "gtest/gtest.h"
#include "carrot_impl/address_device_ram_borrowed.h"
#include "carrot_impl/carrot_tx_builder_inputs.h"
#include "carrot_mock_helpers.h"
using namespace carrot;
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_legacy_to_legacy_v1_mainaddr)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// a
const rct::xmr_amount amount = crypto::rand<rct::xmr_amount>();
// z
const rct::key amount_blinding_factor = rct::skGen();
// k^g_o
const crypto::secret_key sender_extension_g = mock::gen_secret_key();
// K_o = K^0_s + k^g_o G
rct::key onetime_address;
rct::addKeys1(onetime_address,
rct::sk2rct(sender_extension_g),
rct::pk2rct(addr.address_spend_pubkey));
// C_a = z G + a H
const rct::key amount_commitment = rct::commit(amount, amount_blinding_factor);
const LegacyOutputOpeningHintV1 opening_hint{
.onetime_address = rct::rct2pk(onetime_address),
.sender_extension_g = sender_extension_g,
.subaddr_index = {0, 0},
.amount = amount,
.amount_blinding_factor = rct::rct2sk(amount_blinding_factor)
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
sender_extension_g,
crypto::null_skey,
rct::rct2pk(onetime_address));
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({opening_hint.onetime_address},
{amount_commitment},
{amount_blinding_factor},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_legacy_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_legacy_to_legacy_v1_subaddr)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// j
const subaddress_index_extended subaddress_index = mock::gen_subaddress_index_extended();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress(subaddress_index);
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// a
const rct::xmr_amount amount = crypto::rand<rct::xmr_amount>();
// z
const rct::key amount_blinding_factor = rct::skGen();
// k^g_o
const crypto::secret_key sender_extension_g = mock::gen_secret_key();
// K_o = K^0_s + k^g_o G
rct::key onetime_address;
rct::addKeys1(onetime_address,
rct::sk2rct(sender_extension_g),
rct::pk2rct(addr.address_spend_pubkey));
// C_a = z G + a H
const rct::key amount_commitment = rct::commit(amount, amount_blinding_factor);
const LegacyOutputOpeningHintV1 opening_hint{
.onetime_address = rct::rct2pk(onetime_address),
.sender_extension_g = sender_extension_g,
.subaddr_index = subaddress_index.index,
.amount = amount,
.amount_blinding_factor = rct::rct2sk(amount_blinding_factor)
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
sender_extension_g,
crypto::null_skey,
rct::rct2pk(onetime_address));
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({opening_hint.onetime_address},
{amount_commitment},
{amount_blinding_factor},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_legacy_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_mainaddr_normal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
RCTOutputEnoteProposal output_enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
get_output_proposal_normal_v1(normal_payment_proposal,
tx_first_key_image,
output_enote_proposal,
encrypted_payment_id);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
encrypted_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = encrypted_payment_id,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_subaddr_normal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// j
const subaddress_index subaddr_index = mock::gen_subaddress_index();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress({subaddr_index});
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
RCTOutputEnoteProposal output_enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
get_output_proposal_normal_v1(normal_payment_proposal,
tx_first_key_image,
output_enote_proposal,
encrypted_payment_id);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
encrypted_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = encrypted_payment_id,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_subaddr_special)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// j
const subaddress_index subaddr_index = mock::gen_subaddress_index();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress({subaddr_index});
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::CHANGE
};
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);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_legacy_v1_mainaddr_special)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::CHANGE
};
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);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_mainaddr_normal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
RCTOutputEnoteProposal output_enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
get_output_proposal_normal_v1(normal_payment_proposal,
tx_first_key_image,
output_enote_proposal,
encrypted_payment_id);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
encrypted_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = encrypted_payment_id,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_subaddr_normal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// j
const subaddress_index subaddr_index = mock::gen_subaddress_index();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress({subaddr_index});
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
RCTOutputEnoteProposal output_enote_proposal;
encrypted_payment_id_t encrypted_payment_id;
get_output_proposal_normal_v1(normal_payment_proposal,
tx_first_key_image,
output_enote_proposal,
encrypted_payment_id);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
encrypted_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = encrypted_payment_id,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_mainaddr_special)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::PAYMENT
};
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);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_subaddr_special)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// j
const subaddress_index subaddr_index = mock::gen_subaddress_index();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress({subaddr_index});
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::CHANGE
};
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);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_mainaddr_internal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::PAYMENT
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_internal_v1(selfsend_payment_proposal,
keys.s_view_balance_dev,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_to_carrot_v1_subaddr_internal)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// j
const subaddress_index subaddr_index = mock::gen_subaddress_index();
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress({subaddr_index});
const crypto::key_image tx_first_key_image = mock::gen_key_image();
const CarrotPaymentProposalSelfSendV1 selfsend_payment_proposal{
.destination_address_spend_pubkey = addr.address_spend_pubkey,
.amount = crypto::rand<rct::xmr_amount>(),
.enote_type = CarrotEnoteType::CHANGE
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_internal_v1(selfsend_payment_proposal,
keys.s_view_balance_dev,
tx_first_key_image,
gen_x25519_pubkey(),
output_enote_proposal);
const CarrotEnoteV1 &enote = output_enote_proposal.enote;
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_enote_set({enote},
null_payment_id,
keys,
scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotOutputOpeningHintV1 opening_hint{
.source_enote = enote,
.encrypted_payment_id = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({enote.onetime_address},
{enote.amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.s_view_balance_dev,
keys.k_view_incoming_dev,
keys.s_generate_address_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_coinbase_to_legacy_v1)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate(AddressDeriveType::PreCarrot);
const cryptonote_hierarchy_address_device_ram_borrowed addr_dev(
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key,
keys.legacy_acb.get_keys().m_view_secret_key);
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
const uint64_t block_index = mock::gen_block_index();
CarrotCoinbaseEnoteV1 coinbase_enote;
get_coinbase_output_proposal_v1(normal_payment_proposal,
block_index,
coinbase_enote);
const rct::key coinbase_enote_amount_commitment = rct::zeroCommitVartime(coinbase_enote.amount);
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_coinbase_enote_set({coinbase_enote}, keys, scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotCoinbaseOutputOpeningHintV1 opening_hint{
.source_enote = coinbase_enote,
.derive_type = keys.default_derive_type
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
coinbase_enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({coinbase_enote.onetime_address},
{coinbase_enote_amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_coinbase_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
addr_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------
TEST(carrot_tx_builder, make_sal_proof_carrot_coinbase_to_carrot_v1)
{
mock::mock_carrot_and_legacy_keys keys;
keys.generate();
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const CarrotPaymentProposalV1 normal_payment_proposal{
.destination = addr,
.amount = crypto::rand<rct::xmr_amount>(),
.randomness = gen_janus_anchor()
};
const uint64_t block_index = mock::gen_block_index();
CarrotCoinbaseEnoteV1 coinbase_enote;
get_coinbase_output_proposal_v1(normal_payment_proposal,
block_index,
coinbase_enote);
const rct::key coinbase_enote_amount_commitment = rct::zeroCommitVartime(coinbase_enote.amount);
// scan enote to get sender extensions and calculate expected key image
std::vector<mock::mock_scan_result_t> scan_results;
mock::mock_scan_coinbase_enote_set({coinbase_enote}, keys, scan_results);
ASSERT_EQ(1, scan_results.size());
const mock::mock_scan_result_t &scan_result = scan_results.front();
ASSERT_EQ(addr.address_spend_pubkey, scan_result.address_spend_pubkey);
const CarrotCoinbaseOutputOpeningHintV1 opening_hint{
.source_enote = coinbase_enote,
.derive_type = keys.default_derive_type
};
const crypto::key_image expected_key_image = keys.derive_key_image(addr.address_spend_pubkey,
scan_result.sender_extension_g,
scan_result.sender_extension_t,
coinbase_enote.onetime_address);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const rct::key output_amount_blinding_factor = rct::skGen();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
make_carrot_rerandomized_outputs_nonrefundable({coinbase_enote.onetime_address},
{coinbase_enote_amount_commitment},
{rct::sk2rct(scan_result.amount_blinding_factor)},
{output_amount_blinding_factor},
rerandomized_outputs);
ASSERT_EQ(1, rerandomized_outputs.size());
// make SA/L proof for spending aforementioned enote
fcmp_pp::FcmpPpSalProof sal_proof;
crypto::key_image actual_key_image;
make_sal_proof_carrot_coinbase_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
keys.k_view_incoming_dev,
sal_proof,
actual_key_image);
ASSERT_EQ(expected_key_image, actual_key_image);
// verify SA/L
EXPECT_TRUE(fcmp_pp::verify_sal(signable_tx_hash,
rerandomized_outputs.front().input,
actual_key_image,
sal_proof));
}
//----------------------------------------------------------------------------------------------------------------------