mirror of
https://github.com/monero-project/monero.git
synced 2025-04-11 16:52:01 +00:00
carrot_impl<->fcmp++ bridge tests
This commit is contained in:
parent
e1d2d9e5cc
commit
af360d7bee
13 changed files with 1110 additions and 23 deletions
src
carrot_core
carrot_impl
CMakeLists.txtcarrot_tx_builder_inputs.cppcarrot_tx_builder_inputs.hcarrot_tx_format_utils.cppcarrot_tx_format_utils.h
wallet
tests/unit_tests
|
@ -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{});
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
654
src/carrot_impl/carrot_tx_builder_inputs.cpp
Normal file
654
src/carrot_impl/carrot_tx_builder_inputs.cpp
Normal 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
|
199
src/carrot_impl/carrot_tx_builder_inputs.h
Normal file
199
src/carrot_impl/carrot_tx_builder_inputs.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue