Files
wownero/tests/unit_tests/carrot_tx_builder.cpp
+3 a9145f9d38 FCMP++/Carrot mega commit [9/14/26]
Co-authored-by: jeffro256 <jeffro256@tutanota.com>
Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
Co-authored-by: Lee *!* Clagett <code@leeclagett.com>
Co-authored-by: SyntheticBird45 <someoneelse.is_on.github.rio7x@simplelogin.com>
Co-authored-by: jeetrex17 <jeetrex@tutamail.com>
Co-authored-by: UkoeHB <ukoehb@users.noreply.github.com>
Co-authored-by: MrCyjaneK
Co-authored-by: tobtoht
Co-authored-by: selsta
Co-authored-by: Thomas <thomas.giudici@proton.me>
Co-authored-by: navidR
Co-authored-by: Boog900 <boog900@tutanota.com>
2026-09-17 18:33:24 -07:00

1327 lines
48 KiB
C++

// Copyright (c) 2025-2026, 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_core/enote_utils.h"
#include "carrot_impl/address_device_ram_borrowed.h"
#include "carrot_impl/tx_builder_inputs.h"
#include "carrot_mock_helpers.h"
#include "fcmp_pp/prove.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);
// (K^0_s, K^0_v)
const CarrotDestinationV1 addr = keys.cryptonote_address();
const cryptonote_hierarchy_address_device cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
addr.address_spend_pubkey);
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// k_e, K_e
crypto::secret_key enote_ephemeral_privkey;
crypto::public_key enote_ephemeral_pubkey;
crypto::generate_keys(enote_ephemeral_pubkey, enote_ephemeral_privkey);
// 8 k_e K^0_v
crypto::key_derivation kd;
ASSERT_TRUE(crypto::generate_key_derivation(addr.address_view_pubkey, enote_ephemeral_privkey, kd));
// K_o = K^0_s + Hs1(8 k_e K^0_v, i) G
const std::size_t local_output_index = 2;
crypto::public_key onetime_address;
crypto::derive_public_key(kd, local_output_index, addr.address_spend_pubkey, onetime_address);
// a
const rct::xmr_amount amount = crypto::rand<rct::xmr_amount>();
// z
const crypto::secret_key amount_blinding_factor = mock::gen_secret_key();
// C_a = z G + a H
const amount_commitment_t amount_commitment = commit_carrot_amount(amount, amount_blinding_factor);
const LegacyOutputOpeningHintV1 opening_hint{
.onetime_address = onetime_address,
.ephemeral_tx_pubkey = enote_ephemeral_pubkey,
.subaddr_index = {0, 0},
.amount = amount,
.amount_blinding_factor = amount_blinding_factor,
.local_output_index = local_output_index
};
const crypto::key_image expected_key_image = keys.key_image_dev->derive_key_image(opening_hint);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set(
{onetime_address},
{amount_commitment},
{true},
{amount_blinding_factor},
{mock::gen_secret_key()},
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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_key);
// j
const subaddress_index_extended subaddress_index = mock::gen_subaddress_index_extended(keys.default_derive_type);
// (K^j_s, K^j_v)
const CarrotDestinationV1 addr = keys.subaddress(subaddress_index);
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// k_e, K_e = k_e K^j_s
const crypto::secret_key enote_ephemeral_privkey = mock::gen_secret_key();
crypto::public_key enote_ephemeral_pubkey;
{
ge_p3 Kjs;
[[maybe_unused]] const int r = ge_frombytes_vartime(&Kjs, to_bytes(addr.address_spend_pubkey));
ge_p2 Ke;
ge_scalarmult(&Ke, to_bytes(enote_ephemeral_privkey), &Kjs);
ge_tobytes(to_bytes(enote_ephemeral_pubkey), &Ke);
}
// 8 k_e K^j_v
crypto::key_derivation kd;
ASSERT_TRUE(crypto::generate_key_derivation(addr.address_view_pubkey, enote_ephemeral_privkey, kd));
// K_o = K^j_s + Hs1(8 k_e K^0_v, i) G
const std::size_t local_output_index = 0;
crypto::public_key onetime_address;
crypto::derive_public_key(kd, local_output_index, addr.address_spend_pubkey, onetime_address);
// a
const rct::xmr_amount amount = crypto::rand<rct::xmr_amount>();
// z
const crypto::secret_key amount_blinding_factor = mock::gen_secret_key();
// k^g_o
const crypto::secret_key sender_extension_g = mock::gen_secret_key();
// C_a = z G + a H
const amount_commitment_t amount_commitment = commit_carrot_amount(amount, amount_blinding_factor);
const LegacyOutputOpeningHintV1 opening_hint{
.onetime_address = onetime_address,
.ephemeral_tx_pubkey = enote_ephemeral_pubkey,
.subaddr_index = subaddress_index.index,
.amount = amount,
.amount_blinding_factor = amount_blinding_factor,
.local_output_index = local_output_index
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
const crypto::key_image expected_key_image = keys.key_image_dev->derive_key_image(opening_hint);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({opening_hint.onetime_address},
{amount_commitment},
{use_biased_htp},
{amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_special_v1(selfsend_payment_proposal,
keys.k_view_incoming_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_internal_v1(selfsend_payment_proposal,
keys.s_view_balance_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {{0, 0}, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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,
.enote_ephemeral_privkey = mock::gen_secret_key()
};
RCTOutputEnoteProposal output_enote_proposal;
get_output_proposal_internal_v1(selfsend_payment_proposal,
keys.s_view_balance_dev,
tx_first_key_image,
nullptr,
nullptr,
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},
gen_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 = std::nullopt,
.subaddr_index = {subaddr_index, keys.default_derive_type}
};
const bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({enote.onetime_address},
{enote.amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------
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 cn_addr_dev(
std::make_shared<cryptonote_view_incoming_key_ram_borrowed_device>(keys.k_view_incoming_dev),
keys.legacy_acb.get_keys().m_account_address.m_spend_public_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_enote_v1(normal_payment_proposal,
block_index,
coinbase_enote);
const amount_commitment_t coinbase_enote_amount_commitment = commit_carrot_amount(coinbase_enote.amount, {{1}});
// 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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({coinbase_enote.onetime_address},
{coinbase_enote_amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_legacy_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.legacy_acb.get_keys().m_spend_secret_key,
cn_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_enote_v1(normal_payment_proposal,
block_index,
coinbase_enote);
const amount_commitment_t coinbase_enote_amount_commitment = commit_carrot_amount(coinbase_enote.amount, {{1}});
// 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 bool use_biased_htp = use_biased_hash_to_point(opening_hint);
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,
use_biased_htp);
// fake output amount blinding factor in a hypothetical tx where we spent the aforementioned output
const crypto::secret_key output_amount_blinding_factor = mock::gen_secret_key();
const crypto::hash signable_tx_hash = crypto::rand<crypto::hash>();
// make rerandomized outputs
std::vector<FcmpRerandomizedOutputCompressed> rerandomized_outputs;
fcmp_pp::make_balanced_rerandomized_output_set({coinbase_enote.onetime_address},
{coinbase_enote_amount_commitment},
{use_biased_htp},
{scan_result.amount_blinding_factor},
{mock::gen_secret_key()},
{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_any_to_carrot_v1(signable_tx_hash,
rerandomized_outputs.front(),
opening_hint,
keys.k_prove_spend,
keys.k_generate_image,
*keys.addr_dev,
keys.s_view_balance_dev,
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));
}
//----------------------------------------------------------------------------------------------------------------------