diff --git a/.gitmodules b/.gitmodules index 721cce3b4..5e5d01cbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ path = external/supercop url = https://github.com/monero-project/supercop branch = monero +[submodule "external/mx25519"] + path = external/mx25519 + url = https://github.com/tevador/mx25519 diff --git a/CMakeLists.txt b/CMakeLists.txt index 60cda040a..e9527197f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,6 +377,7 @@ if(NOT MANUAL_SUBMODULES) check_submodule(external/trezor-common) check_submodule(external/randomx) check_submodule(external/supercop) + check_submodule(external/mx25519) endif() endif() @@ -463,7 +464,7 @@ elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") set(BSDI TRUE) endif() -include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include) +include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/mx25519/include) if(APPLE) cmake_policy(SET CMP0042 NEW) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 45c0e4f14..d1663e8ba 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -70,3 +70,4 @@ add_subdirectory(db_drivers) add_subdirectory(easylogging++) add_subdirectory(qrcodegen) add_subdirectory(randomx EXCLUDE_FROM_ALL) +add_subdirectory(mx25519) diff --git a/external/mx25519 b/external/mx25519 new file mode 160000 index 000000000..84ca1290f --- /dev/null +++ b/external/mx25519 @@ -0,0 +1 @@ +Subproject commit 84ca1290fa344351c95692f20f41a174b70e0c7b diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6190b40f8..6c53b115d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,6 +83,7 @@ endfunction () include(Version) monero_add_library(version SOURCES ${CMAKE_BINARY_DIR}/version.cpp DEPENDS genversion) +add_subdirectory(carrot_core) add_subdirectory(common) add_subdirectory(crypto) add_subdirectory(ringct) diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt new file mode 100644 index 000000000..299cc88b5 --- /dev/null +++ b/src/carrot_core/CMakeLists.txt @@ -0,0 +1,60 @@ +# 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. + +set(carrot_core_sources + account_secrets.cpp + address_utils.cpp + carrot_enote_scan.cpp + core_types.cpp + destination.cpp + device_ram_borrowed.cpp + enote_utils.cpp + hash_functions.cpp + output_set_finalization.cpp + payment_proposal.cpp) + +monero_find_all_headers(carrot_core_headers, "${CMAKE_CURRENT_SOURCE_DIR}") + +monero_add_library(carrot_core + ${carrot_core_sources} + ${carrot_core_headers}) + +target_link_libraries(carrot_core + PUBLIC + cncrypto + epee + ringct + seraphis_crypto + PRIVATE + ${EXTRA_LIBRARIES}) + +target_include_directories(carrot_core + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + PRIVATE + ${Boost_INCLUDE_DIRS}) diff --git a/src/carrot_core/account_secrets.cpp b/src/carrot_core/account_secrets.cpp new file mode 100644 index 000000000..c7c8652fb --- /dev/null +++ b/src/carrot_core/account_secrets.cpp @@ -0,0 +1,102 @@ +// 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 "account_secrets.h" + +//local headers +#include "config.h" +#include "crypto/generators.h" +#include "hash_functions.h" +#include "ringct/rctOps.h" +#include "transcript_fixed.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_provespend_key(const crypto::secret_key &s_master, + crypto::secret_key &k_prove_spend_out) +{ + // k_ps = H_n(s_m) + const auto transcript = sp::make_fixed_transcript(); + derive_scalar(transcript.data(), transcript.size(), &s_master, to_bytes(k_prove_spend_out)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_viewbalance_secret(const crypto::secret_key &s_master, + crypto::secret_key &s_view_balance_out) +{ + // s_vb = H_32(s_m) + const auto transcript = sp::make_fixed_transcript(); + derive_bytes_32(transcript.data(), transcript.size(), &s_master, to_bytes(s_view_balance_out)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_generateimage_key(const crypto::secret_key &s_view_balance, + crypto::secret_key &k_generate_image_out) +{ + // k_gi = H_n(s_vb) + const auto transcript = sp::make_fixed_transcript(); + derive_scalar(transcript.data(), transcript.size(), &s_view_balance, to_bytes(k_generate_image_out)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_viewincoming_key(const crypto::secret_key &s_view_balance, + crypto::secret_key &k_view_out) +{ + // k_v = H_n(s_vb) + const auto transcript = sp::make_fixed_transcript(); + derive_scalar(transcript.data(), transcript.size(), &s_view_balance, to_bytes(k_view_out)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_generateaddress_secret(const crypto::secret_key &s_view_balance, + crypto::secret_key &s_generate_address_out) +{ + // s_ga = H_32(s_vb) + const auto transcript = sp::make_fixed_transcript(); + derive_bytes_32(transcript.data(), transcript.size(), &s_view_balance, to_bytes(s_generate_address_out)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_spend_pubkey(const crypto::secret_key &k_generate_image, + const crypto::secret_key &k_prove_spend, + crypto::public_key &spend_pubkey_out) +{ + // k_ps T + rct::key tmp; + rct::scalarmultKey(tmp, rct::pk2rct(crypto::get_T()), rct::sk2rct(k_prove_spend)); + + // K_s = k_gi G + k_ps T + rct::addKeys1(tmp, rct::sk2rct(k_generate_image), tmp); + spend_pubkey_out = rct::rct2pk(tmp); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/account_secrets.h b/src/carrot_core/account_secrets.h new file mode 100644 index 000000000..4fef1c55c --- /dev/null +++ b/src/carrot_core/account_secrets.h @@ -0,0 +1,103 @@ +// 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. + +//// +// Core implementation details for making Carrot privkeys, secrets, and pubkeys. +// - Carrot is a specification for FCMP-RingCT compatible addressing +// +// references: +// * https://github.com/jeffro256/carrot/blob/master/carrot.md +/// + +#pragma once + +//local headers +#include "crypto/crypto.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +/** +* brief: make_carrot_provespend_key - prove-spend key, for signing input proofs to spend enotes +* k_ps = H_n(s_m) +* param: s_master - s_m +* outparam: k_prove_spend_out - k_ps +*/ +void make_carrot_provespend_key(const crypto::secret_key &s_master, + crypto::secret_key &k_prove_spend_out); +/** +* brief: make_carrot_viewbalance_secret - view-balance secret, for viewing all balance information +* s_vb = H_n(s_m) +* param: s_master - s_m +* outparam: s_view_balance_out - s_vb +*/ +void make_carrot_viewbalance_secret(const crypto::secret_key &s_master, + crypto::secret_key &s_view_balance_out); +/** +* brief: make_carrot_generateimage_key - generate-image key, for identifying enote spends +* k_gi = H_n(s_vb) +* param: s_view_balance - s_vb +* outparam: k_generate_image_out - k_gi +*/ +void make_carrot_generateimage_key(const crypto::secret_key &s_view_balance, + crypto::secret_key &k_generate_image_out); +/** +* brief: make_carrot_viewincoming_key - view-incoming key, for identifying received external enotes +* k_v = H_n(s_vb) +* param: s_view_balance - s_vb +* outparam: k_view_out - k_v +*/ +void make_carrot_viewincoming_key(const crypto::secret_key &s_view_balance, + crypto::secret_key &k_view_out); +/** +* brief: make_carrot_generateaddress_secret - generate-address secret, for generating addresses +* s_ga = H_32(s_vb) +* param: s_view_balance - s_vb +* outparam: s_generate_address_out - s_ga +*/ +void make_carrot_generateaddress_secret(const crypto::secret_key &s_view_balance, + crypto::secret_key &s_generate_address_out); +/** + * brief: make_carrot_spend_pubkey - base public spendkey for rerandomizable RingCT + * K_s = k_gi G + k_ps T + * param: k_generate_image - k_gi + * param: k_prove_spend - k_ps + * outparam: spend_pubkey_out - K_s +*/ +void make_carrot_spend_pubkey(const crypto::secret_key &k_generate_image, + const crypto::secret_key &k_prove_spend, + crypto::public_key &spend_pubkey_out); + +} //namespace carrot diff --git a/src/carrot_core/address_utils.cpp b/src/carrot_core/address_utils.cpp new file mode 100644 index 000000000..d27479144 --- /dev/null +++ b/src/carrot_core/address_utils.cpp @@ -0,0 +1,85 @@ +// 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 "address_utils.h" + +//local headers +#include "config.h" +#include "hash_functions.h" +#include "ringct/rctOps.h" +#include "transcript_fixed.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_index_extension_generator(const crypto::secret_key &s_generate_address, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::secret_key &address_generator_out) +{ + // s^j_gen = H_32[s_ga](j_major, j_minor) + const auto transcript = sp::make_fixed_transcript(j_major, j_minor); + derive_bytes_32(transcript.data(), transcript.size(), &s_generate_address, &address_generator_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_subaddress_scalar(const crypto::public_key &spend_pubkey, + const crypto::secret_key &s_address_generator, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::secret_key &subaddress_scalar_out) +{ + // k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen) + const auto transcript = sp::make_fixed_transcript( + spend_pubkey, j_major, j_minor); + derive_scalar(transcript.data(), transcript.size(), &s_address_generator, subaddress_scalar_out.data); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_address_spend_pubkey(const crypto::public_key &spend_pubkey, + const crypto::secret_key &s_generate_address, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::public_key &address_spend_pubkey_out) +{ + // k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen) + crypto::secret_key subaddress_scalar; + make_carrot_subaddress_scalar(spend_pubkey, s_generate_address, j_major, j_minor, subaddress_scalar); + + // K^j_s = k^j_subscal * K_s + address_spend_pubkey_out = rct::rct2pk(rct::scalarmultKey( + rct::pk2rct(spend_pubkey), rct::sk2rct(subaddress_scalar))); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/address_utils.h b/src/carrot_core/address_utils.h new file mode 100644 index 000000000..6dbf8928f --- /dev/null +++ b/src/carrot_core/address_utils.h @@ -0,0 +1,95 @@ +// 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. + +// Utilities for building carrot addresses. + +#pragma once + +//local headers +#include "crypto/crypto.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +/** +* brief: is_main_address_index - determine whether j=(j_major, j_minor) represents the main address +*/ +static constexpr bool is_main_address_index(const std::uint32_t j_major, const std::uint32_t j_minor) +{ + return !(j_major || j_minor); +} + +/** +* brief: make_carrot_index_extension_generator - s^j_gen +* s^j_gen = H_32[s_ga](j_major, j_minor) +* param: s_generate_address - s_ga +* param: j_major - +* param: j_minor - +* outparam: address_generator_out - s^j_gen +*/ +void make_carrot_index_extension_generator(const crypto::secret_key &s_generate_address, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::secret_key &address_generator_out); +/** +* brief: make_carrot_address_privkey - d^j_a +* k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen) +* param: spend_pubkey - K_s = k_vb X + k_m U +* param: s_address_generator - s^j_gen +* param: j_major - +* param: j_minor - +* outparam: subaddress_scalar_out - k^j_subscal +*/ +void make_carrot_subaddress_scalar(const crypto::public_key &spend_pubkey, + const crypto::secret_key &s_address_generator, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::secret_key &subaddress_scalar_out); +/** +* brief: make_carrot_address_spend_pubkey - K^j_s +* K^j_s = k^j_subscal * K_s +* param: spend_pubkey - K_s = k_gi G + k_ps U +* param: s_generate_address - s_ga +* param: j_major - +* param: j_minor - +* outparam: address_spend_pubkey_out - K^j_s +*/ +void make_carrot_address_spend_pubkey(const crypto::public_key &spend_pubkey, + const crypto::secret_key &s_generate_address, + const std::uint32_t j_major, + const std::uint32_t j_minor, + crypto::public_key &address_spend_pubkey_out); + +} //namespace carrot diff --git a/src/carrot_core/carrot_enote_scan.cpp b/src/carrot_core/carrot_enote_scan.cpp new file mode 100644 index 000000000..478a71fea --- /dev/null +++ b/src/carrot_core/carrot_enote_scan.cpp @@ -0,0 +1,333 @@ +// 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. + +// Utilities for scanning carrot enotes + +//paired header +#include "carrot_enote_scan.h" + +//local headers +#include "crypto/generators.h" +#include "enote_utils.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_scan_carrot_non_coinbase_core(const CarrotEnoteV1 &enote, + const std::optional encrypted_payment_id, + const crypto::hash &s_sender_receiver, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &payment_id_out, + CarrotEnoteType &enote_type_out, + janus_anchor_t &nominal_janus_anchor_out) +{ + // if cannot recompute C_a, then FAIL + if (!try_get_carrot_amount(s_sender_receiver, + enote.amount_enc, + enote.onetime_address, + enote.amount_commitment, + enote_type_out, + amount_out, + amount_blinding_factor_out)) + return false; + + // k^o_g = H_n("..g..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_g(s_sender_receiver, + enote.amount_commitment, + sender_extension_g_out); + + // k^o_t = H_n("..t..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_t(s_sender_receiver, + enote.amount_commitment, + sender_extension_t_out); + + // K^j_s = Ko - K^o_ext = Ko - (k^o_g G + k^o_t T) + recover_address_spend_pubkey(enote.onetime_address, + s_sender_receiver, + enote.amount_commitment, + address_spend_pubkey_out); + + // pid = pid_enc XOR m_pid, if applicable + if (encrypted_payment_id) + payment_id_out = decrypt_legacy_payment_id(*encrypted_payment_id, s_sender_receiver, enote.onetime_address); + else + payment_id_out = null_payment_id; + + // anchor = anchor_enc XOR m_anchor + nominal_janus_anchor_out = decrypt_carrot_anchor(enote.anchor_enc, + s_sender_receiver, + enote.onetime_address); + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +bool verify_carrot_janus_protection(const input_context_t &input_context, + const crypto::public_key &onetime_address, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const janus_anchor_t &nominal_anchor, + payment_id_t &nominal_payment_id_inout) +{ + const bool is_subaddress = nominal_address_spend_pubkey != account_spend_pubkey; + + // make K^j_v' + crypto::public_key nominal_address_view_pubkey; + if (is_subaddress) + { + // K^j_v' = k_v K^j_s' + if (!k_view_dev.view_key_scalar_mult_ed25519(nominal_address_spend_pubkey, nominal_address_view_pubkey)) + return false; + } + else // cryptonote address + { + // K^j_v' = k_v G + if (!k_view_dev.view_key_scalar_mult_ed25519(crypto::get_G(), nominal_address_view_pubkey)) + return false; + } + + // if can recompute D_e with pid', then PASS + if (verify_carrot_external_janus_protection(nominal_anchor, + input_context, + nominal_address_spend_pubkey, + nominal_address_view_pubkey, + is_subaddress, + nominal_payment_id_inout, + enote_ephemeral_pubkey)) + return true; + + // if can recompute D_e with null pid, then PASS + nominal_payment_id_inout = null_payment_id; + if (verify_carrot_external_janus_protection(nominal_anchor, + input_context, + nominal_address_spend_pubkey, + nominal_address_view_pubkey, + is_subaddress, + null_payment_id, + enote_ephemeral_pubkey)) + return true; + + // anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) + janus_anchor_t expected_special_anchor; + k_view_dev.make_janus_anchor_special(enote_ephemeral_pubkey, + input_context, + onetime_address, + account_spend_pubkey, + expected_special_anchor); + + // attempt special janus check: anchor_sp ?= anchor' + return expected_special_anchor == nominal_anchor; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out) +{ + // input_context + input_context_t input_context; + make_carrot_input_context_coinbase(enote.block_index, input_context); + + // if vt' != vt, then FAIL + if (!test_carrot_view_tag(s_sender_receiver_unctx.data, input_context, enote.onetime_address, enote.view_tag)) + return false; + + // s^ctx_sr = H_32(s_sr, D_e, input_context) + crypto::hash s_sender_receiver; + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, + enote.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + // C_a = G + a H + const rct::key implied_amount_commitment = rct::zeroCommit(enote.amount); + + // k^o_g = H_n("..g..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_g(s_sender_receiver, + implied_amount_commitment, + sender_extension_g_out); + + // k^o_t = H_n("..t..", s^ctx_sr, C_a) + make_carrot_onetime_address_extension_t(s_sender_receiver, + implied_amount_commitment, + sender_extension_t_out); + + // K^j_s = Ko - K^o_ext = Ko - (k^o_g G + k^o_t T) + recover_address_spend_pubkey(enote.onetime_address, + s_sender_receiver, + implied_amount_commitment, + address_spend_pubkey_out); + + // if K^j_s != K^s, then FAIL + // - We have no "hard target" in the amount commitment, so if we want deterministic enote + // scanning without a subaddress table, we reject all non-main addresses in coinbase enotes + if (address_spend_pubkey_out != account_spend_pubkey) + return false; + + // anchor = anchor_enc XOR m_anchor + const janus_anchor_t nominal_anchor = decrypt_carrot_anchor(enote.anchor_enc, + s_sender_receiver, + enote.onetime_address); + + // verify Janus attack protection + payment_id_t dummy_payment_id = null_payment_id; + if (!verify_carrot_janus_protection(input_context, + enote.onetime_address, + k_view_dev, + account_spend_pubkey, + address_spend_pubkey_out, + enote.enote_ephemeral_pubkey, + nominal_anchor, + dummy_payment_id)) + return false; + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, + const std::optional encrypted_payment_id, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &payment_id_out, + CarrotEnoteType &enote_type_out) +{ + // input_context + input_context_t input_context; + make_carrot_input_context(enote.tx_first_key_image, input_context); + + // test view tag + if (!test_carrot_view_tag(s_sender_receiver_unctx.data, input_context, enote.onetime_address, enote.view_tag)) + return false; + + // s^ctx_sr = H_32(s_sr, D_e, input_context) + crypto::hash s_sender_receiver; + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, + enote.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + // do core scanning + janus_anchor_t nominal_anchor; + if (!try_scan_carrot_non_coinbase_core(enote, + encrypted_payment_id, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + amount_out, + amount_blinding_factor_out, + payment_id_out, + enote_type_out, + nominal_anchor)) + return false; + + // verify Janus attack protection + if (!verify_carrot_janus_protection(input_context, + enote.onetime_address, + k_view_dev, + account_spend_pubkey, + address_spend_pubkey_out, + enote.enote_ephemeral_pubkey, + nominal_anchor, + payment_id_out)) + return false; + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, + const view_balance_secret_device &s_view_balance_dev, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out) +{ + // input_context + input_context_t input_context; + make_carrot_input_context(enote.tx_first_key_image, input_context); + + // vt = H_3(s_sr || input_context || Ko) + view_tag_t nominal_view_tag; + s_view_balance_dev.make_internal_view_tag(input_context, enote.onetime_address, nominal_view_tag); + + // test view tag + if (nominal_view_tag != enote.view_tag) + return false; + + // s^ctx_sr = H_32(s_vb, D_e, input_context) + crypto::hash s_sender_receiver; + s_view_balance_dev.make_internal_sender_receiver_secret(enote.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + // do core scanning + janus_anchor_t nominal_anchor; + payment_id_t dummy_payment_id; + if (!try_scan_carrot_non_coinbase_core(enote, + std::nullopt, + s_sender_receiver, + sender_extension_g_out, + sender_extension_t_out, + address_spend_pubkey_out, + amount_out, + amount_blinding_factor_out, + dummy_payment_id, + enote_type_out, + nominal_anchor)) + return false; + + // janus protection checks are not needed for internal scans + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/carrot_enote_scan.h b/src/carrot_core/carrot_enote_scan.h new file mode 100644 index 000000000..3d1e29fe7 --- /dev/null +++ b/src/carrot_core/carrot_enote_scan.h @@ -0,0 +1,86 @@ +// 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. + +//! @file Utilities for scanning carrot enotes + +#pragma once + +//local headers +#include "carrot_enote_types.h" +#include "device.h" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace carrot +{ +bool verify_carrot_janus_protection(const input_context_t &input_context, + const crypto::public_key &onetime_address, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const janus_anchor_t &nominal_anchor, + payment_id_t &nominal_payment_id_inout); + +bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out); + +bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, + const std::optional encrypted_payment_id, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + payment_id_t &payment_id_out, + CarrotEnoteType &enote_type_out); + +bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, + const view_balance_secret_device &s_view_balance_dev, + crypto::secret_key &sender_extension_g_out, + crypto::secret_key &sender_extension_t_out, + crypto::public_key &address_spend_pubkey_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out, + CarrotEnoteType &enote_type_out); + +} //namespace carrot diff --git a/src/carrot_core/carrot_enote_types.h b/src/carrot_core/carrot_enote_types.h new file mode 100644 index 000000000..3fbbf1f89 --- /dev/null +++ b/src/carrot_core/carrot_enote_types.h @@ -0,0 +1,106 @@ +// 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. + +// Seraphis core types. + +#pragma once + +//local headers +#include "crypto/x25519.h" +#include "core_types.h" +#include "ringct/rctTypes.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +//// +// CarrotEnoteV1 +// - onetime address +// - amount commitment +// - encrypted amount +// - encrypted janus anchor +// - view tag +/// +struct CarrotEnoteV1 final +{ + /// K_o + crypto::public_key onetime_address; + /// C_a + rct::key amount_commitment; + /// a_enc + encrypted_amount_t amount_enc; + /// anchor_enc + encrypted_janus_anchor_t anchor_enc; + /// view_tag + view_tag_t view_tag; + /// D_e + crypto::x25519_pubkey enote_ephemeral_pubkey; + /// L_0 + crypto::key_image tx_first_key_image; +}; + +//// +// CarrotCoinbaseEnoteV1 +// - onetime address +// - cleartext amount +// - encrypted janus anchor +// - view tag +/// +struct CarrotCoinbaseEnoteV1 final +{ + /// K_o + crypto::public_key onetime_address; + /// a + rct::xmr_amount amount; + /// anchor_enc + encrypted_janus_anchor_t anchor_enc; + /// view_tag + view_tag_t view_tag; + /// D_e + crypto::x25519_pubkey enote_ephemeral_pubkey; + /// block_index + std::uint64_t block_index; +}; + +/** +* brief: gen_carrot_enote_v1() - generate a carrot v1 enote (all random) +*/ +CarrotEnoteV1 gen_carrot_enote_v1(); +/** +* brief: gen_carrot_coinbase_enote_v1() - generate a carrot coinbase v1 enote (all random) +*/ +CarrotCoinbaseEnoteV1 gen_carrot_coinbase_enote_v1(); + +} //namespace carrot diff --git a/src/carrot_core/config.h b/src/carrot_core/config.h new file mode 100644 index 000000000..c15100c56 --- /dev/null +++ b/src/carrot_core/config.h @@ -0,0 +1,72 @@ +// 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. + +//! @file Constants used in Carrot + +#pragma once + +//local headers + +//third party headers + +//standard headers + +//forward declarations + +namespace carrot +{ + +// Carrot addressing protocol domain separators +static constexpr const unsigned char CARROT_DOMAIN_SEP_AMOUNT_BLINDING_FACTOR[] = "Carrot commitment mask"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_ONETIME_EXTENSION_G[] = "Carrot key extension G"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_ONETIME_EXTENSION_T[] = "Carrot key extension T"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_ENCRYPTION_MASK_ANCHOR[] = "Carrot encryption mask anchor"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_ENCRYPTION_MASK_AMOUNT[] = "Carrot encryption mask a"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_ENCRYPTION_MASK_PAYMENT_ID[] = "Carrot encryption mask pid"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_JANUS_ANCHOR_SPECIAL[] = "Carrot janus anchor special"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_EPHEMERAL_PRIVKEY[] = "Carrot sending key normal"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_VIEW_TAG[] = "Carrot view tag"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_SENDER_RECEIVER_SECRET[] = "Carrot sender-receiver secret"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_INPUT_CONTEXT_COINBASE = 'C'; +static constexpr const unsigned char CARROT_DOMAIN_SEP_INPUT_CONTEXT_RINGCT = 'R'; + +// Carrot account secret domain separators +static constexpr const unsigned char CARROT_DOMAIN_SEP_PROVE_SPEND_KEY[] = "Carrot prove-spend key"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_VIEW_BALANCE_SECRET[] = "Carrot view-balance secret"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_GENERATE_IMAGE_KEY[] = "Carrot generate-image key"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_INCOMING_VIEW_KEY[] = "Carrot incoming view key"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_GENERATE_ADDRESS_SECRET[] = "Carrot generate-address secret"; + +// Carrot address domain separators +static constexpr const unsigned char CARROT_DOMAIN_SEP_ADDRESS_INDEX_GEN[] = "Carrot address index generator"; +static constexpr const unsigned char CARROT_DOMAIN_SEP_SUBADDRESS_SCALAR[] = "Carrot subaddress scalar"; + +// Carrot misc constants +static constexpr const unsigned int CARROT_MIN_TX_OUTPUTS = 2; +static constexpr const unsigned int CARROT_MAX_TX_OUTPUTS = 16; +} //namespace carrot diff --git a/src/carrot_core/core_types.cpp b/src/carrot_core/core_types.cpp new file mode 100644 index 000000000..2145f4dc8 --- /dev/null +++ b/src/carrot_core/core_types.cpp @@ -0,0 +1,121 @@ +// 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 "core_types.h" + +//local headers +#include "crypto/crypto.h" + +//third party headers +#include + +//standard headers + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +static void xor_bytes(const unsigned char(&a)[Sz], const unsigned char(&b)[Sz], unsigned char(&c_out)[Sz]) +{ + for (std::size_t i{0}; i < Sz; ++i) + c_out[i] = a[i] ^ b[i]; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +static T xor_bytes(const T &a, const T &b) +{ + T temp; + xor_bytes(a.bytes, b.bytes, temp.bytes); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const janus_anchor_t &a, const janus_anchor_t &b) +{ + return memcmp(&a, &b, sizeof(janus_anchor_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +janus_anchor_t operator^(const janus_anchor_t &a, const janus_anchor_t &b) +{ + return xor_bytes(a, b); +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const encrypted_amount_t &a, const encrypted_amount_t &b) +{ + return memcmp(&a, &b, sizeof(encrypted_amount_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +encrypted_amount_t operator^(const encrypted_amount_t &a, const encrypted_amount_t &b) +{ + return xor_bytes(a, b); +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const payment_id_t &a, const payment_id_t &b) +{ + return memcmp(&a, &b, sizeof(payment_id_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +payment_id_t operator^(const payment_id_t &a, const payment_id_t &b) +{ + return xor_bytes(a, b); +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const input_context_t &a, const input_context_t &b) +{ + return memcmp(&a, &b, sizeof(input_context_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const view_tag_t &a, const view_tag_t &b) +{ + return memcmp(&a, &b, sizeof(view_tag_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +janus_anchor_t gen_janus_anchor() +{ + return crypto::rand(); +} +//------------------------------------------------------------------------------------------------------------------- +payment_id_t gen_payment_id() +{ + return crypto::rand(); +} +//------------------------------------------------------------------------------------------------------------------- +view_tag_t gen_view_tag() +{ + return crypto::rand(); +} +//------------------------------------------------------------------------------------------------------------------- +input_context_t gen_input_context() +{ + return crypto::rand(); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/core_types.h b/src/carrot_core/core_types.h new file mode 100644 index 000000000..8dc8a0e67 --- /dev/null +++ b/src/carrot_core/core_types.h @@ -0,0 +1,130 @@ +// 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. + +//! @file Supporting types for Carrot (anchor, view tag, etc.). + +#pragma once + +//local headers + +//third party headers + +//standard headers +#include +#include + +//forward declarations + +namespace carrot +{ + +constexpr std::size_t JANUS_ANCHOR_BYTES{16}; + +/// either encodes randomness the private key of, or an HMAC of, the ephemeral pubkey +struct janus_anchor_t final +{ + unsigned char bytes[JANUS_ANCHOR_BYTES]; +}; + +/// carrot janus anchor XORd with a user-defined secret +using encrypted_janus_anchor_t = janus_anchor_t; + +/// carrot enote types +enum class CarrotEnoteType : unsigned char +{ + PAYMENT = 0, + CHANGE = 1 +}; + +/// carrot encrypted amount +constexpr std::size_t ENCRYPTED_AMOUNT_BYTES{8}; +struct encrypted_amount_t final +{ + unsigned char bytes[ENCRYPTED_AMOUNT_BYTES]; +}; + +/// legacy payment ID +constexpr std::size_t PAYMENT_ID_BYTES{8}; +struct payment_id_t final +{ + unsigned char bytes[PAYMENT_ID_BYTES]; +}; +static constexpr payment_id_t null_payment_id{{0}}; + +/// legacy encrypted payment ID +using encrypted_payment_id_t = payment_id_t; + +/// carrot view tags +constexpr std::size_t VIEW_TAG_BYTES{3}; +struct view_tag_t final +{ + unsigned char bytes[VIEW_TAG_BYTES]; +}; + +static_assert(sizeof(view_tag_t) < 32, "uint8_t cannot index all view tag bits"); + +/// carrot input context +constexpr std::size_t INPUT_CONTEXT_BYTES{1 + 32}; +struct input_context_t final +{ + unsigned char bytes[INPUT_CONTEXT_BYTES]; +}; + +/// overloaded operators: address tag +bool operator==(const janus_anchor_t &a, const janus_anchor_t &b); +static inline bool operator!=(const janus_anchor_t &a, const janus_anchor_t &b) { return !(a == b); } +janus_anchor_t operator^(const janus_anchor_t &a, const janus_anchor_t &b); + +/// overloaded operators: encrypted amount +bool operator==(const encrypted_amount_t &a, const encrypted_amount_t &b); +static inline bool operator!=(const encrypted_amount_t &a, const encrypted_amount_t &b) { return !(a == b); } +encrypted_amount_t operator^(const encrypted_amount_t &a, const encrypted_amount_t &b); + +/// overloaded operators: payment ID +bool operator==(const payment_id_t &a, const payment_id_t &b); +static inline bool operator!=(const payment_id_t &a, const payment_id_t &b) { return !(a == b); } +payment_id_t operator^(const payment_id_t &a, const payment_id_t &b); + +/// overloaded operators: input context +bool operator==(const input_context_t &a, const input_context_t &b); +static inline bool operator!=(const input_context_t &a, const input_context_t &b) { return !(a == b); } + +/// overloaded operators: view tag +bool operator==(const view_tag_t &a, const view_tag_t &b); +static inline bool operator!=(const view_tag_t &a, const view_tag_t &b) { return !(a == b); } + +/// generate a random janus anchor +janus_anchor_t gen_janus_anchor(); +/// generate a random (non-zero) payment ID +payment_id_t gen_payment_id(); +/// generate a random view tag +view_tag_t gen_view_tag(); +/// generate a random input context +input_context_t gen_input_context(); + +} //namespace carrot diff --git a/src/carrot_core/destination.cpp b/src/carrot_core/destination.cpp new file mode 100644 index 000000000..839d5bf23 --- /dev/null +++ b/src/carrot_core/destination.cpp @@ -0,0 +1,149 @@ +// 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 "destination.h" + +//local headers +#include "address_utils.h" +#include "misc_log_ex.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const CarrotDestinationV1 &a, const CarrotDestinationV1 &b) +{ + return a.address_spend_pubkey == b.address_spend_pubkey && + a.address_view_pubkey == b.address_view_pubkey && + a.is_subaddress == b.is_subaddress && + a.payment_id == b.payment_id; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_main_address_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &primary_address_view_pubkey, + CarrotDestinationV1 &destination_out) +{ + destination_out = CarrotDestinationV1{ + .address_spend_pubkey = account_spend_pubkey, + .address_view_pubkey = primary_address_view_pubkey, + .is_subaddress = false, + .payment_id = null_payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_subaddress_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &account_view_pubkey, + const crypto::secret_key &s_generate_address, + const std::uint32_t &j_major, + const std::uint32_t &j_minor, + CarrotDestinationV1 &destination_out) +{ + CHECK_AND_ASSERT_THROW_MES(j_major != 0 || j_minor, + "make carrot subaddress v1: j cannot be 0 for a subaddress, only for main addresses"); + + // s^j_gen = H_32[s_ga](j_major, j_minor) + crypto::secret_key address_index_generator; + make_carrot_index_extension_generator(s_generate_address, j_major, j_minor, address_index_generator); + + // k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen) + crypto::secret_key subaddress_scalar; + make_carrot_subaddress_scalar(account_spend_pubkey, address_index_generator, j_major, j_minor, subaddress_scalar); + + // K^j_s = k^j_subscal * K_s + const rct::key address_spend_pubkey = + rct::scalarmultKey(rct::pk2rct(account_spend_pubkey), rct::sk2rct(subaddress_scalar)); + + // K^j_v = k^j_subscal * K_v + const rct::key address_view_pubkey = + rct::scalarmultKey(rct::pk2rct(account_view_pubkey), rct::sk2rct(subaddress_scalar)); + + destination_out = CarrotDestinationV1{ + .address_spend_pubkey = rct::rct2pk(address_spend_pubkey), + .address_view_pubkey = rct::rct2pk(address_view_pubkey), + .is_subaddress = true, + .payment_id = null_payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_integrated_address_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &primary_address_view_pubkey, + const payment_id_t payment_id, + CarrotDestinationV1 &destination_out) +{ + destination_out = CarrotDestinationV1{ + .address_spend_pubkey = account_spend_pubkey, + .address_view_pubkey = primary_address_view_pubkey, + .is_subaddress = false, + .payment_id = payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +CarrotDestinationV1 gen_carrot_main_address_v1() +{ + return CarrotDestinationV1{ + .address_spend_pubkey = rct::rct2pk(rct::pkGen()), + .address_view_pubkey = rct::rct2pk(rct::pkGen()), + .is_subaddress = false, + .payment_id = null_payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +CarrotDestinationV1 gen_carrot_subaddress_v1() +{ + return CarrotDestinationV1{ + .address_spend_pubkey = rct::rct2pk(rct::pkGen()), + .address_view_pubkey = rct::rct2pk(rct::pkGen()), + .is_subaddress = true, + .payment_id = null_payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +CarrotDestinationV1 gen_carrot_integrated_address_v1() +{ + // force generate non-zero payment id + payment_id_t payment_id{gen_payment_id()}; + while (payment_id == null_payment_id) + payment_id = gen_payment_id(); + + return CarrotDestinationV1{ + .address_spend_pubkey = rct::rct2pk(rct::pkGen()), + .address_view_pubkey = rct::rct2pk(rct::pkGen()), + .is_subaddress = false, + .payment_id = payment_id + }; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/destination.h b/src/carrot_core/destination.h new file mode 100644 index 000000000..98a5ea407 --- /dev/null +++ b/src/carrot_core/destination.h @@ -0,0 +1,116 @@ +// 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. + +// A 'payment proposal' is a proposal to make an enote sending funds to a Carrot address. +// Carrot: Cryptonote Address For Rerandomizable-RingCT-Output Transactions + +#pragma once + +//local headers +#include "core_types.h" +#include "crypto/crypto.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +//// +// CarrotDestinationV1 +// - for creating an output proposal to send an amount to someone +/// +struct CarrotDestinationV1 final +{ + /// K^j_s + crypto::public_key address_spend_pubkey; + /// K^j_v + crypto::public_key address_view_pubkey; + /// is a subaddress? + bool is_subaddress; + /// legacy payment id pid: null for main addresses and subaddresses + payment_id_t payment_id; +}; + +/// equality operators +bool operator==(const CarrotDestinationV1 &a, const CarrotDestinationV1 &b); +static inline bool operator!=(const CarrotDestinationV1 &a, const CarrotDestinationV1 &b) { return !(a == b); } + +/** +* brief: make_carrot_main_address_v1 - make a destination address +* param: account_spend_pubkey - K_s = k_gi X + k_ps T +* param: primary_address_view_pubkey - K^0_v = k_v G +* outparam: destination_out - the full main address +*/ +void make_carrot_main_address_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &primary_address_view_pubkey, + CarrotDestinationV1 &destination_out); +/** +* brief: make_carrot_subaddress_v1 - make a destination address +* param: account_spend_pubkey - K_s = k_gi X + k_ps T +* param: account_view_pubkey - K_v = k_v K_s +* param: s_generate_address - s_ga +* param: j_major - +* param: j_minor - +* outparam: destination_out - the full subaddress +*/ +void make_carrot_subaddress_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &account_view_pubkey, + const crypto::secret_key &s_generate_address, + const std::uint32_t &j_major, + const std::uint32_t &j_minor, + CarrotDestinationV1 &destination_out); +/** +* brief: make_carrot_integrated_address_v1 - make a destination address +* param: account_spend_pubkey - K_s = k_gi X + k_ps T +* param: primary_address_view_pubkey - K^0_v = k_v G +* param: payment_id - pid +* outparam: destination_out - the full main address +*/ +void make_carrot_integrated_address_v1(const crypto::public_key &account_spend_pubkey, + const crypto::public_key &primary_address_view_pubkey, + const payment_id_t payment_id, + CarrotDestinationV1 &destination_out); +/** +* brief: gen_carrot_main_address_v1 - generate a random main address +*/ +CarrotDestinationV1 gen_carrot_main_address_v1(); +/** +* brief: gen_carrot_main_address_v1 - generate a random subaddress +*/ +CarrotDestinationV1 gen_carrot_subaddress_v1(); +/** +* brief: gen_carrot_main_address_v1 - generate a random integrated address +*/ +CarrotDestinationV1 gen_carrot_integrated_address_v1(); + +} //namespace carrot diff --git a/src/carrot_core/device.h b/src/carrot_core/device.h new file mode 100644 index 000000000..c305128f7 --- /dev/null +++ b/src/carrot_core/device.h @@ -0,0 +1,165 @@ +// 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. + +//! @file Abstract interfaces for performing scanning without revealing account keys + +#pragma once + +//local headers +#include "core_types.h" +#include "crypto/crypto.h" +#include "crypto/x25519.h" + +//third party headers + +//standard headers +#include +#include +#include + +//forward declarations + + +/** + * These device interfaces were written primarily to be as lean as possible, for ease of the + * implementor, so long as account keys don't leak. As such, the interfaces do not shield + * sender-receiver secrets, and thus temporary access to this device interface can expose + * transaction content permanently in a provable manner. The device interface currently used in + * Monero (hw::device) also exposes transaction content, which can be saved permanently, but it + * wouldn't necessarily be provable. Thus, in the case of a breach, the original user has some + * plausible deniability with (hw::device), which cannot be said of the interfaces in this file. + * It's not impossible to make carrot scanning happen completely on-device, but it is significantly + * more involved. + */ + +namespace carrot +{ +/** + * brief: base exception type for reporting carrot device errors. + * note: devices should only throw this exception or derived classes + */ +struct device_error: public std::runtime_error +{ + /** + * param: dev_make - e.g. "Trezor", "Ledger" + * param: dev_model - e.g. "Model T", "Nano X" + * param: func_called - verbatim device interface method name, e.g. "view_key_8_scalar_mult_x25519" + * param: msg - arbitrary error message + * param: code - arbitrary error code + */ + device_error(std::string &&dev_make, + std::string &&dev_model, + std::string &&func_called, + std::string &&msg, + const int code) + : std::runtime_error(make_formatted_message(dev_make, dev_model, func_called, msg, code)), + dev_make(dev_make), dev_model(dev_model), func_called(func_called), msg(msg), code(code) + {} + + static std::string make_formatted_message(const std::string &dev_make, + const std::string &dev_model, + const std::string &func_called, + const std::string &msg, + const int code) + { + char buf[384]; + snprintf(buf, sizeof(buf), + "%s %s device error (%d), at %s(): %s", + dev_make.c_str(), dev_model.c_str(), code, func_called.c_str(), msg.c_str()); + return {buf}; + } + + const std::string dev_make; + const std::string dev_model; + const std::string func_called; + const std::string msg; + const int code; +}; + +struct view_incoming_key_device +{ + /** + * brief: view_key_scalar_mult_ed25519 - do an Ed25519 scalar mult against the incoming view key + * param: P - Ed25519 base point + * outparam: kvP = k_v P + * return: true on success, false on failure (e.g. unable to decompress point) + */ + virtual bool view_key_scalar_mult_ed25519(const crypto::public_key &P, + crypto::public_key &kvP) const = 0; + + /** + * brief: view_key_8_scalar_mult_x25519 - do an X25519 scalar mult and cofactor clear against the incoming view key + * param: D - X25519 base point + * outparam: kv8D = 8 k_v D + * return: true on success, false on failure (e.g. unable to decompress point) + */ + virtual bool view_key_8_scalar_mult_x25519(const crypto::x25519_pubkey &D, + crypto::x25519_pubkey &kv8D) const = 0; + + /** + * brief: make_janus_anchor_special - make a janus anchor for "special" enotes + * param: enote_ephemeral_pubkey - D_e + * param: input_context - input_context + * param: account_spend_pubkey - K_s + * outparam: anchor_special_out - anchor_sp = anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) + */ + virtual void make_janus_anchor_special(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const crypto::public_key &onetime_address, + const crypto::public_key &account_spend_pubkey, + janus_anchor_t &anchor_special_out) const = 0; + + virtual ~view_incoming_key_device() = default; +}; + +struct view_balance_secret_device +{ + /** + * brief: make_internal_view_tag - make an internal view tag, given non-secret data + * param: input_context - input_context + * param: onetime_address - Ko + * outparam: view_tag_out - vt = H_3(s_vb || input_context || Ko) + */ + virtual void make_internal_view_tag(const input_context_t &input_context, + const crypto::public_key &onetime_address, + view_tag_t &view_tag_out) const = 0; + + /** + * brief: make_internal_sender_receiver_secret - make internal sender-receiver secret, given non-secret data + * param: enote_ephemeral_pubkey - D_e + * param: input_context - input_context + * outparam: s_sender_receiver_out - s_sr = s^ctx_sr = H_32(s_sr, D_e, input_context) + */ + virtual void make_internal_sender_receiver_secret(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + crypto::hash &s_sender_receiver_out) const = 0; + + virtual ~view_balance_secret_device() = default; +}; + +} //namespace carrot diff --git a/src/carrot_core/device_ram_borrowed.cpp b/src/carrot_core/device_ram_borrowed.cpp new file mode 100644 index 000000000..269acd08d --- /dev/null +++ b/src/carrot_core/device_ram_borrowed.cpp @@ -0,0 +1,90 @@ +// 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. + +//pair header +#include "device_ram_borrowed.h" + +//local headers +#include "enote_utils.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +bool view_incoming_key_ram_borrowed_device::view_key_scalar_mult_ed25519(const crypto::public_key &P, + crypto::public_key &kvP) const +{ + kvP = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(P), rct::sk2rct(m_k_view_incoming))); + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool view_incoming_key_ram_borrowed_device::view_key_8_scalar_mult_x25519(const crypto::x25519_pubkey &D, + crypto::x25519_pubkey &kv8D) const +{ + return make_carrot_uncontextualized_shared_key_receiver(m_k_view_incoming, D, kv8D); +} +//------------------------------------------------------------------------------------------------------------------- +void view_incoming_key_ram_borrowed_device::make_janus_anchor_special( + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const crypto::public_key &onetime_address, + const crypto::public_key &account_spend_pubkey, + janus_anchor_t &anchor_special_out) const +{ + return make_carrot_janus_anchor_special(enote_ephemeral_pubkey, + input_context, + onetime_address, + m_k_view_incoming, + account_spend_pubkey, + anchor_special_out); +} +//------------------------------------------------------------------------------------------------------------------- +void view_balance_secret_ram_borrowed_device::make_internal_view_tag(const input_context_t &input_context, + const crypto::public_key &onetime_address, + view_tag_t &view_tag_out) const +{ + make_carrot_view_tag(to_bytes(m_s_view_balance), input_context, onetime_address, view_tag_out); +} +//------------------------------------------------------------------------------------------------------------------- +void view_balance_secret_ram_borrowed_device::make_internal_sender_receiver_secret( + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + crypto::hash &s_sender_receiver_out) const +{ + make_carrot_sender_receiver_secret(to_bytes(m_s_view_balance), + enote_ephemeral_pubkey, + input_context, + s_sender_receiver_out); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/device_ram_borrowed.h b/src/carrot_core/device_ram_borrowed.h new file mode 100644 index 000000000..c0a51755e --- /dev/null +++ b/src/carrot_core/device_ram_borrowed.h @@ -0,0 +1,86 @@ +// 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. + +//! @file Carrot device implementations for in-memory keys & secrets + +#pragma once + +//local headers +#include "device.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +class view_incoming_key_ram_borrowed_device: public view_incoming_key_device +{ +public: + view_incoming_key_ram_borrowed_device(const crypto::secret_key &k_view_incoming): + m_k_view_incoming(k_view_incoming) {} + + bool view_key_scalar_mult_ed25519(const crypto::public_key &P, + crypto::public_key &kvP) const override; + + bool view_key_8_scalar_mult_x25519(const crypto::x25519_pubkey &D, + crypto::x25519_pubkey &kv8D) const override; + + void make_janus_anchor_special(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const crypto::public_key &onetime_address, + const crypto::public_key &account_spend_pubkey, + janus_anchor_t &anchor_special_out) const override; + +private: + const crypto::secret_key &m_k_view_incoming; +}; + +class view_balance_secret_ram_borrowed_device: public view_balance_secret_device +{ +public: + view_balance_secret_ram_borrowed_device(const crypto::secret_key &s_view_balance): + m_s_view_balance(s_view_balance) {} + + void make_internal_view_tag(const input_context_t &input_context, + const crypto::public_key &onetime_address, + view_tag_t &view_tag_out) const override; + + void make_internal_sender_receiver_secret(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + crypto::hash &s_sender_receiver_out) const override; + +private: + const crypto::secret_key &m_s_view_balance; +}; + +} //namespace carrot diff --git a/src/carrot_core/enote_record_types.h b/src/carrot_core/enote_record_types.h new file mode 100644 index 000000000..39b558c46 --- /dev/null +++ b/src/carrot_core/enote_record_types.h @@ -0,0 +1,185 @@ +// 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 "legacy_enote_types.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +struct IntermediateEnoteV1Record final +{ + /// original enote + LegacyEnoteV1 enote; + + /// i: legacy address index + cryptonote::subaddress_index subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; +}; + +struct IntermediateEnoteV2Record final +{ + /// original enote + LegacyEnoteV2 enote; + + /// i: legacy address index (false if unknown) + std::optional subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; + /// a: the enote's amount + rct::xmr_amount amount; +}; + +struct IntermediateEnoteV3Record final +{ + /// original enote + LegacyEnoteV3 enote; + + /// i: legacy address index (false if unknown) + std::optional subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; + /// a: the enote's amount + rct::xmr_amount amount; +}; + +struct IntermediateEnoteV4Record final +{ + /// original enote + LegacyEnoteV4 enote; + + /// i: legacy address index + cryptonote::subaddress_index subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; +}; + +struct IntermediateEnoteV5Record final +{ + /// original enote + LegacyEnoteV5 enote; + + /// i: legacy address index + std::optional subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; + /// a: the enote's amount + rct::xmr_amount amount; +}; + +struct IntermediateEnoteV6Record final +{ + /// original enote + SpCoinbaseEnoteV1 enote; + /// the enote's ephemeral pubkey + crypto::x25519_pubkey enote_ephemeral_pubkey; + /// the enote's input context + jamtis::input_context_t input_context; +}; + +struct IntermediateEnoteV7Record final +{ + /// original enote + SpCoinbaseEnoteV1 enote; + /// the enote's ephemeral pubkey + crypto::x25519_pubkey enote_ephemeral_pubkey; + /// the enote's input context + jamtis::input_context_t input_context; + + /// i: legacy address index + std::optional subaddr_index; + /// pid: payment ID + jamtis::payment_id_t payment_id; + /// a: the enote's amount + rct::xmr_amount amount; + /// enote_type: the enote's type + jamtis::JamtisEnoteType enote_type; + /// is internal: true if used an internal secret for enote scanning + bool is_internal; +}; + +using IntermediateEnoteRecordVariant = tools::variant< + IntermediateEnoteV1Record, + IntermediateEnoteV2Record, + IntermediateEnoteV3Record, + IntermediateEnoteV4Record, + IntermediateEnoteV5Record, + IntermediateEnoteV6Record, + IntermediateEnoteV7Record>; + +rct::xmr_amount amount_ref(const IntermediateEnoteRecordVariant &enote_record) +{ + struct mierv_amount_visitor: tools::variant_static_visitor + { + rct::xmr_amount operator()(const IntermediateEnoteV1Record &v) const { return v.enote.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV2Record &v) const { return v.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV3Record &v) const { return v.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV4Record &v) const { return v.enote.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV5Record &v) const { return v.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV6Record &v) const { return v.enote.core.amount; } + rct::xmr_amount operator()(const IntermediateEnoteV7Record &v) const { return v.amount; } + }; + + return enote_record.visit(mierv_amount_visitor()); +} + +const crypto::public_key& onetime_address_ref(const IntermediateEnoteRecordVariant &enote_record) +{ + struct mierv_onetime_address_visitor: tools::variant_static_visitor + { + const crypto::public_key& operator()(const IntermediateEnoteV1Record &v) const { return v.enote.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV2Record &v) const { return v.enote.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV3Record &v) const { return v.enote.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV4Record &v) const { return v.enote.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV5Record &v) const { return v.enote.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV6Record &v) const { return v.enote.core.onetime_address; } + const crypto::public_key& operator()(const IntermediateEnoteV7Record &v) const { return v.enote.core.onetime_address; } + }; + + return enote_record.visit(mierv_amount_visitor()); +} + +void get_legacy_enote_identifier(const IntermediateEnoteRecordVariant &enote_record, crypto::hash &identifier_out) +{ + rct::key identifier_rk; + get_legacy_enote_identifier(onetime_address_ref(enote_record), amount_ref(enote_record), identifier_rk); + + memcpy(&identifier_out, identifier_rk, sizeof(crypto::hash)); +} + +} //namespace carrot diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp new file mode 100644 index 000000000..c6a40f79c --- /dev/null +++ b/src/carrot_core/enote_utils.cpp @@ -0,0 +1,496 @@ +// 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 "enote_utils.h" + +//local headers +#include "config.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "crypto/generators.h" +#include "crypto/wallet/crypto.h" +#include "cryptonote_config.h" +#include "hash_functions.h" +#include "int-util.h" +#include "misc_language.h" +#include "ringct/rctOps.h" +#include "transcript_fixed.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static encrypted_amount_t enc_amount(const rct::xmr_amount amount, const encrypted_amount_t &mask) +{ + static_assert(sizeof(rct::xmr_amount) == sizeof(encrypted_amount_t), ""); + + // little_endian(amount) XOR H_8(q, Ko) + encrypted_amount_t amount_LE; + memcpy_swap64le(amount_LE.bytes, &amount, 1); + return amount_LE ^ mask; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static rct::xmr_amount dec_amount(const encrypted_amount_t &encrypted_amount, const encrypted_amount_t &mask) +{ + static_assert(sizeof(rct::xmr_amount) == sizeof(encrypted_amount_t), ""); + + // system_endian(encrypted_amount XOR H_8(q, Ko)) + const encrypted_amount_t decryptd_amount{encrypted_amount ^ mask}; + rct::xmr_amount amount; + memcpy_swap64le(&amount, &decryptd_amount, 1); + return amount; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, + const input_context_t &input_context, + const crypto::public_key &address_spend_pubkey, + const crypto::public_key &address_view_pubkey, + const payment_id_t payment_id, + crypto::secret_key &enote_ephemeral_privkey_out) +{ + // k_e = (H_64(anchor_norm, input_context, K^j_s, K^j_v, pid)) mod l + const auto transcript = sp::make_fixed_transcript( + anchor_norm, input_context, address_spend_pubkey, address_view_pubkey, payment_id); + derive_scalar(transcript.data(), transcript.size(), nullptr, &enote_ephemeral_privkey_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_enote_ephemeral_pubkey_cryptonote(const crypto::secret_key &enote_ephemeral_privkey, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out) +{ + // K_e = d_e G + ge_p3 D_e_in_ed25519; + ge_scalarmult_base(&D_e_in_ed25519, to_bytes(enote_ephemeral_privkey)); + + // D_e = ConvertPointE(K_e) + ge_p3_to_x25519(enote_ephemeral_pubkey_out.data, &D_e_in_ed25519); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_enote_ephemeral_pubkey_subaddress(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_spend_pubkey, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out) +{ + // deserialize K^j_s + ge_p3 address_spend_pubkey_p3; + ge_frombytes_vartime(&address_spend_pubkey_p3, to_bytes(address_spend_pubkey)); + + // K_e = d_e K^j_s + ge_p3 D_e_in_ed25519; + ge_scalarmult_p3(&D_e_in_ed25519, to_bytes(enote_ephemeral_privkey), &address_spend_pubkey_p3); + + // D_e = ConvertPointE(K_e) + ge_p3_to_x25519(enote_ephemeral_pubkey_out.data, &D_e_in_ed25519); +} +//------------------------------------------------------------------------------------------------------------------- +bool make_carrot_uncontextualized_shared_key_receiver(const crypto::secret_key &k_view, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + crypto::x25519_pubkey &s_sender_receiver_unctx_out) +{ + // @TODO: this is slower than a turtle on morphine, and will cripple scan speed, but should be correct + + // K_e = ConvertPointM(D_e) + ge_p3 p3_tmp; + if (ge_fromx25519_vartime(&p3_tmp, enote_ephemeral_pubkey.data) != 0) + return false; + + // serialize K_e + crypto::public_key K_e; + ge_p3_tobytes(to_bytes(K_e), &p3_tmp); + + // [ed25519] s_sr = 8 d_e K^j_v + crypto::key_derivation s_sr_in_ed25519; + if (!crypto::wallet::generate_key_derivation(K_e, k_view, s_sr_in_ed25519)) + return false; + else if (memcmp(&s_sr_in_ed25519, &rct::I, sizeof(rct::key)) == 0) + return false; + + // deserialize s_sr + ge_p3 s_sr_in_ed25519_p3; + ge_frombytes_vartime(&s_sr_in_ed25519_p3, to_bytes(s_sr_in_ed25519)); + + // ConvertPointE(s_sr) + ge_p3_to_x25519(s_sender_receiver_unctx_out.data, &s_sr_in_ed25519_p3); + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +bool make_carrot_uncontextualized_shared_key_sender(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_view_pubkey, + crypto::x25519_pubkey &s_sender_receiver_unctx_out) +{ + // [ed25519] s_sr = 8 d_e K^j_v + crypto::key_derivation s_sr_in_ed25519; + if (!crypto::wallet::generate_key_derivation(address_view_pubkey, enote_ephemeral_privkey, s_sr_in_ed25519)) + return false; + + // deserialize s_sr + ge_p3 s_sr_in_ed25519_p3; + ge_frombytes_vartime(&s_sr_in_ed25519_p3, to_bytes(s_sr_in_ed25519)); + + // ConvertPointE(s_sr) + ge_p3_to_x25519(s_sender_receiver_unctx_out.data, &s_sr_in_ed25519_p3); + + return true; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + view_tag_t &view_tag_out) +{ + // vt = H_3(s_sr || input_context || Ko) + const auto transcript = sp::make_fixed_transcript(input_context, onetime_address); + derive_bytes_3(transcript.data(), transcript.size(), s_sender_receiver_unctx, &view_tag_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_input_context_coinbase(const std::uint64_t block_index, input_context_t &input_context_out) +{ + // input_context = "C" || IntToBytes256(block_index) + memset(input_context_out.bytes, 0, sizeof(input_context_t)); + input_context_out.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_COINBASE; + memcpy_swap64le(input_context_out.bytes + 1, &block_index, 1); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_input_context(const crypto::key_image &first_rct_key_image, input_context_t &input_context_out) +{ + // input_context = "R" || KI_1 + input_context_out.bytes[0] = CARROT_DOMAIN_SEP_INPUT_CONTEXT_RINGCT; + memcpy(input_context_out.bytes + 1, first_rct_key_image.data, sizeof(crypto::key_image)); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_sender_receiver_secret(const unsigned char s_sender_receiver_unctx[32], + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + crypto::hash &s_sender_receiver_out) +{ + // s^ctx_sr = H_32(s_sr, D_e, input_context) + const auto transcript = sp::make_fixed_transcript( + enote_ephemeral_pubkey, input_context); + derive_bytes_32(transcript.data(), transcript.size(), s_sender_receiver_unctx, &s_sender_receiver_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_onetime_address_extension_g(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::secret_key &sender_extension_out) +{ + // k^o_g = H_n("..g..", s^ctx_sr, C_a) + const auto transcript = sp::make_fixed_transcript(amount_commitment); + derive_scalar(transcript.data(), transcript.size(), &s_sender_receiver, &sender_extension_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_onetime_address_extension_t(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::secret_key &sender_extension_out) +{ + // k^o_t = H_n("..t..", s^ctx_sr, C_a) + const auto transcript = sp::make_fixed_transcript(amount_commitment); + derive_scalar(transcript.data(), transcript.size(), &s_sender_receiver, &sender_extension_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_onetime_address_extension_pubkey(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &sender_extension_pubkey_out) +{ + // k^o_g = H_n("..g..", s^ctx_sr, C_a) + crypto::secret_key sender_extension_g; + make_carrot_onetime_address_extension_g(s_sender_receiver, amount_commitment, sender_extension_g); + + // k^o_t = H_n("..t..", s^ctx_sr, C_a) + crypto::secret_key sender_extension_t; + make_carrot_onetime_address_extension_t(s_sender_receiver, amount_commitment, sender_extension_t); + + // K^o_ext = k^o_g G + k^o_t T + rct::key sender_extension_pubkey_tmp; + rct::addKeys2(sender_extension_pubkey_tmp, + rct::sk2rct(sender_extension_g), + rct::sk2rct(sender_extension_t), + rct::pk2rct(crypto::get_T())); + + sender_extension_pubkey_out = rct::rct2pk(sender_extension_pubkey_tmp); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_onetime_address(const crypto::public_key &address_spend_pubkey, + const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &onetime_address_out) +{ + // K^o_ext = k^o_g G + k^o_t T + crypto::public_key sender_extension_pubkey; + make_carrot_onetime_address_extension_pubkey(s_sender_receiver, amount_commitment, sender_extension_pubkey); + + // Ko = K^j_s + K^o_ext + onetime_address_out = rct::rct2pk(rct::addKeys( + rct::pk2rct(address_spend_pubkey), rct::pk2rct(sender_extension_pubkey))); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_amount_blinding_factor(const crypto::hash &s_sender_receiver, + const CarrotEnoteType enote_type, + crypto::secret_key &amount_blinding_factor_out) +{ + // k_a = H_n(s^ctx_sr, enote_type) + const auto transcript = sp::make_fixed_transcript( + static_cast(enote_type)); + derive_scalar(transcript.data(), transcript.size(), &s_sender_receiver, &amount_blinding_factor_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_anchor_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_janus_anchor_t &anchor_encryption_mask_out) +{ + // m_anchor = H_16(s^ctx_sr, Ko) + const auto transcript = sp::make_fixed_transcript(onetime_address); + derive_bytes_16(transcript.data(), transcript.size(), &s_sender_receiver, &anchor_encryption_mask_out); +} +//------------------------------------------------------------------------------------------------------------------- +encrypted_janus_anchor_t encrypt_carrot_anchor(const janus_anchor_t &anchor, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_anchor = H_16(s^ctx_sr, Ko) + encrypted_janus_anchor_t mask; + make_carrot_anchor_encryption_mask(s_sender_receiver, onetime_address, mask); + + // anchor_enc = anchor XOR m_anchor + return anchor ^ mask; +} +//------------------------------------------------------------------------------------------------------------------- +janus_anchor_t decrypt_carrot_anchor(const encrypted_janus_anchor_t &encrypted_anchor, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_anchor = H_16(s^ctx_sr, Ko) + encrypted_janus_anchor_t mask; + make_carrot_anchor_encryption_mask(s_sender_receiver, onetime_address, mask); + + // anchor = anchor_enc XOR m_anchor + return encrypted_anchor ^ mask; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_amount_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_amount_t &amount_encryption_mask_out) +{ + // m_a = H_8(s^ctx_sr, Ko) + const auto transcript = sp::make_fixed_transcript(onetime_address); + derive_bytes_8(transcript.data(), transcript.size(), &s_sender_receiver, &amount_encryption_mask_out); +} +//------------------------------------------------------------------------------------------------------------------- +encrypted_amount_t encrypt_carrot_amount(const rct::xmr_amount amount, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_a = H_8(s^ctx_sr, Ko) + encrypted_amount_t mask; + make_carrot_amount_encryption_mask(s_sender_receiver, onetime_address, mask); + + // a_enc = a XOR m_a [paying attention to system endianness] + return enc_amount(amount, mask); +} +//------------------------------------------------------------------------------------------------------------------- +rct::xmr_amount decrypt_carrot_amount(const encrypted_amount_t encrypted_amount, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_a = H_8(s^ctx_sr, Ko) + encrypted_amount_t mask; + make_carrot_amount_encryption_mask(s_sender_receiver, onetime_address, mask); + + // a = a_enc XOR m_a [paying attention to system endianness] + return dec_amount(encrypted_amount, mask); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_payment_id_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_payment_id_t &payment_id_encryption_mask_out) +{ + // m_pid = H_8(s^ctx_sr, Ko) + const auto transcript = sp::make_fixed_transcript(onetime_address); + derive_bytes_8(transcript.data(), transcript.size(), &s_sender_receiver, &payment_id_encryption_mask_out); +} +//------------------------------------------------------------------------------------------------------------------- +encrypted_payment_id_t encrypt_legacy_payment_id(const payment_id_t payment_id, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_pid = H_8(s^ctx_sr, Ko) + encrypted_payment_id_t mask; + make_carrot_payment_id_encryption_mask(s_sender_receiver, onetime_address, mask); + + // pid_enc = pid XOR m_pid + return payment_id ^ mask; +} +//------------------------------------------------------------------------------------------------------------------- +payment_id_t decrypt_legacy_payment_id(const encrypted_payment_id_t encrypted_payment_id, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address) +{ + // m_pid = H_8(s^ctx_sr, Ko) + encrypted_payment_id_t mask; + make_carrot_payment_id_encryption_mask(s_sender_receiver, onetime_address, mask); + + // pid = pid_enc XOR m_pid + return encrypted_payment_id ^ mask; +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_janus_anchor_special(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const crypto::public_key &onetime_address, + const crypto::secret_key &k_view, + const crypto::public_key &account_spend_pubkey, + janus_anchor_t &anchor_special_out) +{ + // anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) + const auto transcript = sp::make_fixed_transcript( + enote_ephemeral_pubkey, input_context, account_spend_pubkey); + derive_bytes_16(transcript.data(), transcript.size(), &k_view, &anchor_special_out); +} +//------------------------------------------------------------------------------------------------------------------- +void recover_address_spend_pubkey(const crypto::public_key &onetime_address, + const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &address_spend_key_out) +{ + // K^o_ext = k^o_g G + k^o_t T + crypto::public_key sender_extension_pubkey; + make_carrot_onetime_address_extension_pubkey(s_sender_receiver, amount_commitment, sender_extension_pubkey); + + // K^j_s = Ko - K^o_ext + rct::key res_tmp; + rct::subKeys(res_tmp, rct::pk2rct(onetime_address), rct::pk2rct(sender_extension_pubkey)); + address_spend_key_out = rct::rct2pk(res_tmp); +} +//------------------------------------------------------------------------------------------------------------------- +bool test_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], + const input_context_t input_context, + const crypto::public_key &onetime_address, + const view_tag_t view_tag) +{ + // vt' = H_3(s_sr || input_context || Ko) + view_tag_t nominal_view_tag; + make_carrot_view_tag(s_sender_receiver_unctx, input_context, onetime_address, nominal_view_tag); + + // vt' ?= vt + return nominal_view_tag == view_tag; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_recompute_carrot_amount_commitment(const crypto::hash &s_sender_receiver, + const CarrotEnoteType nominal_enote_type, + const rct::xmr_amount nominal_amount, + const rct::key &amount_commitment, + crypto::secret_key &amount_blinding_factor_out) +{ + // k_a' = H_n(s^ctx_sr, enote_type') + make_carrot_amount_blinding_factor(s_sender_receiver, nominal_enote_type, amount_blinding_factor_out); + + // C_a' = k_a' G + a' H + const rct::key nominal_amount_commitment = rct::commit(nominal_amount, rct::sk2rct(amount_blinding_factor_out)); + + // C_a' ?= C_a + return nominal_amount_commitment == amount_commitment; +} +//------------------------------------------------------------------------------------------------------------------- +bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, + const encrypted_amount_t &encrypted_amount, + const crypto::public_key &onetime_address, + const rct::key &amount_commitment, + CarrotEnoteType &enote_type_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out) +{ + // a' = a_enc XOR m_a + amount_out = decrypt_carrot_amount(encrypted_amount, s_sender_receiver, onetime_address); + + // set enote_type <- "payment" + enote_type_out = CarrotEnoteType::PAYMENT; + + // if C_a ?= k_a' G + a' H, then PASS + if (try_recompute_carrot_amount_commitment(s_sender_receiver, + enote_type_out, + amount_out, + amount_commitment, + amount_blinding_factor_out)) + return true; + + // set enote_type <- "change" + enote_type_out = CarrotEnoteType::CHANGE; + + // if C_a ?= k_a' G + a' H, then PASS + if (try_recompute_carrot_amount_commitment(s_sender_receiver, + enote_type_out, + amount_out, + amount_commitment, + amount_blinding_factor_out)) + return true; + + // neither attempt at recomputing passed: so FAIL + return false; +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_carrot_external_janus_protection(const janus_anchor_t &nominal_anchor, + const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, + const bool is_subaddress, + const payment_id_t nominal_payment_id, + const crypto::x25519_pubkey &enote_ephemeral_pubkey) +{ + // d_e' = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + crypto::secret_key nominal_enote_ephemeral_privkey; + make_carrot_enote_ephemeral_privkey(nominal_anchor, + input_context, + nominal_address_spend_pubkey, + nominal_address_view_pubkey, + nominal_payment_id, + nominal_enote_ephemeral_privkey); + + // recompute D_e' for d_e' and address type + crypto::x25519_pubkey nominal_enote_ephemeral_pubkey; + if (is_subaddress) + make_carrot_enote_ephemeral_pubkey_subaddress(nominal_enote_ephemeral_privkey, + nominal_address_spend_pubkey, + nominal_enote_ephemeral_pubkey); + else // cryptonote address + make_carrot_enote_ephemeral_pubkey_cryptonote(nominal_enote_ephemeral_privkey, + nominal_enote_ephemeral_pubkey); + + // D_e' ?= D_e + return nominal_enote_ephemeral_pubkey == enote_ephemeral_pubkey; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h new file mode 100644 index 000000000..4165f7a13 --- /dev/null +++ b/src/carrot_core/enote_utils.h @@ -0,0 +1,384 @@ +// 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. + +// @file Utilities for making and handling enotes with carrot. + +#pragma once + +//local headers +#include "crypto/crypto.h" +#include "crypto/x25519.h" +#include "core_types.h" +#include "ringct/rctTypes.h" + +//third party headers + +//standard headers + +//forward declarations + +namespace carrot +{ + +/** + * brief: make_carrot_enote_ephemeral_privkey - enote ephemeral privkey k_e for Carrot enotes + * d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + * param: anchor_norm - anchor_norm + * param: input_context - input_context + * param: address_spend_pubkey - K^j_s + * param: address_view_pubkey - K^j_v + * param: payment_id - pid + * outparam: enote_ephemeral_privkey_out - k_e + */ +void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, + const input_context_t &input_context, + const crypto::public_key &address_spend_pubkey, + const crypto::public_key &address_view_pubkey, + const payment_id_t payment_id, + crypto::secret_key &enote_ephemeral_privkey_out); +/** + * brief: make_carrot_enote_ephemeral_pubkey_main - make enote ephemeral pubkey D_e for a main address + * D_e = d_e B + * param: enote_ephemeral_privkey - d_e + * outparam: enote_ephemeral_pubkey_out - D_e + */ +void make_carrot_enote_ephemeral_pubkey_cryptonote(const crypto::secret_key &enote_ephemeral_privkey, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out); +/** + * brief: make_carrot_enote_ephemeral_pubkey_subaddress - make enote ephemeral pubkey D_e for a subaddress + * D_e = d_e ConvertPointE(K^j_s) + * param: enote_ephemeral_privkey - d_e + * param: address_spend_pubkey - K^j_s + * outparam: enote_ephemeral_pubkey_out - D_e + */ +void make_carrot_enote_ephemeral_pubkey_subaddress(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_spend_pubkey, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out); +/** + * brief: make_carrot_uncontextualized_shared_key_receiver - perform the receiver-side ECDH exchange for Carrot enotes + * s_sr = 8 k_v D_e + * param: k_view - k_v + * param: enote_ephemeral_pubkey - D_e + * outparam: s_sender_receiver_unctx_out - s_sr + * return: true if successful, false if a failure occurred in point decompression + */ +bool make_carrot_uncontextualized_shared_key_receiver(const crypto::secret_key &k_view, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + crypto::x25519_pubkey &s_sender_receiver_unctx_out); +/** + * brief: make_carrot_uncontextualized_shared_key_sender - perform the sender-side ECDH exchange for Carrot enotes + * s_sr = 8 d_e ConvertPointE(K^j_v) + * param: enote_ephemeral_privkey - d_e + * param: address_view_pubkey - K^j_v + * outparam: s_sender_receiver_unctx_out - s_sr + * return: true if successful, false if a failure occurred in point decompression + */ +bool make_carrot_uncontextualized_shared_key_sender(const crypto::secret_key &enote_ephemeral_privkey, + const crypto::public_key &address_view_pubkey, + crypto::x25519_pubkey &s_sender_receiver_unctx_out); +/** +* brief: make_carrot_view_tag - used for optimized identification of enotes +* vt = H_3(s_sr || input_context || Ko) +* param: s_sender_receiver_unctx - s_sr +* param: input_context - input_context +* param: onetime_address - Ko +* outparam: view_tag_out - vt +*/ +void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + view_tag_t &view_tag_out); +/** +* brief: make_carrot_input_context_coinbase - input context for a sender-receiver secret (coinbase txs) +* input_context = "C" || IntToBytes256(block_index) +* param: block_index - block index of the coinbase tx +* outparam: input_context_out - "C" || IntToBytes256(block_index) +*/ +void make_carrot_input_context_coinbase(const std::uint64_t block_index, input_context_t &input_context_out); +/** +* brief: make_carrot_input_context - input context for a sender-receiver secret (standard RingCT txs) +* input_context = "R" || KI_1 +* param: first_rct_key_image - KI_1, the first spent RingCT key image in a tx +* outparam: input_context_out - "S" || KI_1 +*/ +void make_carrot_input_context(const crypto::key_image &first_rct_key_image, input_context_t &input_context_out); +/** +* brief: make_carrot_sender_receiver_secret - contextualized sender-receiver secret s^ctx_sr +* s^ctx_sr = H_32(s_sr, D_e, input_context) +* param: s_sender_receiver_unctx - s_sr +* param: enote_ephemeral_pubkey - D_e +* param: input_context - [standard: KI_1] [coinbase: block index] +* outparam: s_sender_receiver_out - s^ctx_sr +* - note: this is 'crypto::hash' instead of 'crypto::secret_key' for better performance in multithreaded environments +*/ +void make_carrot_sender_receiver_secret(const unsigned char s_sender_receiver_unctx[32], + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + crypto::hash &s_sender_receiver_out); +/** +* brief: make_carrot_onetime_address_extension_g - extension for transforming a receiver's spendkey into an +* enote one-time address +* k^o_g = H_n("..g..", s^ctx_sr, C_a) +* param: s_sender_receiver - s^ctx_sr +* param: amount_commitment - C_a +* outparam: sender_extension_out - k^o_g +*/ +void make_carrot_onetime_address_extension_g(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::secret_key &sender_extension_out); +/** +* brief: make_carrot_onetime_address_extension_t - extension for transforming a receiver's spendkey into an +* enote one-time address +* k^o_t = H_n("..t..", s^ctx_sr, C_a) +* param: s_sender_receiver - s^ctx_sr +* param: amount_commitment - C_a +* outparam: sender_extension_out - k^o_t +*/ +void make_carrot_onetime_address_extension_t(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::secret_key &sender_extension_out); +/** +* brief: make_carrot_onetime_address_extension_pubkey - create a FCMP++ onetime address extension pubkey +* K^o_ext = k^o_g G + k^o_t T +* param: s_sender_receiver - s^ctx_sr +* param: amount_commitment - C_a +* outparam: sender_extension_pubkey_out - K^o_ext +*/ +void make_carrot_onetime_address_extension_pubkey(const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &sender_extension_pubkey_out); +/** +* brief: make_carrot_onetime_address - create a FCMP++ onetime address +* Ko = K^j_s + K^o_ext = K^j_s + (k^o_g G + k^o_t T) +* param: address_spend_pubkey - K^j_s +* param: s_sender_receiver - s^ctx_sr +* param: amount_commitment - C_a +* outparam: onetime_address_out - Ko +*/ +void make_carrot_onetime_address(const crypto::public_key &address_spend_pubkey, + const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &onetime_address_out); +/** +* brief: make_carrot_amount_blinding_factor - create blinding factor for enote's amount commitment C_a +* k_a = H_n(s^ctx_sr, enote_type) +* param: s_sender_receiver - s^ctx_sr +* param: enote_type - enote_type +* outparam: amount_blinding_factor_out - k_a +*/ +void make_carrot_amount_blinding_factor(const crypto::hash &s_sender_receiver, + const CarrotEnoteType enote_type, + crypto::secret_key &amount_blinding_factor_out); +/** +* brief: make_carrot_anchor_encryption_mask - create XOR encryption mask for enote's anchor +* m_anchor = H_16(s^ctx_sr, Ko) +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* outparam: anchor_encryption_mask_out - m_anchor +*/ +void make_carrot_anchor_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_janus_anchor_t &anchor_encryption_mask_out); +/** +* brief: encrypt_carrot_anchor - encrypt a Janus anchor for an enote +* anchor_enc = anchor XOR m_anchor +* param: anchor - +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: anchor_enc +*/ +encrypted_janus_anchor_t encrypt_carrot_anchor(const janus_anchor_t &anchor, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** +* brief: decrypt_carrot_address_tag - decrypt a Janus anchor from an enote +* anchor = anchor_enc XOR m_anchor +* param: encrypted_anchor - anchor_enc +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: anchor +*/ +janus_anchor_t decrypt_carrot_anchor(const encrypted_janus_anchor_t &encrypted_anchor, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** +* brief: make_carrot_amount_encryption_mask - create XOR encryption mask for enote's amount +* m_a = H_16(s^ctx_sr, Ko) +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* outparam: amount_encryption_mask_out - m_a +*/ +void make_carrot_amount_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_amount_t &amount_encryption_mask_out); +/** +* brief: encrypt_carrot_amount - encrypt an amount for an enote +* a_enc = a XOR m_a +* param: amount - a +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: a_enc +*/ +encrypted_amount_t encrypt_carrot_amount(const rct::xmr_amount amount, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** +* brief: decrypt_carrot_amount - decrypt an amount from an enote +* a = a_enc XOR m_a +* param: encrypted_amount - a_enc +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: a +*/ +rct::xmr_amount decrypt_carrot_amount(const encrypted_amount_t encrypted_amount, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** +* brief: make_carrot_payment_id_encryption_mask - create XOR encryption mask for enote's payment ID +* m_pid = H_8(s^ctx_sr, Ko) +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* outparam: payment_id_encryption_mask_out - m_pid +*/ +void make_carrot_payment_id_encryption_mask(const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address, + encrypted_payment_id_t &payment_id_encryption_mask_out); +/** +* brief: encrypt_legacy_payment_id - encrypt a payment ID from an enote +* pid_enc = pid XOR m_pid +* param: payment_id - pid +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: pid_enc +*/ +encrypted_payment_id_t encrypt_legacy_payment_id(const payment_id_t payment_id, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** +* brief: decrypt_legacy_payment_id - decrypt a payment ID from an enote +* pid = pid_enc XOR m_pid +* param: encrypted_payment_id - pid_enc +* param: s_sender_receiver - s^ctx_sr +* param: onetime_address - Ko +* return: pid +*/ +payment_id_t decrypt_legacy_payment_id(const encrypted_payment_id_t encrypted_payment_id, + const crypto::hash &s_sender_receiver, + const crypto::public_key &onetime_address); +/** + * brief: make_carrot_janus_anchor_special - make a janus anchor for "special" enotes + * anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) + * param: enote_ephemeral_pubkey - D_e + * param: input_context - + * param: onetime_address - Ko + * param: k_view - k_v + * param: account_spend_pubkey - K_s + * outparam: anchor_special_out - anchor_sp + */ +void make_carrot_janus_anchor_special(const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const crypto::public_key &onetime_address, + const crypto::secret_key &k_view, + const crypto::public_key &account_spend_pubkey, + janus_anchor_t &anchor_special_out); +/** +* brief: recover_address_spend_pubkey - get the receiver's spend key for which this RingCT onetime address +* can be reconstructed as 'owned' by +* K^j_s = Ko - K^o_ext = Ko - (k^o_g G + k^o_t U) +* param: onetime_address - Ko +* param: s_sender_receiver - s^ctx_sr +* param: amount_commitment - C_a +* outparam: address_spend_key_out: - K^j_s +*/ +void recover_address_spend_pubkey(const crypto::public_key &onetime_address, + const crypto::hash &s_sender_receiver, + const rct::key &amount_commitment, + crypto::public_key &address_spend_key_out); +/** +* brief: test_carrot_view_tag - test carrot view tag +* param: s_sender_receiver_unctx - s_sr +* param: input_context - +* param: onetime_address - Ko +* param: view_tag - vt +* return: true if successfully recomputed the view tag +*/ +bool test_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], + const input_context_t input_context, + const crypto::public_key &onetime_address, + const view_tag_t view_tag); +/** +* brief: try_recompute_carrot_amount_commitment - test recreating the amount commitment for given enote_type and amount +* param: s_sender_receiver - s^ctx_sr +* param: nominal_enote_type - enote_type' +* param: nominal_amount - a' +* param: amount_commitment - C_a +* outparam: amount_blinding_factor_out - k_a' = H_n(s^ctx_sr, enote_type') +* return: true if successfully recomputed the amount commitment (C_a ?= k_a' G + a' H) +*/ +bool try_recompute_carrot_amount_commitment(const crypto::hash &s_sender_receiver, + const CarrotEnoteType nominal_enote_type, + const rct::xmr_amount nominal_amount, + const rct::key &amount_commitment, + crypto::secret_key &amount_blinding_factor_out); +/** +* brief: try_get_amount - test decrypting the amount and recomputing the amount commitment +* param: s_sender_receiver - s^ctx_sr +* param: encrypted_amount - a_enc +* param: onetime_address - Ko +* param: amount_commitment - C_a +* outparam: enote_type_out - enote_type' +* outparam: amount_out - a' = a_enc XOR m_a +* outparam: amount_blinding_factor_out - k_a' = H_n(s^ctx_sr, enote_type') +* return: true if successfully recomputed the amount commitment (C_a ?= k_a' G + a' H) +*/ +bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, + const encrypted_amount_t &encrypted_amount, + const crypto::public_key &onetime_address, + const rct::key &amount_commitment, + CarrotEnoteType &enote_type_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out); +/** + * brief: verify_carrot_external_janus_protection - check normal external enote is Janus safe (i.e. can recompute D_e) + * param: nominal_anchor - anchor' + * param: input_context - + * param: nominal_address_spend_pubkey - K^j_s' + * param: nominal_address_view_pubkey - K^j_v' + * param: is_subaddress - + * param: nominal_payment_id - pid' + * param: enote_ephemeral_pubkey - D_e + * return: true if this normal external enote is safe from Janus attacks + */ +bool verify_carrot_external_janus_protection(const janus_anchor_t &nominal_anchor, + const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &nominal_address_view_pubkey, + const bool is_subaddress, + const payment_id_t nominal_payment_id, + const crypto::x25519_pubkey &enote_ephemeral_pubkey); +} //namespace carrot diff --git a/src/carrot_core/hash_functions.cpp b/src/carrot_core/hash_functions.cpp new file mode 100644 index 000000000..8eca353d1 --- /dev/null +++ b/src/carrot_core/hash_functions.cpp @@ -0,0 +1,109 @@ +// 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 "hash_functions.h" + +//local headers +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "crypto/blake2b.h" +#include "misc_log_ex.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +// H_x[k](data) +// - if derivation_key == nullptr, then the hash is NOT keyed +//------------------------------------------------------------------------------------------------------------------- +static void hash_base(const void *derivation_key, //32 bytes + const void *data, + const std::size_t data_length, + void *hash_out, + const std::size_t out_length) +{ + CHECK_AND_ASSERT_THROW_MES(blake2b(hash_out, + out_length, + data, + data_length, + derivation_key, + derivation_key ? 32 : 0) == 0, + "carrot hash base: blake2b failed."); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +void derive_bytes_3(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_3(x): 2-byte output + hash_base(key, data, data_length, hash_out, 3); +} +//------------------------------------------------------------------------------------------------------------------- +void derive_bytes_8(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_8(x): 8-byte output + hash_base(key, data, data_length, hash_out, 8); +} +//------------------------------------------------------------------------------------------------------------------- +void derive_bytes_16(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_16(x): 16-byte output + hash_base(key, data, data_length, hash_out, 16); +} +//------------------------------------------------------------------------------------------------------------------- +void derive_bytes_32(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_32(x): 32-byte output + hash_base(key, data, data_length, hash_out, 32); +} +//------------------------------------------------------------------------------------------------------------------- +void derive_bytes_64(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_64(x): 64-byte output + hash_base(key, data, data_length, hash_out, 64); +} +//------------------------------------------------------------------------------------------------------------------- +void derive_scalar(const void *data, const std::size_t data_length, const void *key, void *hash_out) +{ + // H_n(x): Ed25519 group scalar output (32 bytes) + // note: hash to 64 bytes then mod l + unsigned char temp[64]; + hash_base(key, data, data_length, temp, 64); + sc_reduce(temp); //mod l + memcpy(hash_out, temp, 32); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/hash_functions.h b/src/carrot_core/hash_functions.h new file mode 100644 index 000000000..cb6baf830 --- /dev/null +++ b/src/carrot_core/hash_functions.h @@ -0,0 +1,59 @@ +// 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. + +// Core hash functions for Seraphis (note: this implementation satisfies the Jamtis specification). + +#pragma once + +//local headers + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace carrot +{ + +/// H_3(x): 3-byte output +void derive_bytes_3(const void *data, const std::size_t data_length, const void *key, void *hash_out); +/// H_8(x): 8-byte output +void derive_bytes_8(const void *data, const std::size_t data_length, const void* key, void *hash_out); +/// H_16(x): 16-byte output +void derive_bytes_16(const void *data, const std::size_t data_length, const void *key, void *hash_out); +/// H_32(x): 32-byte output +void derive_bytes_32(const void *data, const std::size_t data_length, const void *key, void *hash_out); +/// H_64(x): 64-byte output +void derive_bytes_64(const void *data, const std::size_t data_length, const void *key, void *hash_out); +/// H_n(x): unclamped Curve25519/Ed25519 group scalar output (32 bytes) +void derive_scalar(const void *data, const std::size_t data_length, const void *key, void *hash_out); + +} //namespace carrot diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp new file mode 100644 index 000000000..558096681 --- /dev/null +++ b/src/carrot_core/output_set_finalization.cpp @@ -0,0 +1,262 @@ +// 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 "output_set_finalization.h" + +//local headers +#include "common/container_helpers.h" +#include "enote_utils.h" +#include "misc_log_ex.h" + +//third party headers + +//standard headers + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +std::optional get_additional_output_type(const size_t num_outgoing, + const size_t num_selfsend, + const bool remaining_change, + const bool have_payment_type_selfsend) +{ + const size_t num_outputs = num_outgoing + num_selfsend; + const bool already_completed = num_outputs >= 2 && num_selfsend >= 1 && !remaining_change; + if (num_outputs == 0) + { + ASSERT_MES_AND_THROW("get additional output type: set contains 0 outputs"); + } + else if (already_completed) + { + return std::nullopt; + } + else if (num_outputs == 1) + { + if (num_selfsend == 0) + { + return AdditionalOutputType::CHANGE_SHARED; + } + else if (!remaining_change) + { + return AdditionalOutputType::DUMMY; + } + else // num_selfsend == 1 && remaining_change + { + if (have_payment_type_selfsend) + { + return AdditionalOutputType::CHANGE_SHARED; + } + else + { + return AdditionalOutputType::PAYMENT_SHARED; + } + } + } + else if (num_outputs < CARROT_MAX_TX_OUTPUTS) + { + return AdditionalOutputType::CHANGE_UNIQUE; + } + else // num_outputs >= CARROT_MAX_TX_OUTPUTS + { + ASSERT_MES_AND_THROW("get additional output type: " + "set needs finalization but already contains too many outputs"); + } +} +//------------------------------------------------------------------------------------------------------------------- +tools::optional_variant get_additional_output_proposal( + const size_t num_outgoing, + const size_t num_selfsend, + const rct::xmr_amount remaining_change, + const bool have_payment_type_selfsend, + const crypto::public_key &change_address_spend_pubkey, + const crypto::x25519_pubkey &other_enote_ephemeral_pubkey) +{ + const std::optional additional_output_type = get_additional_output_type( + num_outgoing, + num_selfsend, + remaining_change, + have_payment_type_selfsend + ); + + if (!additional_output_type) + return {}; + + switch (*additional_output_type) + { + case AdditionalOutputType::PAYMENT_SHARED: + return CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = change_address_spend_pubkey, + .amount = remaining_change, + .enote_type = CarrotEnoteType::PAYMENT, + .enote_ephemeral_pubkey = other_enote_ephemeral_pubkey + }; + case AdditionalOutputType::CHANGE_SHARED: + return CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = change_address_spend_pubkey, + .amount = remaining_change, + .enote_type = CarrotEnoteType::CHANGE, + .enote_ephemeral_pubkey = other_enote_ephemeral_pubkey + }; + case AdditionalOutputType::CHANGE_UNIQUE: + return CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = change_address_spend_pubkey, + .amount = remaining_change, + .enote_type = CarrotEnoteType::CHANGE, + .enote_ephemeral_pubkey = crypto::x25519_pubkey_gen() + }; + case AdditionalOutputType::DUMMY: + return CarrotPaymentProposalV1{ + .destination = gen_carrot_main_address_v1(), + .amount = 0, + .randomness = gen_janus_anchor() + }; + } + + ASSERT_MES_AND_THROW("get additional output proposal: unrecognized additional output type"); +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_enote_proposals(std::vector &&normal_payment_proposals, + std::vector &&selfsend_payment_proposals, + const view_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::key_image &tx_first_key_image, + std::vector &output_enote_proposals_out, + encrypted_payment_id_t &encrypted_payment_id_out) +{ + output_enote_proposals_out.clear(); + encrypted_payment_id_out = null_payment_id; + + // assert payment proposals numbers + const size_t num_proposals = normal_payment_proposals.size() + selfsend_payment_proposals.size(); + CHECK_AND_ASSERT_THROW_MES(num_proposals >= CARROT_MIN_TX_OUTPUTS, + "get output enote proposals: too few payment proposals"); + CHECK_AND_ASSERT_THROW_MES(num_proposals <= CARROT_MAX_TX_OUTPUTS, + "get output enote proposals: too many payment proposals"); + CHECK_AND_ASSERT_THROW_MES(selfsend_payment_proposals.size(), + "get output enote proposals: no selfsend payment proposal"); + + // assert there is a max of 1 integrated address payment proposals + size_t num_integrated = 0; + for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals) + if (normal_payment_proposal.destination.payment_id != null_payment_id) + ++num_integrated; + CHECK_AND_ASSERT_THROW_MES(num_integrated <= 1, + "get output enote proposals: only one integrated address is allowed per tx output set"); + + // assert anchor_norm != 0 for payments + for (const CarrotPaymentProposalV1 &normal_payment_proposal : normal_payment_proposals) + CHECK_AND_ASSERT_THROW_MES(normal_payment_proposal.randomness != janus_anchor_t{}, + "get output enote proposals: normal payment proposal has unset anchor_norm AKA randomness"); + + // sort normal payment proposals by anchor_norm and assert uniqueness of randomness for each payment + const auto sort_by_randomness = [](const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1 &b) -> bool + { + return memcmp(&a.randomness, &b.randomness, JANUS_ANCHOR_BYTES) < 0; + }; + std::sort(normal_payment_proposals.begin(), normal_payment_proposals.end(), sort_by_randomness); + const bool has_unique_randomness = tools::is_sorted_and_unique(normal_payment_proposals, + sort_by_randomness); + CHECK_AND_ASSERT_THROW_MES(has_unique_randomness, + "get output enote proposals: normal payment proposals contain duplicate anchor_norm AKA randomness"); + + // input_context = "R" || KI_1 + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // construct normal enotes + output_enote_proposals_out.reserve(num_proposals); + for (size_t i = 0; i < normal_payment_proposals.size(); ++i) + { + encrypted_payment_id_t encrypted_payment_id; + get_output_proposal_normal_v1(normal_payment_proposals[i], + tx_first_key_image, + tools::add_element(output_enote_proposals_out), + encrypted_payment_id); + + // set pid to the first payment proposal or only integrated proposal + const bool is_first = i == 0; + const bool is_integrated = normal_payment_proposals[i].destination.payment_id != null_payment_id; + if (is_first || is_integrated) + encrypted_payment_id_out = encrypted_payment_id; + } + + // in the case that the pid target is ambiguous, set it to random bytes + const bool ambiguous_pid_destination = num_integrated == 0 && normal_payment_proposals.size() > 1; + if (ambiguous_pid_destination) + encrypted_payment_id_out = gen_payment_id(); + + // construct selfsend enotes, preferring internal enotes over special enotes when possible + for (const CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals) + { + if (s_view_balance_dev != nullptr) + { + get_output_proposal_internal_v1(selfsend_payment_proposal, + *s_view_balance_dev, + tx_first_key_image, + tools::add_element(output_enote_proposals_out)); + } + else if (k_view_dev != nullptr) + { + get_output_proposal_special_v1(selfsend_payment_proposal, + *k_view_dev, + account_spend_pubkey, + tx_first_key_image, + tools::add_element(output_enote_proposals_out)); + } + else // neither k_v nor s_vb device passed + { + ASSERT_MES_AND_THROW( + "get output enote proposals: neither a view-balance nor view-incoming device was provided"); + } + } + + // sort enotes by D_e and assert uniqueness properties of D_e + const auto sort_by_ephemeral_pubkey = [](const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b) -> bool + { + return memcmp(&a.enote.enote_ephemeral_pubkey, + &b.enote.enote_ephemeral_pubkey, + sizeof(crypto::x25519_pubkey)) < 0; + }; + std::sort(output_enote_proposals_out.begin(), output_enote_proposals_out.end(), sort_by_ephemeral_pubkey); + const bool has_unique_ephemeral_pubkeys = tools::is_sorted_and_unique(output_enote_proposals_out, + sort_by_ephemeral_pubkey); + CHECK_AND_ASSERT_THROW_MES(!(num_proposals == 2 && has_unique_ephemeral_pubkeys), + "get output enote proposals: a 2-out set needs to share an ephemeral pubkey, but this 2-out set doesn't"); + CHECK_AND_ASSERT_THROW_MES(!(num_proposals != 2 && !has_unique_ephemeral_pubkeys), + "get output enote proposals: this >2-out set contains duplicate enote ephemeral pubkeys"); + + // sort enotes by Ko + std::sort(output_enote_proposals_out.begin(), output_enote_proposals_out.end()); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/output_set_finalization.h b/src/carrot_core/output_set_finalization.h new file mode 100644 index 000000000..94b5ea8d0 --- /dev/null +++ b/src/carrot_core/output_set_finalization.h @@ -0,0 +1,113 @@ +// 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. + +//! @file Utilities for constructing output proposal sets that adhere to Carrot rules + +#pragma once + +//local headers +#include "carrot_enote_types.h" +#include "common/variant.h" +#include "config.h" +#include "payment_proposal.h" +#include "ringct/rctTypes.h" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace carrot +{ +enum class AdditionalOutputType +{ + PAYMENT_SHARED, // selfsend proposal with enote_type="payment" with a shared D_e + CHANGE_SHARED, // selfsend proposal with enote_type="change" with a shared D_e + CHANGE_UNIQUE, // selfsend proposal with enote_type="change" with a unique D_e + DUMMY // outgoing proposal to a random address +}; + +/** + * brief: get_additional_output_type - get the type of the additional enote needed to finalize an output set + * param: num_outgoing - number of outgoing transfers + * param: num_selfsend - number of selfsend transfers + * param: remaining_change - whether there is any leftover change needed to be included + * param: have_payment_type_selfsend - true if the enote set has a selfsend enote with enote_type="payment" + * return: AdditionalOutputType if need an additional enote, else std::nullopt + * throw: std::runtime_error if the output set is in a state where it cannot be finalized + */ +std::optional get_additional_output_type(const size_t num_outgoing, + const size_t num_selfsend, + const bool remaining_change, + const bool have_payment_type_selfsend); +/** + * brief: get_additional_output_proposal - get an additional output proposal to complete an output set + * param: num_outgoing - number of outgoing transfers + * param: num_selfsend - number of selfsend transfers + * param: remaining_change - the amount of leftover change needed to be included + * param: have_payment_type_selfsend - true if the enote set has a selfsend enote with enote_type="payment" + * param: change_address_spend_pubkey - K^j_s of our change address + * param: other_enote_ephemeral_pubkey - D^other_e + * return: an output proposal if need an additional enote, else none + * throw: std::runtime_error if the output set is in a state where it cannot be finalized + */ +tools::optional_variant get_additional_output_proposal( + const size_t num_outgoing, + const size_t num_selfsend, + const rct::xmr_amount remaining_change, + const bool have_payment_type_selfsend, + const crypto::public_key &change_address_spend_pubkey, + const crypto::x25519_pubkey &other_enote_ephemeral_pubkey); +/** + * brief: get_output_enote_proposals - convert a *finalized* set of payment proposals into output enote proposals + * param: normal_payment_proposals - + * param: selfsend_payment_proposals - + * param: s_view_balance_dev - pointer to view-balance device (OPTIONAL) + * param: k_view_dev - pointer to view-incoming device (OPTIONAL) + * param: account_spend_pubkey - K_s + * param: tx_first_key_image - KI_1 + * outparam: output_enote_proposals_out - + * outparam: encrypted_payment_id_out - pid_enc + * throw: std::runtime_error if the payment proposals do not represent a valid tx output set, or if no devices + * + * If s_view_balance_dev is not NULL, then the selfsend payments are converted into *internal* enotes. + * Otherwise, if k_view_dev is not NULL, then the selfsend payments are converted into *external* enotes. + */ +void get_output_enote_proposals(std::vector &&normal_payment_proposals, + std::vector &&selfsend_payment_proposals, + const view_balance_secret_device *s_view_balance_dev, + const view_incoming_key_device *k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::key_image &tx_first_key_image, + std::vector &output_enote_proposals_out, + encrypted_payment_id_t &encrypted_payment_id_out); + +} //namespace carrot diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp new file mode 100644 index 000000000..2d6f25bbf --- /dev/null +++ b/src/carrot_core/payment_proposal.cpp @@ -0,0 +1,447 @@ +// 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 "payment_proposal.h" + +//local headers +#include "int-util.h" +#include "enote_utils.h" +#include "misc_language.h" +#include "misc_log_ex.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "carrot" + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static const janus_anchor_t null_anchor{{0}}; +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +template +static auto auto_wiper(T &obj) +{ + static_assert(std::is_trivially_copyable()); + return epee::misc_utils::create_scope_leave_handler([&]{ memwipe(&obj, sizeof(T)); }); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static crypto::secret_key get_enote_ephemeral_privkey(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context) +{ + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + crypto::secret_key enote_ephemeral_privkey; + make_carrot_enote_ephemeral_privkey(proposal.randomness, + input_context, + proposal.destination.address_spend_pubkey, + proposal.destination.address_view_pubkey, + proposal.destination.payment_id, + enote_ephemeral_privkey); + + return enote_ephemeral_privkey; +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out, + crypto::x25519_pubkey &s_sender_receiver_unctx_out) +{ + // 1. d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal, input_context); + + // 2. make D_e + enote_ephemeral_pubkey_out = get_enote_ephemeral_pubkey(proposal, input_context); + + // 3. s_sr = 8 d_e ConvertPointE(K^j_v) + make_carrot_uncontextualized_shared_key_sender(enote_ephemeral_privkey, + proposal.destination.address_view_pubkey, + s_sender_receiver_unctx_out); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static void get_output_proposal_parts(const crypto::hash &s_sender_receiver, + const crypto::public_key &destination_spend_pubkey, + const payment_id_t payment_id, + const rct::xmr_amount amount, + const CarrotEnoteType enote_type, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const bool coinbase_amount_commitment, + crypto::secret_key &amount_blinding_factor_out, + rct::key &amount_commitment_out, + crypto::public_key &onetime_address_out, + encrypted_amount_t &encrypted_amount_out, + encrypted_payment_id_t &encrypted_payment_id_out) +{ + // 1. k_a = H_n(s^ctx_sr, enote_type) if !coinbase, else 1 + if (coinbase_amount_commitment) + amount_blinding_factor_out = rct::rct2sk(rct::I); + else + make_carrot_amount_blinding_factor(s_sender_receiver, + enote_type, + amount_blinding_factor_out); + + // 2. C_a = k_a G + a H + amount_commitment_out = rct::commit(amount, rct::sk2rct(amount_blinding_factor_out)); + + // 3. Ko = K^j_s + K^o_ext = K^j_s + (k^o_g G + k^o_t T) + make_carrot_onetime_address(destination_spend_pubkey, + s_sender_receiver, + amount_commitment_out, + onetime_address_out); + + // 4. a_enc = a XOR m_a + encrypted_amount_out = encrypt_carrot_amount(amount, + s_sender_receiver, + onetime_address_out); + + // 5. pid_enc = pid XOR m_pid + encrypted_payment_id_out = encrypt_legacy_payment_id(payment_id, s_sender_receiver, onetime_address_out); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static void get_external_output_proposal_parts(const crypto::x25519_pubkey &s_sender_receiver_unctx, + const crypto::public_key &destination_spend_pubkey, + const payment_id_t payment_id, + const rct::xmr_amount amount, + const CarrotEnoteType enote_type, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + const input_context_t &input_context, + const bool coinbase_amount_commitment, + crypto::hash &s_sender_receiver_out, + crypto::secret_key &amount_blinding_factor_out, + rct::key &amount_commitment_out, + crypto::public_key &onetime_address_out, + encrypted_amount_t &encrypted_amount_out, + encrypted_payment_id_t &encrypted_payment_id_out, + view_tag_t &view_tag_out) +{ + // 1. s^ctx_sr = H_32(s_sr, D_e, input_context) + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, + enote_ephemeral_pubkey, + input_context, + s_sender_receiver_out); + + // 2. get other parts: k_a, C_a, Ko, a_enc, pid_enc + get_output_proposal_parts(s_sender_receiver_out, + destination_spend_pubkey, + payment_id, + amount, + enote_type, + enote_ephemeral_pubkey, + input_context, + coinbase_amount_commitment, + amount_blinding_factor_out, + amount_commitment_out, + onetime_address_out, + encrypted_amount_out, + encrypted_payment_id_out); + + // 3. vt = H_3(s_sr || input_context || Ko) + make_carrot_view_tag(s_sender_receiver_unctx.data, input_context, onetime_address_out, view_tag_out); +} +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1 &b) +{ + return a.destination == b.destination && + a.amount == b.amount && + a.randomness == b.randomness; +} +//------------------------------------------------------------------------------------------------------------------- +bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentProposalSelfSendV1 &b) +{ + return a.destination_address_spend_pubkey == b.destination_address_spend_pubkey && + a.amount == b.amount && + a.enote_type == b.enote_type && + a.enote_ephemeral_pubkey == b.enote_ephemeral_pubkey; +} +//------------------------------------------------------------------------------------------------------------------- +bool operator<(const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b) +{ + return memcmp(&a.enote.onetime_address, &b.enote.onetime_address, sizeof(crypto::public_key)) < 0; +} +//------------------------------------------------------------------------------------------------------------------- +crypto::x25519_pubkey get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context) +{ + // d_e = H_n(anchor_norm, input_context, K^j_s, K^j_v, pid)) + const crypto::secret_key enote_ephemeral_privkey{get_enote_ephemeral_privkey(proposal, input_context)}; + + crypto::x25519_pubkey enote_ephemeral_pubkey; + if (proposal.destination.is_subaddress) + // D_e = d_e ConvertPointE(K^j_s) + make_carrot_enote_ephemeral_pubkey_subaddress(enote_ephemeral_privkey, + proposal.destination.address_spend_pubkey, + enote_ephemeral_pubkey); + else + // D_e = d_e B + make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey, + enote_ephemeral_pubkey); + + return enote_ephemeral_pubkey; +} +//------------------------------------------------------------------------------------------------------------------- +void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, + const std::uint64_t block_index, + CarrotCoinbaseEnoteV1 &output_enote_out) +{ + // 1. sanity checks + CHECK_AND_ASSERT_THROW_MES(proposal.randomness != null_anchor, + "get coinbase output proposal v1: invalid randomness for janus anchor (zero)."); + CHECK_AND_ASSERT_THROW_MES(!proposal.destination.is_subaddress, + "get coinbase output proposal v1: subaddresses aren't allowed as destinations of coinbase outputs"); + CHECK_AND_ASSERT_THROW_MES(proposal.destination.payment_id == null_payment_id, + "get coinbase output proposal v1: integrated addresses aren't allowed as destinations of coinbase outputs"); + + // 2. coinbase input context + input_context_t input_context; + make_carrot_input_context_coinbase(block_index, input_context); + + // 3. make D_e and do external ECDH + crypto::x25519_pubkey s_sender_receiver_unctx; auto dhe_wiper = auto_wiper(s_sender_receiver_unctx); + get_normal_proposal_ecdh_parts(proposal, + input_context, + output_enote_out.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + // 4. build the output enote address pieces + crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver); + crypto::secret_key dummy_amount_blinding_factor; + rct::key dummy_amount_commitment; + encrypted_amount_t dummy_encrypted_amount; + encrypted_payment_id_t dummy_encrypted_payment_id; + get_external_output_proposal_parts(s_sender_receiver_unctx, + proposal.destination.address_spend_pubkey, + null_payment_id, + proposal.amount, + CarrotEnoteType::PAYMENT, + output_enote_out.enote_ephemeral_pubkey, + input_context, + true, // coinbase_amount_commitment + s_sender_receiver, + dummy_amount_blinding_factor, + dummy_amount_commitment, + output_enote_out.onetime_address, + dummy_encrypted_amount, + dummy_encrypted_payment_id, + output_enote_out.view_tag); + + // 5. anchor_enc = anchor XOR m_anchor + output_enote_out.anchor_enc = encrypt_carrot_anchor(proposal.randomness, + s_sender_receiver, + output_enote_out.onetime_address); + + // 6. save the amount and block index + output_enote_out.amount = proposal.amount; + output_enote_out.block_index = block_index; +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out, + encrypted_payment_id_t &encrypted_payment_id_out) +{ + // 1. sanity checks + CHECK_AND_ASSERT_THROW_MES(proposal.randomness != null_anchor, + "jamtis payment proposal: invalid randomness for janus anchor (zero)."); + + // 2. input context: input_context = "R" || KI_1 + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // 3. make D_e and do external ECDH + crypto::x25519_pubkey s_sender_receiver_unctx; auto dhe_wiper = auto_wiper(s_sender_receiver_unctx); + get_normal_proposal_ecdh_parts(proposal, + input_context, + output_enote_out.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + // 4. build the output enote address pieces + crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver); + get_external_output_proposal_parts(s_sender_receiver_unctx, + proposal.destination.address_spend_pubkey, + proposal.destination.payment_id, + proposal.amount, + CarrotEnoteType::PAYMENT, + output_enote_out.enote.enote_ephemeral_pubkey, + input_context, + false, // coinbase_amount_commitment + s_sender_receiver, + output_enote_out.amount_blinding_factor, + output_enote_out.enote.amount_commitment, + output_enote_out.enote.onetime_address, + output_enote_out.enote.amount_enc, + encrypted_payment_id_out, + output_enote_out.enote.view_tag); + + // 5. anchor_enc = anchor XOR m_anchor + output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(proposal.randomness, + s_sender_receiver, + output_enote_out.enote.onetime_address); + + // 6. save the amount and first key image + output_enote_out.amount = proposal.amount; + output_enote_out.enote.tx_first_key_image = tx_first_key_image; +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out) +{ + // 1. sanity checks + // @TODO + + // 2. input context: input_context = "R" || KI_1 + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // 3. s_sr = 8 * k_v * D_e + crypto::x25519_pubkey s_sender_receiver_unctx; + CHECK_AND_ASSERT_THROW_MES(k_view_dev.view_key_8_scalar_mult_x25519(proposal.enote_ephemeral_pubkey, + s_sender_receiver_unctx), + "get output proposal special v1: HW device failed to perform ECDH with ephemeral pubkey"); + + // 4. build the output enote address pieces + crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver); + encrypted_payment_id_t dummy_encrypted_payment_id; + get_external_output_proposal_parts(s_sender_receiver_unctx, + proposal.destination_address_spend_pubkey, + null_payment_id, + proposal.amount, + proposal.enote_type, + proposal.enote_ephemeral_pubkey, + input_context, + false, // coinbase_amount_commitment + s_sender_receiver, + output_enote_out.amount_blinding_factor, + output_enote_out.enote.amount_commitment, + output_enote_out.enote.onetime_address, + output_enote_out.enote.amount_enc, + dummy_encrypted_payment_id, + output_enote_out.enote.view_tag); + + // 5. make special janus anchor: anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) + janus_anchor_t janus_anchor_special; + k_view_dev.make_janus_anchor_special(proposal.enote_ephemeral_pubkey, + input_context, + output_enote_out.enote.onetime_address, + account_spend_pubkey, + janus_anchor_special); + + // 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor + output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(janus_anchor_special, + s_sender_receiver, + output_enote_out.enote.onetime_address); + + // 7. save the enote ephemeral pubkey, first tx key image, and amount + output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; + output_enote_out.enote.tx_first_key_image = tx_first_key_image; + output_enote_out.amount = proposal.amount; +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const view_balance_secret_device &s_view_balance_dev, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out) +{ + // 1. sanity checks + // @TODO + + // 2. input_context = "R" || KI_1 + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // 3. s^ctx_sr = H_32(s_vb, D_e, input_context) + crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver); + s_view_balance_dev.make_internal_sender_receiver_secret(proposal.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + // 4. build the output enote address pieces + encrypted_payment_id_t dummy_encrypted_payment_id; + get_output_proposal_parts(s_sender_receiver, + proposal.destination_address_spend_pubkey, + null_payment_id, + proposal.amount, + proposal.enote_type, + proposal.enote_ephemeral_pubkey, + input_context, + false, // coinbase_amount_commitment + output_enote_out.amount_blinding_factor, + output_enote_out.enote.amount_commitment, + output_enote_out.enote.onetime_address, + output_enote_out.enote.amount_enc, + dummy_encrypted_payment_id); + + // 5. vt = H_3(s_vb || input_context || Ko) + s_view_balance_dev.make_internal_view_tag(input_context, + output_enote_out.enote.onetime_address, + output_enote_out.enote.view_tag); + + // 4. generate random encrypted anchor + output_enote_out.enote.anchor_enc = gen_janus_anchor(); + + // 5. save the enote ephemeral pubkey, first tx key image, and amount + output_enote_out.enote.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; + output_enote_out.enote.tx_first_key_image = tx_first_key_image; + output_enote_out.amount = proposal.amount; +} +//------------------------------------------------------------------------------------------------------------------- +CarrotPaymentProposalV1 gen_carrot_payment_proposal_v1(const bool is_subaddress, + const bool has_payment_id, + const rct::xmr_amount amount, + const std::size_t num_random_memo_elements) +{ + CarrotPaymentProposalV1 temp; + + if (is_subaddress) + temp.destination = gen_carrot_subaddress_v1(); + else if (has_payment_id) + temp.destination = gen_carrot_integrated_address_v1(); + else + temp.destination = gen_carrot_main_address_v1(); + + temp.amount = amount; + temp.randomness = gen_janus_anchor(); + + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h new file mode 100644 index 000000000..51fcb850b --- /dev/null +++ b/src/carrot_core/payment_proposal.h @@ -0,0 +1,165 @@ +// 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. + +// A 'payment proposal' is a proposal to make an enote sending funds to a Carrot address. +// Carrot: Cryptonote Address For Rerandomizable-RingCT-Output Transactions + +#pragma once + +//local headers +#include "carrot_enote_types.h" +#include "crypto/x25519.h" +#include "destination.h" +#include "device.h" +#include "ringct/rctTypes.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +//// +// CarrotPaymentProposalV1 +// - for creating an output proposal to send an amount to someone +/// +struct CarrotPaymentProposalV1 final +{ + /// user address + CarrotDestinationV1 destination; + /// b + rct::xmr_amount amount; + /// anchor_norm: secret 16-byte randomness for Janus anchor + janus_anchor_t randomness; +}; + +//// +// CarrotPaymentProposalSelfSendV1 +// - for creating an output proposal to send an change to yourself +/// +struct CarrotPaymentProposalSelfSendV1 final +{ + /// one of our own address spend pubkeys: K^j_s + crypto::public_key destination_address_spend_pubkey; + /// a + rct::xmr_amount amount; + + /// enote_type + CarrotEnoteType enote_type; + /// enote ephemeral pubkey: xr G + crypto::x25519_pubkey enote_ephemeral_pubkey; +}; + +struct RCTOutputEnoteProposal +{ + CarrotEnoteV1 enote; + + // we need this opening information to make amount range proofs + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; +}; + +/// equality operators +bool operator==(const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1 &b); +/// equality operators +bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentProposalSelfSendV1 &b); + +/// comparison operators +bool operator<(const RCTOutputEnoteProposal &a, const RCTOutputEnoteProposal &b); + +/** +* brief: get_enote_ephemeral_pubkey - get the proposal's enote ephemeral pubkey D_e +* param: proposal - +* param: input_context - +* return: D_e +*/ +crypto::x25519_pubkey get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context); +/** +* brief: get_coinbase_output_proposal_v1 - convert the carrot proposal to a coinbase output proposal +* param: proposal - +* param: block_index - index of the coinbase tx's block +* outparam: output_enote_out - +* outparam: partial_memo_out - +*/ +void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, + const std::uint64_t block_index, + CarrotCoinbaseEnoteV1 &output_enote_out); +/** +* brief: get_output_proposal_normal_v1 - convert the carrot proposal to an output proposal +* param: proposal - +* param: tx_first_key_image - +* outparam: output_enote_out - +* outparam: encrypted_payment_id_out - pid_enc +*/ +void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out, + encrypted_payment_id_t &encrypted_payment_id_out); +/** +* brief: get_output_proposal_v1 - convert the carrot proposal to an output proposal (external selfsend) +* param: proposal - +* param: k_view_dev - +* param: account_spend_pubkey - +* param: tx_first_key_image - +* outparam: output_enote_out - +*/ +void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const view_incoming_key_device &k_view_dev, + const crypto::public_key &account_spend_pubkey, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out); +/** +* brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) +* param: proposal - +* param: s_view_balance_dev - +* param: account_spend_pubkey - +* param: tx_first_key_image - +* outparam: output_enote_out - +* outparam: partial_memo_out - +*/ +void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const view_balance_secret_device &s_view_balance_dev, + const crypto::key_image &tx_first_key_image, + RCTOutputEnoteProposal &output_enote_out); +/** +* brief: gen_jamtis_payment_proposal_v1 - generate a random proposal +* param: is_subaddress - whether to generate a proposal to subaddress +* param: has_payment_id - true to generate non-zero payment ID, false for null payment ID +* param: amount - +* return: a random proposal +*/ +CarrotPaymentProposalV1 gen_carrot_payment_proposal_v1(const bool is_subaddress, + const bool has_payment_id, + const rct::xmr_amount amount); + +} //namespace carrot diff --git a/src/carrot_core/transcript_fixed.h b/src/carrot_core/transcript_fixed.h new file mode 100644 index 000000000..3179cbe23 --- /dev/null +++ b/src/carrot_core/transcript_fixed.h @@ -0,0 +1,185 @@ +// 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. + +// Transcript class for assembling data that needs to be hashed. + +#pragma once + +//local headers +#include "int-util.h" +#include "memwipe.h" + +//third party headers + +//standard headers +#include +#include +#include + +//forward declarations + + +namespace sp +{ +namespace detail +{ +template +constexpr size_t sizeof_sum() +{ + return (sizeof(Ts) + ...); +} + +template <> +constexpr size_t sizeof_sum<>() +{ + return 0; +} +} //namespace detail + +//// +// SpFixedTranscript +// - build a transcript of a fixed bytesize and input types, enforced at compile time +// - written to be the simplest correct transcript of data possible +// - requires domain separators at compile-time as well +// - ensures that no two transcripts with different domain separators will ever be equal +// - does not use dynamic allocation +// - unsigned integers are added to the transcript in little-endian form +// - signed integers are not allowed +// - domain separator is length-prefixed with a single unsigned byte at the beginning +// - passed domain separator can be null terminated or not, null bytes and after will be dropped +/// +template +class SpFixedTranscript final +{ +public: +//constructors + /// normal constructor + SpFixedTranscript(const Ts&... args) + { + // copy domain separator length prefix + m_transcript[0] = static_cast(domain_sep_size()); + + // copy domain separator + memcpy(m_transcript + 1, domain_sep, domain_sep_size()); + + // copy types into buffer + append<1 + domain_sep_size()>(args...); + } + +//overloaded operators + /// disable copy/move + SpFixedTranscript& operator=(const SpFixedTranscript&) = delete; + SpFixedTranscript& operator=(SpFixedTranscript&&) = delete; + +//member functions + constexpr const void* data() const noexcept { return m_transcript; } + + static constexpr std::size_t size() + { + return 1 + domain_sep_size() + detail::sizeof_sum(); + } + +//destructors + ~SpFixedTranscript() + { + // wipe the buffer on leave in case it contains sensitive data + memwipe(m_transcript, sizeof(m_transcript)); + } + +private: +//member functions + template + void append() {} + + template + void append(const U0 &arg0, const Us&... args) + { + // write current argument to buffer + write(arg0); + + // call append for next argument + static constexpr size_t new_offset = offset + sizeof(arg0); + append(args...); + } + + template + void write(const U &val) + { + static_assert(std::has_unique_object_representations_v); + static_assert(std::is_standard_layout_v); + static_assert(!std::is_signed_v || std::is_same_v); + static_assert(alignof(U) == 1); + static_assert(!std::is_pointer_v); + + memcpy(m_transcript + offset, &val, sizeof(val)); + } + + template + void write(std::uint16_t val) + { + val = SWAP16LE(val); + memcpy(m_transcript + offset, &val, sizeof(val)); + } + + template + void write(std::uint32_t val) + { + val = SWAP32LE(val); + memcpy(m_transcript + offset, &val, sizeof(val)); + } + + template + void write(std::uint64_t val) + { + val = SWAP64LE(val); + memcpy(m_transcript + offset, &val, sizeof(val)); + } + + static constexpr std::size_t domain_sep_size() + { + for (std::size_t i = 0; i < N; ++i) + if (domain_sep[i] == '\0') + return i; + + return N; + } + + static_assert(domain_sep_size() <= 255, "domain separator must be less than 256 characters long"); + +//member variables + /// the transcript buffer + unsigned char m_transcript[size()]; +}; + +template +auto make_fixed_transcript(const Ts&... args) +{ + return SpFixedTranscript(args...); +} + +} //namespace sp diff --git a/src/common/variant.h b/src/common/variant.h index a7e2637e5..7d8e61e15 100644 --- a/src/common/variant.h +++ b/src/common/variant.h @@ -74,15 +74,14 @@ struct variant_static_visitor : public boost::static_visitor }; template -class variant final +class variant { - using VType = boost::variant; + using VType = boost::variant; public: //constructors /// default constructor variant() = default; - variant(boost::none_t) : variant{} {} //act like boost::optional /// construct from variant type (use enable_if to avoid issues with copy/move constructor) template ::type = true> variant(T &&value) : m_value{std::forward(value)} {} -//overloaded operators - /// boolean operator: true if the variant isn't empty/uninitialized - explicit operator bool() const noexcept { return !this->is_empty(); } - //member functions - /// check if empty/uninitialized - bool is_empty() const noexcept { return m_value.which() == 0; } - /// check the variant type template bool is_type() const noexcept { return this->index() == this->type_index_of(); } @@ -136,7 +128,7 @@ public: template static constexpr int type_index_of() noexcept { - using types = boost::mpl::vector; + using types = typename VType::types; using elem = typename boost::mpl::find::type; using begin = typename boost::mpl::begin::type; return boost::mpl::distance::value; @@ -162,6 +154,41 @@ private: //member variables /// variant of all value types VType m_value; + +//friend functions +template +friend bool do_serialize(Archive &ar, variant &v); +}; + +template +class optional_variant: public variant +{ +public: +//constructors + /// default constructor + optional_variant() = default; + + /// construct from variant type (use enable_if to avoid issues with copy/move constructor) + template >, + optional_variant + >::value, + bool + >::type = true> + optional_variant(T &&value) : variant(std::forward(value)) {} + + // construct like boost::optional + optional_variant(boost::none_t) {} + +//overloaded operators + /// boolean operator: true if the variant isn't empty/uninitialized + explicit operator bool() const noexcept { return !this->is_empty(); } + +//member functions + /// check if empty/uninitialized + bool is_empty() const noexcept { return this->index() == 0; } }; } //namespace tools diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index b0006faba..8a7542c42 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -50,7 +50,8 @@ set(crypto_sources slow-hash.c rx-slow-hash.c CryptonightR_JIT.c - tree-hash.c) + tree-hash.c + x25519.cpp) if(ARCH_ID STREQUAL "i386" OR ARCH_ID STREQUAL "x86_64" OR ARCH_ID STREQUAL "x86-64" OR ARCH_ID STREQUAL "amd64") list(APPEND crypto_sources CryptonightR_template.S) @@ -71,11 +72,15 @@ monero_add_library(cncrypto target_link_libraries(cncrypto PUBLIC epee + mx25519_static randomx ${Boost_SYSTEM_LIBRARY} ${sodium_LIBRARIES} PRIVATE ${EXTRA_LIBRARIES}) +target_include_directories(cncrypto + PRIVATE + ${MX25519_INCLUDE}) if (ARM) option(NO_OPTIMIZED_MULTIPLY_ON_ARM diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 314fe448a..ee053b628 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -3829,6 +3829,199 @@ int sc_isnonzero(const unsigned char *s) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } +static void edwardsYZ_to_x25519(unsigned char *xbytes, const fe Y, const fe Z) { + // y = Y/Z + // x_mont = (1 + y) / (1 - y) + // = (1 + Y/Z) / (1 - Y/Z) + // = (Z + Y) / (Z - Y) + + fe tmp0; + fe tmp1; + fe_add(tmp0, Z, Y); // Z + Y + fe_sub(tmp1, Z, Y); // Z - Y + fe_invert(tmp1, tmp1); // 1/(Z - Y) + fe_mul(tmp0, tmp0, tmp1); // (Z + Y) / (Z - Y) + fe_tobytes(xbytes, tmp0); // tobytes((Z + Y) / (Z - Y)) +} + +void ge_p2_to_x25519(unsigned char *xbytes, const ge_p2 *h) +{ + edwardsYZ_to_x25519(xbytes, h->Y, h->Z); +} + +void ge_p3_to_x25519(unsigned char *xbytes, const ge_p3 *h) +{ + edwardsYZ_to_x25519(xbytes, h->Y, h->Z); +} + +int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *s) +{ + /* From fe_frombytes.c */ + + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + /* Validate the number to be canonical */ + if (h9 == 33554428 && h8 == 268435440 && h7 == 536870880 && h6 == 2147483520 && + h5 == 4294967295 && h4 == 67108860 && h3 == 134217720 && h2 == 536870880 && + h1 == 1073741760 && h0 >= 4294967277) { + return -1; + } + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + fe Y; + Y[0] = h0; + Y[1] = h1; + Y[2] = h2; + Y[3] = h3; + Y[4] = h4; + Y[5] = h5; + Y[6] = h6; + Y[7] = h7; + Y[8] = h8; + Y[9] = h9; + + fe Z; + fe_1(Z); + + edwardsYZ_to_x25519(xbytes, Y, Z); + + return 0; +} + +int ge_fromx25519_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe vxx; + fe check; + + /* From fe_frombytes.c */ + + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + /* Validate the number to be canonical */ + if (h9 == 33554428 && h8 == 268435440 && h7 == 536870880 && h6 == 2147483520 && + h5 == 4294967295 && h4 == 67108860 && h3 == 134217720 && h2 == 536870880 && + h1 == 1073741760 && h0 >= 4294967277) { + return -1; + } + + carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25; + carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25; + carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25; + carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25; + carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25; + + carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26; + carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26; + carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26; + carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26; + carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26; + + /* End fe_frombytes.c */ + + /* Start ge_fromx25519_vartime specific code */ + + check[0] = h0; + check[1] = h1; + check[2] = h2; + check[3] = h3; + check[4] = h4; + check[5] = h5; + check[6] = h6; + check[7] = h7; + check[8] = h8; + check[9] = h9; /* load s into `check`, where s is the X coordinate of the X25519 point */ + + fe_1(v); /* v = 1 */ + fe_copy(u, v); /* u = 1 */ + u[0] = -1; /* u = -1 */ + fe_add(u, u, check); /* u = s - 1 */ + fe_add(v, v, check); /* v = s + 1 */ + fe_invert(v, v); /* v = 1 / (s + 1) */ + fe_mul(h->Y, u, v); /* Y = (s - 1) / (s + 1) */ + + /* End ge_fromx25519_vartime specific code */ + + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, fe_d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + + fe_divpowm1(h->X, u, v); /* x = uv^3(uv^7)^((q-5)/8) */ + + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + if (fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + if (fe_isnonzero(check)) { + return -1; + } + fe_mul(h->X, h->X, fe_sqrtm1); + } + + if (fe_isnegative(h->X)) { + /* If x = 0, the sign must be positive */ + if (!fe_isnonzero(h->X)) { + return -1; + } + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { // https://eprint.iacr.org/2008/522 // X == T == 0 and Y/Z == 1 diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index c103f1f78..a9a52338d 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -156,6 +156,18 @@ void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, int sc_check(const unsigned char *); int sc_isnonzero(const unsigned char *); /* Doesn't normalize */ +/** + * brief: Convert Ed25519 y-coord to X25519 x-coord, AKA "ConvertPointE()" in the Carrot spec + */ +void ge_p2_to_x25519(unsigned char *xbytes, const ge_p2 *h); +void ge_p3_to_x25519(unsigned char *xbytes, const ge_p3 *h); +int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *s); + +/** + * brief: Convert X25519 x-coord to Ed25519 point with positive sign + */ +int ge_fromx25519_vartime(ge_p3 *h, const unsigned char *s); + // internal uint64_t load_3(const unsigned char *in); uint64_t load_4(const unsigned char *in); diff --git a/src/crypto/generators.cpp b/src/crypto/generators.cpp index 493d18334..44dc7299e 100644 --- a/src/crypto/generators.cpp +++ b/src/crypto/generators.cpp @@ -39,6 +39,7 @@ extern "C" #include #include #include +#include namespace crypto { @@ -68,14 +69,35 @@ constexpr public_key G = bytes_to({ 0x58, 0x66, 0x66, 0x66, 0x66, 0x //pedersen commitment generator H: toPoint(cn_fast_hash(G)) constexpr public_key H = bytes_to({ 0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea, 0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94 }); +//seraphis generator T: keccak_to_pt(keccak("Monero Generator T")) +constexpr public_key T = bytes_to({ 0x96, 0x6f, 0xc6, 0x6b, 0x82, 0xcd, 0x56, 0xcf, 0x85, 0xea, 0xec, 0x80, 0x1c, + 0x42, 0x84, 0x5f, 0x5f, 0x40, 0x88, 0x78, 0xd1, 0x56, 0x1e, 0x00, 0xd3, 0xd7, 0xde, 0xd2, 0x79, 0x4d, 0x09, 0x4f }); static ge_p3 G_p3; static ge_p3 H_p3; +static ge_p3 T_p3; static ge_cached G_cached; static ge_cached H_cached; +static ge_cached T_cached; // misc static std::once_flag init_gens_once_flag; +//------------------------------------------------------------------------------------------------------------------- +// hash-to-point: H_p(x) = 8*point_from_bytes(keccak(x)) +//------------------------------------------------------------------------------------------------------------------- +static void hash_to_point(const hash &x, crypto::ec_point &point_out) +{ + hash h; + ge_p3 temp_p3; + ge_p2 temp_p2; + ge_p1p1 temp_p1p1; + + cn_fast_hash(reinterpret_cast(&x), sizeof(hash), h); + ge_fromfe_frombytes_vartime(&temp_p2, reinterpret_cast(&h)); + ge_mul8(&temp_p1p1, &temp_p2); + ge_p1p1_to_p3(&temp_p3, &temp_p1p1); + ge_p3_tobytes(to_bytes(point_out), &temp_p3); +} //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- static public_key reproduce_generator_G() @@ -120,6 +142,18 @@ static public_key reproduce_generator_H() return reproduced_H; } //------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static public_key reproduce_generator_T() +{ + // U = H_p(keccak("Monero Generator T")) + const std::string_view T_seed{"Monero Generator T"}; + const hash T_temp_hash{cn_fast_hash(T_seed.data(), T_seed.size())}; + public_key reproduced_T; + hash_to_point(T_temp_hash, reproduced_T); + + return reproduced_T; +} +//------------------------------------------------------------------------------------------------------------------- // Make generators, but only once //------------------------------------------------------------------------------------------------------------------- static void init_gens() @@ -130,21 +164,26 @@ static void init_gens() // sanity check the generators static_assert(static_cast(G.data[0]) == 0x58, "compile-time constant sanity check"); static_assert(static_cast(H.data[0]) == 0x8b, "compile-time constant sanity check"); + static_assert(static_cast(T.data[0]) == 0x96, "compile-time constant sanity check"); // build ge_p3 representations of generators const int G_deserialize = ge_frombytes_vartime(&G_p3, to_bytes(G)); const int H_deserialize = ge_frombytes_vartime(&H_p3, to_bytes(H)); + const int T_deserialize = ge_frombytes_vartime(&T_p3, to_bytes(T)); (void)G_deserialize; assert(G_deserialize == 0); (void)H_deserialize; assert(H_deserialize == 0); + (void)T_deserialize; assert(T_deserialize == 0); // get cached versions ge_p3_to_cached(&G_cached, &G_p3); ge_p3_to_cached(&H_cached, &H_p3); + ge_p3_to_cached(&T_cached, &T_p3); // in debug mode, check that generators are reproducible (void)reproduce_generator_G; assert(reproduce_generator_G() == G); (void)reproduce_generator_H; assert(reproduce_generator_H() == H); + (void)reproduce_generator_T; assert(reproduce_generator_T() == T); }); } @@ -159,6 +198,11 @@ public_key get_H() return H; } //------------------------------------------------------------------------------------------------------------------- +public_key get_T() +{ + return T; +} +//------------------------------------------------------------------------------------------------------------------- ge_p3 get_G_p3() { init_gens(); @@ -171,6 +215,12 @@ ge_p3 get_H_p3() return H_p3; } //------------------------------------------------------------------------------------------------------------------- +ge_p3 get_T_p3() +{ + init_gens(); + return T_p3; +} +//------------------------------------------------------------------------------------------------------------------- ge_cached get_G_cached() { init_gens(); @@ -183,4 +233,10 @@ ge_cached get_H_cached() return H_cached; } //------------------------------------------------------------------------------------------------------------------- +ge_cached get_T_cached() +{ + init_gens(); + return T_cached; +} +//------------------------------------------------------------------------------------------------------------------- } //namespace crypto diff --git a/src/crypto/generators.h b/src/crypto/generators.h index c7d5e693e..5ab40848a 100644 --- a/src/crypto/generators.h +++ b/src/crypto/generators.h @@ -39,9 +39,12 @@ namespace crypto public_key get_G(); public_key get_H(); +public_key get_T(); ge_p3 get_G_p3(); ge_p3 get_H_p3(); +ge_p3 get_T_p3(); ge_cached get_G_cached(); ge_cached get_H_cached(); +ge_cached get_T_cached(); } //namespace crypto diff --git a/src/crypto/x25519.cpp b/src/crypto/x25519.cpp new file mode 100644 index 000000000..9fa59e0dd --- /dev/null +++ b/src/crypto/x25519.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2022, 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 "x25519.h" + +//local headers +#include "crypto/crypto.h" +extern "C" +{ +#include "mx25519.h" +} + +//third party headers + +//standard headers +#include + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "crypto" + +namespace crypto +{ + +/// File-scope data +static const x25519_scalar X25519_EIGHT{ mx25519_privkey{ .data = { 8 } } }; + +//------------------------------------------------------------------------------------------------------------------- +x25519_scalar x25519_eight() +{ + return X25519_EIGHT; +} +//------------------------------------------------------------------------------------------------------------------- +x25519_secret_key x25519_secret_key_gen() +{ + x25519_secret_key privkey; + do + { + crypto::rand(32, privkey.data); + privkey.data[0] &= 255 - 7; + privkey.data[31] &= 127; + } while (privkey == x25519_secret_key{}); + + return privkey; +} +//------------------------------------------------------------------------------------------------------------------- +x25519_pubkey x25519_pubkey_gen() +{ + const x25519_secret_key privkey{x25519_secret_key_gen()}; + x25519_pubkey pubkey; + x25519_scmul_base(privkey, pubkey); + + return pubkey; +} +//------------------------------------------------------------------------------------------------------------------- +bool x25519_scalar_is_canonical(const x25519_scalar &test_scalar) +{ + //todo: is this constant time? + return (test_scalar.data[0] & 7) == 0 && + (test_scalar.data[31] & 128) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +void x25519_scmul_base(const x25519_scalar &scalar, x25519_pubkey &result_out) +{ + static const mx25519_impl *impl{mx25519_select_impl(mx25519_type::MX25519_TYPE_AUTO)}; + mx25519_scmul_base(impl, &result_out, &scalar); +} +//------------------------------------------------------------------------------------------------------------------- +void x25519_scmul_key(const x25519_scalar &scalar, const x25519_pubkey &pubkey, x25519_pubkey &result_out) +{ + static const mx25519_impl *impl{mx25519_select_impl(mx25519_type::MX25519_TYPE_AUTO)}; + mx25519_scmul_key(impl, &result_out, &scalar, &pubkey); +} +//------------------------------------------------------------------------------------------------------------------- +void x25519_invmul_key(std::vector privkeys_to_invert, + const x25519_pubkey &initial_pubkey, + x25519_pubkey &result_out) +{ + // 1. (1/({privkey1 * privkey2 * ...})) + // note: mx25519_invkey() will error if the resulting X25519 scalar is >= 2^255, so we 'search' for a valid solution + x25519_secret_key inverted_xkey; + result_out = initial_pubkey; + + while (mx25519_invkey(&inverted_xkey, privkeys_to_invert.data(), privkeys_to_invert.size()) != 0) + { + privkeys_to_invert.emplace_back(X25519_EIGHT); //add 8 to keys to invert + x25519_scmul_key(X25519_EIGHT, result_out, result_out); //xK = 8 * xK + } + + // 2. (1/([8*8*...*8] * {privkey1 * privkey2 * ...})) * [8*8*...*8] * xK + x25519_scmul_key(inverted_xkey, result_out, result_out); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace crypto diff --git a/src/crypto/x25519.h b/src/crypto/x25519.h new file mode 100644 index 000000000..8edff8bf3 --- /dev/null +++ b/src/crypto/x25519.h @@ -0,0 +1,127 @@ +// Copyright (c) 2022, 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. + +// Interface for an x25519 implementation (mx25519). + +#pragma once + +//local headers +#include "crypto.h" +#include "generic-ops.h" +#include "memwipe.h" +#include "mlocker.h" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace crypto +{ + +extern "C" +{ +#include "mx25519.h" +} + +struct x25519_pubkey : public mx25519_pubkey +{ + x25519_pubkey() = default; + x25519_pubkey(const mx25519_pubkey &other) { *this = other; } + x25519_pubkey& operator=(const mx25519_pubkey &other) { memcpy(data, other.data, 32); return *this; } +}; +struct x25519_scalar : public mx25519_privkey +{ + x25519_scalar() = default; + x25519_scalar(const mx25519_privkey &other) { *this = other; } + x25519_scalar& operator=(const mx25519_privkey &other) { memcpy(data, other.data, 32); return *this; } +}; +struct x25519_secret_key : public epee::mlocked> +{ + x25519_secret_key() = default; + x25519_secret_key(const x25519_scalar &other) { *this = other; } + x25519_secret_key& operator=(const x25519_scalar &other) { memcpy(data, other.data, 32); return *this; } +}; + +/** +* brief: x25519_eight - scalar 8 +* return: scalar 8 +*/ +x25519_scalar x25519_eight(); +/** +* brief: x25519_secret_key_gen - generate a random x25519 privkey +* return: random canonical x25519 privkey +*/ +x25519_secret_key x25519_secret_key_gen(); +/** +* brief: x25519_pubkey_gen - generate a random x25519 pubkey +* return: random x25519 pubkey +*/ +x25519_pubkey x25519_pubkey_gen(); +/** +* brief: x25519_scalar_is_canonical - check that an X25519 scalar is canonical +* - expect: 2^255 > scalar >= 8 (i.e. last bit and first three bits not set) +* result: true if input scalar is canonical +*/ +bool x25519_scalar_is_canonical(const x25519_scalar &test_scalar); +/** +* brief: x25519_scmul_base - compute scalar * xG +* param: scalar - scalar to multiply +* result: scalar * xG +*/ +void x25519_scmul_base(const x25519_scalar &scalar, x25519_pubkey &result_out); +/** +* brief: x25519_scmul_key - compute scalar * pubkey +* param: scalar - scalar to multiply +* param: pubkey - public key to multiple against +* result: scalar * pubkey +*/ +void x25519_scmul_key(const x25519_scalar &scalar, const x25519_pubkey &pubkey, x25519_pubkey &result_out); +/** +* brief: x25519_invmul_key - compute (1/({privkey1 * privkey2 * ...})) * initial_pubkey +* param: privkeys_to_invert - {privkey1, privkey2, ...} +* param: initial_pubkey - base key for inversion +* result: (1/({privkey1 * privkey2 * ...})) * initial_pubkey +*/ +void x25519_invmul_key(std::vector privkeys_to_invert, + const x25519_pubkey &initial_pubkey, + x25519_pubkey &result_out); + +} //namespace crypto + +inline const unsigned char* to_bytes(const crypto::x25519_pubkey &point) { return &reinterpret_cast(point); } +inline const unsigned char* to_bytes(const crypto::x25519_scalar &scalar) { return &reinterpret_cast(scalar); } +inline const unsigned char* to_bytes(const crypto::x25519_secret_key &skey) { return &reinterpret_cast(skey); } + +/// upgrade x25519 keys +CRYPTO_MAKE_HASHABLE(x25519_pubkey) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(x25519_scalar) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(x25519_secret_key) diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 8659b0ed0..01385495b 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -38,6 +38,9 @@ set(unit_tests_sources bulletproofs.cpp bulletproofs_plus.cpp canonical_amounts.cpp + carrot_core.cpp + carrot_legacy.cpp + carrot_transcript_fixed.cpp chacha.cpp checkpoints.cpp command_line.cpp @@ -113,6 +116,7 @@ monero_add_minimal_executable(unit_tests target_link_libraries(unit_tests PRIVATE ringct + carrot_core cryptonote_protocol cryptonote_core daemon_messages diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp new file mode 100644 index 000000000..3dbc1b48c --- /dev/null +++ b/tests/unit_tests/carrot_core.cpp @@ -0,0 +1,1136 @@ +// 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. + +#include "gtest/gtest.h" + +#include "carrot_core/account_secrets.h" +#include "carrot_core/address_utils.h" +#include "carrot_core/carrot_enote_scan.h" +#include "carrot_core/device_ram_borrowed.h" +#include "carrot_core/enote_utils.h" +#include "carrot_core/output_set_finalization.h" +#include "carrot_core/payment_proposal.h" +#include "crypto/crypto.h" +#include "crypto/generators.h" +#include "ringct/rctOps.h" + +using namespace carrot; + +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +// https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L17 + static const crypto::x25519_pubkey x25519_small_order_points[7] = { + /* 0 (order 4) */ + {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}, + /* 1 (order 1) */ + {{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}, + /* 325606250916557431795983626356110631294008115727848805560023387167927233504 + (order 8) */ + {{ 0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, + 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, + 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00 }}, + /* 39382357235489614581723060781553021112529911719440698176882885853963445705823 + (order 8) */ + {{ 0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, + 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, + 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57 }}, + /* p-1 (order 2) */ + {{ 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f }}, + /* p (=0, order 4) */ + {{ 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f }}, + /* p+1 (=1, order 1) */ + {{ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f }} + }; +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +struct mock_carrot_keys +{ + crypto::secret_key s_master; + crypto::secret_key k_prove_spend; + crypto::secret_key s_view_balance; + crypto::secret_key k_generate_image; + crypto::secret_key k_view; + crypto::secret_key s_generate_address; + crypto::public_key account_spend_pubkey; + crypto::public_key account_view_pubkey; + crypto::public_key main_address_view_pubkey; + + view_incoming_key_ram_borrowed_device k_view_dev; + view_balance_secret_ram_borrowed_device s_view_balance_dev; + + mock_carrot_keys(): k_view_dev(k_view), s_view_balance_dev(s_view_balance) + {} + + static mock_carrot_keys generate() + { + mock_carrot_keys k; + crypto::generate_random_bytes_thread_safe(sizeof(crypto::secret_key), to_bytes(k.s_master)); + make_carrot_provespend_key(k.s_master, k.k_prove_spend); + make_carrot_viewbalance_secret(k.s_master, k.s_view_balance); + make_carrot_generateimage_key(k.s_view_balance, k.k_generate_image); + make_carrot_viewincoming_key(k.s_view_balance, k.k_view); + make_carrot_generateaddress_secret(k.s_view_balance, k.s_generate_address); + make_carrot_spend_pubkey(k.k_generate_image, k.k_prove_spend, k.account_spend_pubkey); + k.account_view_pubkey = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(k.account_spend_pubkey), + rct::sk2rct(k.k_view))); + k.main_address_view_pubkey = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k.k_view))); + return k; + } +}; +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool can_open_fcmp_onetime_address(const crypto::secret_key &k_prove_spend, + const crypto::secret_key &k_generate_image, + const crypto::secret_key &subaddr_scalar, + const crypto::secret_key &sender_extension_g, + const crypto::secret_key &sender_extension_t, + const crypto::public_key &onetime_address) +{ + // K_s = k_gi G + k_ps T + // K^j_s = k^j_subscal * K_s + // Ko = K^j_s + k^o_g G + k^o_t T + // = (k^o_g + k^j_subscal * k_gi) G + (k^o_t + k^j_subscal * k_ps) T + + // combined_g = k^o_g + k^j_subscal * k_gi + rct::key combined_g; + sc_muladd(combined_g.bytes, to_bytes(subaddr_scalar), to_bytes(k_generate_image), to_bytes(sender_extension_g)); + + // combined_t = k^o_t + k^j_subscal * k_ps + rct::key combined_t; + sc_muladd(combined_t.bytes, to_bytes(subaddr_scalar), to_bytes(k_prove_spend), to_bytes(sender_extension_t)); + + // Ko' = combined_g G + combined_t T + rct::key recomputed_onetime_address; + rct::addKeys2(recomputed_onetime_address, combined_g, combined_t, rct::pk2rct(crypto::get_T())); + + // Ko' ?= Ko + return recomputed_onetime_address == onetime_address; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +struct unittest_carrot_scan_result_t +{ + crypto::public_key address_spend_pubkey = rct::rct2pk(rct::I); + crypto::secret_key sender_extension_g = rct::rct2sk(rct::I); + crypto::secret_key sender_extension_t = rct::rct2sk(rct::I); + + rct::xmr_amount amount = 0; + crypto::secret_key amount_blinding_factor = rct::rct2sk(rct::I); + + CarrotEnoteType enote_type = CarrotEnoteType::PAYMENT; + + payment_id_t payment_id = null_payment_id; + + size_t output_index = 0; +}; +static void unittest_scan_enote_set(const std::vector &enotes, + const encrypted_payment_id_t encrypted_payment_id, + const mock_carrot_keys keys, + std::vector &res) +{ + res.clear(); + + // external scans + for (size_t output_index = 0; output_index < enotes.size(); ++output_index) + { + const CarrotEnoteV1 &enote = enotes.at(output_index); + + // s_sr = 8 k_v D_e + crypto::x25519_pubkey s_sr; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, enote.enote_ephemeral_pubkey, s_sr); + + unittest_carrot_scan_result_t scan_result{}; + const bool r = try_scan_carrot_enote_external(enote, + encrypted_payment_id, + s_sr, + keys.k_view_dev, + keys.account_spend_pubkey, + scan_result.sender_extension_g, + scan_result.sender_extension_t, + scan_result.address_spend_pubkey, + scan_result.amount, + scan_result.amount_blinding_factor, + scan_result.payment_id, + scan_result.enote_type); + + scan_result.output_index = output_index; + + if (r) + res.push_back(scan_result); + } + + // internal scans + for (size_t output_index = 0; output_index < enotes.size(); ++output_index) + { + const CarrotEnoteV1 &enote = enotes.at(output_index); + + unittest_carrot_scan_result_t scan_result{}; + const bool r = try_scan_carrot_enote_internal(enote, + keys.s_view_balance_dev, + scan_result.sender_extension_g, + scan_result.sender_extension_t, + scan_result.address_spend_pubkey, + scan_result.amount, + scan_result.amount_blinding_factor, + scan_result.enote_type); + + scan_result.output_index = output_index; + + if (r) + res.push_back(scan_result); + } +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, ECDH_cryptonote_completeness) +{ + crypto::secret_key k_view = rct::rct2sk(rct::skGen()); + crypto::public_key view_pubkey = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k_view))); + crypto::secret_key k_ephem = rct::rct2sk(rct::skGen()); + ASSERT_NE(k_view, k_ephem); + + crypto::x25519_pubkey enote_ephemeral_pubkey; + make_carrot_enote_ephemeral_pubkey_cryptonote(k_ephem, enote_ephemeral_pubkey); + + crypto::x25519_pubkey s_sr_sender; + ASSERT_TRUE(make_carrot_uncontextualized_shared_key_sender(k_ephem, view_pubkey, s_sr_sender)); + + crypto::x25519_pubkey s_sr_receiver; + ASSERT_TRUE(make_carrot_uncontextualized_shared_key_receiver(k_view, enote_ephemeral_pubkey, s_sr_receiver)); + + EXPECT_EQ(s_sr_sender, s_sr_receiver); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, ECDH_subaddress_completeness) +{ + crypto::secret_key k_view = rct::rct2sk(rct::skGen()); + crypto::public_key spend_pubkey = rct::rct2pk(rct::pkGen()); + crypto::public_key view_pubkey = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(spend_pubkey), rct::sk2rct(k_view))); + crypto::secret_key k_ephem = rct::rct2sk(rct::skGen()); + ASSERT_NE(k_view, k_ephem); + + crypto::x25519_pubkey enote_ephemeral_pubkey; + make_carrot_enote_ephemeral_pubkey_subaddress(k_ephem, spend_pubkey, enote_ephemeral_pubkey); + + crypto::x25519_pubkey s_sr_sender; + ASSERT_TRUE(make_carrot_uncontextualized_shared_key_sender(k_ephem, view_pubkey, s_sr_sender)); + + crypto::x25519_pubkey s_sr_receiver; + ASSERT_TRUE(make_carrot_uncontextualized_shared_key_receiver(k_view, enote_ephemeral_pubkey, s_sr_receiver)); + + EXPECT_EQ(s_sr_sender, s_sr_receiver); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, ECDH_mx25519_convergence) +{ + const crypto::x25519_pubkey P = crypto::x25519_pubkey_gen(); + const crypto::x25519_secret_key a = crypto::x25519_secret_key_gen(); + const crypto::x25519_secret_key eight = crypto::x25519_scalar{{8}}; + + // do Q = 8 * a * P using mx25519 + crypto::x25519_pubkey Q_mx25519; + crypto::x25519_scmul_key(eight, P, Q_mx25519); + crypto::x25519_scmul_key(a, Q_mx25519, Q_mx25519); + + // do Q = 8 * a * P using make_carrot_uncontextualized_shared_key_receiver() + crypto::secret_key a_sk; + memcpy(&a_sk, &a, sizeof(a_sk)); + crypto::x25519_pubkey Q_carrot; + ASSERT_TRUE(make_carrot_uncontextualized_shared_key_receiver(a_sk, P, Q_carrot)); + + // check equal + EXPECT_EQ(Q_mx25519, Q_carrot); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, ECDH_catch_small_order_points) +{ + const crypto::secret_key sk = rct::rct2sk(rct::skGen()); + + for (const crypto::x25519_pubkey &P : x25519_small_order_points) + { + crypto::x25519_pubkey Q; + EXPECT_FALSE(make_carrot_uncontextualized_shared_key_receiver(sk, P, Q)); + } +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main_address_normal_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + CarrotDestinationV1 main_address; + make_carrot_main_address_v1(keys.account_spend_pubkey, keys.main_address_view_pubkey, main_address); + + const CarrotPaymentProposalV1 proposal = CarrotPaymentProposalV1{ + .destination = main_address, + .amount = crypto::rand(), + .randomness = gen_janus_anchor() + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + encrypted_payment_id_t encrypted_payment_id; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote_proposal, + encrypted_payment_id); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote_proposal.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + encrypted_payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_payment_id, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(null_payment_id, recovered_payment_id); + EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type); + + // check spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + rct::rct2sk(rct::I), + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, subaddress_normal_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + const uint32_t j_major = crypto::rand(); + const uint32_t j_minor = crypto::rand(); + + CarrotDestinationV1 subaddress; + make_carrot_subaddress_v1(keys.account_spend_pubkey, + keys.account_view_pubkey, + keys.s_generate_address, + j_major, + j_minor, + subaddress); + + const CarrotPaymentProposalV1 proposal = CarrotPaymentProposalV1{ + .destination = subaddress, + .amount = crypto::rand(), + .randomness = gen_janus_anchor() + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + encrypted_payment_id_t encrypted_payment_id; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote_proposal, + encrypted_payment_id); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote_proposal.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + encrypted_payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_payment_id, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(null_payment_id, recovered_payment_id); + EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type); + + // check spendability + crypto::secret_key address_generator; + make_carrot_index_extension_generator(keys.s_generate_address, + j_major, + j_minor, + address_generator); + + crypto::secret_key subaddr_scalar; + make_carrot_subaddress_scalar(keys.account_spend_pubkey, + address_generator, + j_major, + j_minor, + subaddr_scalar); + + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + subaddr_scalar, + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, integrated_address_normal_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + CarrotDestinationV1 integrated_address; + make_carrot_integrated_address_v1(keys.account_spend_pubkey, + keys.main_address_view_pubkey, + gen_payment_id(), + integrated_address); + + const CarrotPaymentProposalV1 proposal = CarrotPaymentProposalV1{ + .destination = integrated_address, + .amount = crypto::rand(), + .randomness = gen_janus_anchor() + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + encrypted_payment_id_t encrypted_payment_id; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote_proposal, + encrypted_payment_id); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote_proposal.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + encrypted_payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_payment_id, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(integrated_address.payment_id, recovered_payment_id); + EXPECT_EQ(CarrotEnoteType::PAYMENT, recovered_enote_type); + + // check spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + rct::rct2sk(rct::I), + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main_address_special_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + CarrotDestinationV1 main_address; + make_carrot_main_address_v1(keys.account_spend_pubkey, keys.main_address_view_pubkey, main_address); + + // try once with PAYMENT, once with CHANGE + for (int i = 0; i < 2; ++i) + { + const CarrotEnoteType enote_type = i ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE; + + const CarrotPaymentProposalSelfSendV1 proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = keys.account_spend_pubkey, + .amount = crypto::rand(), + .enote_type = enote_type, + .enote_ephemeral_pubkey = crypto::x25519_pubkey_gen(), + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + get_output_proposal_special_v1(proposal, + keys.k_view_dev, + keys.account_spend_pubkey, + tx_first_key_image, + enote_proposal); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote_proposal.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + encrypted_payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + std::nullopt, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_payment_id, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(null_payment_id, recovered_payment_id); + EXPECT_EQ(enote_type, recovered_enote_type); + + // check spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + rct::rct2sk(rct::I), + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); + } +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, subaddress_special_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + const uint32_t j_major = crypto::rand(); + const uint32_t j_minor = crypto::rand(); + + CarrotDestinationV1 subaddress; + make_carrot_subaddress_v1(keys.account_spend_pubkey, + keys.account_view_pubkey, + keys.s_generate_address, + j_major, + j_minor, + subaddress); + + // try once with PAYMENT, once with CHANGE + for (int i = 0; i < 2; ++i) + { + const CarrotEnoteType enote_type = i ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE; + + const CarrotPaymentProposalSelfSendV1 proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = subaddress.address_spend_pubkey, + .amount = crypto::rand(), + .enote_type = enote_type, + .enote_ephemeral_pubkey = crypto::x25519_pubkey_gen(), + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + get_output_proposal_special_v1(proposal, + keys.k_view_dev, + keys.account_spend_pubkey, + tx_first_key_image, + enote_proposal); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote_proposal.enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + encrypted_payment_id_t recovered_payment_id; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, + std::nullopt, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_payment_id, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(null_payment_id, recovered_payment_id); + EXPECT_EQ(enote_type, recovered_enote_type); + + // check spendability + crypto::secret_key address_generator; + make_carrot_index_extension_generator(keys.s_generate_address, + j_major, + j_minor, + address_generator); + + crypto::secret_key subaddr_scalar; + make_carrot_subaddress_scalar(keys.account_spend_pubkey, + address_generator, + j_major, + j_minor, + subaddr_scalar); + + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + subaddr_scalar, + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); + } +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main_address_internal_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + CarrotDestinationV1 main_address; + make_carrot_main_address_v1(keys.account_spend_pubkey, keys.main_address_view_pubkey, main_address); + + // try once with PAYMENT, once with CHANGE + for (int i = 0; i < 2; ++i) + { + const CarrotEnoteType enote_type = i ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE; + + const CarrotPaymentProposalSelfSendV1 proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = main_address.address_spend_pubkey, + .amount = crypto::rand(), + .enote_type = enote_type, + .enote_ephemeral_pubkey = crypto::x25519_pubkey_gen() + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + get_output_proposal_internal_v1(proposal, + keys.s_view_balance_dev, + tx_first_key_image, + enote_proposal); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote, + keys.s_view_balance_dev, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(enote_type, recovered_enote_type); + + // check spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + rct::rct2sk(rct::I), + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); + } +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, subaddress_internal_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + const uint32_t j_major = crypto::rand(); + const uint32_t j_minor = crypto::rand(); + + CarrotDestinationV1 subaddress; + make_carrot_subaddress_v1(keys.account_spend_pubkey, + keys.account_view_pubkey, + keys.s_generate_address, + j_major, + j_minor, + subaddress); + + // try once with PAYMENT, once with CHANGE + for (int i = 0; i < 2; ++i) + { + const CarrotEnoteType enote_type = i ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE; + + const CarrotPaymentProposalSelfSendV1 proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = subaddress.address_spend_pubkey, + .amount = crypto::rand(), + .enote_type = enote_type, + .enote_ephemeral_pubkey = crypto::x25519_pubkey_gen() + }; + + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + + RCTOutputEnoteProposal enote_proposal; + get_output_proposal_internal_v1(proposal, + keys.s_view_balance_dev, + tx_first_key_image, + enote_proposal); + + ASSERT_EQ(proposal.amount, enote_proposal.amount); + const rct::key recomputed_amount_commitment = rct::commit(enote_proposal.amount, rct::sk2rct(enote_proposal.amount_blinding_factor)); + ASSERT_EQ(enote_proposal.enote.amount_commitment, recomputed_amount_commitment); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + rct::xmr_amount recovered_amount; + crypto::secret_key recovered_amount_blinding_factor; + CarrotEnoteType recovered_enote_type; + const bool scan_success = try_scan_carrot_enote_internal(enote_proposal.enote, + keys.s_view_balance_dev, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey, + recovered_amount, + recovered_amount_blinding_factor, + recovered_enote_type); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); + EXPECT_EQ(proposal.amount, recovered_amount); + EXPECT_EQ(enote_proposal.amount_blinding_factor, recovered_amount_blinding_factor); + EXPECT_EQ(enote_type, recovered_enote_type); + + // check spendability + crypto::secret_key address_generator; + make_carrot_index_extension_generator(keys.s_generate_address, + j_major, + j_minor, + address_generator); + + crypto::secret_key subaddr_scalar; + make_carrot_subaddress_scalar(keys.account_spend_pubkey, + address_generator, + j_major, + j_minor, + subaddr_scalar); + + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + subaddr_scalar, + recovered_sender_extension_g, + recovered_sender_extension_t, + enote_proposal.enote.onetime_address)); + } +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main_address_coinbase_scan_completeness) +{ + const mock_carrot_keys keys = mock_carrot_keys::generate(); + + CarrotDestinationV1 main_address; + make_carrot_main_address_v1(keys.account_spend_pubkey, keys.main_address_view_pubkey, main_address); + + const CarrotPaymentProposalV1 proposal = CarrotPaymentProposalV1{ + .destination = main_address, + .amount = crypto::rand(), + .randomness = gen_janus_anchor() + }; + + const uint64_t block_index = crypto::rand(); + + CarrotCoinbaseEnoteV1 enote; + get_coinbase_output_proposal_v1(proposal, + block_index, + enote); + + ASSERT_EQ(proposal.amount, enote.amount); + + crypto::x25519_pubkey s_sender_receiver_unctx; + make_carrot_uncontextualized_shared_key_receiver(keys.k_view, + enote.enote_ephemeral_pubkey, + s_sender_receiver_unctx); + + crypto::secret_key recovered_sender_extension_g; + crypto::secret_key recovered_sender_extension_t; + crypto::public_key recovered_address_spend_pubkey; + const bool scan_success = try_scan_carrot_coinbase_enote(enote, + s_sender_receiver_unctx, + keys.k_view_dev, + keys.account_spend_pubkey, + recovered_sender_extension_g, + recovered_sender_extension_t, + recovered_address_spend_pubkey); + + ASSERT_TRUE(scan_success); + + // check recovered data + EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); + + // check spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(keys.k_prove_spend, + keys.k_generate_image, + rct::rct2sk(rct::I), + recovered_sender_extension_g, + recovered_sender_extension_t, + enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +static void subtest_2out_transfer_get_output_enote_proposals_completeness(const bool alice_subaddress, + const bool bob_subaddress, + const bool bob_integrated, + const CarrotEnoteType alice_selfsend_type, + const bool alice_internal_selfsends) +{ + // generate alice keys and address + const mock_carrot_keys alice = mock_carrot_keys::generate(); + const uint32_t alice_j_major = crypto::rand(); + const uint32_t alice_j_minor = crypto::rand(); + CarrotDestinationV1 alice_address; + if (alice_subaddress) + { + make_carrot_subaddress_v1(alice.account_spend_pubkey, + alice.account_view_pubkey, + alice.s_generate_address, + alice_j_major, + alice_j_minor, + alice_address); + } + else + { + make_carrot_main_address_v1(alice.account_spend_pubkey, + alice.main_address_view_pubkey, + alice_address); + } + + // generate bob keys and address + const mock_carrot_keys bob = mock_carrot_keys::generate(); + const uint32_t bob_j_major = crypto::rand(); + const uint32_t bob_j_minor = crypto::rand(); + CarrotDestinationV1 bob_address; + if (bob_subaddress) + { + make_carrot_subaddress_v1(bob.account_spend_pubkey, + bob.account_view_pubkey, + bob.s_generate_address, + bob_j_major, + bob_j_minor, + bob_address); + } + else if (bob_integrated) + { + make_carrot_integrated_address_v1(bob.account_spend_pubkey, + bob.main_address_view_pubkey, + gen_payment_id(), + bob_address); + } + else + { + make_carrot_main_address_v1(bob.account_spend_pubkey, + bob.main_address_view_pubkey, + bob_address); + } + + // generate input context + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // outgoing payment proposal to bob + const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ + .destination = bob_address, + .amount = crypto::rand_idx(1000000), + .randomness = gen_janus_anchor() + }; + + // selfsend payment proposal to alice + const CarrotPaymentProposalSelfSendV1 alice_payment_proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = alice_address.address_spend_pubkey, + .amount = crypto::rand_idx(1000000), + .enote_type = CarrotEnoteType::CHANGE, + .enote_ephemeral_pubkey = get_enote_ephemeral_pubkey(bob_payment_proposal, input_context) + }; + + // turn payment proposals into enotes + std::vector enote_proposals; + encrypted_payment_id_t encrypted_payment_id; + get_output_enote_proposals({bob_payment_proposal}, + {alice_payment_proposal}, + alice_internal_selfsends ? &alice.s_view_balance_dev : nullptr, + &alice.k_view_dev, + alice.account_spend_pubkey, + tx_first_key_image, + enote_proposals, + encrypted_payment_id); + + ASSERT_EQ(2, enote_proposals.size()); // 2-out tx + + // collect enotes + std::vector enotes; + for (const RCTOutputEnoteProposal &enote_proposal : enote_proposals) + enotes.push_back(enote_proposal.enote); + + // check that alice scanned 1 enote + std::vector alice_scan_vec; + unittest_scan_enote_set(enotes, encrypted_payment_id, alice, alice_scan_vec); + ASSERT_EQ(1, alice_scan_vec.size()); + unittest_carrot_scan_result_t alice_scan = alice_scan_vec.front(); + + // check that bob scanned 1 enote + std::vector bob_scan_vec; + unittest_scan_enote_set(enotes, encrypted_payment_id, bob, bob_scan_vec); + ASSERT_EQ(1, bob_scan_vec.size()); + unittest_carrot_scan_result_t bob_scan = bob_scan_vec.front(); + + // set named references to enotes + ASSERT_TRUE((alice_scan.output_index == 0 && bob_scan.output_index == 1) || + (alice_scan.output_index == 1 && bob_scan.output_index == 0)); + const CarrotEnoteV1 &alice_enote = enotes.at(alice_scan.output_index); + const CarrotEnoteV1 &bob_enote = enotes.at(bob_scan.output_index); + + // check Alice's recovered data + EXPECT_EQ(alice_payment_proposal.destination_address_spend_pubkey, alice_scan.address_spend_pubkey); + EXPECT_EQ(alice_payment_proposal.amount, alice_scan.amount); + EXPECT_EQ(alice_enote.amount_commitment, rct::commit(alice_scan.amount, rct::sk2rct(alice_scan.amount_blinding_factor))); + EXPECT_EQ(null_payment_id, alice_scan.payment_id); + EXPECT_EQ(alice_payment_proposal.enote_type, alice_scan.enote_type); + + // check Bob's recovered data + EXPECT_EQ(bob_payment_proposal.destination.address_spend_pubkey, bob_scan.address_spend_pubkey); + EXPECT_EQ(bob_payment_proposal.amount, bob_scan.amount); + EXPECT_EQ(bob_enote.amount_commitment, rct::commit(bob_scan.amount, rct::sk2rct(bob_scan.amount_blinding_factor))); + EXPECT_EQ(bob_integrated ? bob_address.payment_id : null_payment_id, bob_scan.payment_id); + EXPECT_EQ(CarrotEnoteType::PAYMENT, bob_scan.enote_type); + + // check Alice spendability + crypto::secret_key alice_address_generator; + make_carrot_index_extension_generator(alice.s_generate_address, + alice_j_major, + alice_j_minor, + alice_address_generator); + + crypto::secret_key alice_subaddr_scalar; + make_carrot_subaddress_scalar(alice.account_spend_pubkey, + alice_address_generator, + alice_j_major, + alice_j_minor, + alice_subaddr_scalar); + + EXPECT_TRUE(can_open_fcmp_onetime_address(alice.k_prove_spend, + alice.k_generate_image, + alice_subaddress ? alice_subaddr_scalar : crypto::secret_key{{1}}, + alice_scan.sender_extension_g, + alice_scan.sender_extension_t, + alice_enote.onetime_address)); + + // check Bob spendability + crypto::secret_key bob_address_generator; + make_carrot_index_extension_generator(bob.s_generate_address, + bob_j_major, + bob_j_minor, + bob_address_generator); + + crypto::secret_key bob_subaddr_scalar; + make_carrot_subaddress_scalar(bob.account_spend_pubkey, + bob_address_generator, + bob_j_major, + bob_j_minor, + bob_subaddr_scalar); + + EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, + bob.k_generate_image, + bob_subaddress ? bob_subaddr_scalar : crypto::secret_key{{1}}, + bob_scan.sender_extension_g, + bob_scan.sender_extension_t, + bob_enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2main_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2sub_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2integ_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2main_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2sub_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2integ_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::PAYMENT, true); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::CHANGE, true); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_main2main_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_main2sub_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_main2integ_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_sub2main_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_sub2sub_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_external_ss_sub2integ_completeness) +{ + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::PAYMENT, false); + subtest_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::CHANGE, false); +} +//---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/carrot_legacy.cpp b/tests/unit_tests/carrot_legacy.cpp new file mode 100644 index 000000000..d08aebe8a --- /dev/null +++ b/tests/unit_tests/carrot_legacy.cpp @@ -0,0 +1,291 @@ +// 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. + +#include "gtest/gtest.h" + +#include "carrot_core/carrot_enote_scan.h" +#include "carrot_core/device_ram_borrowed.h" +#include "carrot_core/enote_utils.h" +#include "carrot_core/output_set_finalization.h" +#include "carrot_core/payment_proposal.h" +#include "crypto/crypto.h" +#include "crypto/generators.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/subaddress_index.h" +#include "device/device_default.hpp" +#include "ringct/rctOps.h" + +using namespace carrot; + +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +static bool can_open_fcmp_onetime_address_from_legacy_addr(const cryptonote::account_keys &keys, + const uint32_t j_major, + const uint32_t j_minor, + const crypto::secret_key &sender_extension_g, + const crypto::secret_key &sender_extension_t, + const crypto::public_key &onetime_address) +{ + // K_s = k_s G + // m = Hn(k_v || j_major || j_minor) if j else 0 + // K^j_s = K_s + m G + // Ko = K^j_s + k^o_g G + k^o_t T + // = (k^o_g + m + k_s) G + k^o_t T + + // m = Hn(k_v || j_major || j_minor) if j else 0 + crypto::secret_key subaddress_ext{}; + if (j_major || j_minor) + subaddress_ext = keys.get_device().get_subaddress_secret_key(keys.m_view_secret_key, {j_major, j_minor}); + + // combined_g = k^o_g + m + k_s + rct::key combined_g; + sc_add(combined_g.bytes, to_bytes(sender_extension_g), to_bytes(subaddress_ext)); + sc_add(combined_g.bytes, combined_g.bytes, to_bytes(keys.m_spend_secret_key)); + + // Ko' = combined_g G + k^o_t T + rct::key recomputed_onetime_address; + rct::addKeys2(recomputed_onetime_address, combined_g, rct::sk2rct(sender_extension_t), rct::pk2rct(crypto::get_T())); + + // Ko' ?= Ko + return recomputed_onetime_address == onetime_address; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +struct unittest_carrot_scan_result_t +{ + crypto::public_key address_spend_pubkey = rct::rct2pk(rct::I); + crypto::secret_key sender_extension_g = rct::rct2sk(rct::I); + crypto::secret_key sender_extension_t = rct::rct2sk(rct::I); + + rct::xmr_amount amount = 0; + crypto::secret_key amount_blinding_factor = rct::rct2sk(rct::I); + + CarrotEnoteType enote_type = CarrotEnoteType::PAYMENT; + + payment_id_t payment_id = null_payment_id; + + size_t output_index = 0; +}; +static void unittest_legacy_scan_enote_set(const std::vector &enotes, + const encrypted_payment_id_t encrypted_payment_id, + const cryptonote::account_base acb, + std::vector &res) +{ + res.clear(); + + // external scans + for (size_t output_index = 0; output_index < enotes.size(); ++output_index) + { + const CarrotEnoteV1 &enote = enotes.at(output_index); + + // s_sr = 8 k_v D_e + crypto::x25519_pubkey s_sr; + make_carrot_uncontextualized_shared_key_receiver(acb.get_keys().m_view_secret_key, + enote.enote_ephemeral_pubkey, + s_sr); + + unittest_carrot_scan_result_t scan_result{}; + const bool r = try_scan_carrot_enote_external(enote, + encrypted_payment_id, + s_sr, + view_incoming_key_ram_borrowed_device(acb.get_keys().m_view_secret_key), + acb.get_keys().m_account_address.m_spend_public_key, + scan_result.sender_extension_g, + scan_result.sender_extension_t, + scan_result.address_spend_pubkey, + scan_result.amount, + scan_result.amount_blinding_factor, + scan_result.payment_id, + scan_result.enote_type); + + scan_result.output_index = output_index; + + if (r) + res.push_back(scan_result); + } +} +//---------------------------------------------------------------------------------------------------------------------- +static void subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(const bool alice_subaddress, + const bool bob_subaddress, + const bool bob_integrated, + const CarrotEnoteType alice_selfsend_type) +{ + hw::device &hwdev = hw::get_device("default"); + + // generate alice keys and address + cryptonote::account_base alice; + alice.generate(); + const uint32_t alice_j_major = alice_subaddress ? crypto::rand() : 0; + const uint32_t alice_j_minor = alice_subaddress ? crypto::rand() : 0; + CarrotDestinationV1 alice_address{}; + cryptonote::account_public_address subaddr = hwdev.get_subaddress(alice.get_keys(), + {alice_j_major, alice_j_minor}); + alice_address.address_spend_pubkey = subaddr.m_spend_public_key; + alice_address.address_view_pubkey = subaddr.m_view_public_key; + alice_address.is_subaddress = alice_subaddress; + alice_address.payment_id = null_payment_id; + + // generate bob keys and address + cryptonote::account_base bob; + bob.generate(); + const uint32_t bob_j_major = bob_subaddress ? crypto::rand() : 0; + const uint32_t bob_j_minor = bob_subaddress ? crypto::rand() : 0; + CarrotDestinationV1 bob_address{}; + subaddr = hwdev.get_subaddress(bob.get_keys(), {bob_j_major, bob_j_minor}); + bob_address.address_spend_pubkey = subaddr.m_spend_public_key; + bob_address.address_view_pubkey = subaddr.m_view_public_key; + bob_address.is_subaddress = bob_subaddress; + bob_address.payment_id = bob_integrated ? gen_payment_id() : null_payment_id; + + // generate input context + const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + input_context_t input_context; + make_carrot_input_context(tx_first_key_image, input_context); + + // outgoing payment proposal to bob + const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ + .destination = bob_address, + .amount = crypto::rand_idx(1000000), + .randomness = gen_janus_anchor() + }; + + // selfsend payment proposal to alice + const CarrotPaymentProposalSelfSendV1 alice_payment_proposal = CarrotPaymentProposalSelfSendV1{ + .destination_address_spend_pubkey = alice_address.address_spend_pubkey, + .amount = crypto::rand_idx(1000000), + .enote_type = CarrotEnoteType::CHANGE, + .enote_ephemeral_pubkey = get_enote_ephemeral_pubkey(bob_payment_proposal, input_context) + }; + + // alice mem devices + view_incoming_key_ram_borrowed_device alive_k_v_dev(alice.get_keys().m_view_secret_key); + + // turn payment proposals into enotes + std::vector enote_proposals; + encrypted_payment_id_t encrypted_payment_id; + get_output_enote_proposals({bob_payment_proposal}, + {alice_payment_proposal}, + nullptr, + &alive_k_v_dev, + alice.get_keys().m_account_address.m_spend_public_key, + tx_first_key_image, + enote_proposals, + encrypted_payment_id); + + ASSERT_EQ(2, enote_proposals.size()); // 2-out tx + + // collect enotes + std::vector enotes; + for (const RCTOutputEnoteProposal &enote_proposal : enote_proposals) + enotes.push_back(enote_proposal.enote); + + // check that alice scanned 1 enote + std::vector alice_scan_vec; + unittest_legacy_scan_enote_set(enotes, encrypted_payment_id, alice, alice_scan_vec); + ASSERT_EQ(1, alice_scan_vec.size()); + unittest_carrot_scan_result_t alice_scan = alice_scan_vec.front(); + + // check that bob scanned 1 enote + std::vector bob_scan_vec; + unittest_legacy_scan_enote_set(enotes, encrypted_payment_id, bob, bob_scan_vec); + ASSERT_EQ(1, bob_scan_vec.size()); + unittest_carrot_scan_result_t bob_scan = bob_scan_vec.front(); + + // set named references to enotes + ASSERT_TRUE((alice_scan.output_index == 0 && bob_scan.output_index == 1) || + (alice_scan.output_index == 1 && bob_scan.output_index == 0)); + const CarrotEnoteV1 &alice_enote = enotes.at(alice_scan.output_index); + const CarrotEnoteV1 &bob_enote = enotes.at(bob_scan.output_index); + + // check Alice's recovered data + EXPECT_EQ(alice_payment_proposal.destination_address_spend_pubkey, alice_scan.address_spend_pubkey); + EXPECT_EQ(alice_payment_proposal.amount, alice_scan.amount); + EXPECT_EQ(alice_enote.amount_commitment, rct::commit(alice_scan.amount, rct::sk2rct(alice_scan.amount_blinding_factor))); + EXPECT_EQ(null_payment_id, alice_scan.payment_id); + EXPECT_EQ(alice_payment_proposal.enote_type, alice_scan.enote_type); + + // check Bob's recovered data + EXPECT_EQ(bob_payment_proposal.destination.address_spend_pubkey, bob_scan.address_spend_pubkey); + EXPECT_EQ(bob_payment_proposal.amount, bob_scan.amount); + EXPECT_EQ(bob_enote.amount_commitment, rct::commit(bob_scan.amount, rct::sk2rct(bob_scan.amount_blinding_factor))); + EXPECT_EQ(bob_integrated ? bob_address.payment_id : null_payment_id, bob_scan.payment_id); + EXPECT_EQ(CarrotEnoteType::PAYMENT, bob_scan.enote_type); + + // check Alice spendability + EXPECT_TRUE(can_open_fcmp_onetime_address_from_legacy_addr(alice.get_keys(), + alice_j_major, + alice_j_minor, + alice_scan.sender_extension_g, + alice_scan.sender_extension_t, + alice_enote.onetime_address)); + + // check Bob spendability + EXPECT_TRUE(can_open_fcmp_onetime_address_from_legacy_addr(bob.get_keys(), + bob_j_major, + bob_j_minor, + bob_scan.sender_extension_g, + bob_scan.sender_extension_t, + bob_enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_main2main_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, false, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_main2sub_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, true, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_main2integ_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(false, false, true, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_sub2main_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, false, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_sub2sub_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, true, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_legacy, legacy_get_enote_output_proposals_sub2integ_completeness) +{ + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::PAYMENT); + subtest_legacy_2out_transfer_get_output_enote_proposals_completeness(true, false, true, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/carrot_transcript_fixed.cpp b/tests/unit_tests/carrot_transcript_fixed.cpp new file mode 100644 index 000000000..860619a83 --- /dev/null +++ b/tests/unit_tests/carrot_transcript_fixed.cpp @@ -0,0 +1,60 @@ +// 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. + +#include "gtest/gtest.h" + +#include "carrot_core/config.h" +#include "carrot_core/core_types.h" +#include "carrot_core/transcript_fixed.h" +#include "crypto/crypto.h" + +#include + +TEST(carrot_transcript_fixed, sizeof_sum) +{ + EXPECT_EQ(0, sp::detail::sizeof_sum<>()); + EXPECT_EQ(1, sp::detail::sizeof_sum()); + EXPECT_EQ(12, (sp::detail::sizeof_sum())); +} + +TEST(carrot_transcript_fixed, ts_size) +{ + static constexpr const unsigned char DS1[] = "perspicacious"; + const auto transcript1 = sp::make_fixed_transcript((uint32_t)32); + EXPECT_EQ(1 + 13 + 4, transcript1.size()); + + static constexpr const unsigned char DS2[] = "recrudescence"; + const auto transcript2 = sp::make_fixed_transcript((uint32_t)32, (uint64_t)64); + EXPECT_EQ(1 + 13 + 4 + 8, transcript2.size()); + + // vt = H_3(s_sr || input_context || Ko) + const auto transcript_vt = sp::make_fixed_transcript( + carrot::input_context_t{}, + crypto::public_key{}); + EXPECT_EQ(1 + 15 + 33 + 32, transcript_vt.size()); +} diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp index 1c4841bb7..5affe69c1 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -36,6 +36,7 @@ extern "C" { #include "crypto/crypto-ops.h" } +#include "crypto/x25519.h" #include "crypto/generators.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/merge_mining.h" @@ -345,3 +346,104 @@ TEST(Crypto, generator_consistency) // ringct/rctTypes.h ASSERT_TRUE(memcmp(H.data, rct::H.bytes, 32) == 0); } + +TEST(Crypto, ConvertPointE_Base) +{ + const crypto::public_key G = crypto::get_G(); + const crypto::x25519_pubkey B_expected = {{9}}; + + crypto::x25519_pubkey B_actual; + edwards_bytes_to_x25519_vartime(B_actual.data, to_bytes(G)); + + EXPECT_EQ(B_expected, B_actual); +} + +TEST(Crypto, ConvertPointE_PreserveScalarMultBase) +{ + // *clamped* private key a + const crypto::x25519_secret_key a = crypto::x25519_secret_key_gen(); + rct::key a_key; + memcpy(&a_key, &a, sizeof(rct::key)); + + // P_ed = a G + const rct::key P_edward = rct::scalarmultBase(a_key); + + // P_mont = a B + crypto::x25519_pubkey P_mont; + crypto::x25519_scmul_base(a, P_mont); + + // P_mont' = ConvertPointE(P_ed) + crypto::x25519_pubkey P_mont_converted; + edwards_bytes_to_x25519_vartime(P_mont_converted.data, P_edward.bytes); + + // P_mont' ?= P_mont + EXPECT_EQ(P_mont_converted, P_mont); +} + +TEST(Crypto, ConvertPointE_PreserveScalarMultBase_gep3) +{ + // compared to ConvertPointE_PreserveScalarMultBase, this test will use Z != 1 (probably) + + // *clamped* private key a + const crypto::x25519_secret_key a = crypto::x25519_secret_key_gen(); + rct::key a_key; + memcpy(&a_key, &a, sizeof(rct::key)); + + // P_ed = a G + ge_p3 P_p3; + ge_scalarmult_base(&P_p3, a.data); + + // check that Z != 1, otherwise this test is a dup of ConvertPointE_PreserveScalarMultBase + const unsigned char one_bytes[32] = {1}; + unsigned char Z_bytes[32]; + fe_tobytes(Z_bytes, P_p3.Z); + ASSERT_TRUE(memcmp(Z_bytes, one_bytes, 32)); // check Z != 1 + + // P_mont = a B + crypto::x25519_pubkey P_mont; + crypto::x25519_scmul_base(a, P_mont); + + // P_mont' = ConvertPointE(P_ed) + crypto::x25519_pubkey P_mont_converted; + ge_p3_to_x25519(P_mont_converted.data, &P_p3); + + // P_mont' ?= P_mont + EXPECT_EQ(P_mont_converted, P_mont); +} + +TEST(Crypto, ConvertPointE_EraseSign) +{ + // generate a random point P and test that ConvertPointE(P) == ConvertPointE(-P) + + const rct::key P = rct::pkGen(); + rct::key negP; + rct::subKeys(negP, rct::I, P); + + crypto::x25519_pubkey P_mont; + edwards_bytes_to_x25519_vartime(P_mont.data, P.bytes); + + crypto::x25519_pubkey negP_mont; + edwards_bytes_to_x25519_vartime(negP_mont.data, negP.bytes); + + EXPECT_EQ(P_mont, negP_mont); +} + +TEST(Crypto, ge_fromx25519_vartime_Base) +{ + const crypto::x25519_pubkey B = {{9}}; + + crypto::public_key G_actual; + ge_p3 G_actual_p3; + ge_fromx25519_vartime(&G_actual_p3, B.data); + ge_p3_tobytes(to_bytes(G_actual), &G_actual_p3); + + EXPECT_EQ(crypto::get_G(), G_actual); +} + +TEST(Crypto, ge_fromx25519_vartime_RandomPointNominalSuccess) +{ + const crypto::x25519_pubkey P = crypto::x25519_pubkey_gen(); + + ge_p3 h; + EXPECT_EQ(0, ge_fromx25519_vartime(&h, P.data)); +} diff --git a/tests/unit_tests/variant.cpp b/tests/unit_tests/variant.cpp index 21e1f2309..d7b106e63 100644 --- a/tests/unit_tests/variant.cpp +++ b/tests/unit_tests/variant.cpp @@ -38,6 +38,7 @@ #include #include +using tools::optional_variant; using tools::variant; using tools::variant_static_visitor; @@ -239,7 +240,7 @@ struct test_stringify_visitor: public variant_static_visitor //------------------------------------------------------------------------------------------------------------------- TEST(variant, operatorbool) { - variant v; + optional_variant v; EXPECT_FALSE(v); v = (int16_t) 2023; EXPECT_TRUE(v); @@ -251,7 +252,7 @@ TEST(variant, operatorbool) //------------------------------------------------------------------------------------------------------------------- TEST(variant, is_empty) { - variant v; + optional_variant v; EXPECT_TRUE(v.is_empty()); v = (int16_t) 2023; EXPECT_FALSE(v.is_empty()); @@ -260,7 +261,7 @@ TEST(variant, is_empty) v = boost::blank{}; EXPECT_TRUE(v.is_empty()); - variant<> v2; + optional_variant<> v2; EXPECT_TRUE(v2.is_empty()); v2 = boost::blank{}; EXPECT_TRUE(v2.is_empty()); @@ -269,7 +270,7 @@ TEST(variant, is_empty) TEST(variant, is_type) { variant v; - EXPECT_TRUE(v.is_type()); + EXPECT_TRUE(v.is_type()); v = (int16_t) 2023; EXPECT_TRUE(v.is_type()); @@ -279,7 +280,7 @@ TEST(variant, is_type) TEST(variant, try_unwrap) { variant v; - EXPECT_FALSE(v.try_unwrap()); + EXPECT_TRUE(v.try_unwrap()); v = (int16_t) 5252; ASSERT_TRUE(v.try_unwrap()); EXPECT_EQ(5252, *v.try_unwrap()); @@ -290,7 +291,7 @@ TEST(variant, try_unwrap) TEST(variant, unwrap) { variant v; - EXPECT_THROW(v.unwrap(), std::runtime_error); + EXPECT_EQ(0, v.unwrap()); v = (int16_t) 5252; EXPECT_EQ(5252, v.unwrap()); EXPECT_THROW(v.unwrap(), std::runtime_error); @@ -321,35 +322,55 @@ TEST(variant, index) variant v; EXPECT_EQ(0, v.index()); v = (int8_t) 7; - EXPECT_EQ(1, v.index()); + EXPECT_EQ(0, v.index()); v = (uint8_t) 7; - EXPECT_EQ(2, v.index()); + EXPECT_EQ(1, v.index()); v = (int16_t) 7; - EXPECT_EQ(3, v.index()); + EXPECT_EQ(2, v.index()); v = (uint16_t) 7; - EXPECT_EQ(4, v.index()); + EXPECT_EQ(3, v.index()); v = "verifiable variant vying for vengence versus visa"; - EXPECT_EQ(5, v.index()); + EXPECT_EQ(4, v.index()); + + optional_variant vo; + EXPECT_EQ(0, vo.index()); + vo = (int8_t) 7; + EXPECT_EQ(1, vo.index()); + vo = (uint8_t) 7; + EXPECT_EQ(2, vo.index()); + vo = (int16_t) 7; + EXPECT_EQ(3, vo.index()); + vo = (uint16_t) 7; + EXPECT_EQ(4, vo.index()); + vo = "verifiable variant vying for vengence versus visa"; + EXPECT_EQ(5, vo.index()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, type_index_of) { variant v; - EXPECT_EQ(0, decltype(v)::type_index_of()); - EXPECT_EQ(1, decltype(v)::type_index_of()); - EXPECT_EQ(2, decltype(v)::type_index_of()); - EXPECT_EQ(3, decltype(v)::type_index_of()); - EXPECT_EQ(4, decltype(v)::type_index_of()); - EXPECT_EQ(5, decltype(v)::type_index_of()); + EXPECT_EQ(0, decltype(v)::type_index_of()); + EXPECT_EQ(1, decltype(v)::type_index_of()); + EXPECT_EQ(2, decltype(v)::type_index_of()); + EXPECT_EQ(3, decltype(v)::type_index_of()); + EXPECT_EQ(4, decltype(v)::type_index_of()); + + optional_variant vo; + EXPECT_EQ(0, decltype(vo)::type_index_of()); + EXPECT_EQ(1, decltype(vo)::type_index_of()); + EXPECT_EQ(2, decltype(vo)::type_index_of()); + EXPECT_EQ(3, decltype(vo)::type_index_of()); + EXPECT_EQ(4, decltype(vo)::type_index_of()); + EXPECT_EQ(5, decltype(vo)::type_index_of()); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, constexpr_type_index_of) { variant v; - constexpr int TINDEX0 = decltype(v)::type_index_of(); - EXPECT_EQ(0, TINDEX0); - constexpr int TINDEX5 = decltype(v)::type_index_of(); - EXPECT_EQ(5, TINDEX5); + constexpr int TINDEX2 = decltype(v)::type_index_of(); + EXPECT_EQ(2, TINDEX2); + constexpr int TINDEX4 = decltype(v)::type_index_of(); + EXPECT_EQ(4, TINDEX4); } //------------------------------------------------------------------------------------------------------------------- TEST(variant, same_type) @@ -362,7 +383,6 @@ TEST(variant, same_type) TEST(variant, visit) { variant v; - EXPECT_THROW(v.visit(test_stringify_visitor()), std::runtime_error); v = "Rev"; test_stringify_visitor::test_visitation(v, std::string("Rev")); @@ -377,7 +397,7 @@ TEST(variant, ad_hoc_recursion) struct left_t; struct right_t; - using twisty = variant, boost::recursive_wrapper>; + using twisty = optional_variant, boost::recursive_wrapper>; struct left_t {