// Copyright (c) 2014, 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/device_ram_borrowed.h" #include "carrot_impl/format_utils.h" #include "carrot_impl/address_device_ram_borrowed.h" #include "carrot_impl/spend_device_ram_borrowed.h" #include "carrot_impl/subaddress_map_legacy.h" #include "common/container_helpers.h" #include "common/threadpool.h" #include "crypto/crypto.h" #include "crypto/generators.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/verification_context.h" #include "cryptonote_core/tx_verification_utils.h" #include "curve_trees.h" #include "fcmp_pp/fcmp_pp_serialization.h" #include "fcmp_pp/fcmp_pp_types.h" #include "fcmp_pp/prove.h" #include "fcmp_pp/tower_cycle.h" #include "misc_log_ex.h" #include "profile_tools.h" #include "ringct/rctOps.h" #include "ringct/rctSigs.h" #include "unit_tests_utils.h" #include "string_tools.h" #include "wallet/tx_builder.h" #include #include #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "unit_tests.fcmp_pp" //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- struct UnifiedOutputsAndKeys { std::vector x_vec; std::vector y_vec; std::vector outputs; }; //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static rct::key biased_derive_key_image_generator(const rct::key O) { crypto::public_key I; crypto::derive_key_image_generator(rct::rct2pk(O), true/*biased*/, I); return rct::pk2rct(I); } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static void store_key(uint8_t b[32], const rct::key &k) { memcpy(b, k.bytes, 32); } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static rct::key load_key(const uint8_t b[32]) { rct::key k; memcpy(k.bytes, b, sizeof(k)); return k; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static crypto::secret_key load_sk(const uint8_t b[32]) { crypto::secret_key sk; memcpy(sk.data, b, sizeof(sk)); return sk; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- template static constexpr bool is_power_of_2(T v) { static_assert(std::is_integral_v); return (v > 0) && ((v & (v - 1)) == 0); } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static FcmpRerandomizedOutputCompressed rerandomize_output_manual(const rct::key &O, const rct::key &C) { FcmpRerandomizedOutputCompressed res; // sample random r_o, r_i, r_r_i, r_c in [0, l) rct::key r_o = rct::skGen(); rct::key r_i = rct::skGen(); rct::key r_r_i = rct::skGen(); rct::key r_c = rct::skGen(); store_key(res.r_o, r_o); store_key(res.r_i, r_i); store_key(res.r_r_i, r_r_i); store_key(res.r_c, r_c); // O~ = O + r_o T rct::key O_tilde = rct::scalarmultKey(rct::pk2rct(crypto::get_T()), r_o); O_tilde = rct::addKeys(O_tilde, O); store_key(res.input.O_tilde, O_tilde); // I = Hp(O) // I~ = I + r_i U const rct::key I = biased_derive_key_image_generator(O); rct::key I_tilde = rct::scalarmultKey(rct::pk2rct(crypto::get_U()), r_i); I_tilde = rct::addKeys(I_tilde, I); store_key(res.input.I_tilde, I_tilde); // precomp T const ge_p3 T_p3 = crypto::get_T_p3(); ge_dsmp T_dsmp; ge_dsm_precomp(T_dsmp, &T_p3); // R = r_i V + r_r_i T rct::key R; rct::addKeys3(R, r_i, rct::pk2rct(crypto::get_V()), r_r_i, T_dsmp); store_key(res.input.R, R); // C~ = C + r_c G rct::key C_tilde; rct::addKeys1(C_tilde, r_c, C); store_key(res.input.C_tilde, C_tilde); return res; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static const UnifiedOutputsAndKeys generate_random_outputs(const CurveTreesV1 &curve_trees, const std::size_t old_n_leaf_tuples, const std::size_t new_n_leaf_tuples) { UnifiedOutputsAndKeys outs; outs.x_vec.resize(new_n_leaf_tuples); outs.y_vec.resize(new_n_leaf_tuples); outs.outputs.resize(new_n_leaf_tuples); tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); const std::size_t batch_size = 1 + (new_n_leaf_tuples / (std::size_t)tpool.get_max_concurrency()); for (std::size_t i = 0; i < new_n_leaf_tuples; i+= batch_size) { const std::size_t end = std::min(i + batch_size, new_n_leaf_tuples); tpool.submit(&waiter, [&outs, i, end, old_n_leaf_tuples]() { for (std::size_t j = i; j < end; ++j) { const std::uint64_t unified_id = old_n_leaf_tuples + j; // Generate random output tuple // Note: lock contention slows this down quite a bit crypto::secret_key o,c; crypto::public_key O,C; crypto::generate_keys(O, o, o, false); crypto::generate_keys(C, c, c, false); const crypto::ec_point C_pt = rct::rct2pt(rct::pk2rct(C)); const fcmp_pp::OutputPair output_pair = fcmp_pp::LegacyOutputPair{{O, C_pt}}; auto unified_output = fcmp_pp::UnifiedOutput{ .unified_id = unified_id, .output_pair = std::move(output_pair) }; // Output pubkey O = xG + yT // In this test, x is o, y is zero crypto::secret_key x = std::move(o); crypto::secret_key y; sc_0((unsigned char *)y.data); outs.x_vec[j] = std::move(x); outs.y_vec[j] = std::move(y); outs.outputs[j] = std::move(unified_output); } }); } CHECK_AND_ASSERT_THROW_MES(waiter.wait(), "Failed generating random outputs"); return outs; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static void make_empty_fcmp_pp_pruned_tx_of_size(const size_t n_inputs, const size_t n_outputs, const size_t extra_len, const bool max_int_fields, cryptonote::transaction &tx_out) { const cryptonote::txin_to_key in{}; const cryptonote::txin_v in_v{in}; const cryptonote::txout_to_carrot_v1 out_targ{}; const cryptonote::tx_out out{.target = out_targ}; tx_out.set_null(); // prefix tx_out.version = 2; tx_out.vin = std::vector(n_inputs, in_v); tx_out.vout = std::vector(n_outputs, out); tx_out.extra.resize(extra_len); // rctSigBase rct::rctSig &rv = tx_out.rct_signatures; rv.type = rct::RCTTypeFcmpPlusPlus; rv.txnFee = max_int_fields ? std::numeric_limits::max() : 0; rv.outPk.resize(n_outputs); rv.ecdhInfo.resize(n_outputs); tx_out.pruned = true; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static void make_empty_fcmp_pp_tx_of_size(const size_t n_inputs, const size_t n_outputs, const size_t extra_len, const uint8_t n_tree_layers, const bool max_int_fields, cryptonote::transaction &tx_out) { make_empty_fcmp_pp_pruned_tx_of_size(n_inputs, n_outputs, extra_len, max_int_fields, tx_out); tx_out.pruned = false; // BulletproofPlus size_t nlr = 0; while ((1u << nlr) < n_outputs) ++nlr; nlr += 6; rct::BulletproofPlus bpp; bpp.L = rct::keyV(nlr); bpp.R = rct::keyV(nlr); // rctSigPrunable rct::rctSig &rv = tx_out.rct_signatures; rv.p.bulletproofs_plus = {bpp}; rv.p.pseudoOuts.resize(n_inputs); rv.p.reference_block = max_int_fields ? CRYPTONOTE_MAX_BLOCK_NUMBER : 0; rv.p.n_tree_layers = n_tree_layers; rv.p.fcmp_pp.resize(fcmp_pp::fcmp_pp_proof_len(n_inputs, n_tree_layers)); } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static bool is_canonical_serializable_fcmp_pp_tx(const cryptonote::transaction &tx) { // To check canonical-ness: serialize, deserialize, then re-serialize and: // a. check serialization success // b. check blob equality // c. check tx hash equality CHECK_AND_ASSERT_MES(tx.version == 2 && tx.rct_signatures.type == rct::RCTTypeFcmpPlusPlus, false, "not a FCMP++ tx"); CHECK_AND_ASSERT_MES(!tx.pruned, false, "not an unpruned tx"); // a cryptonote::blobdata tx_blob_1; CHECK_AND_ASSERT_MES(cryptonote::tx_to_blob(tx, tx_blob_1), false, "failed to serialize tx"); crypto::hash tx_hash_1; CHECK_AND_ASSERT_MES(cryptonote::calculate_transaction_hash(tx, tx_hash_1, nullptr), false, "failed to calculate tx hash"); cryptonote::transaction tx_2; CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(tx_blob_1, tx_2), false, "failed to deserialize tx"); cryptonote::blobdata tx_blob_2; CHECK_AND_ASSERT_MES(cryptonote::tx_to_blob(tx_2, tx_blob_2), false, "failed to re-serialize tx"); crypto::hash tx_hash_2; CHECK_AND_ASSERT_MES(cryptonote::calculate_transaction_hash(tx_2, tx_hash_2, nullptr), false, "failed to calculate tx 2 hash"); // b CHECK_AND_ASSERT_MES(tx_blob_1 == tx_blob_2, false, "re-serialized tx blob differs"); // c CHECK_AND_ASSERT_MES(tx_hash_1 == tx_hash_2, false, "deserialized tx hash differs"); return true; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- static std::map, uint64_t> get_all_fcmp_tx_weights() { // return a map of (n_inputs, n_outputs, extra_len) -> FCMP++ weight for all valid values std::map, uint64_t> res; for (size_t m = 1; m <= FCMP_PLUS_PLUS_MAX_INPUTS; ++m) for (size_t n = 2; n <= FCMP_PLUS_PLUS_MAX_OUTPUTS; ++n) for (size_t el = 0; el <= MAX_TX_EXTRA_SIZE; ++el) res.insert({{m, n, el}, cryptonote::get_fcmp_pp_transaction_weight_v1(m, n, el)}); static constexpr size_t expected_n_weights = FCMP_PLUS_PLUS_MAX_INPUTS * (FCMP_PLUS_PLUS_MAX_OUTPUTS - 1) * (MAX_TX_EXTRA_SIZE + 1); CHECK_AND_ASSERT_THROW_MES(res.size() == expected_n_weights, "bad tx weights counts"); return res; } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, prove) { static const std::size_t selene_chunk_width = fcmp_pp::curve_trees::SELENE_CHUNK_WIDTH; static const std::size_t helios_chunk_width = fcmp_pp::curve_trees::HELIOS_CHUNK_WIDTH; static const std::size_t tree_depth = 3; LOG_PRINT_L1("Test prove with selene chunk width " << selene_chunk_width << ", helios chunk width " << helios_chunk_width << ", tree depth " << tree_depth); uint64_t min_leaves_needed_for_tree_depth = 0; const auto curve_trees = test::init_curve_trees_test(selene_chunk_width, helios_chunk_width, tree_depth, min_leaves_needed_for_tree_depth); LOG_PRINT_L1("Initializing tree with " << min_leaves_needed_for_tree_depth << " leaves"); // Init tree in memory CurveTreesGlobalTree global_tree(*curve_trees); const auto new_outputs = generate_random_outputs(*curve_trees, 0, min_leaves_needed_for_tree_depth); LOG_PRINT_L1("Finished generating random outputs"); ASSERT_TRUE(global_tree.grow_tree(0, min_leaves_needed_for_tree_depth, new_outputs.outputs, false/*audit_tree*/)); LOG_PRINT_L1("Finished initializing tree with " << min_leaves_needed_for_tree_depth << " leaves"); const auto tree_root = global_tree.get_tree_root(); // Keep them cached across runs std::vector selene_branch_blinds; std::vector helios_branch_blinds; std::vector rerandomized_outputs; std::vector sal_proofs; std::vector fcmp_prove_inputs; std::vector key_images; std::vector pseudo_outs; std::unordered_set selected_indices; CHECK_AND_ASSERT_THROW_MES(global_tree.get_n_leaf_tuples() >= FCMP_PLUS_PLUS_MAX_INPUTS, "too few leaves"); // Create proofs with n inputs {1, 2, 2 < random value < 128, 128} static_assert(FCMP_PLUS_PLUS_MAX_INPUTS > 2, "Expected max inputs > 2"); const std::size_t rand_n_inputs = crypto::rand_range(size_t(3), size_t(FCMP_PLUS_PLUS_MAX_INPUTS-1)); const std::vector n_inputs_test_vec{1, 2, rand_n_inputs, FCMP_PLUS_PLUS_MAX_INPUTS}; const crypto::hash signable_tx_hash{}; // Create proofs with random leaf idxs for each n_inputs for (const std::size_t n_inputs : n_inputs_test_vec) { LOG_PRINT_L1("Preparing for " << n_inputs << "-input proof"); while (fcmp_prove_inputs.size() < n_inputs) { // Generate a random unique leaf tuple index within the tree const size_t leaf_idx = crypto::rand_idx(global_tree.get_n_leaf_tuples()); if (selected_indices.count(leaf_idx)) continue; selected_indices.insert(leaf_idx); LOG_PRINT_L1("Constructing proof inputs for leaf idx " << leaf_idx << " (" << fcmp_prove_inputs.size()+1 << "/" << n_inputs << ")"); const auto path = global_tree.get_path_at_leaf_idx(leaf_idx); const std::size_t output_idx = leaf_idx % curve_trees->m_c1_width; const fcmp_pp::OutputPair output_pair = fcmp_pp::LegacyOutputPair{{ (crypto::public_key&)path.leaves[output_idx].O, (crypto::ec_point&)path.leaves[output_idx].C}}; const auto output_tuple = fcmp_pp::curve_trees::output_to_tuple(output_pair); // ASSERT_TRUE(curve_trees->audit_path(path, output_pair, global_tree.get_n_leaf_tuples())); // LOG_PRINT_L1("Passed the audit...\n"); const auto &x = new_outputs.x_vec[leaf_idx]; const auto &y = new_outputs.y_vec[leaf_idx]; // Leaves const auto path_for_proof = curve_trees->path_for_proof(path, output_tuple); const FcmpRerandomizedOutputCompressed rerandomized_output = fcmp_pp::rerandomize_output(output_tuple); pseudo_outs.emplace_back(rct::rct2pt(load_key(rerandomized_output.input.C_tilde))); key_images.emplace_back(); crypto::generate_key_image((crypto::public_key&)path.leaves[output_idx].O, new_outputs.x_vec[leaf_idx], key_images.back()); // Set path const auto helios_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c2_scalar_chunks); const auto selene_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c1_scalar_chunks); const auto path_rust = fcmp_pp::path_new({path_for_proof.leaves.data(), path_for_proof.leaves.size()}, path_for_proof.output_idx, {helios_scalar_chunks.data(), helios_scalar_chunks.size()}, {selene_scalar_chunks.data(), selene_scalar_chunks.size()}); // Collect blinds for rerandomized output const auto o_blind = fcmp_pp::o_blind(rerandomized_output); const auto i_blind = fcmp_pp::i_blind(rerandomized_output); const auto i_blind_blind = fcmp_pp::i_blind_blind(rerandomized_output); const auto c_blind = fcmp_pp::c_blind(rerandomized_output); const auto blinded_o_blind = fcmp_pp::blind_o_blind(o_blind); const auto blinded_i_blind = fcmp_pp::blind_i_blind(i_blind); const auto blinded_i_blind_blind = fcmp_pp::blind_i_blind_blind(i_blind_blind); const auto blinded_c_blind = fcmp_pp::blind_c_blind(c_blind); const auto output_blinds = fcmp_pp::output_blinds_new(blinded_o_blind, blinded_i_blind, blinded_i_blind_blind, blinded_c_blind); // Cache branch blinds if (selene_branch_blinds.empty()) for (std::size_t i = 0; i < helios_scalar_chunks.size(); ++i) selene_branch_blinds.emplace_back(fcmp_pp::gen_selene_branch_blind()); if (helios_branch_blinds.empty()) for (std::size_t i = 0; i < selene_scalar_chunks.size(); ++i) helios_branch_blinds.emplace_back(fcmp_pp::gen_helios_branch_blind()); // Construct SAL proof const auto sal_proof = fcmp_pp::prove_sal( signable_tx_hash, x, y, rerandomized_output); // Collect input for membership proof auto fcmp_pp_prove_input = fcmp_pp::fcmp_pp_prove_input_new( path_rust, output_blinds, selene_branch_blinds, helios_branch_blinds); rerandomized_outputs.emplace_back(std::move(rerandomized_output)); sal_proofs.emplace_back(std::move(sal_proof.first)); fcmp_prove_inputs.emplace_back(std::move(fcmp_pp_prove_input)); } LOG_PRINT_L1("Constructing membership proof and verifying (n_inputs=" << n_inputs << ")"); const std::size_t n_layers = curve_trees->n_layers(global_tree.get_n_leaf_tuples()); // Create membership proof LOG_PRINT_L1("Proving " << n_inputs << "-in " << n_layers << "-layer FCMP"); const auto membership_proof = fcmp_pp::prove_membership(fcmp_prove_inputs, n_layers); // Serialize FCMP++ proof and verify const auto fcmp_pp_proof = fcmp_pp::fcmp_pp_proof_from_parts_v1(rerandomized_outputs, sal_proofs, membership_proof, n_layers); LOG_PRINT_L1("Verifying (n_inputs=" << n_inputs << ")"); bool verify = fcmp_pp::verify( signable_tx_hash, fcmp_pp_proof, n_layers, tree_root, pseudo_outs, key_images ); ASSERT_TRUE(verify); LOG_PRINT_L1("Successfully verified (n_inputs=" << n_inputs << ")"); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, verify) { const uint8_t n_layers = 7; const auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1(); // Generate full chunks of outputs, enough to construct a tx with max inputs std::size_t n_generated_outputs = FCMP_PLUS_PLUS_MAX_INPUTS; if (n_generated_outputs % curve_trees->m_c1_width) n_generated_outputs = curve_trees->m_c1_width * ((n_generated_outputs / curve_trees->m_c1_width) + 1); const auto new_outputs = generate_random_outputs(*curve_trees, 0, n_generated_outputs); // Make sure we generated enough outputs and calculate expected n leaves in a dummy tree uint64_t expected_n_leaves = curve_trees->m_c1_width; for (uint8_t i = 1; i < n_layers; ++i) expected_n_leaves *= (i % 2 != 0) ? curve_trees->m_c2_width : curve_trees->m_c1_width; ASSERT_EQ(curve_trees->n_layers(expected_n_leaves), n_layers); ASSERT_GE(expected_n_leaves, new_outputs.outputs.size()); // Instantiate dummy paths const auto paths = curve_trees->get_dummy_paths(new_outputs.outputs, n_layers); const auto tree_root = n_layers % 2 == 0 ? fcmp_pp::helios_tree_root(paths.c2_layers.back().find(0)->second.back()) : fcmp_pp::selene_tree_root(paths.c1_layers.back().find(0)->second.back()); // Make branch blinds once purely for performance reasons (DO NOT DO THIS IN PRODUCTION) const size_t expected_num_selene_branch_blinds = n_layers / 2; LOG_PRINT_L1("Calculating " << expected_num_selene_branch_blinds << " Selene branch blinds"); std::vector selene_branch_blinds; for (size_t i = 0; i < expected_num_selene_branch_blinds; ++i) selene_branch_blinds.emplace_back(fcmp_pp::gen_selene_branch_blind()); const size_t expected_num_helios_branch_blinds = (n_layers - 1) / 2; LOG_PRINT_L1("Calculating " << expected_num_helios_branch_blinds << " Helios branch blinds"); std::vector helios_branch_blinds; for (size_t i = 0; i < expected_num_helios_branch_blinds; ++i) helios_branch_blinds.emplace_back(fcmp_pp::gen_helios_branch_blind()); const crypto::hash signable_tx_hash{}; // Reuse inputs across runs to minimize time to construct std::vector rerandomized_outputs; std::vector sal_proofs; std::vector fcmp_prove_inputs; std::vector key_images; std::vector pseudo_outs; std::unordered_set selected_indices; // Create proofs with n inputs {1, 2, 2 < random value < 128, 128} static_assert(FCMP_PLUS_PLUS_MAX_INPUTS > 2, "Expected max inputs > 2"); const std::size_t rand_n_inputs = crypto::rand_range(size_t(3), size_t(FCMP_PLUS_PLUS_MAX_INPUTS-1)); const std::vector n_inputs_test_vec{1, 2, rand_n_inputs, FCMP_PLUS_PLUS_MAX_INPUTS}; // Create proofs with random leaf idxs for each n_inputs for (const std::size_t n_inputs : n_inputs_test_vec) { std::unordered_set selected_indices; LOG_PRINT_L1("Preparing for " << n_inputs << "-input proof"); while (fcmp_prove_inputs.size() < n_inputs) { // Generate a random unique leaf tuple index within the tree const size_t leaf_idx = crypto::rand_idx(new_outputs.outputs.size()); if (selected_indices.count(leaf_idx)) continue; selected_indices.insert(leaf_idx); LOG_PRINT_L1("Constructing proof inputs for leaf idx " << leaf_idx << " (" << fcmp_prove_inputs.size()+1 << "/" << n_inputs << ")"); const uint64_t leaf_chunk_idx = leaf_idx / curve_trees->m_c1_width; const auto leaf_offset = leaf_idx % curve_trees->m_c1_width; const auto &leaf_chunk = paths.leaves_by_chunk_idx.find(leaf_chunk_idx)->second; const auto &leaf = leaf_chunk[leaf_offset]; const fcmp_pp::OutputPair output_pair = fcmp_pp::LegacyOutputPair{{ (crypto::public_key&)leaf.O, (crypto::ec_point&)leaf.C}}; const auto output_tuple = fcmp_pp::curve_trees::output_to_tuple(output_pair); const auto &x = new_outputs.x_vec[leaf_idx]; const auto &y = new_outputs.y_vec[leaf_idx]; // Construct single path from dummy paths const auto path = curve_trees->get_single_dummy_path(paths, expected_n_leaves, leaf_idx); ASSERT_TRUE(curve_trees->audit_path(path, output_pair, expected_n_leaves)); // Leaves const auto path_for_proof = curve_trees->path_for_proof(path, output_tuple); const FcmpRerandomizedOutputCompressed rerandomized_output = fcmp_pp::rerandomize_output(output_tuple); pseudo_outs.emplace_back(rct::rct2pt(load_key(rerandomized_output.input.C_tilde))); key_images.emplace_back(); crypto::generate_key_image((crypto::public_key&)leaf.O, new_outputs.x_vec[leaf_idx], key_images.back()); // Set path const auto helios_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c2_scalar_chunks); const auto selene_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c1_scalar_chunks); const auto path_rust = fcmp_pp::path_new({path_for_proof.leaves.data(), path_for_proof.leaves.size()}, path_for_proof.output_idx, {helios_scalar_chunks.data(), helios_scalar_chunks.size()}, {selene_scalar_chunks.data(), selene_scalar_chunks.size()}); // Collect blinds for rerandomized output const auto o_blind = fcmp_pp::o_blind(rerandomized_output); const auto i_blind = fcmp_pp::i_blind(rerandomized_output); const auto i_blind_blind = fcmp_pp::i_blind_blind(rerandomized_output); const auto c_blind = fcmp_pp::c_blind(rerandomized_output); const auto blinded_o_blind = fcmp_pp::blind_o_blind(o_blind); const auto blinded_i_blind = fcmp_pp::blind_i_blind(i_blind); const auto blinded_i_blind_blind = fcmp_pp::blind_i_blind_blind(i_blind_blind); const auto blinded_c_blind = fcmp_pp::blind_c_blind(c_blind); const auto output_blinds = fcmp_pp::output_blinds_new(blinded_o_blind, blinded_i_blind, blinded_i_blind_blind, blinded_c_blind); // Construct SAL proof const auto sal_proof = fcmp_pp::prove_sal( signable_tx_hash, x, y, rerandomized_output); // Collect input for membership proof auto fcmp_pp_prove_input = fcmp_pp::fcmp_pp_prove_input_new( path_rust, output_blinds, selene_branch_blinds, helios_branch_blinds); rerandomized_outputs.emplace_back(std::move(rerandomized_output)); sal_proofs.emplace_back(std::move(sal_proof.first)); fcmp_prove_inputs.emplace_back(std::move(fcmp_pp_prove_input)); } LOG_PRINT_L1("Constructing membership proof and verifying (n_inputs=" << n_inputs << ")"); // Create membership proof LOG_PRINT_L1("Proving " << n_inputs << "-in " << std::to_string(n_layers) << "-layer FCMP"); TIME_MEASURE_START(prove_ms); const auto membership_proof = fcmp_pp::prove_membership(fcmp_prove_inputs, n_layers); TIME_MEASURE_FINISH(prove_ms); LOG_PRINT_L0("Constructed membership proof (n_inputs=" << n_inputs << ", " << prove_ms << "ms)"); // Serialize FCMP++ proof and verify const auto fcmp_pp_proof = fcmp_pp::fcmp_pp_proof_from_parts_v1(rerandomized_outputs, sal_proofs, membership_proof, n_layers); LOG_PRINT_L1("Verifying (n_inputs=" << n_inputs << ")"); TIME_MEASURE_START(verify_ms); bool verify = fcmp_pp::verify( signable_tx_hash, fcmp_pp_proof, n_layers, tree_root, pseudo_outs, key_images ); TIME_MEASURE_FINISH(verify_ms); ASSERT_TRUE(verify); LOG_PRINT_L0("Successfully verified (n_inputs=" << n_inputs << ", " << verify_ms << "ms)"); // const crypto::ec_point tree_root_bytes = n_layers % 2 == 0 // ? curve_trees->m_c2->to_bytes(paths.c2_layers.back().find(0)->second.back()) // : curve_trees->m_c1->to_bytes(paths.c1_layers.back().find(0)->second.back()); // unit_test::write_fcmp_pp_verify_input_to_file( // n_inputs, // signable_tx_hash, // fcmp_pp_proof, // n_layers, // tree_root_bytes, // pseudo_outs, // key_images); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, batch_verify_from_file) { const std::size_t n_inputs = 128; // We want a proof per thread, or 4 proofs if < 4 threads tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); const std::size_t n_proofs = std::max(4, tpool.get_max_concurrency()); // We repeat verify attempts in hopes of using all malloc arenas (glibc specific) const std::size_t repeat_n_times = 10; // Read from file crypto::hash signable_tx_hash; std::vector fcmp_pp_proof; uint8_t n_layers; fcmp_pp::TreeRootShared tree_root; std::vector pseudo_outs; std::vector key_images; unit_test::read_fcmp_pp_verify_input_from_file( n_inputs, signable_tx_hash, fcmp_pp_proof, n_layers, tree_root, pseudo_outs, key_images); // Test single verification LOG_PRINT_L1("Verifying (n_inputs=" << n_inputs << ")"); bool verify = fcmp_pp::verify( signable_tx_hash, fcmp_pp_proof, n_layers, tree_root, pseudo_outs, key_images ); ASSERT_TRUE(verify); LOG_PRINT_L1("Successfully verified (n_inputs=" << n_inputs << ")"); // Repeat batch verify attempts and observe memory usage for (std::size_t i = 0; i < repeat_n_times; ++i) { // Collect the FCMP++ verify inputs std::vector fcmp_pp_verify_inputs; fcmp_pp_verify_inputs.reserve(n_proofs); for (std::size_t i = 0; i < n_proofs; ++i) { fcmp_pp_verify_inputs.emplace_back(fcmp_pp::fcmp_pp_verify_input_new( signable_tx_hash, fcmp_pp_proof, n_layers, tree_root, pseudo_outs, key_images )); } // Verify FCMP++ 128-in proofs in parallel using the batch verifier LOG_PRINT_L1("Batch verifying " << n_proofs << " FCMP++ txs, attempt " << i+1); ASSERT_TRUE(rct::batchVerifyFcmpPpProofs(std::move(fcmp_pp_verify_inputs))); LOG_PRINT_L1("Successfully batch verified " << n_proofs << " FCMP++ txs, attempt " << i+1); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, sal_completeness) { // O, I, C, L const rct::key x = rct::skGen(); const rct::key y = rct::skGen(); rct::key O; rct::addKeys2(O, x, y, rct::pk2rct(crypto::get_T())); // O = x G + y T const rct::key I = biased_derive_key_image_generator(O); const rct::key C = rct::pkGen(); crypto::key_image L; crypto::generate_key_image(rct::rct2pk(O), rct::rct2sk(x), L); // Rerandomize const auto output = fcmp_pp::output_tuple_from_bytes(rct::rct2pt(O), rct::rct2pt(I), rct::rct2pt(C)); const FcmpRerandomizedOutputCompressed rerandomized_output{fcmp_pp::rerandomize_output(output)}; // Generate signable_tx_hash const crypto::hash signable_tx_hash = crypto::rand(); // Prove const std::pair sal_proof = fcmp_pp::prove_sal( signable_tx_hash, rct::rct2sk(x), rct::rct2sk(y), rerandomized_output); EXPECT_EQ(L, sal_proof.second); // Verify const bool ver = fcmp_pp::verify_sal(signable_tx_hash, rerandomized_output.input, L, sal_proof.first); EXPECT_TRUE(ver); } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, membership_completeness) { // TODO: raise max num inputs and make test execute in reasonable time static const std::size_t MAX_NUM_INPUTS = 8; static const std::size_t selene_chunk_width = fcmp_pp::curve_trees::SELENE_CHUNK_WIDTH; static const std::size_t helios_chunk_width = fcmp_pp::curve_trees::HELIOS_CHUNK_WIDTH; static const std::size_t tree_depth = 3; static const std::size_t n_layers = 1 + tree_depth; LOG_PRINT_L1("Test prove with selene chunk width " << selene_chunk_width << ", helios chunk width " << helios_chunk_width << ", tree depth " << tree_depth); uint64_t min_leaves_needed_for_tree_depth = 0; const auto curve_trees = test::init_curve_trees_test(selene_chunk_width, helios_chunk_width, tree_depth, min_leaves_needed_for_tree_depth); LOG_PRINT_L1("Initializing tree with " << min_leaves_needed_for_tree_depth << " leaves"); // Init tree in memory CurveTreesGlobalTree global_tree(*curve_trees); const auto new_outputs = generate_random_outputs(*curve_trees, 0, min_leaves_needed_for_tree_depth); ASSERT_TRUE(global_tree.grow_tree(0, min_leaves_needed_for_tree_depth, new_outputs.outputs)); LOG_PRINT_L1("Finished initializing tree with " << min_leaves_needed_for_tree_depth << " leaves"); const size_t num_tree_leaves = global_tree.get_n_leaf_tuples(); // Make branch blinds once purely for performance reasons (DO NOT DO THIS IN PRODUCTION) const size_t expected_num_selene_branch_blinds = (tree_depth + 1) / 2; LOG_PRINT_L1("Calculating " << expected_num_selene_branch_blinds << " Selene branch blinds"); std::vector selene_branch_blinds; for (size_t i = 0; i < expected_num_selene_branch_blinds; ++i) selene_branch_blinds.emplace_back(fcmp_pp::gen_selene_branch_blind()); const size_t expected_num_helios_branch_blinds = tree_depth / 2; LOG_PRINT_L1("Calculating " << expected_num_helios_branch_blinds << " Helios branch blinds"); std::vector helios_branch_blinds; for (size_t i = 0; i < expected_num_helios_branch_blinds; ++i) helios_branch_blinds.emplace_back(fcmp_pp::gen_helios_branch_blind()); // For every supported input size... for (size_t num_inputs = 1; num_inputs <= MAX_NUM_INPUTS; ++num_inputs) { LOG_PRINT_L1("Starting " << num_inputs << "-in " << n_layers << "-layer test case"); // Build up a set of `num_inputs` inputs to prove membership on ASSERT_LE(num_inputs, num_tree_leaves); std::set selected_indices; std::vector fcmp_raw_inputs; fcmp_raw_inputs.reserve(num_inputs); std::vector fcmp_provable_inputs; fcmp_provable_inputs.reserve(num_inputs); while (selected_indices.size() < num_inputs) { // Generate a random unique leaf tuple index within the tree const size_t leaf_idx = crypto::rand_idx(num_tree_leaves); if (selected_indices.count(leaf_idx)) continue; else selected_indices.insert(leaf_idx); // Fetch path const auto path = global_tree.get_path_at_leaf_idx(leaf_idx); const std::size_t output_idx = leaf_idx % curve_trees->m_c1_width; // Set up rust path const fcmp_pp::OutputPair output_pair = fcmp_pp::LegacyOutputPair{{ (crypto::public_key&)path.leaves[output_idx].O, (crypto::ec_point&)path.leaves[output_idx].C}}; const auto output_tuple = fcmp_pp::curve_trees::output_to_tuple(output_pair); const auto path_for_proof = curve_trees->path_for_proof(path, output_tuple); const auto helios_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c2_scalar_chunks); const auto selene_scalar_chunks = fcmp_pp::tower_cycle::scalar_chunks_to_chunk_vector( path_for_proof.c1_scalar_chunks); const auto path_rust = fcmp_pp::path_new({path_for_proof.leaves.data(), path_for_proof.leaves.size()}, path_for_proof.output_idx, {helios_scalar_chunks.data(), helios_scalar_chunks.size()}, {selene_scalar_chunks.data(), selene_scalar_chunks.size()}); // Rerandomize output. We use rerandomize_output_manual() here just to test out the U, V // generators and manually constructing a FcmpRerandomizedOutputCompressed. But // fcmp_pp::rerandomize_output() would work perfectly fine here as well, especially // since we're not balancing C~ and thus don't need to modify it. const FcmpRerandomizedOutputCompressed rerandomized_output = rerandomize_output_manual( (rct::key&)path.leaves.at(output_idx).O, (rct::key&)path.leaves.at(output_idx).C); // check the size of our precalculated branch blind cache ASSERT_EQ(helios_scalar_chunks.size(), expected_num_selene_branch_blinds); ASSERT_EQ(selene_scalar_chunks.size(), expected_num_helios_branch_blinds); // Calculate output blinds for rerandomized output LOG_PRINT_L1("Calculating output blind"); const SeleneScalar o_blind = fcmp_pp::o_blind(rerandomized_output); const SeleneScalar i_blind = fcmp_pp::i_blind(rerandomized_output); const SeleneScalar i_blind_blind = fcmp_pp::i_blind_blind(rerandomized_output); const SeleneScalar c_blind = fcmp_pp::c_blind(rerandomized_output); const auto blinded_o_blind = fcmp_pp::blind_o_blind(o_blind); const auto blinded_i_blind = fcmp_pp::blind_i_blind(i_blind); const auto blinded_i_blind_blind = fcmp_pp::blind_i_blind_blind(i_blind_blind); const auto blinded_c_blind = fcmp_pp::blind_c_blind(c_blind); const auto output_blinds = fcmp_pp::output_blinds_new(blinded_o_blind, blinded_i_blind, blinded_i_blind_blind, blinded_c_blind); // make provable FCMP input fcmp_provable_inputs.push_back(fcmp_pp::fcmp_pp_prove_input_new( path_rust, output_blinds, selene_branch_blinds, helios_branch_blinds)); // get FCMP input fcmp_raw_inputs.push_back(rerandomized_output.input); } ASSERT_EQ(fcmp_raw_inputs.size(), fcmp_provable_inputs.size()); // Create FCMP proof LOG_PRINT_L1("Proving " << num_inputs << "-in " << n_layers << "-layer FCMP"); const fcmp_pp::FcmpMembershipProof proof = fcmp_pp::prove_membership(fcmp_provable_inputs, n_layers); // Verify LOG_PRINT_L1("Verifying " << num_inputs << "-in " << n_layers << "-layer FCMP"); EXPECT_TRUE(fcmp_pp::verify_membership(proof, n_layers, global_tree.get_tree_root(), fcmp_raw_inputs)); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, force_init_gen_u_v) { #ifdef NDEBUG GTEST_SKIP() << "Generator reproduction assert statements don't trigger on Release builds"; #endif // these will cause assertion failures in debug mode if the seeds are wrong const ge_p3 U_p3 = crypto::get_U_p3(); const ge_p3 V_p3 = crypto::get_V_p3(); const ge_cached U_cached = crypto::get_U_cached(); const ge_cached V_cached = crypto::get_V_cached(); (void) U_p3, (void) V_p3, (void) U_cached, (void) V_cached; } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, calculate_fcmp_input_for_rerandomizations_convergence) { const rct::key onetime_address = rct::pkGen(); const rct::key amount_commitment = rct::pkGen(); const rct::key I = biased_derive_key_image_generator(onetime_address); const fcmp_pp::OutputTuple output = fcmp_pp::output_tuple_from_bytes(rct::rct2pt(onetime_address), rct::rct2pt(I), rct::rct2pt(amount_commitment)); const FcmpRerandomizedOutputCompressed rerandomized_output = fcmp_pp::rerandomize_output(output); const FcmpInputCompressed recomputed_input = fcmp_pp::calculate_fcmp_input_for_rerandomizations( rct::rct2pk(onetime_address), rct::rct2pt(amount_commitment), /*use_biased_hash_to_point=*/true, load_sk(rerandomized_output.r_o), load_sk(rerandomized_output.r_i), load_sk(rerandomized_output.r_r_i), load_sk(rerandomized_output.r_c)); EXPECT_EQ(0, memcmp(&rerandomized_output.input, &recomputed_input, sizeof(FcmpInputCompressed))); } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, proof_size_table) { static const uint32_t PROOF_LEN_TABLE[FCMP_PLUS_PLUS_MAX_INPUTS][FCMP_PLUS_PLUS_MAX_LAYERS] { {3136, 3776, 4160, 4800, 5184, 5824, 6208, 6848, 6272, 5696, 5952, 6336, }, {4288, 5568, 4992, 6272, 6784, 6592, 7104, 7872, 7424, 6976, 7360, 7872, }, {3840, 5760, 6528, 6976, 6528, 6592, 7232, 8000, 8512, 9280, 9088, 8768, }, {4352, 6912, 6592, 7168, 7936, 8000, 8768, 9792, 9600, 9408, 10048, 10816, }, {4992, 6976, 6528, 7232, 8256, 9536, 9344, 9408, 10176, 11200, 11968, 12864, }, {3904, 6272, 7424, 8256, 8192, 8640, 9664, 10816, 11712, 12864, 13056, 13120, }, {4160, 6912, 7104, 8064, 9216, 9792, 10944, 12224, 12416, 12736, 13760, 14912, }, {4416, 7552, 7744, 8832, 10112, 10688, 11968, 13504, 13824, 14144, 15296, 16576, }, {4800, 7232, 7424, 8640, 10048, 11840, 12288, 12864, 14144, 15680, 16960, 18368, }, {5056, 7744, 7936, 9280, 10944, 12864, 13312, 14016, 15424, 17088, 18496, 20032, }, {3840, 6784, 8576, 10048, 10624, 11840, 13504, 15168, 16704, 18496, 20160, 20992, }, {3968, 7168, 9088, 10688, 11392, 12608, 14400, 16320, 17984, 19904, 20864, 21696, }, {4096, 7552, 9600, 11328, 12160, 13504, 15424, 17472, 19264, 20480, 21440, 23232, }, {4224, 7936, 9024, 10880, 12928, 14400, 16448, 18624, 19712, 20928, 22848, 24896, }, {4352, 8320, 9536, 11520, 13696, 15296, 17472, 19776, 20864, 22208, 24256, 26432, }, {4480, 8704, 9920, 12032, 14336, 15936, 18240, 20800, 22144, 23488, 25664, 27968, }, {4736, 8256, 9472, 11712, 14144, 16832, 18304, 20032, 22464, 24896, 27200, 29632, }, {4864, 8512, 9856, 12224, 14784, 17728, 19328, 21056, 23488, 26176, 28608, 31168, }, {4992, 8896, 10368, 12864, 15552, 18624, 20224, 22080, 24768, 27456, 30016, 32832, }, {5120, 9152, 10624, 13248, 16192, 19392, 21120, 23104, 25792, 28736, 31424, 34240, }, {5248, 9536, 11136, 13888, 15872, 19200, 22016, 24128, 27072, 30144, 32960, 35904, }, {3904, 8320, 11520, 14400, 16384, 19008, 22080, 25152, 28096, 31296, 34368, 36608, }, {3904, 8576, 11904, 14912, 17152, 19776, 22848, 26176, 29376, 32704, 34944, 37184, }, {4032, 8832, 12288, 15424, 17664, 20416, 23744, 27200, 30400, 33856, 36352, 38720, }, {4160, 9216, 12800, 16064, 18432, 21312, 24640, 28224, 31680, 35264, 37760, 40256, }, {4160, 9344, 13056, 16448, 18944, 21952, 25536, 29248, 32704, 35584, 38208, 41664, }, {4288, 9728, 13568, 17088, 19584, 22720, 26432, 30272, 33152, 36032, 39616, 43328, }, {4288, 9856, 12736, 16384, 20224, 23488, 27328, 31296, 34176, 37184, 40896, 44736, }, {4416, 10240, 13120, 16896, 20864, 24128, 28096, 32320, 35328, 38464, 42304, 46144, }, {4416, 10368, 13504, 17408, 21504, 25024, 29120, 33344, 36352, 39616, 43584, 47680, }, {4544, 10752, 13888, 17920, 22144, 25664, 29888, 34368, 37632, 40896, 44992, 49216, }, {4544, 10880, 14144, 18304, 22656, 26304, 30656, 35264, 38656, 42048, 46272, 50624, }, {4672, 10304, 13696, 17984, 22464, 27200, 30720, 34496, 38848, 43328, 47680, 52160, }, {4800, 10560, 13952, 18368, 22976, 27840, 31488, 35392, 40000, 44608, 49088, 53696, }, {4800, 10688, 14336, 18880, 23616, 28736, 32512, 36416, 41024, 45760, 50368, 55232, }, {4928, 10944, 14592, 19264, 24128, 29376, 33280, 37312, 42048, 47040, 51776, 56640, }, {4928, 11200, 14976, 19776, 24896, 30144, 34048, 38336, 43200, 48192, 53056, 58048, }, {5056, 11456, 15360, 20288, 25408, 30912, 34944, 39232, 44352, 49472, 54464, 59712, }, {5056, 11584, 15616, 20672, 25920, 31552, 35840, 40256, 45376, 50752, 55872, 61120, }, {5184, 11840, 15872, 21056, 26560, 32320, 36608, 41152, 46400, 51904, 57152, 62528, }, {5312, 12224, 16384, 21696, 26240, 32128, 37504, 42176, 47552, 53056, 58560, 64192, }, {5312, 12352, 16640, 22080, 26752, 32768, 38272, 43072, 48704, 54464, 59968, 65600, }, {3968, 11136, 16896, 22464, 27264, 32576, 38336, 44096, 49728, 55616, 61248, 66176, }, {3968, 11264, 17280, 22976, 27776, 33216, 39104, 44992, 50752, 56768, 62656, 67712, }, {3968, 11520, 17664, 23488, 28544, 33984, 39872, 46016, 51904, 58048, 64064, 69120, }, {3968, 11648, 17920, 23872, 29056, 34624, 40640, 46912, 53056, 59328, 64512, 69696, }, {4096, 11904, 18304, 24384, 29568, 35264, 41536, 47936, 54080, 60480, 65792, 71104, }, {4096, 12032, 18560, 24768, 30080, 35904, 42304, 48832, 55104, 61632, 67200, 72640, }, {4096, 12288, 18944, 25280, 30720, 36672, 43200, 49856, 56384, 63040, 68608, 74176, }, {4224, 12544, 19328, 25792, 31360, 37440, 43968, 50752, 57408, 64192, 69888, 75584, }, {4224, 12672, 19584, 26176, 31872, 38080, 44736, 51648, 58432, 65344, 71168, 76992, }, {4224, 12800, 19840, 26560, 32384, 38720, 45632, 52672, 59456, 65664, 71616, 78400, }, {4224, 13056, 20352, 27200, 33024, 39360, 46400, 53696, 60736, 66944, 72896, 79808, }, {4352, 13312, 20608, 27584, 33536, 40128, 47296, 54592, 60928, 67264, 74304, 81472, }, {4352, 13440, 19776, 26880, 34176, 40896, 48064, 55488, 61952, 68416, 75584, 82880, }, {4352, 13568, 20032, 27264, 34688, 41536, 48960, 56512, 62976, 69568, 76864, 84288, }, {4480, 13952, 20416, 27776, 35328, 42176, 49728, 57536, 64128, 70720, 78144, 85696, }, {4480, 14080, 20672, 28160, 35840, 42816, 50496, 58432, 65152, 72000, 79552, 87104, }, {4480, 14208, 21056, 28672, 36352, 43584, 51392, 59328, 66176, 73152, 80832, 88640, }, {4480, 14336, 21312, 29056, 36992, 44352, 52288, 60352, 67200, 74304, 82112, 90048, }, {4608, 14720, 21696, 29568, 37632, 44992, 53056, 61376, 68480, 75584, 83520, 91584, }, {4608, 14848, 21952, 29952, 38144, 45632, 53824, 62272, 69504, 76736, 84800, 92992, }, {4608, 14976, 22208, 30336, 38656, 46272, 54592, 63168, 70528, 77888, 86080, 94400, }, {4608, 15104, 22464, 30720, 39168, 46912, 55360, 64064, 71552, 79040, 87360, 95808, }, {4736, 14528, 22016, 30400, 38976, 47808, 55424, 63296, 71744, 80320, 88768, 97344, }, {4736, 14656, 22272, 30784, 39488, 48448, 56192, 64192, 72768, 81472, 90048, 98752, }, {4736, 14784, 22528, 31168, 40000, 49088, 56960, 65088, 73792, 82624, 91328, 100160, }, {4864, 15040, 22784, 31552, 40512, 49728, 57728, 65984, 74944, 83904, 92736, 101696, }, {4864, 15168, 23040, 31936, 41152, 50496, 58624, 67008, 75968, 85056, 94016, 103104, }, {4864, 15296, 23424, 32448, 41664, 51264, 59520, 67904, 76992, 86208, 95296, 104640, }, {4864, 15424, 23680, 32832, 42176, 51904, 60288, 68800, 78016, 87488, 96704, 106048, }, {4992, 15680, 23936, 33216, 42688, 52544, 61056, 69696, 79040, 88640, 97984, 107456, }, {4992, 15936, 24320, 33728, 43328, 53184, 61824, 70720, 80192, 89792, 99264, 108864, }, {4992, 16064, 24576, 34112, 43968, 53952, 62592, 71616, 81216, 90944, 100544, 110272, }, {5120, 16320, 24832, 34496, 44480, 54720, 63488, 72512, 82240, 92096, 101952, 111936, }, {5120, 16448, 25216, 35008, 44992, 55360, 64256, 73408, 83392, 93376, 103232, 113344, }, {5120, 16576, 25472, 35392, 45504, 56000, 65152, 74432, 84416, 94656, 104640, 114752, }, {5120, 16704, 25728, 35776, 46016, 56640, 65920, 75328, 85440, 95808, 105920, 116160, }, {5248, 16960, 25984, 36160, 46656, 57408, 66688, 76224, 86464, 96960, 107200, 117568, }, {5248, 17088, 26240, 36544, 47168, 58048, 67456, 77120, 87488, 98112, 108480, 118976, }, {5248, 17344, 26624, 37056, 47808, 58816, 68352, 78144, 88640, 99264, 109888, 120640, }, {5376, 17600, 27008, 37568, 47360, 58496, 69120, 79040, 89664, 100416, 111168, 122048, }, {5376, 17728, 27264, 37952, 47872, 59136, 69888, 79936, 90816, 101696, 112448, 123456, }, {5376, 17856, 27520, 38336, 48384, 59776, 70656, 80832, 91840, 102976, 113856, 124864, }, {3904, 16512, 27776, 38720, 48896, 60416, 71424, 81728, 92864, 104128, 115136, 126272, }, {4032, 16768, 28032, 39104, 49408, 60224, 71488, 82752, 93888, 105280, 116416, 126848, }, {4032, 16896, 28288, 39488, 49920, 60864, 72256, 83648, 94912, 106432, 117696, 128256, }, {4032, 17024, 28672, 40000, 50432, 61504, 73024, 84544, 95936, 107584, 119104, 129792, }, {4032, 17280, 29056, 40512, 51200, 62272, 73792, 85568, 97088, 108736, 120384, 131200, }, {4032, 17408, 29312, 40896, 51712, 62912, 74560, 86464, 98112, 110016, 121792, 132608, }, {4032, 17536, 29568, 41280, 52224, 63552, 75328, 87360, 99264, 111296, 123072, 134016, }, {4032, 17664, 29824, 41664, 52736, 64192, 76096, 88256, 100288, 112448, 123520, 134592, }, {4160, 17920, 30080, 42048, 53248, 64832, 76864, 89152, 101312, 113600, 124800, 136000, }, {4160, 18048, 30464, 42560, 53760, 65472, 77760, 90176, 102336, 114752, 126080, 137408, }, {4160, 18176, 30720, 42944, 54272, 66112, 78528, 91072, 103360, 115904, 127488, 138944, }, {4160, 18304, 30976, 43328, 54784, 66752, 79296, 91968, 104384, 117056, 128768, 140352, }, {4160, 18560, 31360, 43840, 55424, 67520, 80192, 92992, 105536, 118336, 130176, 141888, }, {4160, 18688, 31616, 44224, 55936, 68160, 80960, 93888, 106688, 119616, 131456, 143296, }, {4160, 18816, 32000, 44736, 56576, 68928, 81728, 94784, 107712, 120768, 132736, 144704, }, {4288, 19072, 32256, 45120, 57088, 69568, 82496, 95680, 108736, 121920, 134016, 146112, }, {4288, 19200, 32512, 45504, 57600, 70208, 83264, 96576, 109760, 123072, 135296, 147520, }, {4288, 19328, 32768, 45888, 58112, 70848, 84032, 97472, 110784, 124224, 136576, 148928, }, {4288, 19456, 33024, 46272, 58624, 71488, 84928, 98496, 111808, 124544, 137024, 150336, }, {4288, 19584, 33280, 46656, 59136, 72128, 85696, 99392, 112832, 125696, 138304, 151744, }, {4288, 19840, 33792, 47296, 59776, 72768, 86464, 100416, 113984, 126848, 139584, 153152, }, {4288, 19968, 34048, 47680, 60288, 73408, 87232, 101312, 115136, 128128, 140864, 154560, }, {4416, 20224, 34304, 48064, 60800, 74176, 88128, 102208, 116160, 129280, 142144, 156096, }, {4416, 20352, 34560, 48448, 61312, 74816, 88896, 103104, 116352, 129600, 143552, 157632, }, {4416, 20480, 34816, 48832, 61952, 75584, 89664, 104000, 117376, 130752, 144832, 159040, }, {4416, 20608, 33984, 48128, 62464, 76224, 90432, 104896, 118400, 131904, 146112, 160448, }, {4416, 20736, 34240, 48512, 62976, 76864, 91328, 105920, 119424, 133056, 147392, 161856, }, {4416, 20864, 34496, 48896, 63488, 77504, 92096, 106816, 120448, 134208, 148672, 163264, }, {4416, 21120, 34880, 49408, 64128, 78144, 92864, 107840, 121600, 135360, 149952, 164672, }, {4544, 21376, 35136, 49792, 64640, 78784, 93632, 108736, 122624, 136512, 151232, 166080, }, {4544, 21504, 35392, 50176, 65152, 79424, 94400, 109632, 123648, 137664, 152512, 167488, }, {4544, 21632, 35648, 50560, 65664, 80064, 95168, 110528, 124672, 138944, 153920, 168896, }, {4544, 21760, 36032, 51072, 66176, 80704, 95936, 111424, 125696, 140096, 155200, 170304, }, {4544, 21888, 36288, 51456, 66688, 81472, 96832, 112320, 126720, 141248, 156480, 171840, }, {4544, 22016, 36544, 51840, 67328, 82240, 97600, 113216, 127744, 142400, 157760, 173248, }, {4544, 22144, 36800, 52224, 67840, 82880, 98496, 114240, 128768, 143552, 159040, 174656, }, {4672, 22528, 37184, 52736, 68480, 83520, 99264, 115264, 130048, 144832, 160320, 176064, }, {4672, 22656, 37440, 53120, 68992, 84160, 100032, 116160, 131072, 145984, 161728, 177600, }, {4672, 22784, 37696, 53504, 69504, 84800, 100800, 117056, 132096, 147136, 163008, 179008, }, {4672, 22912, 37952, 53888, 70016, 85440, 101568, 117952, 133120, 148288, 164288, 180416, }, {4672, 23040, 38208, 54272, 70528, 86080, 102336, 118848, 134144, 149440, 165568, 181824, }, {4672, 23168, 38464, 54656, 71040, 86720, 103104, 119744, 135168, 150592, 166848, 183232, }, {4672, 23296, 38720, 55040, 71552, 87360, 103872, 120640, 136192, 151744, 168128, 184640, }, {4672, 23424, 38976, 55424, 72064, 88000, 104640, 121536, 137216, 152896, 169408, 186048, }, }; for (std::size_t i = 1; i <= FCMP_PLUS_PLUS_MAX_INPUTS; ++i) { // Uncomment the prints to construct the table // printf("{"); for (std::size_t j = 1; j <= FCMP_PLUS_PLUS_MAX_LAYERS; ++j) { const std::size_t membership_proof_len = fcmp_pp::membership_proof_len(i, j); const std::size_t fcmp_pp_proof_len = fcmp_pp::fcmp_pp_proof_len(i, j); EXPECT_EQ(membership_proof_len, PROOF_LEN_TABLE[i-1][j-1]); EXPECT_EQ(fcmp_pp_proof_len, ::fcmp_pp_proof_size(i, j)); // printf("%lu, ", membership_proof_len); } // printf("},\n"); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, tx_weight_monotonicity) { static_assert(is_power_of_2(FCMP_PLUS_PLUS_MAX_INPUTS), "max input count must be a power of 2"); static_assert(is_power_of_2(FCMP_PLUS_PLUS_MAX_OUTPUTS), "max output count must be a power of 2"); // build up table of weight by in/out/extra count const auto tx_weights = get_all_fcmp_tx_weights(); // assert that weight calculation is strictly monotonically increasing in all dimensions uint64_t max_weight = 0; for (const auto &p : tx_weights) { size_t m, n, el; std::tie(m, n, el) = p.first; const uint64_t weight = p.second; max_weight = std::max(weight, max_weight); if (m > 1) { const size_t other_weight = tx_weights.at({m - 1, n, el}); ASSERT_GT(weight, other_weight); } if (n > 2) { const size_t other_weight = tx_weights.at({m, n - 1, el}); ASSERT_GT(weight, other_weight); } if (el > 0) { const size_t other_weight = tx_weights.at({m, n, el - 1}); ASSERT_GT(weight, other_weight); } } MDEBUG("Max FCMP++ tx weight: " << max_weight); } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, max_tx_weight) { const uint64_t max_weight = cryptonote::get_fcmp_pp_transaction_weight_v1(FCMP_PLUS_PLUS_MAX_INPUTS, FCMP_PLUS_PLUS_MAX_OUTPUTS, MAX_TX_EXTRA_SIZE); ASSERT_EQ(max_weight, FCMP_PLUS_PLUS_MAX_TX_WEIGHT); } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, tx_weight_accuracy) { // see fake_n_tree_layers comment in cryptonote::get_fcmp_pp_transaction_weight_v1 static constexpr uint8_t fake_n_tree_layers = 7; // build up table of weight by in/out/extra count const auto tx_weights = get_all_fcmp_tx_weights(); // collect all pairs of valid (n_inputs, n_outputs) std::set> valid_inout_counts; for (const auto &p : tx_weights) valid_inout_counts.insert({std::get<0>(p.first), std::get<1>(p.first)}); ASSERT_EQ(FCMP_PLUS_PLUS_MAX_INPUTS * (FCMP_PLUS_PLUS_MAX_OUTPUTS - 1), valid_inout_counts.size()); // for each valid value of tx in/out count... cryptonote::transaction example_tx; for (const auto &inout_count : valid_inout_counts) { size_t m, n; std::tie(m, n) = inout_count; // once with max_int_fields=false, once with max_int_fields=true... for (unsigned max_int_fields = 0; max_int_fields < 2; ++max_int_fields) { make_empty_fcmp_pp_tx_of_size( m, n, /*extra_len=*/0, fake_n_tree_layers, max_int_fields, example_tx); ASSERT_TRUE(is_canonical_serializable_fcmp_pp_tx(example_tx)); ASSERT_EQ(m, example_tx.vin.size()); ASSERT_EQ(n, example_tx.vout.size()); ASSERT_EQ(fake_n_tree_layers, example_tx.rct_signatures.p.n_tree_layers); ASSERT_EQ(2, example_tx.version); ASSERT_EQ(rct::RCTTypeFcmpPlusPlus, example_tx.rct_signatures.type); example_tx.extra.reserve(MAX_TX_EXTRA_SIZE); // for each valid length of tx_extra... for (size_t el = 0; el <= MAX_TX_EXTRA_SIZE; ++el) { MDEBUG("Testing prefix/unprunable weight accuracy for " << m << "-in " << n << "-out " << el << "-byte-extra tx"); // make tx_extra the right size example_tx.extra.resize(el); // serialize example tx const cryptonote::blobdata example_tx_blob = cryptonote::tx_to_blob(example_tx); // check that prefix weight is always equal to actual byte size EXPECT_EQ(example_tx.prefix_size, cryptonote::get_fcmp_pp_prefix_weight_v1(m, n, el)); // check that the unprunable weight is equal to actual byte size when integer fields // are maxed out, and ever so slightly pessimistic (within 9 bytes) if not const uint64_t unprunable_weight = cryptonote::get_fcmp_pp_unprunable_weight_v1(m, n, el); const uint64_t allowed_unprunable_weight_margin = max_int_fields ? 0 : 9; ASSERT_GE(unprunable_weight, example_tx.unprunable_size); ASSERT_LE(unprunable_weight - example_tx.unprunable_size, allowed_unprunable_weight_margin); // check that the full weight is equal to actual byte size when integer fields // are maxed out, and ever so slightly pessimistic (within 13 bytes) if not const uint64_t full_weight = cryptonote::get_fcmp_pp_transaction_weight_v1(m, n, el); const uint64_t allowed_full_weight_margin = max_int_fields ? 0 : 13; ASSERT_GE(full_weight, example_tx_blob.size()); ASSERT_LE(full_weight - example_tx_blob.size(), allowed_full_weight_margin); // check that the TX-based overload of `get_fcmp_pp_transaction_weight_v1` returns the same const uint64_t full_weight_2 = cryptonote::get_fcmp_pp_transaction_weight_v1( static_cast(example_tx)); ASSERT_EQ(full_weight, full_weight_2); } } } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, dump_tx_bytesizes) { cryptonote::transaction example_tx; for (unsigned max_int_fields = 0; max_int_fields < 2; ++max_int_fields) { for (uint8_t n_tree_layers = 6; n_tree_layers <= 7; ++n_tree_layers) { std::cout << "layers=" << static_cast(n_tree_layers) << " "; if (max_int_fields) std::cout << "maxvarint,"; else std::cout << "minvarint,"; for (std::size_t n = 2; n <= FCMP_PLUS_PLUS_MAX_OUTPUTS; ++n) std::cout << n << ','; std::cout << '\n'; for (std::size_t m = 1; m <= FCMP_PLUS_PLUS_MAX_INPUTS; ++m) { std::cout << m << ','; for (std::size_t n = 2; n <= FCMP_PLUS_PLUS_MAX_OUTPUTS; ++n) { make_empty_fcmp_pp_tx_of_size( m, n, carrot::get_carrot_default_tx_extra_size(n), n_tree_layers, max_int_fields, example_tx); const cryptonote::blobdata serialized_blob = cryptonote::tx_to_blob(example_tx); std::cout << serialized_blob.size() << ','; } std::cout << '\n'; } std::cout << "-----------------------------------------\n"; } } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, dump_pruned_tx_bytesizes) { cryptonote::transaction example_tx; std::cout << ','; for (std::size_t n = 2; n <= FCMP_PLUS_PLUS_MAX_OUTPUTS; ++n) std::cout << n << ','; std::cout << '\n'; for (std::size_t m = 1; m <= FCMP_PLUS_PLUS_MAX_INPUTS; ++m) { std::cout << m << ','; for (std::size_t n = 2; n <= FCMP_PLUS_PLUS_MAX_OUTPUTS; ++n) { make_empty_fcmp_pp_pruned_tx_of_size( m, n, carrot::get_carrot_default_tx_extra_size(n), /*max_int_fields=*/true, example_tx); const cryptonote::blobdata serialized_blob = cryptonote::tx_to_blob(example_tx); std::cout << serialized_blob.size() << ','; } std::cout << '\n'; } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, output_serialization) { static const crypto::public_key O{0x01}; static const crypto::ec_point C{0x02}; static const std::string O_str = "0100000000000000000000000000000000000000000000000000000000000000"; static const std::string C_str = "0200000000000000000000000000000000000000000000000000000000000000"; // 1. Check serialized bytes size const fcmp_pp::OutputPair legacy_output_pair = fcmp_pp::LegacyOutputPair{{O, C}}; const fcmp_pp::OutputPair carrot_output_pair = fcmp_pp::CarrotOutputPairV1{{O, C}}; ASSERT_FALSE(legacy_output_pair == carrot_output_pair); const cryptonote::blobdata legacy_serialized = cryptonote::t_serializable_object_to_blob(legacy_output_pair); const cryptonote::blobdata carrot_out_v1_ser = cryptonote::t_serializable_object_to_blob(carrot_output_pair); ASSERT_EQ(legacy_serialized.size(), (1+32+32)); ASSERT_EQ(carrot_out_v1_ser.size(), (1+32+32)); // 2. Check serialized byte string const std::string legacy_hex = epee::string_tools::buff_to_hex_nodelimer(legacy_serialized); const std::string carrot_hex = epee::string_tools::buff_to_hex_nodelimer(carrot_out_v1_ser); static const std::string LEGACY_TAG = "70"; static const std::string CARROT_TAG = "71"; ASSERT_EQ(legacy_hex, LEGACY_TAG + O_str + C_str); ASSERT_EQ(carrot_hex, CARROT_TAG + O_str + C_str); // 3. Check de-serialization const std::vector> op_vec{ {legacy_output_pair, legacy_serialized}, {carrot_output_pair, carrot_out_v1_ser} }; for (const auto &serialized : op_vec) { fcmp_pp::OutputPair output; ASSERT_TRUE(cryptonote::t_serializable_object_from_blob(output, serialized.second)); ASSERT_EQ(output, serialized.first); } // 4. Check UnifiedOutput const uint64_t output_id = 8321; const fcmp_pp::UnifiedOutput legacy_unified_output{output_id, legacy_output_pair}; const fcmp_pp::UnifiedOutput carrot_unified_output{output_id, carrot_output_pair}; const cryptonote::blobdata legacy_unified_ser = cryptonote::t_serializable_object_to_blob(legacy_unified_output); const cryptonote::blobdata carrot_unified_ser = cryptonote::t_serializable_object_to_blob(carrot_unified_output); ASSERT_EQ(legacy_unified_ser.size(), SIZEOF_SERIALIZED_UNIFIED_OUTPUT); ASSERT_EQ(carrot_unified_ser.size(), SIZEOF_SERIALIZED_UNIFIED_OUTPUT); const std::string legacy_unified_hex = epee::string_tools::buff_to_hex_nodelimer(legacy_unified_ser); const std::string carrot_unified_hex = epee::string_tools::buff_to_hex_nodelimer(carrot_unified_ser); const std::string outpt_id_hex = epee::string_tools::buff_to_hex_nodelimer( cryptonote::t_serializable_object_to_blob(output_id) ); ASSERT_EQ(legacy_unified_hex, outpt_id_hex + LEGACY_TAG + O_str + C_str); ASSERT_EQ(carrot_unified_hex, outpt_id_hex + CARROT_TAG + O_str + C_str); const std::vector> unified_vec{ {legacy_unified_output, legacy_unified_ser}, {carrot_unified_output, carrot_unified_ser} }; for (const auto &serialized : unified_vec) { fcmp_pp::UnifiedOutput output; ASSERT_TRUE(cryptonote::t_serializable_object_from_blob(output, serialized.second)); ASSERT_EQ(output, serialized.first); } } //---------------------------------------------------------------------------------------------------------------------- TEST(fcmp_pp, tx_sizes_and_verification_times) { // 1. Make fake paths for a tree of 7 layers const uint8_t n_layers = 7; const auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1(); // Generate full chunks of outputs, enough to construct a tx with max inputs std::size_t n_generated_outputs = FCMP_PLUS_PLUS_MAX_INPUTS; if (n_generated_outputs % curve_trees->m_c1_width) n_generated_outputs = curve_trees->m_c1_width * ((n_generated_outputs / curve_trees->m_c1_width) + 1); // Make sure we generated enough outputs and calculate expected n leaves in a dummy tree uint64_t expected_n_leaves = curve_trees->m_c1_width; for (uint8_t i = 1; i < n_layers; ++i) expected_n_leaves *= (i % 2 != 0) ? curve_trees->m_c2_width : curve_trees->m_c1_width; ASSERT_EQ(curve_trees->n_layers(expected_n_leaves), n_layers); // Init an account we're going to "receive" the outputs to and "send" from cryptonote::account_base account; account.generate(); const auto account_keys = account.get_keys(); const crypto::secret_key &k_s = account_keys.m_spend_secret_key; const crypto::secret_key &k_v = account_keys.m_view_secret_key; const crypto::public_key &K_s = account_keys.m_account_address.m_spend_public_key; const auto k_view_incoming_dev = std::make_shared(k_v); const carrot::cryptonote_hierarchy_address_device cn_addr_dev(k_view_incoming_dev, K_s); // Generate a bunch of miner txs, we'll use outputs from the miner txs to make the tree std::vector outputs; std::vector txs; std::unordered_map transparent_amount_commitments; for (std::size_t i = 0; i < n_generated_outputs; ++i) { bool r = cryptonote::construct_miner_tx(0, 0, 5000, 500, 500, account_keys.m_account_address, txs.emplace_back()); ASSERT_TRUE(r); const auto tx_out = txs.back().vout.back(); crypto::public_key out_pubkey; ASSERT_TRUE(cryptonote::get_output_public_key(tx_out, out_pubkey)); if (transparent_amount_commitments.find(tx_out.amount) == transparent_amount_commitments.end()) transparent_amount_commitments[tx_out.amount] = rct::rct2pt(rct::zeroCommitVartime(tx_out.amount)); fcmp_pp::LegacyOutputPair legacy_out{{out_pubkey, transparent_amount_commitments[tx_out.amount]}}; outputs.push_back(fcmp_pp::UnifiedOutput{ .unified_id = i, .output_pair = std::move(legacy_out), }); } ASSERT_GE(expected_n_leaves, outputs.size()); // Instantiate dummy paths in the truee const auto paths = curve_trees->get_dummy_paths(outputs, n_layers); // 2. Initialize tree cache fcmp_pp::curve_trees::TreeCacheV1 tree_cache(curve_trees); tree_cache.force_set_top_block_unsafe({ .blk_idx = 60/*coinbase unlock*/, .blk_hash = {}, .n_leaf_tuples = expected_n_leaves }); // 3. Force add paths for every input for (std::size_t n_inputs = 1; n_inputs <= FCMP_PLUS_PLUS_MAX_INPUTS; ++n_inputs) { const size_t leaf_idx = n_inputs - 1; const auto &output = outputs.at(leaf_idx).output_pair; const auto path = curve_trees->get_single_dummy_path(paths, expected_n_leaves, leaf_idx); tree_cache.register_output(output); const auto path_idxs = curve_trees->get_path_indexes(expected_n_leaves, leaf_idx); std::vector unified_outputs; for (std::size_t l_idx = path_idxs.leaf_range.first; l_idx < path_idxs.leaf_range.second; ++l_idx) unified_outputs.push_back(outputs.at(l_idx)); const auto compressed_path = curve_trees->compress_path(path, unified_outputs); tree_cache.force_add_output_path(output, leaf_idx, compressed_path, expected_n_leaves); } crypto::ec_point tree_root; tree_cache.get_tree_root(tree_root); const auto tree_root_ptr = curve_trees->get_tree_root_from_bytes(n_layers, tree_root); const std::unordered_map subaddrs{{ account_keys.m_account_address.m_spend_public_key, {0,0} }}; // 4. Build the txs printf("Inputs, Outputs, Tx Size (bytes), Verify (ms), Membership Proof Size (bytes), Membership Proof Verify (ms)\n"); for (std::size_t n_inputs = 1; n_inputs <= FCMP_PLUS_PLUS_MAX_INPUTS; ++n_inputs) { if (!is_power_of_2(n_inputs)) continue; // 4a. Collect inputs std::vector inputs; inputs.reserve(n_inputs); for (std::size_t i = 0; i < n_inputs; ++i) { const auto &input_tx = txs.at(i); const auto &output = outputs.at(i); const size_t internal_output_idx = input_tx.vout.size() - 1; const crypto::public_key in_tx_pub_key = cryptonote::get_tx_pub_key_from_extra(input_tx); cryptonote::keypair in_ephemeral; crypto::key_image key_image; bool r = cryptonote::generate_key_image_helper( account_keys, subaddrs, fcmp_pp::output_pubkey_cref(output.output_pair), in_tx_pub_key, {}, internal_output_idx, in_ephemeral, key_image, hw::get_device("default")); ASSERT_TRUE(r); wallet2_basic::transfer_details wallet2_td{ .m_block_height = 0, .m_tx = input_tx, .m_txid = cryptonote::get_transaction_hash(input_tx), .m_internal_output_index = internal_output_idx, .m_global_output_index = i, .m_spent = false, .m_frozen = false, .m_spent_height = 0, .m_key_image = key_image, .m_mask = rct::identity(), .m_amount = input_tx.vout.at(internal_output_idx).amount, .m_rct = input_tx.version >= 2, .m_key_image_known = true, .m_key_image_request = false, .m_pk_index = 0, .m_subaddr_index = {}, .m_key_image_partial = false, .m_multisig_k = {}, .m_multisig_info = {}, .m_uses = {}, }; inputs.emplace_back(std::move(wallet2_td)); } // 4b. Make the txs with the inputs for (std::size_t n_outputs = 2; n_outputs <= 2/*FCMP_PLUS_PLUS_MAX_OUTPUTS*/; ++n_outputs) { const auto tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_sweep_all( inputs, carrot::subaddress_map_legacy(subaddrs), /*only_below*/MONEY_SUPPLY, /*address*/account_keys.m_account_address, /*is_subaddress*/false, /*n_dests_per_tx*/n_outputs, /*fee_per_weight=*/1, /*extra=*/{}, /*subaddr_account=*/0, /*subaddr_indices=*/{}, /*top_block_index*/tree_cache.n_synced_blocks()-1); ASSERT_EQ(tx_proposals.size(), 1); const cryptonote::transaction finalized_tx = tools::wallet::finalize_all_fcmp_pp_proofs( tx_proposals.front(), tree_cache, *curve_trees, cn_addr_dev, *k_view_incoming_dev, /*s_view_balance_dev=*/nullptr, carrot::spend_device_ram_borrowed(k_s, k_v)); // Serialize and de-serialize, then validate the de-serialized tx cryptonote::blobdata tx_blob; ASSERT_TRUE(cryptonote::tx_to_blob(finalized_tx, tx_blob)); // 4c. Validate the tx const uint64_t start_validate = tools::get_tick_count(); // Parse the tx const uint64_t start_parse = tools::get_tick_count(); cryptonote::transaction tx; ASSERT_TRUE(cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)); const uint64_t end_parse = tools::get_tick_count(); // Verify non-input consensus rules const uint64_t start_non_input = tools::get_tick_count(); cryptonote::tx_verification_context tvc{}; ASSERT_TRUE(cryptonote::ver_non_input_consensus(tx, tvc, HF_VERSION_FCMP_PLUS_PLUS)); ASSERT_FALSE(tvc.m_verifivation_failed); const uint64_t end_non_input = tools::get_tick_count(); // Verify input proofs const uint64_t start_input = tools::get_tick_count(); ASSERT_TRUE(cryptonote::ver_input_proofs_fcmps(tx, tree_root)); const uint64_t end_input = tools::get_tick_count(); // 4d. Print byte size and verification time // Collect timings const auto ticks_to_ms = [](const uint64_t ticks) -> uint64_t { return tools::ticks_to_ns(ticks) / 1e6; }; const uint64_t validate_ms = ticks_to_ms(end_input - start_validate); const uint64_t parse_ms = ticks_to_ms(end_parse - start_parse); const uint64_t non_input_ms = ticks_to_ms(end_non_input - start_non_input); const uint64_t input_ms = ticks_to_ms(end_input - start_input); // 5. Do the membership proof only fcmp_pp::FcmpMembershipProof membership_proof; std::vector fcmp_raw_inputs; // Collect the membership proof inputs only { std::vector pseudo_outs; for (const auto &po : finalized_tx.rct_signatures.p.pseudoOuts) pseudo_outs.emplace_back(rct::rct2pt(po)); std::vector sal_proofs; fcmp_pp::fcmp_pp_parts_from_proof_v1( tx.rct_signatures.p.fcmp_pp, pseudo_outs, n_layers, membership_proof, sal_proofs, fcmp_raw_inputs); } const uint64_t start_membership = tools::get_tick_count(); ASSERT_TRUE(fcmp_pp::verify_membership(membership_proof, n_layers, tree_root_ptr, fcmp_raw_inputs)); const uint64_t end_membership = tools::get_tick_count(); const uint64_t membership_ms = ticks_to_ms(end_membership - start_membership); LOG_PRINT_L1("Tx: " << obj_to_json_str(tx)); LOG_PRINT_L1("Timings (ms) ... validate: " << validate_ms << " , parse: " << parse_ms << " , non_input_ms: " << non_input_ms << " , input_ms: " << input_ms << " , membership_ms: " << membership_ms); // Inputs, Outputs, Tx Size (bytes), Verify (ms), Membership Proof Size (bytes), Membership Proof Verify (ms) printf("%lu, %lu, %lu, %lu, %lu, %lu\n", tx.vin.size(), tx.vout.size(), tx_blob.size(), validate_ms, membership_proof.size(), membership_ms); } } } //----------------------------------------------------------------------------------------------------------------------