From c3e2ebd3ebafb2b06eccb9d48b7c8035b70f661e Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Fri, 8 Nov 2024 14:33:54 -0600 Subject: [PATCH 01/14] carrot_core draft Nov 8 --- .gitmodules | 3 + CMakeLists.txt | 3 +- external/CMakeLists.txt | 1 + external/mx25519 | 1 + src/CMakeLists.txt | 1 + src/carrot_core/CMakeLists.txt | 58 ++ src/carrot_core/account_secrets.cpp | 102 +++ src/carrot_core/account_secrets.h | 103 +++ src/carrot_core/address_utils.cpp | 101 +++ src/carrot_core/address_utils.h | 113 +++ src/carrot_core/carrot_enote_scan.cpp | 262 +++++++ src/carrot_core/carrot_enote_scan.h | 76 ++ src/carrot_core/carrot_enote_types.h | 106 +++ src/carrot_core/config.h | 69 ++ src/carrot_core/core_types.cpp | 121 +++ src/carrot_core/core_types.h | 130 ++++ src/carrot_core/destination.cpp | 149 ++++ src/carrot_core/destination.h | 116 +++ src/carrot_core/enote_record_types.h | 185 +++++ src/carrot_core/enote_utils.cpp | 581 ++++++++++++++ src/carrot_core/enote_utils.h | 440 +++++++++++ src/carrot_core/hash_functions.cpp | 109 +++ src/carrot_core/hash_functions.h | 59 ++ src/carrot_core/payment_proposal.cpp | 408 ++++++++++ src/carrot_core/payment_proposal.h | 165 ++++ src/carrot_core/transcript_fixed.h | 183 +++++ src/carrot_impl/enote_store.h | 290 +++++++ src/carrot_impl/legacy_enote_types.cpp | 88 +++ src/carrot_impl/legacy_enote_types.h | 172 +++++ src/common/variant.h | 49 +- src/crypto/CMakeLists.txt | 7 +- src/crypto/crypto-ops.c | 193 +++++ src/crypto/crypto-ops.h | 12 + src/crypto/generators.cpp | 56 ++ src/crypto/generators.h | 3 + src/crypto/x25519.cpp | 119 +++ src/crypto/x25519.h | 127 +++ tests/unit_tests/CMakeLists.txt | 3 + tests/unit_tests/carrot_core.cpp | 767 +++++++++++++++++++ tests/unit_tests/carrot_transcript_fixed.cpp | 60 ++ tests/unit_tests/crypto.cpp | 102 +++ tests/unit_tests/variant.cpp | 66 +- 42 files changed, 5723 insertions(+), 36 deletions(-) create mode 160000 external/mx25519 create mode 100644 src/carrot_core/CMakeLists.txt create mode 100644 src/carrot_core/account_secrets.cpp create mode 100644 src/carrot_core/account_secrets.h create mode 100644 src/carrot_core/address_utils.cpp create mode 100644 src/carrot_core/address_utils.h create mode 100644 src/carrot_core/carrot_enote_scan.cpp create mode 100644 src/carrot_core/carrot_enote_scan.h create mode 100644 src/carrot_core/carrot_enote_types.h create mode 100644 src/carrot_core/config.h create mode 100644 src/carrot_core/core_types.cpp create mode 100644 src/carrot_core/core_types.h create mode 100644 src/carrot_core/destination.cpp create mode 100644 src/carrot_core/destination.h create mode 100644 src/carrot_core/enote_record_types.h create mode 100644 src/carrot_core/enote_utils.cpp create mode 100644 src/carrot_core/enote_utils.h create mode 100644 src/carrot_core/hash_functions.cpp create mode 100644 src/carrot_core/hash_functions.h create mode 100644 src/carrot_core/payment_proposal.cpp create mode 100644 src/carrot_core/payment_proposal.h create mode 100644 src/carrot_core/transcript_fixed.h create mode 100644 src/carrot_impl/enote_store.h create mode 100644 src/carrot_impl/legacy_enote_types.cpp create mode 100644 src/carrot_impl/legacy_enote_types.h create mode 100644 src/crypto/x25519.cpp create mode 100644 src/crypto/x25519.h create mode 100644 tests/unit_tests/carrot_core.cpp create mode 100644 tests/unit_tests/carrot_transcript_fixed.cpp 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 78350338d..5fb6c195b 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..c71de2a68 --- /dev/null +++ b/src/carrot_core/CMakeLists.txt @@ -0,0 +1,58 @@ +# 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 + enote_utils.cpp + hash_functions.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..f393821f6 --- /dev/null +++ b/src/carrot_core/account_secrets.cpp @@ -0,0 +1,102 @@ +// 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 "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..460f8beeb --- /dev/null +++ b/src/carrot_core/account_secrets.h @@ -0,0 +1,103 @@ +// 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. + +//// +// 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..18bbfd964 --- /dev/null +++ b/src/carrot_core/address_utils.cpp @@ -0,0 +1,101 @@ +// 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 "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))); +} +//------------------------------------------------------------------------------------------------------------------- +void make_carrot_address(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, + const crypto::secret_key &k_view, + crypto::public_key &address_spend_pubkey_out) +{ + // K^j_s = k^j_subscal * K_s + crypto::public_key address_spend_pubkey; + make_carrot_address_spend_pubkey(spend_pubkey, s_generate_address, j_major, j_minor, address_spend_pubkey); + + // K^j_v = k_v * K^j_s + address_spend_pubkey_out = rct::rct2pk(rct::scalarmultKey( + rct::pk2rct(address_spend_pubkey), rct::sk2rct(k_view))); +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/address_utils.h b/src/carrot_core/address_utils.h new file mode 100644 index 000000000..0ecbf850c --- /dev/null +++ b/src/carrot_core/address_utils.h @@ -0,0 +1,113 @@ +// 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. + +// 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); + +/** +* brief: make_carrot_address - (K^j_s, K^j_v) +* K^j_s = k^j_subscal * K_s +* K^j_v = k_v K^j_s +* param: spend_pubkey - K_s = k_gi G + k_ps U +* param: s_generate_address - s_ga +* param: j_major - +* param: j_minor - +* param: k_view - k_v +* outparam: address_spend_pubkey_out - K^j_s +*/ +void make_carrot_address(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, + const crypto::secret_key &k_view, + 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..09f6c5d2b --- /dev/null +++ b/src/carrot_core/carrot_enote_scan.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. + +// Utilities for scanning carrot enotes + +//paired header +#include "carrot_enote_scan.h" + +//local headers +#include "enote_utils.h" +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------------------------- +static bool try_scan_carrot_non_coinbase_no_janus(const CarrotEnoteV1 &enote, + const std::optional encrypted_payment_id, + const input_context_t &input_context, + const unsigned char s_sender_receiver_unctx[32], + 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 vt' != vt, then FAIL + if (!test_carrot_view_tag(s_sender_receiver_unctx, 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, + enote.enote_ephemeral_pubkey, + input_context, + s_sender_receiver); + + // 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 try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const crypto::secret_key &k_view, + 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, + 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 crypto::secret_key &k_view, + 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); + + // do core scanning + janus_anchor_t nominal_anchor; + if (!try_scan_carrot_non_coinbase_no_janus(enote, + encrypted_payment_id, + input_context, + s_sender_receiver_unctx.data, + 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, + 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 crypto::secret_key &s_view_balance, + 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); + + // do core scanning + janus_anchor_t nominal_anchor; + payment_id_t dummy_payment_id; + if (!try_scan_carrot_non_coinbase_no_janus(enote, + std::nullopt, + input_context, + to_bytes(s_view_balance), + 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..01ae7947c --- /dev/null +++ b/src/carrot_core/carrot_enote_scan.h @@ -0,0 +1,76 @@ +// 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" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace carrot +{ +bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, + const crypto::x25519_pubkey &s_sender_receiver_unctx, + const crypto::secret_key &k_view, + 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 crypto::secret_key &k_view, + 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 crypto::secret_key &s_view_balance, + 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..bf398ecb0 --- /dev/null +++ b/src/carrot_core/config.h @@ -0,0 +1,69 @@ +// 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"; + +} //namespace carrot diff --git a/src/carrot_core/core_types.cpp b/src/carrot_core/core_types.cpp new file mode 100644 index 000000000..2d91a1ba7 --- /dev/null +++ b/src/carrot_core/core_types.cpp @@ -0,0 +1,121 @@ +// 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 "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..d461f4168 --- /dev/null +++ b/src/carrot_core/core_types.h @@ -0,0 +1,130 @@ +// 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. + +//! @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..53f5cda0e --- /dev/null +++ b/src/carrot_core/destination.h @@ -0,0 +1,116 @@ +// 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. + +// 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/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..9d846592b --- /dev/null +++ b/src/carrot_core/enote_utils.cpp @@ -0,0 +1,581 @@ +// 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 "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 D_e_in_ed25519_p3; + if (ge_fromx25519_vartime(&D_e_in_ed25519_p3, enote_ephemeral_pubkey.data) != 0) + return false; + + // serialize K_e + crypto::public_key D_e_in_ed25519; + ge_p3_tobytes(to_bytes(D_e_in_ed25519), &D_e_in_ed25519_p3); + + // do ECDH exchange 8 k_v D_e + return make_carrot_uncontextualized_shared_key_sender(k_view, D_e_in_ed25519, s_sender_receiver_unctx_out); +} +//------------------------------------------------------------------------------------------------------------------- +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_external_carrot_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; +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_external_carrot_janus_protection_receiver(const janus_anchor_t &nominal_anchor, + const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &account_spend_pubkey, + const crypto::secret_key &k_view, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + payment_id_t &nominal_payment_id_inout) +{ + const bool is_subaddress = nominal_address_spend_pubkey != account_spend_pubkey; + + // make K^j_v', given K^j_s' + crypto::public_key nominal_address_view_pubkey; + if (is_subaddress) + nominal_address_view_pubkey = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(nominal_address_spend_pubkey), + rct::sk2rct(k_view))); + else // cryptonote address + nominal_address_view_pubkey = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k_view))); + + // if can recompute D_e with pid', then PASS + if (verify_external_carrot_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_external_carrot_janus_protection(nominal_anchor, + input_context, + nominal_address_spend_pubkey, + nominal_address_view_pubkey, + is_subaddress, + null_payment_id, + enote_ephemeral_pubkey)) + return true; + + // neither D_e recompute attempt passed, so FAIL + return false; +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_special_carrot_janus_protection(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, + const janus_anchor_t &nominal_anchor) +{ + // anchor_sp' = H_16(D_e, input_context, Ko, k_v, K_s) + janus_anchor_t nominal_special_anchor; + make_carrot_janus_anchor_special(enote_ephemeral_pubkey, + input_context, + onetime_address, + k_view, + account_spend_pubkey, + nominal_special_anchor); + + // anchor_sp' ?= anchor' + return nominal_special_anchor == nominal_anchor; +} +//------------------------------------------------------------------------------------------------------------------- +bool verify_carrot_janus_protection(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, + 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) +{ + // try checking for Janus protection, normal external path + if (verify_external_carrot_janus_protection_receiver(nominal_anchor, + input_context, + nominal_address_spend_pubkey, + account_spend_pubkey, + k_view, + enote_ephemeral_pubkey, + nominal_payment_id_inout)) + return true; + + // pid is always null for self-send enotes + nominal_payment_id_inout = null_payment_id; + + // try checking for Janus protection, special path + if (verify_special_carrot_janus_protection(enote_ephemeral_pubkey, + input_context, + onetime_address, + k_view, + account_spend_pubkey, + nominal_anchor)) + return true; + + // neither attempt at checking Janus protection worked + return false; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h new file mode 100644 index 000000000..420b114a0 --- /dev/null +++ b/src/carrot_core/enote_utils.h @@ -0,0 +1,440 @@ +// 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. + +// @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_external_carrot_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_external_carrot_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); +/** + * brief: verify_external_carrot_janus_protection_receiver - check normal external enote is Janus safe (i.e. can + * recompute D_e), and perhaps set nominal pid to null + * param: nominal_anchor - anchor' + * param: input_context - + * param: nominal_address_spend_pubkey - K^j_s' + * param: account_spend_pubkey - K_s + * param: k_view - k_v + * param: enote_ephemeral_pubkey - D_e + * outparam: nominal_payment_id_inout - pid', first tries recomputing D_e with pid', then sets to null and tries again + * return: true if this normal external enote is safe from Janus attacks + */ +bool verify_external_carrot_janus_protection_receiver(const janus_anchor_t &nominal_anchor, + const input_context_t &input_context, + const crypto::public_key &nominal_address_spend_pubkey, + const crypto::public_key &account_spend_pubkey, + const crypto::secret_key &k_view, + const crypto::x25519_pubkey &enote_ephemeral_pubkey, + payment_id_t &nominal_payment_id_inout); +/** + * brief: verify_special_carrot_janus_protection - check special enote is Janus safe (i.e. can recompute anchor_sp) + * param: enote_ephemeral_pubkey - D_e + * param: input_context - + * param: onetime_address - Ko + * param: k_view - k_v + * param: account_spend_pubkey - K_s + * param: nominal_anchor - anchor' + * return: true if this special enote is safe from Janus attacks + */ +bool verify_special_carrot_janus_protection(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, + const janus_anchor_t &nominal_anchor); +/** + * brief: verify_carrot_janus_protection - check whether a received Carrot enote is Janus protected (all paths) + * param: input_context - + * param: onetime_address - Ko + * param: k_view - k_v + * param: account_spend_pubkey - K_s + * param: nominal_address_spend_pubkey - K^j_s' + * param: enote_ephemeral_pubkey - D_e + * param: nominal_anchor - anchor' + * outparam: nominal_payment_id_inout - pass possible pid, set to null if the sender didn't explicitly bind to that pid + * return: true if this received enote is safe from Janus attacks + */ +bool verify_carrot_janus_protection(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, + 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); + +} //namespace carrot diff --git a/src/carrot_core/hash_functions.cpp b/src/carrot_core/hash_functions.cpp new file mode 100644 index 000000000..2b59960c7 --- /dev/null +++ b/src/carrot_core/hash_functions.cpp @@ -0,0 +1,109 @@ +// 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 "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..6ddc09529 --- /dev/null +++ b/src/carrot_core/hash_functions.h @@ -0,0 +1,59 @@ +// 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. + +// 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/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp new file mode 100644 index 000000000..7a0472460 --- /dev/null +++ b/src/carrot_core/payment_proposal.cpp @@ -0,0 +1,408 @@ +// 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 "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 + get_enote_ephemeral_pubkey(proposal, input_context, enote_ephemeral_pubkey_out); + + // 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 unsigned char s_sender_receiver_unctx[32], + 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, + enote_ephemeral_pubkey, + input_context, + s_sender_receiver_out); + + // 2. 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_out, + enote_type, + amount_blinding_factor_out); + + // 3. C_a = k_a G + a H + amount_commitment_out = rct::commit(amount, rct::sk2rct(amount_blinding_factor_out)); + + // 4. 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_out, + amount_commitment_out, + onetime_address_out); + + // 5. a_enc = a XOR m_a + encrypted_amount_out = encrypt_carrot_amount(amount, + s_sender_receiver_out, + onetime_address_out); + + // 6. pid_enc = pid XOR m_pid + encrypted_payment_id_out = encrypt_legacy_payment_id(payment_id, s_sender_receiver_out, onetime_address_out); + + // 7. view tag: vt = H_3(s_sr || input_context || Ko) + make_carrot_view_tag(s_sender_receiver_unctx, 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; +} +//------------------------------------------------------------------------------------------------------------------- +void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out) +{ + // 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)}; + + 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_out); + else + // D_e = d_e B + make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey, + enote_ephemeral_pubkey_out); +} +//------------------------------------------------------------------------------------------------------------------- +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_output_proposal_parts(s_sender_receiver_unctx.data, + proposal.destination.address_spend_pubkey, + null_payment_id, + proposal.amount, + CarrotEnoteType::PAYMENT, + output_enote_out.enote_ephemeral_pubkey, + input_context, + true, + 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, + CarrotEnoteV1 &output_enote_out, + encrypted_payment_id_t &encrypted_payment_id_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_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_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_output_proposal_parts(s_sender_receiver_unctx.data, + proposal.destination.address_spend_pubkey, + proposal.destination.payment_id, + proposal.amount, + CarrotEnoteType::PAYMENT, + output_enote_out.enote_ephemeral_pubkey, + input_context, + false, + s_sender_receiver, + amount_blinding_factor_out, + output_enote_out.amount_commitment, + output_enote_out.onetime_address, + output_enote_out.amount_enc, + encrypted_payment_id_out, + 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 first key image + amount_out = proposal.amount; + output_enote_out.tx_first_key_image = tx_first_key_image; +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const crypto::secret_key &k_view, + const crypto::public_key &primary_address_spend_pubkey, + const crypto::key_image &tx_first_key_image, + CarrotEnoteV1 &output_enote_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_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; + make_carrot_uncontextualized_shared_key_receiver(k_view, + proposal.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); + encrypted_payment_id_t dummy_encrypted_payment_id; + get_output_proposal_parts(s_sender_receiver_unctx.data, + proposal.destination_address_spend_pubkey, + null_payment_id, + proposal.amount, + proposal.enote_type, + proposal.enote_ephemeral_pubkey, + input_context, + false, + s_sender_receiver, + amount_blinding_factor_out, + output_enote_out.amount_commitment, + output_enote_out.onetime_address, + output_enote_out.amount_enc, + dummy_encrypted_payment_id, + output_enote_out.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; + make_carrot_janus_anchor_special(proposal.enote_ephemeral_pubkey, + input_context, + output_enote_out.onetime_address, + k_view, + primary_address_spend_pubkey, + janus_anchor_special); + + // 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor + output_enote_out.anchor_enc = encrypt_carrot_anchor(janus_anchor_special, + s_sender_receiver, + output_enote_out.onetime_address); + + // 7. save the enote ephemeral pubkey, first tx key image, and amount + output_enote_out.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; + output_enote_out.tx_first_key_image = tx_first_key_image; + amount_out = proposal.amount; +} +//------------------------------------------------------------------------------------------------------------------- +void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const crypto::secret_key &s_view_balance, + const crypto::key_image &tx_first_key_image, + CarrotEnoteV1 &output_enote_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_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. 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_output_proposal_parts(to_bytes(s_view_balance), + proposal.destination_address_spend_pubkey, + null_payment_id, + proposal.amount, + proposal.enote_type, + proposal.enote_ephemeral_pubkey, + input_context, + false, + s_sender_receiver, + amount_blinding_factor_out, + output_enote_out.amount_commitment, + output_enote_out.onetime_address, + output_enote_out.amount_enc, + dummy_encrypted_payment_id, + output_enote_out.view_tag); + + // 4. generate random encrypted anchor + output_enote_out.anchor_enc = gen_janus_anchor(); + + // 5. save the enote ephemeral pubkey, first tx key image, and amount + output_enote_out.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; + output_enote_out.tx_first_key_image = tx_first_key_image; + amount_out = 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..874cc491f --- /dev/null +++ b/src/carrot_core/payment_proposal.h @@ -0,0 +1,165 @@ +// 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. + +// 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 "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; +}; + +/// equality operators +bool operator==(const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1 &b); +/// equality operators +bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentProposalSelfSendV1 &b); + +/** +* brief: get_enote_ephemeral_pubkey - get the proposal's enote ephemeral pubkey D_e +* param: proposal - +* param: input_context - +* outparam: enote_ephemeral_pubkey_out - +*/ +void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, + const input_context_t &input_context, + crypto::x25519_pubkey &enote_ephemeral_pubkey_out); +/** +* 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 +* outparam: amount_out - used to open commitment C_a +* outparam: amount_blinding_factor_out - used to open commitment C_a +*/ +void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, + const crypto::key_image &tx_first_key_image, + CarrotEnoteV1 &output_enote_out, + encrypted_payment_id_t &encrypted_payment_id_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out); +/** +* brief: get_output_proposal_v1 - convert the carrot proposal to an output proposal (external selfsend) +* param: proposal - +* param: k_view - +* param: primary_address_spend_pubkey - +* param: tx_first_key_image - +* outparam: output_enote_out - +* outparam: amount_out - used to open commitment C_a +* outparam: amount_blinding_factor_out - used to open commitment C_a +*/ +void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const crypto::secret_key &k_view, + const crypto::public_key &primary_address_spend_pubkey, + const crypto::key_image &tx_first_key_image, + CarrotEnoteV1 &output_enote_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_out); +/** +* brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) +* param: proposal - +* param: s_view_balance - +* param: primary_address_spend_pubkey - +* param: tx_first_key_image - +* outparam: output_enote_out - +* outparam: partial_memo_out - +* outparam: amount_out - used to open commitment C_a +* outparam: amount_blinding_factor_out - used to open commitment C_a +*/ +void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, + const crypto::secret_key &s_view_balance, + const crypto::key_image &tx_first_key_image, + CarrotEnoteV1 &output_enote_out, + rct::xmr_amount &amount_out, + crypto::secret_key &amount_blinding_factor_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..082bbb7fa --- /dev/null +++ b/src/carrot_core/transcript_fixed.h @@ -0,0 +1,183 @@ +// Copyright (c) 2022-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: +//public static member variables + static constexpr std::size_t size = 1 + SpFixedTranscript::domain_sep_size() + detail::sizeof_sum(); + +//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; } + +//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/carrot_impl/enote_store.h b/src/carrot_impl/enote_store.h new file mode 100644 index 000000000..9651478bb --- /dev/null +++ b/src/carrot_impl/enote_store.h @@ -0,0 +1,290 @@ +// 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. + +// Enote store that supports full-featured balance recovery by managing enote-related caches. + +#pragma once + +//local headers +#include "checkpoint_cache.h" +#include "expect.h" +#include "enote_store_event_types.h" +#include "seraphis_main/contextual_enote_record_types.h" + +//third party headers + +//standard headers +#include + +//forward declarations + + +namespace sp +{ + + +struct CarrotBaseDBInterface +{ + virtual expect sync() = 0; + + virtual ~CarrotBaseDBInterface() = default; +}; + +struct CarrotEnoteDBInterface: CarrotBaseDBInterface +{ + virtual expect update_enote_record( + const MinimalIntermediateEnoteRecordVariant &enote_record, + const LegacyEnoteOriginContext &origin_context) = 0; + + virtual expect remove_records_with_txid(const crypto::hash &txid) = 0; + virtual expect remove_non_ledger_records() = 0; + virtual expect remove_ledger_records_before(const uint64_t block_index) = 0; + virtual expect clear() = 0; + + virtual expect get_all_record_identifiers(std::vector &record_identifiers_out) const = 0; + + virtual expect get_record(const crypto::hash &record_identifier, + MinimalIntermediateEnoteRecordVariant &enote_record_out, + LegacyEnoteOriginContext &origin_context_out) const = 0; + + virtual expect has_enote_at_global_index(const legacy_output_index_t &output_index) const = 0; +}; + +class CarrotEnoteRamDB: public CarrotEnoteDBInterface +{ +public: +///CarrotEnoteDBInterface + expect update_enote_record( + const MinimalIntermediateEnoteRecordVariant &enote_record, + const LegacyEnoteOriginContext &origin_context) override; + + expect remove_records_with_txid(const crypto::hash &txid) override; + expect remove_non_ledger_records() override; + expect remove_ledger_records_before(const uint64_t block_index) override; + expect clear() override; + + expect get_all_record_identifiers(std::vector &record_identifiers_out) const override; + + expect get_record(const crypto::hash &record_identifier, + MinimalIntermediateEnoteRecordVariant &enote_record_out, + LegacyEnoteOriginContext &origin_context_out) const override; + + expect has_enote_at_global_index(const legacy_output_index_t &output_index) const override; + +///CarrotBaseDBInterface + expect sync() override {} + +private: +///member variables + std::unordered_map + > m_records; + std::unordered_set m_owned_enote_indices; +}; + +expect CarrotEnoteRamDB::update_enote_record( + const MinimalIntermediateEnoteRecordVariant &enote_record, + const LegacyEnoteOriginContext &origin_context) override +{ +} + +struct CarrotKeyImageDBInterface: CarrotBaseDBInterface +{ + virtual expect add_key_image( + const crypto::key_image &key_image, + const SpEnoteSpentContextV1 &spent_context) = 0; + + virtual expect associate_key_image(const crypto::key_image &key_image, + const crypto::public_key &onetime_address) = 0; + + virtual expect remove_key_images_with_txid(const crypto::hash &txid) = 0; + virtual expect remove_non_ledger_key_images() = 0; + virtual expect remove_key_images_before(const uint64_t block_index) = 0; + virtual expect clear() = 0; + + virtual expect get_all_key_images(std::vector &key_images_out) const = 0; + + virtual expect get_key_image_info(const crypto::key_image &key_image, + SpEnoteSpentContextV1 &spent_context_out, + std::optional &associated_onetime_address_out) const = 0; +}; + +struct CarrotBalanceDBInterface: CarrotEnoteDBInterface, CarrotKeyImageDBInterface +{ + virtual expect get_all_spendable_enote_identifiers( + std::vector &enote_identifiers_out) const = 0; + + virtual expect get_all_spendable_unspent_enote_identifiers( + std::vector &enote_identifiers_out) const = 0; +}; + +struct CarrotScanStateDBInterface: CarrotBaseDBInterface +{ + virtual expect extend_chain( + const crypto::hash &alignment_block_id, + const std::vector &new_block_ids) = 0; + + virtual expect rollback_chain(const crypto::hash &block_id) = 0; + + virtual expect trim_chain() = 0; + + virtual expect set_genesis_block_id(const crypto::hash &genesis_block_id) = 0; + + virtual expect set_restore_index(const uint64_t restore_block_index) = 0; + + virtual expect mark_region_legacy_view_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; + virtual expect mark_region_carrot_external_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; + virtual expect mark_region_carrot_internal_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; +}; + +//// +// CarrotEnoteStore +// - tracks legacy and carrot enotes +/// +class CarrotBalanceStore final +{ +public: + /// config: get index of the first block the enote store cares about + std::uint64_t get_restore_index() const; + + /// get index of the highest recorded block (legacy refresh index - 1 if no recorded blocks) + std::uint64_t top_block_index() const; + /// get index of the highest block that was legacy partialscanned (view-scan only) + std::uint64_t top_legacy_partialscanned_block_index() const { return m_legacy_partialscan_index; } + /// get index of the highest block that was legacy fullscanned (view-scan + comprehensive key image checks) + std::uint64_t top_legacy_fullscanned_block_index() const { return m_legacy_fullscan_index; } + /// get index of the highest block that was seraphis view-balance scanned + std::uint64_t top_sp_scanned_block_index() const { return m_sp_scanned_index; } + + /// get the next cached block index > the requested index (-1 on failure) + std::uint64_t next_legacy_partialscanned_block_index(const std::uint64_t block_index) const; + std::uint64_t next_legacy_fullscanned_block_index (const std::uint64_t block_index) const; + std::uint64_t next_sp_scanned_block_index (const std::uint64_t block_index) const; + /// get the nearest cached block index <= the requested index (refresh index - 1 on failure) + std::uint64_t nearest_legacy_partialscanned_block_index(const std::uint64_t block_index) const; + std::uint64_t nearest_legacy_fullscanned_block_index (const std::uint64_t block_index) const; + std::uint64_t nearest_sp_scanned_block_index (const std::uint64_t block_index) const; + /// try to get the cached block id for a given index and specified scan mode + /// note: during scanning, different scan modes are assumed to 'not see' block ids obtained by a different scan mode; + /// this is necessary to reliably recover from reorgs involving multiple scan modes + bool try_get_block_id_for_legacy_partialscan(const std::uint64_t block_index, rct::key &block_id_out) const; + bool try_get_block_id_for_legacy_fullscan (const std::uint64_t block_index, rct::key &block_id_out) const; + bool try_get_block_id_for_sp (const std::uint64_t block_index, rct::key &block_id_out) const; + /// try to get the cached block id for a given index (checks legacy block ids then seraphis block ids) + bool try_get_block_id(const std::uint64_t block_index, rct::key &block_id_out) const; + /// check if any stored enote has a given key image + bool has_enote_with_key_image(const crypto::key_image &key_image) const; + /// get the legacy [ legacy identifier : legacy intermediate record ] map + /// - note: useful for collecting onetime addresses and viewkey extensions for key image recovery + const std::unordered_map& legacy_intermediate_records() const + { return m_legacy_intermediate_contextual_enote_records; } + /// get the legacy [ legacy identifier : legacy record ] map + const std::unordered_map& legacy_records() const + { return m_legacy_contextual_enote_records; } + /// get the legacy [ Ko : [ legacy identifier ] ] map + const std::unordered_map>& legacy_onetime_address_identifier_map() const + { return m_tracked_legacy_onetime_address_duplicates; } + /// get the legacy [ KI : Ko ] map + const std::unordered_map& legacy_key_images() const + { return m_legacy_key_images; } + /// get the seraphis [ KI : sp record ] map + const std::unordered_map& sp_records() const + { return m_sp_contextual_enote_records; } + /// try to get the legacy enote with a specified key image + /// - will only return the highest-amount legacy enote among duplicates, and will return false if the + /// highest-amount legacy enote is currently in the intermediate records map + bool try_get_legacy_enote_record(const crypto::key_image &key_image, + LegacyContextualEnoteRecordV1 &contextual_record_out) const; + /// try to get the seraphis enote with a specified key image + bool try_get_sp_enote_record(const crypto::key_image &key_image, + SpContextualEnoteRecordV1 &contextual_record_out) const; + + /// try to import a legacy key image + /// - PRECONDITION1: the legacy key image was computed from/for the input onetime address + /// - returns false if the onetime address is unknown (e.g. due to a reorg that removed the corresponding record) + bool try_import_legacy_key_image(const crypto::key_image &legacy_key_image, + const rct::key &onetime_address, + std::list &events_inout); + /// update the legacy fullscan index as part of a legacy key image import cycle + void update_legacy_fullscan_index_for_import_cycle(const std::uint64_t saved_index); + + /// setters for scan indices + /// WARNING: misuse of these will mess up the enote store's state (to recover: set index below problem then rescan) + /// note: to repair the enote store in case of an exception or other error during an update, save all of the last + /// scanned indices from before the update, reset the enote store with them (after the failure), and then + /// re-scan to repair + void set_last_legacy_partialscan_index(const std::uint64_t new_index); + void set_last_legacy_fullscan_index (const std::uint64_t new_index); + void set_last_sp_scanned_index (const std::uint64_t new_index); + + /// update the store with legacy enote records and associated context + void update_with_intermediate_legacy_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + std::list &events_inout); + void update_with_intermediate_legacy_records_from_ledger(const rct::key &alignment_block_id, + const std::uint64_t first_new_block, + const std::vector &new_block_ids, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + std::list &events_inout); + void update_with_intermediate_legacy_found_spent_key_images( + const std::unordered_map &found_spent_key_images, + std::list &events_inout); + void update_with_legacy_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + std::list &events_inout); + void update_with_legacy_records_from_ledger(const rct::key &alignment_block_id, + const std::uint64_t first_new_block, + const std::vector &new_block_ids, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + std::list &events_inout); + + /// update the store with seraphis enote records and associated context + void update_with_sp_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + const std::unordered_map &legacy_key_images_in_sp_selfsends, + std::list &events_inout); + void update_with_sp_records_from_ledger(const rct::key &alignment_block_id, + const std::uint64_t first_new_block, + const std::vector &new_block_ids, + const std::unordered_map &found_enote_records, + const std::unordered_map &found_spent_key_images, + const std::unordered_map &legacy_key_images_in_sp_selfsends, + std::list &events_inout); + +private: + std::unique_ptr m_external_balance_db; + std::unique_ptr m_internal_balance_db; + std::unique_ptr m_scan_state_db; +}; + +} //namespace sp diff --git a/src/carrot_impl/legacy_enote_types.cpp b/src/carrot_impl/legacy_enote_types.cpp new file mode 100644 index 000000000..01374fbc6 --- /dev/null +++ b/src/carrot_impl/legacy_enote_types.cpp @@ -0,0 +1,88 @@ +// 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 "legacy_enote_types.h" + +//local headers +#include "ringct/rctOps.h" + +//third party headers + +//standard headers + +namespace carrot +{ +//------------------------------------------------------------------------------------------------------------------- +LegacyEnoteV1 gen_legacy_enote_v1() +{ + LegacyEnoteV1 temp; + temp.onetime_address = rct::rct2pk(rct::pkGen()); + temp.amount = crypto::rand_idx(0); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +LegacyEnoteV2 gen_legacy_enote_v2() +{ + LegacyEnoteV2 temp; + temp.onetime_address = rct::rct2pk(rct::pkGen()); + temp.amount_commitment = rct::pkGen(); + temp.encrypted_amount_blinding_factor = rct::skGen(); + temp.encrypted_amount = rct::skGen(); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +LegacyEnoteV3 gen_legacy_enote_v3() +{ + LegacyEnoteV3 temp; + temp.onetime_address = rct::rct2pk(rct::pkGen()); + temp.amount_commitment = rct::pkGen(); + crypto::rand(sizeof(temp.encrypted_amount), temp.encrypted_amount.bytes); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +LegacyEnoteV4 gen_legacy_enote_v4() +{ + LegacyEnoteV4 temp; + temp.onetime_address = rct::rct2pk(rct::pkGen()); + temp.amount = crypto::rand_idx(0); + temp.view_tag.data = static_cast(crypto::rand_idx(0)); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +LegacyEnoteV5 gen_legacy_enote_v5() +{ + LegacyEnoteV5 temp; + temp.onetime_address = rct::rct2pk(rct::pkGen()); + temp.amount_commitment = rct::pkGen(); + crypto::rand(sizeof(temp.encrypted_amount), temp.encrypted_amount.bytes); + temp.view_tag.data = static_cast(crypto::rand_idx(0)); + return temp; +} +//------------------------------------------------------------------------------------------------------------------- +} //namespace carrot diff --git a/src/carrot_impl/legacy_enote_types.h b/src/carrot_impl/legacy_enote_types.h new file mode 100644 index 000000000..8ee5fc5fc --- /dev/null +++ b/src/carrot_impl/legacy_enote_types.h @@ -0,0 +1,172 @@ +// 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. + +// Seraphis core types. + +#pragma once + +//local headers +#include "carrot_core/core_types.h" +#include "crypto/crypto.h" +#include "ringct/rctTypes.h" + +//third party headers + +//standard headers + +//forward declarations + + +namespace carrot +{ + +//// +// LegacyEnoteV1 (all pre-RingCT enotes, then post-RingCT pre-viewtag coinbase) +// - onetime address +// - cleartext amount +/// +struct LegacyEnoteV1 final +{ + /// Ko + crypto::public_key onetime_address; + /// a + rct::xmr_amount amount; + /// the enote's ephemeral pubkey + crypto::public_key enote_ephemeral_pubkey; + /// t: the enote's index in its transaction + std::uint64_t tx_output_index; +}; + +//// +// LegacyEnoteV2 +// - onetime address +// - amount commitment +// - encrypted amount commitment mask +// - encrypted amount (version 1: 32 bytes) +/// +struct LegacyEnoteV2 final +{ + /// Ko + crypto::public_key onetime_address; + /// C + rct::key amount_commitment; + /// enc(x) + rct::key encrypted_amount_blinding_factor; + /// enc(a) + rct::key encrypted_amount; + /// the enote's ephemeral pubkey + crypto::public_key enote_ephemeral_pubkey; + /// t: the enote's index in its transaction + std::uint64_t tx_output_index; +}; + +//// +// LegacyEnoteV3 +// - onetime address +// - amount commitment +// - encrypted amount (version 2: 8 bytes) +/// +struct LegacyEnoteV3 final +{ + /// Ko + crypto::public_key onetime_address; + /// C + rct::key amount_commitment; + /// enc(a) + encrypted_amount_t encrypted_amount; + /// the enote's ephemeral pubkey + crypto::public_key enote_ephemeral_pubkey; + /// t: the enote's index in its transaction + std::uint64_t tx_output_index; +}; + +//// +// LegacyEnoteV4 (post-viewtag coinbase, also post-viewtag v1 unmixable dust txs) +// - onetime address +// - cleartext amount +// - view tag +/// +struct LegacyEnoteV4 final +{ + /// Ko + crypto::public_key onetime_address; + /// a + rct::xmr_amount amount; + /// view_tag + crypto::view_tag view_tag; + /// the enote's ephemeral pubkey + crypto::public_key enote_ephemeral_pubkey; + /// t: the enote's index in its transaction + std::uint64_t tx_output_index; +}; + +//// +// LegacyEnoteV5 +// - onetime address +// - amount commitment +// - encrypted amount (version 2: 8 bytes) +// - view tag +/// +struct LegacyEnoteV5 final +{ + /// Ko + crypto::public_key onetime_address; + /// C + rct::key amount_commitment; + /// enc(a) + encrypted_amount_t encrypted_amount; + /// view_tag + crypto::view_tag view_tag; + /// the enote's ephemeral pubkey + crypto::public_key enote_ephemeral_pubkey; + /// t: the enote's index in its transaction + std::uint64_t tx_output_index; +}; + +/** +* brief: gen_legacy_enote_v1() - generate a legacy v1 enote (all random) +*/ +LegacyEnoteV1 gen_legacy_enote_v1(); +/** +* brief: gen_legacy_enote_v2() - generate a legacy v2 enote (all random) +*/ +LegacyEnoteV2 gen_legacy_enote_v2(); +/** +* brief: gen_legacy_enote_v3() - generate a legacy v3 enote (all random) +*/ +LegacyEnoteV3 gen_legacy_enote_v3(); +/** +* brief: gen_legacy_enote_v4() - generate a legacy v4 enote (all random) +*/ +LegacyEnoteV4 gen_legacy_enote_v4(); +/** +* brief: gen_legacy_enote_v5() - generate a legacy v5 enote (all random) +*/ +LegacyEnoteV5 gen_legacy_enote_v5(); + +} //namespace carrot 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..a876f632c 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -38,6 +38,8 @@ set(unit_tests_sources bulletproofs.cpp bulletproofs_plus.cpp canonical_amounts.cpp + carrot_core.cpp + carrot_transcript_fixed.cpp chacha.cpp checkpoints.cpp command_line.cpp @@ -113,6 +115,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..88817d4ef --- /dev/null +++ b/tests/unit_tests/carrot_core.cpp @@ -0,0 +1,767 @@ +// 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/enote_utils.h" +#include "carrot_core/payment_proposal.h" +#include "crypto/crypto.h" +#include "crypto/generators.h" +#include "ringct/rctOps.h" + +using namespace carrot; + +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +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; + + 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) + (k^o_t + k^j_subscal * k_ps) + + // 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; +} +//---------------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------------- +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, 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()); + + CarrotEnoteV1 enote; + encrypted_payment_id_t encrypted_payment_id; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote, + encrypted_payment_id, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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; + 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, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + encrypted_payment_id_t encrypted_payment_id; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote, + encrypted_payment_id, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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; + 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, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + encrypted_payment_id_t encrypted_payment_id; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_normal_v1(proposal, + tx_first_key_image, + enote, + encrypted_payment_id, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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; + 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, + encrypted_payment_id, + s_sender_receiver_unctx, + keys.k_view, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_special_v1(proposal, + keys.k_view, + keys.account_spend_pubkey, + tx_first_key_image, + enote, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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; + 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, + std::nullopt, + s_sender_receiver_unctx, + keys.k_view, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_special_v1(proposal, + keys.k_view, + keys.account_spend_pubkey, + tx_first_key_image, + enote, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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; + 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, + std::nullopt, + s_sender_receiver_unctx, + keys.k_view, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_internal_v1(proposal, + keys.s_view_balance, + tx_first_key_image, + enote, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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, + keys.s_view_balance, + 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(amount, recovered_amount); + EXPECT_EQ(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.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()); + + CarrotEnoteV1 enote; + rct::xmr_amount amount; + crypto::secret_key amount_blinding_factor; + get_output_proposal_internal_v1(proposal, + keys.s_view_balance, + tx_first_key_image, + enote, + amount, + amount_blinding_factor); + + ASSERT_EQ(proposal.amount, amount); + ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + + 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, + keys.s_view_balance, + 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(amount, recovered_amount); + EXPECT_EQ(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.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, + 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)); +} +//---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/unit_tests/carrot_transcript_fixed.cpp b/tests/unit_tests/carrot_transcript_fixed.cpp new file mode 100644 index 000000000..e708a8ac7 --- /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 { From 71325177bdb5bfd0ddd4cfc98d3271ddde0e8266 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Fri, 8 Nov 2024 14:38:04 -0600 Subject: [PATCH 02/14] rm impl dir --- src/carrot_impl/enote_store.h | 290 ------------------------- src/carrot_impl/legacy_enote_types.cpp | 88 -------- src/carrot_impl/legacy_enote_types.h | 172 --------------- 3 files changed, 550 deletions(-) delete mode 100644 src/carrot_impl/enote_store.h delete mode 100644 src/carrot_impl/legacy_enote_types.cpp delete mode 100644 src/carrot_impl/legacy_enote_types.h diff --git a/src/carrot_impl/enote_store.h b/src/carrot_impl/enote_store.h deleted file mode 100644 index 9651478bb..000000000 --- a/src/carrot_impl/enote_store.h +++ /dev/null @@ -1,290 +0,0 @@ -// 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. - -// Enote store that supports full-featured balance recovery by managing enote-related caches. - -#pragma once - -//local headers -#include "checkpoint_cache.h" -#include "expect.h" -#include "enote_store_event_types.h" -#include "seraphis_main/contextual_enote_record_types.h" - -//third party headers - -//standard headers -#include - -//forward declarations - - -namespace sp -{ - - -struct CarrotBaseDBInterface -{ - virtual expect sync() = 0; - - virtual ~CarrotBaseDBInterface() = default; -}; - -struct CarrotEnoteDBInterface: CarrotBaseDBInterface -{ - virtual expect update_enote_record( - const MinimalIntermediateEnoteRecordVariant &enote_record, - const LegacyEnoteOriginContext &origin_context) = 0; - - virtual expect remove_records_with_txid(const crypto::hash &txid) = 0; - virtual expect remove_non_ledger_records() = 0; - virtual expect remove_ledger_records_before(const uint64_t block_index) = 0; - virtual expect clear() = 0; - - virtual expect get_all_record_identifiers(std::vector &record_identifiers_out) const = 0; - - virtual expect get_record(const crypto::hash &record_identifier, - MinimalIntermediateEnoteRecordVariant &enote_record_out, - LegacyEnoteOriginContext &origin_context_out) const = 0; - - virtual expect has_enote_at_global_index(const legacy_output_index_t &output_index) const = 0; -}; - -class CarrotEnoteRamDB: public CarrotEnoteDBInterface -{ -public: -///CarrotEnoteDBInterface - expect update_enote_record( - const MinimalIntermediateEnoteRecordVariant &enote_record, - const LegacyEnoteOriginContext &origin_context) override; - - expect remove_records_with_txid(const crypto::hash &txid) override; - expect remove_non_ledger_records() override; - expect remove_ledger_records_before(const uint64_t block_index) override; - expect clear() override; - - expect get_all_record_identifiers(std::vector &record_identifiers_out) const override; - - expect get_record(const crypto::hash &record_identifier, - MinimalIntermediateEnoteRecordVariant &enote_record_out, - LegacyEnoteOriginContext &origin_context_out) const override; - - expect has_enote_at_global_index(const legacy_output_index_t &output_index) const override; - -///CarrotBaseDBInterface - expect sync() override {} - -private: -///member variables - std::unordered_map - > m_records; - std::unordered_set m_owned_enote_indices; -}; - -expect CarrotEnoteRamDB::update_enote_record( - const MinimalIntermediateEnoteRecordVariant &enote_record, - const LegacyEnoteOriginContext &origin_context) override -{ -} - -struct CarrotKeyImageDBInterface: CarrotBaseDBInterface -{ - virtual expect add_key_image( - const crypto::key_image &key_image, - const SpEnoteSpentContextV1 &spent_context) = 0; - - virtual expect associate_key_image(const crypto::key_image &key_image, - const crypto::public_key &onetime_address) = 0; - - virtual expect remove_key_images_with_txid(const crypto::hash &txid) = 0; - virtual expect remove_non_ledger_key_images() = 0; - virtual expect remove_key_images_before(const uint64_t block_index) = 0; - virtual expect clear() = 0; - - virtual expect get_all_key_images(std::vector &key_images_out) const = 0; - - virtual expect get_key_image_info(const crypto::key_image &key_image, - SpEnoteSpentContextV1 &spent_context_out, - std::optional &associated_onetime_address_out) const = 0; -}; - -struct CarrotBalanceDBInterface: CarrotEnoteDBInterface, CarrotKeyImageDBInterface -{ - virtual expect get_all_spendable_enote_identifiers( - std::vector &enote_identifiers_out) const = 0; - - virtual expect get_all_spendable_unspent_enote_identifiers( - std::vector &enote_identifiers_out) const = 0; -}; - -struct CarrotScanStateDBInterface: CarrotBaseDBInterface -{ - virtual expect extend_chain( - const crypto::hash &alignment_block_id, - const std::vector &new_block_ids) = 0; - - virtual expect rollback_chain(const crypto::hash &block_id) = 0; - - virtual expect trim_chain() = 0; - - virtual expect set_genesis_block_id(const crypto::hash &genesis_block_id) = 0; - - virtual expect set_restore_index(const uint64_t restore_block_index) = 0; - - virtual expect mark_region_legacy_view_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; - virtual expect mark_region_carrot_external_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; - virtual expect mark_region_carrot_internal_scanned(const uint64_t start_index, const uint64_t stop_index) = 0; -}; - -//// -// CarrotEnoteStore -// - tracks legacy and carrot enotes -/// -class CarrotBalanceStore final -{ -public: - /// config: get index of the first block the enote store cares about - std::uint64_t get_restore_index() const; - - /// get index of the highest recorded block (legacy refresh index - 1 if no recorded blocks) - std::uint64_t top_block_index() const; - /// get index of the highest block that was legacy partialscanned (view-scan only) - std::uint64_t top_legacy_partialscanned_block_index() const { return m_legacy_partialscan_index; } - /// get index of the highest block that was legacy fullscanned (view-scan + comprehensive key image checks) - std::uint64_t top_legacy_fullscanned_block_index() const { return m_legacy_fullscan_index; } - /// get index of the highest block that was seraphis view-balance scanned - std::uint64_t top_sp_scanned_block_index() const { return m_sp_scanned_index; } - - /// get the next cached block index > the requested index (-1 on failure) - std::uint64_t next_legacy_partialscanned_block_index(const std::uint64_t block_index) const; - std::uint64_t next_legacy_fullscanned_block_index (const std::uint64_t block_index) const; - std::uint64_t next_sp_scanned_block_index (const std::uint64_t block_index) const; - /// get the nearest cached block index <= the requested index (refresh index - 1 on failure) - std::uint64_t nearest_legacy_partialscanned_block_index(const std::uint64_t block_index) const; - std::uint64_t nearest_legacy_fullscanned_block_index (const std::uint64_t block_index) const; - std::uint64_t nearest_sp_scanned_block_index (const std::uint64_t block_index) const; - /// try to get the cached block id for a given index and specified scan mode - /// note: during scanning, different scan modes are assumed to 'not see' block ids obtained by a different scan mode; - /// this is necessary to reliably recover from reorgs involving multiple scan modes - bool try_get_block_id_for_legacy_partialscan(const std::uint64_t block_index, rct::key &block_id_out) const; - bool try_get_block_id_for_legacy_fullscan (const std::uint64_t block_index, rct::key &block_id_out) const; - bool try_get_block_id_for_sp (const std::uint64_t block_index, rct::key &block_id_out) const; - /// try to get the cached block id for a given index (checks legacy block ids then seraphis block ids) - bool try_get_block_id(const std::uint64_t block_index, rct::key &block_id_out) const; - /// check if any stored enote has a given key image - bool has_enote_with_key_image(const crypto::key_image &key_image) const; - /// get the legacy [ legacy identifier : legacy intermediate record ] map - /// - note: useful for collecting onetime addresses and viewkey extensions for key image recovery - const std::unordered_map& legacy_intermediate_records() const - { return m_legacy_intermediate_contextual_enote_records; } - /// get the legacy [ legacy identifier : legacy record ] map - const std::unordered_map& legacy_records() const - { return m_legacy_contextual_enote_records; } - /// get the legacy [ Ko : [ legacy identifier ] ] map - const std::unordered_map>& legacy_onetime_address_identifier_map() const - { return m_tracked_legacy_onetime_address_duplicates; } - /// get the legacy [ KI : Ko ] map - const std::unordered_map& legacy_key_images() const - { return m_legacy_key_images; } - /// get the seraphis [ KI : sp record ] map - const std::unordered_map& sp_records() const - { return m_sp_contextual_enote_records; } - /// try to get the legacy enote with a specified key image - /// - will only return the highest-amount legacy enote among duplicates, and will return false if the - /// highest-amount legacy enote is currently in the intermediate records map - bool try_get_legacy_enote_record(const crypto::key_image &key_image, - LegacyContextualEnoteRecordV1 &contextual_record_out) const; - /// try to get the seraphis enote with a specified key image - bool try_get_sp_enote_record(const crypto::key_image &key_image, - SpContextualEnoteRecordV1 &contextual_record_out) const; - - /// try to import a legacy key image - /// - PRECONDITION1: the legacy key image was computed from/for the input onetime address - /// - returns false if the onetime address is unknown (e.g. due to a reorg that removed the corresponding record) - bool try_import_legacy_key_image(const crypto::key_image &legacy_key_image, - const rct::key &onetime_address, - std::list &events_inout); - /// update the legacy fullscan index as part of a legacy key image import cycle - void update_legacy_fullscan_index_for_import_cycle(const std::uint64_t saved_index); - - /// setters for scan indices - /// WARNING: misuse of these will mess up the enote store's state (to recover: set index below problem then rescan) - /// note: to repair the enote store in case of an exception or other error during an update, save all of the last - /// scanned indices from before the update, reset the enote store with them (after the failure), and then - /// re-scan to repair - void set_last_legacy_partialscan_index(const std::uint64_t new_index); - void set_last_legacy_fullscan_index (const std::uint64_t new_index); - void set_last_sp_scanned_index (const std::uint64_t new_index); - - /// update the store with legacy enote records and associated context - void update_with_intermediate_legacy_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - std::list &events_inout); - void update_with_intermediate_legacy_records_from_ledger(const rct::key &alignment_block_id, - const std::uint64_t first_new_block, - const std::vector &new_block_ids, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - std::list &events_inout); - void update_with_intermediate_legacy_found_spent_key_images( - const std::unordered_map &found_spent_key_images, - std::list &events_inout); - void update_with_legacy_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - std::list &events_inout); - void update_with_legacy_records_from_ledger(const rct::key &alignment_block_id, - const std::uint64_t first_new_block, - const std::vector &new_block_ids, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - std::list &events_inout); - - /// update the store with seraphis enote records and associated context - void update_with_sp_records_from_nonledger(const SpEnoteOriginStatus nonledger_origin_status, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - const std::unordered_map &legacy_key_images_in_sp_selfsends, - std::list &events_inout); - void update_with_sp_records_from_ledger(const rct::key &alignment_block_id, - const std::uint64_t first_new_block, - const std::vector &new_block_ids, - const std::unordered_map &found_enote_records, - const std::unordered_map &found_spent_key_images, - const std::unordered_map &legacy_key_images_in_sp_selfsends, - std::list &events_inout); - -private: - std::unique_ptr m_external_balance_db; - std::unique_ptr m_internal_balance_db; - std::unique_ptr m_scan_state_db; -}; - -} //namespace sp diff --git a/src/carrot_impl/legacy_enote_types.cpp b/src/carrot_impl/legacy_enote_types.cpp deleted file mode 100644 index 01374fbc6..000000000 --- a/src/carrot_impl/legacy_enote_types.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// 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 "legacy_enote_types.h" - -//local headers -#include "ringct/rctOps.h" - -//third party headers - -//standard headers - -namespace carrot -{ -//------------------------------------------------------------------------------------------------------------------- -LegacyEnoteV1 gen_legacy_enote_v1() -{ - LegacyEnoteV1 temp; - temp.onetime_address = rct::rct2pk(rct::pkGen()); - temp.amount = crypto::rand_idx(0); - return temp; -} -//------------------------------------------------------------------------------------------------------------------- -LegacyEnoteV2 gen_legacy_enote_v2() -{ - LegacyEnoteV2 temp; - temp.onetime_address = rct::rct2pk(rct::pkGen()); - temp.amount_commitment = rct::pkGen(); - temp.encrypted_amount_blinding_factor = rct::skGen(); - temp.encrypted_amount = rct::skGen(); - return temp; -} -//------------------------------------------------------------------------------------------------------------------- -LegacyEnoteV3 gen_legacy_enote_v3() -{ - LegacyEnoteV3 temp; - temp.onetime_address = rct::rct2pk(rct::pkGen()); - temp.amount_commitment = rct::pkGen(); - crypto::rand(sizeof(temp.encrypted_amount), temp.encrypted_amount.bytes); - return temp; -} -//------------------------------------------------------------------------------------------------------------------- -LegacyEnoteV4 gen_legacy_enote_v4() -{ - LegacyEnoteV4 temp; - temp.onetime_address = rct::rct2pk(rct::pkGen()); - temp.amount = crypto::rand_idx(0); - temp.view_tag.data = static_cast(crypto::rand_idx(0)); - return temp; -} -//------------------------------------------------------------------------------------------------------------------- -LegacyEnoteV5 gen_legacy_enote_v5() -{ - LegacyEnoteV5 temp; - temp.onetime_address = rct::rct2pk(rct::pkGen()); - temp.amount_commitment = rct::pkGen(); - crypto::rand(sizeof(temp.encrypted_amount), temp.encrypted_amount.bytes); - temp.view_tag.data = static_cast(crypto::rand_idx(0)); - return temp; -} -//------------------------------------------------------------------------------------------------------------------- -} //namespace carrot diff --git a/src/carrot_impl/legacy_enote_types.h b/src/carrot_impl/legacy_enote_types.h deleted file mode 100644 index 8ee5fc5fc..000000000 --- a/src/carrot_impl/legacy_enote_types.h +++ /dev/null @@ -1,172 +0,0 @@ -// 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. - -// Seraphis core types. - -#pragma once - -//local headers -#include "carrot_core/core_types.h" -#include "crypto/crypto.h" -#include "ringct/rctTypes.h" - -//third party headers - -//standard headers - -//forward declarations - - -namespace carrot -{ - -//// -// LegacyEnoteV1 (all pre-RingCT enotes, then post-RingCT pre-viewtag coinbase) -// - onetime address -// - cleartext amount -/// -struct LegacyEnoteV1 final -{ - /// Ko - crypto::public_key onetime_address; - /// a - rct::xmr_amount amount; - /// the enote's ephemeral pubkey - crypto::public_key enote_ephemeral_pubkey; - /// t: the enote's index in its transaction - std::uint64_t tx_output_index; -}; - -//// -// LegacyEnoteV2 -// - onetime address -// - amount commitment -// - encrypted amount commitment mask -// - encrypted amount (version 1: 32 bytes) -/// -struct LegacyEnoteV2 final -{ - /// Ko - crypto::public_key onetime_address; - /// C - rct::key amount_commitment; - /// enc(x) - rct::key encrypted_amount_blinding_factor; - /// enc(a) - rct::key encrypted_amount; - /// the enote's ephemeral pubkey - crypto::public_key enote_ephemeral_pubkey; - /// t: the enote's index in its transaction - std::uint64_t tx_output_index; -}; - -//// -// LegacyEnoteV3 -// - onetime address -// - amount commitment -// - encrypted amount (version 2: 8 bytes) -/// -struct LegacyEnoteV3 final -{ - /// Ko - crypto::public_key onetime_address; - /// C - rct::key amount_commitment; - /// enc(a) - encrypted_amount_t encrypted_amount; - /// the enote's ephemeral pubkey - crypto::public_key enote_ephemeral_pubkey; - /// t: the enote's index in its transaction - std::uint64_t tx_output_index; -}; - -//// -// LegacyEnoteV4 (post-viewtag coinbase, also post-viewtag v1 unmixable dust txs) -// - onetime address -// - cleartext amount -// - view tag -/// -struct LegacyEnoteV4 final -{ - /// Ko - crypto::public_key onetime_address; - /// a - rct::xmr_amount amount; - /// view_tag - crypto::view_tag view_tag; - /// the enote's ephemeral pubkey - crypto::public_key enote_ephemeral_pubkey; - /// t: the enote's index in its transaction - std::uint64_t tx_output_index; -}; - -//// -// LegacyEnoteV5 -// - onetime address -// - amount commitment -// - encrypted amount (version 2: 8 bytes) -// - view tag -/// -struct LegacyEnoteV5 final -{ - /// Ko - crypto::public_key onetime_address; - /// C - rct::key amount_commitment; - /// enc(a) - encrypted_amount_t encrypted_amount; - /// view_tag - crypto::view_tag view_tag; - /// the enote's ephemeral pubkey - crypto::public_key enote_ephemeral_pubkey; - /// t: the enote's index in its transaction - std::uint64_t tx_output_index; -}; - -/** -* brief: gen_legacy_enote_v1() - generate a legacy v1 enote (all random) -*/ -LegacyEnoteV1 gen_legacy_enote_v1(); -/** -* brief: gen_legacy_enote_v2() - generate a legacy v2 enote (all random) -*/ -LegacyEnoteV2 gen_legacy_enote_v2(); -/** -* brief: gen_legacy_enote_v3() - generate a legacy v3 enote (all random) -*/ -LegacyEnoteV3 gen_legacy_enote_v3(); -/** -* brief: gen_legacy_enote_v4() - generate a legacy v4 enote (all random) -*/ -LegacyEnoteV4 gen_legacy_enote_v4(); -/** -* brief: gen_legacy_enote_v5() - generate a legacy v5 enote (all random) -*/ -LegacyEnoteV5 gen_legacy_enote_v5(); - -} //namespace carrot From 9a0379c1a1ebec41c0eca3a09bf14e86c03b4287 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Tue, 26 Nov 2024 12:58:22 -0600 Subject: [PATCH 03/14] remove incorrect dead function --- src/carrot_core/address_utils.cpp | 16 --- src/carrot_core/address_utils.h | 18 ---- src/carrot_core/device.h | 161 ++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 34 deletions(-) create mode 100644 src/carrot_core/device.h diff --git a/src/carrot_core/address_utils.cpp b/src/carrot_core/address_utils.cpp index 18bbfd964..09a2bfe81 100644 --- a/src/carrot_core/address_utils.cpp +++ b/src/carrot_core/address_utils.cpp @@ -82,20 +82,4 @@ void make_carrot_address_spend_pubkey(const crypto::public_key &spend_pubkey, rct::pk2rct(spend_pubkey), rct::sk2rct(subaddress_scalar))); } //------------------------------------------------------------------------------------------------------------------- -void make_carrot_address(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, - const crypto::secret_key &k_view, - crypto::public_key &address_spend_pubkey_out) -{ - // K^j_s = k^j_subscal * K_s - crypto::public_key address_spend_pubkey; - make_carrot_address_spend_pubkey(spend_pubkey, s_generate_address, j_major, j_minor, address_spend_pubkey); - - // K^j_v = k_v * K^j_s - address_spend_pubkey_out = rct::rct2pk(rct::scalarmultKey( - rct::pk2rct(address_spend_pubkey), rct::sk2rct(k_view))); -} -//------------------------------------------------------------------------------------------------------------------- } //namespace carrot diff --git a/src/carrot_core/address_utils.h b/src/carrot_core/address_utils.h index 0ecbf850c..4f66725bf 100644 --- a/src/carrot_core/address_utils.h +++ b/src/carrot_core/address_utils.h @@ -92,22 +92,4 @@ void make_carrot_address_spend_pubkey(const crypto::public_key &spend_pubkey, const std::uint32_t j_minor, crypto::public_key &address_spend_pubkey_out); -/** -* brief: make_carrot_address - (K^j_s, K^j_v) -* K^j_s = k^j_subscal * K_s -* K^j_v = k_v K^j_s -* param: spend_pubkey - K_s = k_gi G + k_ps U -* param: s_generate_address - s_ga -* param: j_major - -* param: j_minor - -* param: k_view - k_v -* outparam: address_spend_pubkey_out - K^j_s -*/ -void make_carrot_address(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, - const crypto::secret_key &k_view, - crypto::public_key &address_spend_pubkey_out); - } //namespace carrot diff --git a/src/carrot_core/device.h b/src/carrot_core/device.h new file mode 100644 index 000000000..09eec74fe --- /dev/null +++ b/src/carrot_core/device.h @@ -0,0 +1,161 @@ +// 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. + +//! @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", + code, dev_make, dev_model, func_called, msg); + 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::public_key &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; +}; + +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; +}; + +} //namespace carrot From f9be7571c25f8ade777a4fa00da944b517556dea Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Tue, 26 Nov 2024 15:57:04 -0600 Subject: [PATCH 04/14] scan using a device interface --- src/carrot_core/CMakeLists.txt | 1 + src/carrot_core/account_secrets.cpp | 2 +- src/carrot_core/account_secrets.h | 2 +- src/carrot_core/address_utils.cpp | 2 +- src/carrot_core/address_utils.h | 2 +- src/carrot_core/carrot_enote_scan.cpp | 121 +++++++++++++++++++----- src/carrot_core/carrot_enote_scan.h | 16 +++- src/carrot_core/core_types.cpp | 2 +- src/carrot_core/core_types.h | 2 +- src/carrot_core/destination.h | 2 +- src/carrot_core/device.h | 10 +- src/carrot_core/device_ram_borrowed.cpp | 90 ++++++++++++++++++ src/carrot_core/device_ram_borrowed.h | 86 +++++++++++++++++ src/carrot_core/enote_utils.cpp | 102 +------------------- src/carrot_core/enote_utils.h | 62 +----------- src/carrot_core/hash_functions.cpp | 2 +- src/carrot_core/hash_functions.h | 2 +- src/carrot_core/payment_proposal.cpp | 2 +- src/carrot_core/payment_proposal.h | 2 +- src/carrot_core/transcript_fixed.h | 2 +- tests/unit_tests/carrot_core.cpp | 23 +++-- 21 files changed, 325 insertions(+), 210 deletions(-) create mode 100644 src/carrot_core/device_ram_borrowed.cpp create mode 100644 src/carrot_core/device_ram_borrowed.h diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt index c71de2a68..e81cf1a46 100644 --- a/src/carrot_core/CMakeLists.txt +++ b/src/carrot_core/CMakeLists.txt @@ -32,6 +32,7 @@ set(carrot_core_sources carrot_enote_scan.cpp core_types.cpp destination.cpp + device_ram_borrowed.cpp enote_utils.cpp hash_functions.cpp payment_proposal.cpp) diff --git a/src/carrot_core/account_secrets.cpp b/src/carrot_core/account_secrets.cpp index f393821f6..2f8b16d56 100644 --- a/src/carrot_core/account_secrets.cpp +++ b/src/carrot_core/account_secrets.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/account_secrets.h b/src/carrot_core/account_secrets.h index 460f8beeb..4fef1c55c 100644 --- a/src/carrot_core/account_secrets.h +++ b/src/carrot_core/account_secrets.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/address_utils.cpp b/src/carrot_core/address_utils.cpp index 09a2bfe81..bfa429f31 100644 --- a/src/carrot_core/address_utils.cpp +++ b/src/carrot_core/address_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/address_utils.h b/src/carrot_core/address_utils.h index 4f66725bf..6dbf8928f 100644 --- a/src/carrot_core/address_utils.h +++ b/src/carrot_core/address_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/carrot_enote_scan.cpp b/src/carrot_core/carrot_enote_scan.cpp index 09f6c5d2b..478a71fea 100644 --- a/src/carrot_core/carrot_enote_scan.cpp +++ b/src/carrot_core/carrot_enote_scan.cpp @@ -32,6 +32,7 @@ #include "carrot_enote_scan.h" //local headers +#include "crypto/generators.h" #include "enote_utils.h" #include "ringct/rctOps.h" @@ -44,10 +45,9 @@ namespace carrot { //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- -static bool try_scan_carrot_non_coinbase_no_janus(const CarrotEnoteV1 &enote, +static bool try_scan_carrot_non_coinbase_core(const CarrotEnoteV1 &enote, const std::optional encrypted_payment_id, - const input_context_t &input_context, - const unsigned char s_sender_receiver_unctx[32], + 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, @@ -57,17 +57,6 @@ static bool try_scan_carrot_non_coinbase_no_janus(const CarrotEnoteV1 &enote, CarrotEnoteType &enote_type_out, janus_anchor_t &nominal_janus_anchor_out) { - // if vt' != vt, then FAIL - if (!test_carrot_view_tag(s_sender_receiver_unctx, 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, - enote.enote_ephemeral_pubkey, - input_context, - s_sender_receiver); - // if cannot recompute C_a, then FAIL if (!try_get_carrot_amount(s_sender_receiver, enote.amount_enc, @@ -109,9 +98,68 @@ static bool try_scan_carrot_non_coinbase_no_janus(const CarrotEnoteV1 &enote, } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- +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 crypto::secret_key &k_view, + 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, @@ -166,7 +214,7 @@ bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, payment_id_t dummy_payment_id = null_payment_id; if (!verify_carrot_janus_protection(input_context, enote.onetime_address, - k_view, + k_view_dev, account_spend_pubkey, address_spend_pubkey_out, enote.enote_ephemeral_pubkey, @@ -180,7 +228,7 @@ bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, const std::optional encrypted_payment_id, const crypto::x25519_pubkey &s_sender_receiver_unctx, - const crypto::secret_key &k_view, + 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, @@ -194,12 +242,22 @@ bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, 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_no_janus(enote, + if (!try_scan_carrot_non_coinbase_core(enote, encrypted_payment_id, - input_context, - s_sender_receiver_unctx.data, + s_sender_receiver, sender_extension_g_out, sender_extension_t_out, address_spend_pubkey_out, @@ -213,7 +271,7 @@ bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, // verify Janus attack protection if (!verify_carrot_janus_protection(input_context, enote.onetime_address, - k_view, + k_view_dev, account_spend_pubkey, address_spend_pubkey_out, enote.enote_ephemeral_pubkey, @@ -225,7 +283,7 @@ bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, } //------------------------------------------------------------------------------------------------------------------- bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, - const crypto::secret_key &s_view_balance, + 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, @@ -237,13 +295,26 @@ bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, 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_no_janus(enote, + if (!try_scan_carrot_non_coinbase_core(enote, std::nullopt, - input_context, - to_bytes(s_view_balance), + s_sender_receiver, sender_extension_g_out, sender_extension_t_out, address_spend_pubkey_out, diff --git a/src/carrot_core/carrot_enote_scan.h b/src/carrot_core/carrot_enote_scan.h index 01ae7947c..3d1e29fe7 100644 --- a/src/carrot_core/carrot_enote_scan.h +++ b/src/carrot_core/carrot_enote_scan.h @@ -32,6 +32,7 @@ //local headers #include "carrot_enote_types.h" +#include "device.h" //third party headers @@ -43,9 +44,18 @@ 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 crypto::secret_key &k_view, + 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, @@ -54,7 +64,7 @@ bool try_scan_carrot_coinbase_enote(const CarrotCoinbaseEnoteV1 &enote, bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, const std::optional encrypted_payment_id, const crypto::x25519_pubkey &s_sender_receiver_unctx, - const crypto::secret_key &k_view, + 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, @@ -65,7 +75,7 @@ bool try_scan_carrot_enote_external(const CarrotEnoteV1 &enote, CarrotEnoteType &enote_type_out); bool try_scan_carrot_enote_internal(const CarrotEnoteV1 &enote, - const crypto::secret_key &s_view_balance, + 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, diff --git a/src/carrot_core/core_types.cpp b/src/carrot_core/core_types.cpp index 2d91a1ba7..2145f4dc8 100644 --- a/src/carrot_core/core_types.cpp +++ b/src/carrot_core/core_types.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/core_types.h b/src/carrot_core/core_types.h index d461f4168..8dc8a0e67 100644 --- a/src/carrot_core/core_types.h +++ b/src/carrot_core/core_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/destination.h b/src/carrot_core/destination.h index 53f5cda0e..98a5ea407 100644 --- a/src/carrot_core/destination.h +++ b/src/carrot_core/destination.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/device.h b/src/carrot_core/device.h index 09eec74fe..c305128f7 100644 --- a/src/carrot_core/device.h +++ b/src/carrot_core/device.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // @@ -90,7 +90,7 @@ struct device_error: public std::runtime_error char buf[384]; snprintf(buf, sizeof(buf), "%s %s device error (%d), at %s(): %s", - code, dev_make, dev_model, func_called, msg); + dev_make.c_str(), dev_model.c_str(), code, func_called.c_str(), msg.c_str()); return {buf}; } @@ -119,7 +119,7 @@ struct view_incoming_key_device * 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::public_key &kv8D) const = 0; + crypto::x25519_pubkey &kv8D) const = 0; /** * brief: make_janus_anchor_special - make a janus anchor for "special" enotes @@ -133,6 +133,8 @@ struct view_incoming_key_device 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 @@ -156,6 +158,8 @@ struct view_balance_secret_device 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_utils.cpp b/src/carrot_core/enote_utils.cpp index 9d846592b..72f24cff5 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // @@ -449,7 +449,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, return false; } //------------------------------------------------------------------------------------------------------------------- -bool verify_external_carrot_janus_protection(const janus_anchor_t &nominal_anchor, +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, @@ -480,102 +480,4 @@ bool verify_external_carrot_janus_protection(const janus_anchor_t &nominal_ancho return nominal_enote_ephemeral_pubkey == enote_ephemeral_pubkey; } //------------------------------------------------------------------------------------------------------------------- -bool verify_external_carrot_janus_protection_receiver(const janus_anchor_t &nominal_anchor, - const input_context_t &input_context, - const crypto::public_key &nominal_address_spend_pubkey, - const crypto::public_key &account_spend_pubkey, - const crypto::secret_key &k_view, - const crypto::x25519_pubkey &enote_ephemeral_pubkey, - payment_id_t &nominal_payment_id_inout) -{ - const bool is_subaddress = nominal_address_spend_pubkey != account_spend_pubkey; - - // make K^j_v', given K^j_s' - crypto::public_key nominal_address_view_pubkey; - if (is_subaddress) - nominal_address_view_pubkey = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(nominal_address_spend_pubkey), - rct::sk2rct(k_view))); - else // cryptonote address - nominal_address_view_pubkey = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k_view))); - - // if can recompute D_e with pid', then PASS - if (verify_external_carrot_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_external_carrot_janus_protection(nominal_anchor, - input_context, - nominal_address_spend_pubkey, - nominal_address_view_pubkey, - is_subaddress, - null_payment_id, - enote_ephemeral_pubkey)) - return true; - - // neither D_e recompute attempt passed, so FAIL - return false; -} -//------------------------------------------------------------------------------------------------------------------- -bool verify_special_carrot_janus_protection(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, - const janus_anchor_t &nominal_anchor) -{ - // anchor_sp' = H_16(D_e, input_context, Ko, k_v, K_s) - janus_anchor_t nominal_special_anchor; - make_carrot_janus_anchor_special(enote_ephemeral_pubkey, - input_context, - onetime_address, - k_view, - account_spend_pubkey, - nominal_special_anchor); - - // anchor_sp' ?= anchor' - return nominal_special_anchor == nominal_anchor; -} -//------------------------------------------------------------------------------------------------------------------- -bool verify_carrot_janus_protection(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, - 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) -{ - // try checking for Janus protection, normal external path - if (verify_external_carrot_janus_protection_receiver(nominal_anchor, - input_context, - nominal_address_spend_pubkey, - account_spend_pubkey, - k_view, - enote_ephemeral_pubkey, - nominal_payment_id_inout)) - return true; - - // pid is always null for self-send enotes - nominal_payment_id_inout = null_payment_id; - - // try checking for Janus protection, special path - if (verify_special_carrot_janus_protection(enote_ephemeral_pubkey, - input_context, - onetime_address, - k_view, - account_spend_pubkey, - nominal_anchor)) - return true; - - // neither attempt at checking Janus protection worked - return false; -} -//------------------------------------------------------------------------------------------------------------------- } //namespace carrot diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h index 420b114a0..4165f7a13 100644 --- a/src/carrot_core/enote_utils.h +++ b/src/carrot_core/enote_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // @@ -364,7 +364,7 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, rct::xmr_amount &amount_out, crypto::secret_key &amount_blinding_factor_out); /** - * brief: verify_external_carrot_janus_protection - check normal external enote is Janus safe (i.e. can recompute D_e) + * 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' @@ -374,67 +374,11 @@ bool try_get_carrot_amount(const crypto::hash &s_sender_receiver, * param: enote_ephemeral_pubkey - D_e * return: true if this normal external enote is safe from Janus attacks */ -bool verify_external_carrot_janus_protection(const janus_anchor_t &nominal_anchor, +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); -/** - * brief: verify_external_carrot_janus_protection_receiver - check normal external enote is Janus safe (i.e. can - * recompute D_e), and perhaps set nominal pid to null - * param: nominal_anchor - anchor' - * param: input_context - - * param: nominal_address_spend_pubkey - K^j_s' - * param: account_spend_pubkey - K_s - * param: k_view - k_v - * param: enote_ephemeral_pubkey - D_e - * outparam: nominal_payment_id_inout - pid', first tries recomputing D_e with pid', then sets to null and tries again - * return: true if this normal external enote is safe from Janus attacks - */ -bool verify_external_carrot_janus_protection_receiver(const janus_anchor_t &nominal_anchor, - const input_context_t &input_context, - const crypto::public_key &nominal_address_spend_pubkey, - const crypto::public_key &account_spend_pubkey, - const crypto::secret_key &k_view, - const crypto::x25519_pubkey &enote_ephemeral_pubkey, - payment_id_t &nominal_payment_id_inout); -/** - * brief: verify_special_carrot_janus_protection - check special enote is Janus safe (i.e. can recompute anchor_sp) - * param: enote_ephemeral_pubkey - D_e - * param: input_context - - * param: onetime_address - Ko - * param: k_view - k_v - * param: account_spend_pubkey - K_s - * param: nominal_anchor - anchor' - * return: true if this special enote is safe from Janus attacks - */ -bool verify_special_carrot_janus_protection(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, - const janus_anchor_t &nominal_anchor); -/** - * brief: verify_carrot_janus_protection - check whether a received Carrot enote is Janus protected (all paths) - * param: input_context - - * param: onetime_address - Ko - * param: k_view - k_v - * param: account_spend_pubkey - K_s - * param: nominal_address_spend_pubkey - K^j_s' - * param: enote_ephemeral_pubkey - D_e - * param: nominal_anchor - anchor' - * outparam: nominal_payment_id_inout - pass possible pid, set to null if the sender didn't explicitly bind to that pid - * return: true if this received enote is safe from Janus attacks - */ -bool verify_carrot_janus_protection(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, - 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); - } //namespace carrot diff --git a/src/carrot_core/hash_functions.cpp b/src/carrot_core/hash_functions.cpp index 2b59960c7..8eca353d1 100644 --- a/src/carrot_core/hash_functions.cpp +++ b/src/carrot_core/hash_functions.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/hash_functions.h b/src/carrot_core/hash_functions.h index 6ddc09529..cb6baf830 100644 --- a/src/carrot_core/hash_functions.h +++ b/src/carrot_core/hash_functions.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 7a0472460..67f73757d 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 874cc491f..d4250d25a 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/src/carrot_core/transcript_fixed.h b/src/carrot_core/transcript_fixed.h index 082bbb7fa..8dcf2963e 100644 --- a/src/carrot_core/transcript_fixed.h +++ b/src/carrot_core/transcript_fixed.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2024, The Monero Project +// Copyright (c) 2024, The Monero Project // // All rights reserved. // diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 88817d4ef..9f250511e 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -31,6 +31,7 @@ #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/payment_proposal.h" #include "crypto/crypto.h" @@ -53,6 +54,12 @@ struct mock_carrot_keys 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; @@ -183,7 +190,7 @@ TEST(carrot_core, main_address_normal_scan_completeness) const bool scan_success = try_scan_carrot_enote_external(enote, encrypted_payment_id, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, @@ -263,7 +270,7 @@ TEST(carrot_core, subaddress_normal_scan_completeness) const bool scan_success = try_scan_carrot_enote_external(enote, encrypted_payment_id, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, @@ -351,7 +358,7 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) const bool scan_success = try_scan_carrot_enote_external(enote, encrypted_payment_id, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, @@ -429,7 +436,7 @@ TEST(carrot_core, main_address_special_scan_completeness) const bool scan_success = try_scan_carrot_enote_external(enote, std::nullopt, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, @@ -516,7 +523,7 @@ TEST(carrot_core, subaddress_special_scan_completeness) const bool scan_success = try_scan_carrot_enote_external(enote, std::nullopt, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, @@ -599,7 +606,7 @@ TEST(carrot_core, main_address_internal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; CarrotEnoteType recovered_enote_type; const bool scan_success = try_scan_carrot_enote_internal(enote, - keys.s_view_balance, + keys.s_view_balance_dev, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -674,7 +681,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness) crypto::secret_key recovered_amount_blinding_factor; CarrotEnoteType recovered_enote_type; const bool scan_success = try_scan_carrot_enote_internal(enote, - keys.s_view_balance, + keys.s_view_balance_dev, recovered_sender_extension_g, recovered_sender_extension_t, recovered_address_spend_pubkey, @@ -745,7 +752,7 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) crypto::public_key recovered_address_spend_pubkey; const bool scan_success = try_scan_carrot_coinbase_enote(enote, s_sender_receiver_unctx, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, recovered_sender_extension_g, recovered_sender_extension_t, From d8d07334ce053eb6eac8027f2f928f13af221dc1 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Tue, 26 Nov 2024 16:15:10 -0600 Subject: [PATCH 05/14] hopefully better mac compiling --- src/carrot_core/account_secrets.cpp | 10 +++++----- src/carrot_core/address_utils.cpp | 4 ++-- src/carrot_core/enote_utils.cpp | 20 ++++++++++---------- src/carrot_core/transcript_fixed.h | 10 ++++++---- tests/unit_tests/carrot_transcript_fixed.cpp | 6 +++--- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/carrot_core/account_secrets.cpp b/src/carrot_core/account_secrets.cpp index 2f8b16d56..c7c8652fb 100644 --- a/src/carrot_core/account_secrets.cpp +++ b/src/carrot_core/account_secrets.cpp @@ -51,7 +51,7 @@ void make_carrot_provespend_key(const crypto::secret_key &s_master, { // 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)); + 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, @@ -59,7 +59,7 @@ void make_carrot_viewbalance_secret(const crypto::secret_key &s_master, { // 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)); + 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, @@ -67,7 +67,7 @@ void make_carrot_generateimage_key(const crypto::secret_key &s_view_balance, { // 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)); + 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, @@ -75,7 +75,7 @@ void make_carrot_viewincoming_key(const crypto::secret_key &s_view_balance, { // 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)); + 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, @@ -83,7 +83,7 @@ void make_carrot_generateaddress_secret(const crypto::secret_key &s_view_balance { // 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)); + 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, diff --git a/src/carrot_core/address_utils.cpp b/src/carrot_core/address_utils.cpp index bfa429f31..d27479144 100644 --- a/src/carrot_core/address_utils.cpp +++ b/src/carrot_core/address_utils.cpp @@ -52,7 +52,7 @@ void make_carrot_index_extension_generator(const crypto::secret_key &s_generate_ { // 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); + derive_bytes_32(transcript.data(), transcript.size(), &s_generate_address, &address_generator_out); } //------------------------------------------------------------------------------------------------------------------- void make_carrot_subaddress_scalar(const crypto::public_key &spend_pubkey, @@ -64,7 +64,7 @@ void make_carrot_subaddress_scalar(const crypto::public_key &spend_pubkey, // 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); + 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, diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index 72f24cff5..98c82ad39 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -88,7 +88,7 @@ void make_carrot_enote_ephemeral_privkey(const janus_anchor_t &anchor_norm, // 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); + 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, @@ -163,7 +163,7 @@ void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], { // 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); + 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) @@ -189,7 +189,7 @@ void make_carrot_sender_receiver_secret(const unsigned char s_sender_receiver_un // 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); + 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, @@ -198,7 +198,7 @@ void make_carrot_onetime_address_extension_g(const crypto::hash &s_sender_receiv { // 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); + 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, @@ -207,7 +207,7 @@ void make_carrot_onetime_address_extension_t(const crypto::hash &s_sender_receiv { // 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); + 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, @@ -253,7 +253,7 @@ void make_carrot_amount_blinding_factor(const crypto::hash &s_sender_receiver, // 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); + 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, @@ -262,7 +262,7 @@ void make_carrot_anchor_encryption_mask(const crypto::hash &s_sender_receiver, { // 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); + 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, @@ -295,7 +295,7 @@ void make_carrot_amount_encryption_mask(const crypto::hash &s_sender_receiver, { // 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); + 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, @@ -328,7 +328,7 @@ void make_carrot_payment_id_encryption_mask(const crypto::hash &s_sender_receive { // 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); + 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, @@ -365,7 +365,7 @@ void make_carrot_janus_anchor_special(const crypto::x25519_pubkey &enote_ephemer // 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); + derive_bytes_16(transcript.data(), transcript.size(), &k_view, &anchor_special_out); } //------------------------------------------------------------------------------------------------------------------- void recover_address_spend_pubkey(const crypto::public_key &onetime_address, diff --git a/src/carrot_core/transcript_fixed.h b/src/carrot_core/transcript_fixed.h index 8dcf2963e..3179cbe23 100644 --- a/src/carrot_core/transcript_fixed.h +++ b/src/carrot_core/transcript_fixed.h @@ -77,9 +77,6 @@ template class SpFixedTranscript final { public: -//public static member variables - static constexpr std::size_t size = 1 + SpFixedTranscript::domain_sep_size() + detail::sizeof_sum(); - //constructors /// normal constructor SpFixedTranscript(const Ts&... args) @@ -102,6 +99,11 @@ public: //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() { @@ -171,7 +173,7 @@ private: //member variables /// the transcript buffer - unsigned char m_transcript[size]; + unsigned char m_transcript[size()]; }; template diff --git a/tests/unit_tests/carrot_transcript_fixed.cpp b/tests/unit_tests/carrot_transcript_fixed.cpp index e708a8ac7..860619a83 100644 --- a/tests/unit_tests/carrot_transcript_fixed.cpp +++ b/tests/unit_tests/carrot_transcript_fixed.cpp @@ -46,15 +46,15 @@ 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); + 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); + 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); + EXPECT_EQ(1 + 15 + 33 + 32, transcript_vt.size()); } From 1604edf3016a82e96d7599d1e5110fb7ddcee845 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Tue, 26 Nov 2024 16:53:36 -0600 Subject: [PATCH 06/14] construct enotes using device interface --- src/carrot_core/payment_proposal.cpp | 130 +++++++++++++++++---------- src/carrot_core/payment_proposal.h | 9 +- tests/unit_tests/carrot_core.cpp | 8 +- 3 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index 67f73757d..639cff960 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -93,7 +93,48 @@ static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &propos } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- -static void get_output_proposal_parts(const unsigned char s_sender_receiver_unctx[32], +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, @@ -110,38 +151,28 @@ static void get_output_proposal_parts(const unsigned char s_sender_receiver_unct 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, + make_carrot_sender_receiver_secret(s_sender_receiver_unctx.data, enote_ephemeral_pubkey, input_context, s_sender_receiver_out); - // 2. 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_out, - enote_type, - amount_blinding_factor_out); - - // 3. C_a = k_a G + a H - amount_commitment_out = rct::commit(amount, rct::sk2rct(amount_blinding_factor_out)); - - // 4. 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_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); - - // 5. a_enc = a XOR m_a - encrypted_amount_out = encrypt_carrot_amount(amount, - s_sender_receiver_out, - onetime_address_out); - - // 6. pid_enc = pid XOR m_pid - encrypted_payment_id_out = encrypt_legacy_payment_id(payment_id, s_sender_receiver_out, onetime_address_out); + onetime_address_out, + encrypted_amount_out, + encrypted_payment_id_out); - // 7. view tag: vt = H_3(s_sr || input_context || Ko) - make_carrot_view_tag(s_sender_receiver_unctx, input_context, onetime_address_out, view_tag_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); } //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- @@ -207,14 +238,14 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, rct::key dummy_amount_commitment; encrypted_amount_t dummy_encrypted_amount; encrypted_payment_id_t dummy_encrypted_payment_id; - get_output_proposal_parts(s_sender_receiver_unctx.data, + 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, + true, // coinbase_amount_commitment s_sender_receiver, dummy_amount_blinding_factor, dummy_amount_commitment, @@ -257,14 +288,14 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, // 4. build the output enote address pieces crypto::hash s_sender_receiver; auto q_wiper = auto_wiper(s_sender_receiver); - get_output_proposal_parts(s_sender_receiver_unctx.data, + 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_ephemeral_pubkey, input_context, - false, + false, // coinbase_amount_commitment s_sender_receiver, amount_blinding_factor_out, output_enote_out.amount_commitment, @@ -284,7 +315,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, } //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, - const crypto::secret_key &k_view, + const view_incoming_key_device &k_view_dev, const crypto::public_key &primary_address_spend_pubkey, const crypto::key_image &tx_first_key_image, CarrotEnoteV1 &output_enote_out, @@ -300,21 +331,21 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo // 3. s_sr = 8 * k_v * D_e crypto::x25519_pubkey s_sender_receiver_unctx; - make_carrot_uncontextualized_shared_key_receiver(k_view, - proposal.enote_ephemeral_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_output_proposal_parts(s_sender_receiver_unctx.data, + 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, + false, // coinbase_amount_commitment s_sender_receiver, amount_blinding_factor_out, output_enote_out.amount_commitment, @@ -325,10 +356,9 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo // 5. make special janus anchor: anchor_sp = H_16(D_e, input_context, Ko, k_v, K_s) janus_anchor_t janus_anchor_special; - make_carrot_janus_anchor_special(proposal.enote_ephemeral_pubkey, + k_view_dev.make_janus_anchor_special(proposal.enote_ephemeral_pubkey, input_context, output_enote_out.onetime_address, - k_view, primary_address_spend_pubkey, janus_anchor_special); @@ -344,7 +374,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo } //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, - const crypto::secret_key &s_view_balance, + const view_balance_secret_device &s_view_balance_dev, const crypto::key_image &tx_first_key_image, CarrotEnoteV1 &output_enote_out, rct::xmr_amount &amount_out, @@ -353,27 +383,35 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop // 1. sanity checks // @TODO - // 2. input context: input_context = "R" || KI_1 + // 2. input_context = "R" || KI_1 input_context_t input_context; make_carrot_input_context(tx_first_key_image, input_context); - // 3. build the output enote address pieces + // 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(to_bytes(s_view_balance), + 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, - s_sender_receiver, + false, // coinbase_amount_commitment amount_blinding_factor_out, output_enote_out.amount_commitment, output_enote_out.onetime_address, output_enote_out.amount_enc, - dummy_encrypted_payment_id, + 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.onetime_address, output_enote_out.view_tag); // 4. generate random encrypted anchor diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index d4250d25a..77544a6c0 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -35,6 +35,7 @@ #include "carrot_enote_types.h" #include "crypto/x25519.h" #include "destination.h" +#include "device.h" #include "ringct/rctTypes.h" //third party headers @@ -120,7 +121,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, /** * brief: get_output_proposal_v1 - convert the carrot proposal to an output proposal (external selfsend) * param: proposal - -* param: k_view - +* param: k_view_dev - * param: primary_address_spend_pubkey - * param: tx_first_key_image - * outparam: output_enote_out - @@ -128,7 +129,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, * outparam: amount_blinding_factor_out - used to open commitment C_a */ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, - const crypto::secret_key &k_view, + const view_incoming_key_device &k_view_dev, const crypto::public_key &primary_address_spend_pubkey, const crypto::key_image &tx_first_key_image, CarrotEnoteV1 &output_enote_out, @@ -137,7 +138,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo /** * brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) * param: proposal - -* param: s_view_balance - +* param: s_view_balance_dev - * param: primary_address_spend_pubkey - * param: tx_first_key_image - * outparam: output_enote_out - @@ -146,7 +147,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo * outparam: amount_blinding_factor_out - used to open commitment C_a */ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &proposal, - const crypto::secret_key &s_view_balance, + const view_balance_secret_device &s_view_balance_dev, const crypto::key_image &tx_first_key_image, CarrotEnoteV1 &output_enote_out, rct::xmr_amount &amount_out, diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 9f250511e..2482cd7d2 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -411,7 +411,7 @@ TEST(carrot_core, main_address_special_scan_completeness) rct::xmr_amount amount; crypto::secret_key amount_blinding_factor; get_output_proposal_special_v1(proposal, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, tx_first_key_image, enote, @@ -498,7 +498,7 @@ TEST(carrot_core, subaddress_special_scan_completeness) rct::xmr_amount amount; crypto::secret_key amount_blinding_factor; get_output_proposal_special_v1(proposal, - keys.k_view, + keys.k_view_dev, keys.account_spend_pubkey, tx_first_key_image, enote, @@ -590,7 +590,7 @@ TEST(carrot_core, main_address_internal_scan_completeness) rct::xmr_amount amount; crypto::secret_key amount_blinding_factor; get_output_proposal_internal_v1(proposal, - keys.s_view_balance, + keys.s_view_balance_dev, tx_first_key_image, enote, amount, @@ -665,7 +665,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness) rct::xmr_amount amount; crypto::secret_key amount_blinding_factor; get_output_proposal_internal_v1(proposal, - keys.s_view_balance, + keys.s_view_balance_dev, tx_first_key_image, enote, amount, From 965a5d88c92bd9c6823a175f6cd53ca369a16a73 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 10:27:41 -0600 Subject: [PATCH 07/14] draft output set finalization code --- src/carrot_core/CMakeLists.txt | 1 + src/carrot_core/config.h | 3 + src/carrot_core/output_set_finalization.cpp | 228 ++++++++++++++++++++ src/carrot_core/output_set_finalization.h | 99 +++++++++ src/carrot_core/payment_proposal.cpp | 97 ++++----- src/carrot_core/payment_proposal.h | 39 ++-- tests/unit_tests/carrot_core.cpp | 165 +++++++------- 7 files changed, 471 insertions(+), 161 deletions(-) create mode 100644 src/carrot_core/output_set_finalization.cpp create mode 100644 src/carrot_core/output_set_finalization.h diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt index e81cf1a46..299cc88b5 100644 --- a/src/carrot_core/CMakeLists.txt +++ b/src/carrot_core/CMakeLists.txt @@ -35,6 +35,7 @@ set(carrot_core_sources 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}") diff --git a/src/carrot_core/config.h b/src/carrot_core/config.h index bf398ecb0..c15100c56 100644 --- a/src/carrot_core/config.h +++ b/src/carrot_core/config.h @@ -66,4 +66,7 @@ static constexpr const unsigned char CARROT_DOMAIN_SEP_GENERATE_ADDRESS_SECRET[] 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/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp new file mode 100644 index 000000000..7f39815a0 --- /dev/null +++ b/src/carrot_core/output_set_finalization.cpp @@ -0,0 +1,228 @@ +// 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 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"); + + // 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 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 + for (const CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals) + { + get_output_proposal_internal_v1(selfsend_payment_proposal, + s_view_balance_dev, + tx_first_key_image, + tools::add_element(output_enote_proposals_out)); + } + + // 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..90da62f75 --- /dev/null +++ b/src/carrot_core/output_set_finalization.h @@ -0,0 +1,99 @@ +// 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 payment proposals into output enote proposals + */ +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 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 index 639cff960..e09f65f4b 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -84,7 +84,7 @@ static void get_normal_proposal_ecdh_parts(const CarrotPaymentProposalV1 &propos const crypto::secret_key enote_ephemeral_privkey = get_enote_ephemeral_privkey(proposal, input_context); // 2. make D_e - get_enote_ephemeral_pubkey(proposal, input_context, enote_ephemeral_pubkey_out); + 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, @@ -191,22 +191,29 @@ bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentPro a.enote_ephemeral_pubkey == b.enote_ephemeral_pubkey; } //------------------------------------------------------------------------------------------------------------------- -void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, - const input_context_t &input_context, - crypto::x25519_pubkey &enote_ephemeral_pubkey_out) +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_out); + enote_ephemeral_pubkey); else // D_e = d_e B make_carrot_enote_ephemeral_pubkey_cryptonote(enote_ephemeral_privkey, - enote_ephemeral_pubkey_out); + enote_ephemeral_pubkey); + + return enote_ephemeral_pubkey; } //------------------------------------------------------------------------------------------------------------------- void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, @@ -266,10 +273,8 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - CarrotEnoteV1 &output_enote_out, - encrypted_payment_id_t &encrypted_payment_id_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out) + RCTOutputEnoteProposal &output_enote_out, + encrypted_payment_id_t &encrypted_payment_id_out) { // 1. sanity checks CHECK_AND_ASSERT_THROW_MES(proposal.randomness != null_anchor, @@ -283,7 +288,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, 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, + output_enote_out.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); // 4. build the output enote address pieces @@ -293,34 +298,32 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, proposal.destination.payment_id, proposal.amount, CarrotEnoteType::PAYMENT, - output_enote_out.enote_ephemeral_pubkey, + output_enote_out.enote.enote_ephemeral_pubkey, input_context, false, // coinbase_amount_commitment s_sender_receiver, - amount_blinding_factor_out, - output_enote_out.amount_commitment, - output_enote_out.onetime_address, - output_enote_out.amount_enc, + 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.view_tag); + output_enote_out.enote.view_tag); // 5. anchor_enc = anchor XOR m_anchor - output_enote_out.anchor_enc = encrypt_carrot_anchor(proposal.randomness, + output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(proposal.randomness, s_sender_receiver, - output_enote_out.onetime_address); + output_enote_out.enote.onetime_address); // 6. save the amount and first key image - amount_out = proposal.amount; - output_enote_out.tx_first_key_image = tx_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 &primary_address_spend_pubkey, const crypto::key_image &tx_first_key_image, - CarrotEnoteV1 &output_enote_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out) + RCTOutputEnoteProposal &output_enote_out) { // 1. sanity checks // @TODO @@ -347,38 +350,36 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo input_context, false, // coinbase_amount_commitment s_sender_receiver, - amount_blinding_factor_out, - output_enote_out.amount_commitment, - output_enote_out.onetime_address, - output_enote_out.amount_enc, + 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.view_tag); + 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.onetime_address, + output_enote_out.enote.onetime_address, primary_address_spend_pubkey, janus_anchor_special); // 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor - output_enote_out.anchor_enc = encrypt_carrot_anchor(janus_anchor_special, + output_enote_out.enote.anchor_enc = encrypt_carrot_anchor(janus_anchor_special, s_sender_receiver, - output_enote_out.onetime_address); + output_enote_out.enote.onetime_address); // 7. save the enote ephemeral pubkey, first tx key image, and amount - output_enote_out.enote_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; - output_enote_out.tx_first_key_image = tx_first_key_image; - amount_out = proposal.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, - CarrotEnoteV1 &output_enote_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out) + RCTOutputEnoteProposal &output_enote_out) { // 1. sanity checks // @TODO @@ -403,24 +404,24 @@ void get_output_proposal_internal_v1(const CarrotPaymentProposalSelfSendV1 &prop proposal.enote_ephemeral_pubkey, input_context, false, // coinbase_amount_commitment - amount_blinding_factor_out, - output_enote_out.amount_commitment, - output_enote_out.onetime_address, - output_enote_out.amount_enc, + 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.onetime_address, - output_enote_out.view_tag); + output_enote_out.enote.onetime_address, + output_enote_out.enote.view_tag); // 4. generate random encrypted anchor - output_enote_out.anchor_enc = gen_janus_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_ephemeral_pubkey = proposal.enote_ephemeral_pubkey; - output_enote_out.tx_first_key_image = tx_first_key_image; - amount_out = proposal.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, diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 77544a6c0..04fa0bcdb 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -79,20 +79,31 @@ struct CarrotPaymentProposalSelfSendV1 final 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 - -* outparam: enote_ephemeral_pubkey_out - +* return: D_e */ -void get_enote_ephemeral_pubkey(const CarrotPaymentProposalV1 &proposal, - const input_context_t &input_context, - crypto::x25519_pubkey &enote_ephemeral_pubkey_out); +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 - @@ -109,15 +120,11 @@ void get_coinbase_output_proposal_v1(const CarrotPaymentProposalV1 &proposal, * param: tx_first_key_image - * outparam: output_enote_out - * outparam: encrypted_payment_id_out - pid_enc -* outparam: amount_out - used to open commitment C_a -* outparam: amount_blinding_factor_out - used to open commitment C_a */ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, const crypto::key_image &tx_first_key_image, - CarrotEnoteV1 &output_enote_out, - encrypted_payment_id_t &encrypted_payment_id_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out); + 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 - @@ -125,16 +132,12 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, * param: primary_address_spend_pubkey - * param: tx_first_key_image - * outparam: output_enote_out - -* outparam: amount_out - used to open commitment C_a -* outparam: amount_blinding_factor_out - used to open commitment C_a */ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, const crypto::public_key &primary_address_spend_pubkey, const crypto::key_image &tx_first_key_image, - CarrotEnoteV1 &output_enote_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out); + RCTOutputEnoteProposal &output_enote_out); /** * brief: get_output_proposal_internal_v1 - convert the carrot proposal to an output proposal (internal) * param: proposal - @@ -143,15 +146,11 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo * param: tx_first_key_image - * outparam: output_enote_out - * outparam: partial_memo_out - -* outparam: amount_out - used to open commitment C_a -* outparam: amount_blinding_factor_out - used to open commitment C_a */ 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, - CarrotEnoteV1 &output_enote_out, - rct::xmr_amount &amount_out, - crypto::secret_key &amount_blinding_factor_out); + RCTOutputEnoteProposal &output_enote_out); /** * brief: gen_jamtis_payment_proposal_v1 - generate a random proposal * param: is_subaddress - whether to generate a proposal to subaddress diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 2482cd7d2..165aeb6ff 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -161,23 +161,20 @@ TEST(carrot_core, main_address_normal_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; + RCTOutputEnoteProposal enote_proposal; encrypted_payment_id_t encrypted_payment_id; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; get_output_proposal_normal_v1(proposal, tx_first_key_image, - enote, - encrypted_payment_id, - amount, - amount_blinding_factor); + enote_proposal, + encrypted_payment_id); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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.enote_ephemeral_pubkey, + enote_proposal.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); crypto::secret_key recovered_sender_extension_g; @@ -187,7 +184,7 @@ TEST(carrot_core, main_address_normal_scan_completeness) 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, + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, keys.k_view_dev, @@ -204,8 +201,8 @@ TEST(carrot_core, main_address_normal_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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); @@ -215,7 +212,7 @@ TEST(carrot_core, main_address_normal_scan_completeness) rct::rct2sk(rct::I), recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, subaddress_normal_scan_completeness) @@ -241,23 +238,20 @@ TEST(carrot_core, subaddress_normal_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; + RCTOutputEnoteProposal enote_proposal; encrypted_payment_id_t encrypted_payment_id; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; get_output_proposal_normal_v1(proposal, tx_first_key_image, - enote, - encrypted_payment_id, - amount, - amount_blinding_factor); + enote_proposal, + encrypted_payment_id); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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.enote_ephemeral_pubkey, + enote_proposal.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); crypto::secret_key recovered_sender_extension_g; @@ -267,7 +261,7 @@ TEST(carrot_core, subaddress_normal_scan_completeness) 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, + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, keys.k_view_dev, @@ -284,8 +278,8 @@ TEST(carrot_core, subaddress_normal_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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); @@ -308,7 +302,7 @@ TEST(carrot_core, subaddress_normal_scan_completeness) subaddr_scalar, recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, integrated_address_normal_scan_completeness) @@ -329,23 +323,20 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; + RCTOutputEnoteProposal enote_proposal; encrypted_payment_id_t encrypted_payment_id; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; get_output_proposal_normal_v1(proposal, tx_first_key_image, - enote, - encrypted_payment_id, - amount, - amount_blinding_factor); + enote_proposal, + encrypted_payment_id); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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.enote_ephemeral_pubkey, + enote_proposal.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); crypto::secret_key recovered_sender_extension_g; @@ -355,7 +346,7 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) 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, + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, encrypted_payment_id, s_sender_receiver_unctx, keys.k_view_dev, @@ -372,8 +363,8 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination.address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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); @@ -383,7 +374,7 @@ TEST(carrot_core, integrated_address_normal_scan_completeness) rct::rct2sk(rct::I), recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, main_address_special_scan_completeness) @@ -407,23 +398,20 @@ TEST(carrot_core, main_address_special_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; + RCTOutputEnoteProposal enote_proposal; get_output_proposal_special_v1(proposal, keys.k_view_dev, keys.account_spend_pubkey, tx_first_key_image, - enote, - amount, - amount_blinding_factor); + enote_proposal); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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.enote_ephemeral_pubkey, + enote_proposal.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); crypto::secret_key recovered_sender_extension_g; @@ -433,7 +421,7 @@ TEST(carrot_core, main_address_special_scan_completeness) 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, + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, std::nullopt, s_sender_receiver_unctx, keys.k_view_dev, @@ -450,8 +438,8 @@ TEST(carrot_core, main_address_special_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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); @@ -461,7 +449,7 @@ TEST(carrot_core, main_address_special_scan_completeness) rct::rct2sk(rct::I), recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } } //---------------------------------------------------------------------------------------------------------------------- @@ -494,23 +482,20 @@ TEST(carrot_core, subaddress_special_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; + RCTOutputEnoteProposal enote_proposal; get_output_proposal_special_v1(proposal, keys.k_view_dev, keys.account_spend_pubkey, tx_first_key_image, - enote, - amount, - amount_blinding_factor); + enote_proposal); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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.enote_ephemeral_pubkey, + enote_proposal.enote.enote_ephemeral_pubkey, s_sender_receiver_unctx); crypto::secret_key recovered_sender_extension_g; @@ -520,7 +505,7 @@ TEST(carrot_core, subaddress_special_scan_completeness) 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, + const bool scan_success = try_scan_carrot_enote_external(enote_proposal.enote, std::nullopt, s_sender_receiver_unctx, keys.k_view_dev, @@ -537,8 +522,8 @@ TEST(carrot_core, subaddress_special_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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); @@ -555,13 +540,13 @@ TEST(carrot_core, subaddress_special_scan_completeness) 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.onetime_address)); + enote_proposal.enote.onetime_address)); } } //---------------------------------------------------------------------------------------------------------------------- @@ -586,18 +571,15 @@ TEST(carrot_core, main_address_internal_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; + RCTOutputEnoteProposal enote_proposal; get_output_proposal_internal_v1(proposal, keys.s_view_balance_dev, tx_first_key_image, - enote, - amount, - amount_blinding_factor); + enote_proposal); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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; @@ -605,7 +587,7 @@ TEST(carrot_core, main_address_internal_scan_completeness) 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, + 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, @@ -618,8 +600,8 @@ TEST(carrot_core, main_address_internal_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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 @@ -628,7 +610,7 @@ TEST(carrot_core, main_address_internal_scan_completeness) rct::rct2sk(rct::I), recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } } //---------------------------------------------------------------------------------------------------------------------- @@ -661,18 +643,15 @@ TEST(carrot_core, subaddress_internal_scan_completeness) const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); - CarrotEnoteV1 enote; - rct::xmr_amount amount; - crypto::secret_key amount_blinding_factor; + RCTOutputEnoteProposal enote_proposal; get_output_proposal_internal_v1(proposal, keys.s_view_balance_dev, tx_first_key_image, - enote, - amount, - amount_blinding_factor); + enote_proposal); - ASSERT_EQ(proposal.amount, amount); - ASSERT_EQ(enote.amount_commitment, rct::commit(amount, rct::sk2rct(amount_blinding_factor))); + 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; @@ -680,7 +659,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness) 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, + 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, @@ -693,8 +672,8 @@ TEST(carrot_core, subaddress_internal_scan_completeness) // check recovered data EXPECT_EQ(proposal.destination_address_spend_pubkey, recovered_address_spend_pubkey); - EXPECT_EQ(amount, recovered_amount); - EXPECT_EQ(amount_blinding_factor, recovered_amount_blinding_factor); + 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 @@ -716,7 +695,7 @@ TEST(carrot_core, subaddress_internal_scan_completeness) subaddr_scalar, recovered_sender_extension_g, recovered_sender_extension_t, - enote.onetime_address)); + enote_proposal.enote.onetime_address)); } } //---------------------------------------------------------------------------------------------------------------------- From 2a638842a4ca8360ac6f787923c50e916cb06f34 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 11:28:18 -0600 Subject: [PATCH 08/14] better assertions of crypto assumptions --- src/carrot_core/enote_utils.cpp | 27 +++-- src/carrot_core/output_set_finalization.cpp | 16 +++ tests/unit_tests/carrot_core.cpp | 110 ++++++++++++++++++++ 3 files changed, 146 insertions(+), 7 deletions(-) diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index 98c82ad39..c6a40f79c 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -125,16 +125,29 @@ bool make_carrot_uncontextualized_shared_key_receiver(const crypto::secret_key & // @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 D_e_in_ed25519_p3; - if (ge_fromx25519_vartime(&D_e_in_ed25519_p3, enote_ephemeral_pubkey.data) != 0) + 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; - // serialize K_e - crypto::public_key D_e_in_ed25519; - ge_p3_tobytes(to_bytes(D_e_in_ed25519), &D_e_in_ed25519_p3); + // deserialize s_sr + ge_p3 s_sr_in_ed25519_p3; + ge_frombytes_vartime(&s_sr_in_ed25519_p3, to_bytes(s_sr_in_ed25519)); - // do ECDH exchange 8 k_v D_e - return make_carrot_uncontextualized_shared_key_sender(k_view, D_e_in_ed25519, s_sender_receiver_unctx_out); + // 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, diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp index 7f39815a0..e05654492 100644 --- a/src/carrot_core/output_set_finalization.cpp +++ b/src/carrot_core/output_set_finalization.cpp @@ -171,6 +171,22 @@ void get_output_enote_proposals(std::vector &&normal_pa 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); diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 165aeb6ff..0313fe5e4 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -146,6 +146,27 @@ TEST(carrot_core, ECDH_subaddress_completeness) 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, main_address_normal_scan_completeness) { const mock_carrot_keys keys = mock_carrot_keys::generate(); @@ -751,3 +772,92 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- +/* +TEST(carrot_core, subaddress_transfer_2out_completeness) +{ + + const mock_carrot_keys alice = mock_carrot_keys::generate(); + const mock_carrot_keys bob = mock_carrot_keys::generate(); + + const uint32_t alice_j_major = crypto::rand(); + const uint32_t alice_j_minor = crypto::rand(); + CarrotDestinationV1 alice_address; + make_carrot_subaddress_v1(alice.account_spend_pubkey, + alice.main_address_view_pubkey, + alice.s_generate_address, + alice_j_major, + alice_j_minor, + alice_address); + + const uint32_t bob_j_major = crypto::rand(); + const uint32_t bob_j_minor = crypto::rand(); + CarrotDestinationV1 bob_address; + make_carrot_subaddress_v1(bob.account_spend_pubkey, + bob.main_address_view_pubkey, + bob.s_generate_address, + bob_j_major, + bob_j_minor, + bob_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)); +}*/ +//---------------------------------------------------------------------------------------------------------------------- From 995484b1e08adfca025121e2226154e883bdc0fc Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 11:32:55 -0600 Subject: [PATCH 09/14] test catch ECDH small order points --- tests/unit_tests/carrot_core.cpp | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 0313fe5e4..af2eb541c 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -40,6 +40,41 @@ 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 @@ -167,6 +202,17 @@ TEST(carrot_core, ECDH_mx25519_convergence) 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(); From 36cb2e58468af1783be206de21d8fe2f6c1930ad Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 13:31:48 -0600 Subject: [PATCH 10/14] test get_output_enote_proposals --- tests/unit_tests/carrot_core.cpp | 231 +++++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 56 deletions(-) diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index af2eb541c..8c19890b5 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -33,6 +33,7 @@ #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" @@ -142,6 +143,80 @@ static bool can_open_fcmp_onetime_address(const crypto::secret_key &k_prove_spen } //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- +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()); @@ -818,10 +893,8 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- -/* -TEST(carrot_core, subaddress_transfer_2out_completeness) +TEST(carrot_core, sub2sub_transfer_2out_completeness) { - const mock_carrot_keys alice = mock_carrot_keys::generate(); const mock_carrot_keys bob = mock_carrot_keys::generate(); @@ -829,7 +902,7 @@ TEST(carrot_core, subaddress_transfer_2out_completeness) const uint32_t alice_j_minor = crypto::rand(); CarrotDestinationV1 alice_address; make_carrot_subaddress_v1(alice.account_spend_pubkey, - alice.main_address_view_pubkey, + alice.account_view_pubkey, alice.s_generate_address, alice_j_major, alice_j_minor, @@ -839,71 +912,117 @@ TEST(carrot_core, subaddress_transfer_2out_completeness) const uint32_t bob_j_minor = crypto::rand(); CarrotDestinationV1 bob_address; make_carrot_subaddress_v1(bob.account_spend_pubkey, - bob.main_address_view_pubkey, + bob.account_view_pubkey, bob.s_generate_address, bob_j_major, bob_j_minor, bob_address); - const CarrotPaymentProposalV1 proposal = CarrotPaymentProposalV1{ - .destination = main_address, - .amount = crypto::rand(), + 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); + + const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ + .destination = bob_address, + .amount = crypto::rand_idx(1000000), .randomness = gen_janus_anchor() }; - const crypto::key_image tx_first_key_image = rct::rct2ki(rct::pkGen()); + 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) + }; - RCTOutputEnoteProposal enote_proposal; + std::vector enote_proposals; encrypted_payment_id_t encrypted_payment_id; - get_output_proposal_normal_v1(proposal, + get_output_enote_proposals({bob_payment_proposal}, + {alice_payment_proposal}, + alice.s_view_balance_dev, tx_first_key_image, - enote_proposal, + enote_proposals, 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); + ASSERT_EQ(2, enote_proposals.size()); // 2-out tx - // 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); + // collect enotes + std::vector enotes; + for (const RCTOutputEnoteProposal &enote_proposal : enote_proposals) + enotes.push_back(enote_proposal.enote); - // 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)); -}*/ + // 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(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_subaddr_scalar, + 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_subaddr_scalar, + bob_scan.sender_extension_g, + bob_scan.sender_extension_t, + bob_enote.onetime_address)); +} //---------------------------------------------------------------------------------------------------------------------- From d02c1040edfc7eda2026ecea9a989676336f83f7 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 13:54:35 -0600 Subject: [PATCH 11/14] more get_output_enote_proposals tests --- tests/unit_tests/carrot_core.cpp | 195 +++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 8c19890b5..bb5db9f41 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -893,6 +893,103 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main2main_transfer_2out_completeness) +{ + const mock_carrot_keys alice = mock_carrot_keys::generate(); + const mock_carrot_keys bob = mock_carrot_keys::generate(); + + CarrotDestinationV1 alice_address; + make_carrot_main_address_v1(alice.account_spend_pubkey, + alice.main_address_view_pubkey, + alice_address); + + CarrotDestinationV1 bob_address; + make_carrot_main_address_v1(bob.account_spend_pubkey, + bob.main_address_view_pubkey, + bob_address); + + 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); + + const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ + .destination = bob_address, + .amount = crypto::rand_idx(1000000), + .randomness = gen_janus_anchor() + }; + + 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) + }; + + std::vector enote_proposals; + encrypted_payment_id_t encrypted_payment_id; + get_output_enote_proposals({bob_payment_proposal}, + {alice_payment_proposal}, + alice.s_view_balance_dev, + 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(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(alice.k_prove_spend, + alice.k_generate_image, + crypto::secret_key{{1}}, + alice_scan.sender_extension_g, + alice_scan.sender_extension_t, + alice_enote.onetime_address)); + + // check Bob spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, + bob.k_generate_image, + crypto::secret_key{{1}}, + bob_scan.sender_extension_g, + bob_scan.sender_extension_t, + bob_enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, sub2sub_transfer_2out_completeness) { const mock_carrot_keys alice = mock_carrot_keys::generate(); @@ -1026,3 +1123,101 @@ TEST(carrot_core, sub2sub_transfer_2out_completeness) bob_enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, main2integ_transfer_2out_completeness) +{ + const mock_carrot_keys alice = mock_carrot_keys::generate(); + const mock_carrot_keys bob = mock_carrot_keys::generate(); + + CarrotDestinationV1 alice_address; + make_carrot_main_address_v1(alice.account_spend_pubkey, + alice.main_address_view_pubkey, + alice_address); + + CarrotDestinationV1 bob_address; + make_carrot_integrated_address_v1(bob.account_spend_pubkey, + bob.main_address_view_pubkey, + gen_payment_id(), + bob_address); + + 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); + + const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ + .destination = bob_address, + .amount = crypto::rand_idx(1000000), + .randomness = gen_janus_anchor() + }; + + 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) + }; + + std::vector enote_proposals; + encrypted_payment_id_t encrypted_payment_id; + get_output_enote_proposals({bob_payment_proposal}, + {alice_payment_proposal}, + alice.s_view_balance_dev, + 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_address.payment_id, bob_scan.payment_id); // DIFFERENT FROM MAIN + EXPECT_EQ(CarrotEnoteType::PAYMENT, bob_scan.enote_type); + + // check Alice spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(alice.k_prove_spend, + alice.k_generate_image, + crypto::secret_key{{1}}, + alice_scan.sender_extension_g, + alice_scan.sender_extension_t, + alice_enote.onetime_address)); + + // check Bob spendability + EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, + bob.k_generate_image, + crypto::secret_key{{1}}, + bob_scan.sender_extension_g, + bob_scan.sender_extension_t, + bob_enote.onetime_address)); +} +//---------------------------------------------------------------------------------------------------------------------- From 8dc6a12ec721e324d22f6147a3f7bcdf1d60d5f6 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 14:23:30 -0600 Subject: [PATCH 12/14] test get_enote_output_proposals for all address combos --- tests/unit_tests/carrot_core.cpp | 296 +++++++++---------------------- 1 file changed, 85 insertions(+), 211 deletions(-) diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index bb5db9f41..f2f0f9736 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -893,138 +893,73 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- -TEST(carrot_core, main2main_transfer_2out_completeness) +static void subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(const bool alice_subaddress, + const bool bob_subaddress, + const bool bob_integrated, + const CarrotEnoteType alice_selfsend_type) { + // generate alice keys and address const mock_carrot_keys alice = mock_carrot_keys::generate(); - const mock_carrot_keys bob = mock_carrot_keys::generate(); - - CarrotDestinationV1 alice_address; - make_carrot_main_address_v1(alice.account_spend_pubkey, - alice.main_address_view_pubkey, - alice_address); - - CarrotDestinationV1 bob_address; - make_carrot_main_address_v1(bob.account_spend_pubkey, - bob.main_address_view_pubkey, - bob_address); - - 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); - - const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ - .destination = bob_address, - .amount = crypto::rand_idx(1000000), - .randomness = gen_janus_anchor() - }; - - 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) - }; - - std::vector enote_proposals; - encrypted_payment_id_t encrypted_payment_id; - get_output_enote_proposals({bob_payment_proposal}, - {alice_payment_proposal}, - alice.s_view_balance_dev, - 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(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(alice.k_prove_spend, - alice.k_generate_image, - crypto::secret_key{{1}}, - alice_scan.sender_extension_g, - alice_scan.sender_extension_t, - alice_enote.onetime_address)); - - // check Bob spendability - EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, - bob.k_generate_image, - crypto::secret_key{{1}}, - bob_scan.sender_extension_g, - bob_scan.sender_extension_t, - bob_enote.onetime_address)); -} -//---------------------------------------------------------------------------------------------------------------------- -TEST(carrot_core, sub2sub_transfer_2out_completeness) -{ - const mock_carrot_keys alice = mock_carrot_keys::generate(); - const mock_carrot_keys bob = mock_carrot_keys::generate(); - const uint32_t alice_j_major = crypto::rand(); const uint32_t alice_j_minor = crypto::rand(); CarrotDestinationV1 alice_address; - make_carrot_subaddress_v1(alice.account_spend_pubkey, - alice.account_view_pubkey, - alice.s_generate_address, - alice_j_major, - alice_j_minor, - 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; - make_carrot_subaddress_v1(bob.account_spend_pubkey, - bob.account_view_pubkey, - bob.s_generate_address, - bob_j_major, - bob_j_minor, - 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), @@ -1032,6 +967,7 @@ TEST(carrot_core, sub2sub_transfer_2out_completeness) .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}, @@ -1077,7 +1013,7 @@ TEST(carrot_core, sub2sub_transfer_2out_completeness) 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(null_payment_id, bob_scan.payment_id); + 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 @@ -1096,7 +1032,7 @@ TEST(carrot_core, sub2sub_transfer_2out_completeness) EXPECT_TRUE(can_open_fcmp_onetime_address(alice.k_prove_spend, alice.k_generate_image, - alice_subaddr_scalar, + alice_subaddress ? alice_subaddr_scalar : crypto::secret_key{{1}}, alice_scan.sender_extension_g, alice_scan.sender_extension_t, alice_enote.onetime_address)); @@ -1117,107 +1053,45 @@ TEST(carrot_core, sub2sub_transfer_2out_completeness) EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, bob.k_generate_image, - bob_subaddr_scalar, + 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, main2integ_transfer_2out_completeness) +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2main_completeness) { - const mock_carrot_keys alice = mock_carrot_keys::generate(); - const mock_carrot_keys bob = mock_carrot_keys::generate(); - - CarrotDestinationV1 alice_address; - make_carrot_main_address_v1(alice.account_spend_pubkey, - alice.main_address_view_pubkey, - alice_address); - - CarrotDestinationV1 bob_address; - make_carrot_integrated_address_v1(bob.account_spend_pubkey, - bob.main_address_view_pubkey, - gen_payment_id(), - bob_address); - - 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); - - const CarrotPaymentProposalV1 bob_payment_proposal = CarrotPaymentProposalV1{ - .destination = bob_address, - .amount = crypto::rand_idx(1000000), - .randomness = gen_janus_anchor() - }; - - 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) - }; - - std::vector enote_proposals; - encrypted_payment_id_t encrypted_payment_id; - get_output_enote_proposals({bob_payment_proposal}, - {alice_payment_proposal}, - alice.s_view_balance_dev, - 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_address.payment_id, bob_scan.payment_id); // DIFFERENT FROM MAIN - EXPECT_EQ(CarrotEnoteType::PAYMENT, bob_scan.enote_type); - - // check Alice spendability - EXPECT_TRUE(can_open_fcmp_onetime_address(alice.k_prove_spend, - alice.k_generate_image, - crypto::secret_key{{1}}, - alice_scan.sender_extension_g, - alice_scan.sender_extension_t, - alice_enote.onetime_address)); - - // check Bob spendability - EXPECT_TRUE(can_open_fcmp_onetime_address(bob.k_prove_spend, - bob.k_generate_image, - crypto::secret_key{{1}}, - bob_scan.sender_extension_g, - bob_scan.sender_extension_t, - bob_enote.onetime_address)); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, false, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2sub_completeness) +{ + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, true, false, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, true, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_main2integ_completeness) +{ + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, true, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, true, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2main_completeness) +{ + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, false, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2sub_completeness) +{ + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, true, false, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, true, false, CarrotEnoteType::CHANGE); +} +//---------------------------------------------------------------------------------------------------------------------- +TEST(carrot_core, get_enote_output_proposals_internal_ss_sub2integ_completeness) +{ + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, true, CarrotEnoteType::PAYMENT); + subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, true, CarrotEnoteType::CHANGE); } //---------------------------------------------------------------------------------------------------------------------- From b70222c774e7a3713901704bf0d4d7b5b3b4c47a Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 14:47:35 -0600 Subject: [PATCH 13/14] get_output_enote_proposals now supports external selfsends --- src/carrot_core/output_set_finalization.cpp | 32 ++++++++-- src/carrot_core/output_set_finalization.h | 18 +++++- src/carrot_core/payment_proposal.cpp | 4 +- src/carrot_core/payment_proposal.h | 6 +- tests/unit_tests/carrot_core.cpp | 71 ++++++++++++++++----- 5 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/carrot_core/output_set_finalization.cpp b/src/carrot_core/output_set_finalization.cpp index e05654492..558096681 100644 --- a/src/carrot_core/output_set_finalization.cpp +++ b/src/carrot_core/output_set_finalization.cpp @@ -146,7 +146,9 @@ tools::optional_variant &&normal_payment_proposals, std::vector &&selfsend_payment_proposals, - const view_balance_secret_device &s_view_balance_dev, + 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) @@ -208,18 +210,34 @@ void get_output_enote_proposals(std::vector &&normal_pa encrypted_payment_id_out = encrypted_payment_id; } - // in the case that the pid is ambiguous, set it to random bytes + // 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 + // construct selfsend enotes, preferring internal enotes over special enotes when possible for (const CarrotPaymentProposalSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals) { - get_output_proposal_internal_v1(selfsend_payment_proposal, - s_view_balance_dev, - tx_first_key_image, - tools::add_element(output_enote_proposals_out)); + 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 diff --git a/src/carrot_core/output_set_finalization.h b/src/carrot_core/output_set_finalization.h index 90da62f75..94b5ea8d0 100644 --- a/src/carrot_core/output_set_finalization.h +++ b/src/carrot_core/output_set_finalization.h @@ -87,11 +87,25 @@ tools::optional_variant &&normal_payment_proposals, std::vector &&selfsend_payment_proposals, - const view_balance_secret_device &s_view_balance_dev, + 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); diff --git a/src/carrot_core/payment_proposal.cpp b/src/carrot_core/payment_proposal.cpp index e09f65f4b..2d6f25bbf 100644 --- a/src/carrot_core/payment_proposal.cpp +++ b/src/carrot_core/payment_proposal.cpp @@ -321,7 +321,7 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, //------------------------------------------------------------------------------------------------------------------- void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &proposal, const view_incoming_key_device &k_view_dev, - const crypto::public_key &primary_address_spend_pubkey, + const crypto::public_key &account_spend_pubkey, const crypto::key_image &tx_first_key_image, RCTOutputEnoteProposal &output_enote_out) { @@ -362,7 +362,7 @@ void get_output_proposal_special_v1(const CarrotPaymentProposalSelfSendV1 &propo k_view_dev.make_janus_anchor_special(proposal.enote_ephemeral_pubkey, input_context, output_enote_out.enote.onetime_address, - primary_address_spend_pubkey, + account_spend_pubkey, janus_anchor_special); // 6. encrypt special anchor: anchor_enc = anchor XOR m_anchor diff --git a/src/carrot_core/payment_proposal.h b/src/carrot_core/payment_proposal.h index 04fa0bcdb..51fcb850b 100644 --- a/src/carrot_core/payment_proposal.h +++ b/src/carrot_core/payment_proposal.h @@ -129,20 +129,20 @@ void get_output_proposal_normal_v1(const CarrotPaymentProposalV1 &proposal, * brief: get_output_proposal_v1 - convert the carrot proposal to an output proposal (external selfsend) * param: proposal - * param: k_view_dev - -* param: primary_address_spend_pubkey - +* 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 &primary_address_spend_pubkey, + 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: primary_address_spend_pubkey - +* param: account_spend_pubkey - * param: tx_first_key_image - * outparam: output_enote_out - * outparam: partial_memo_out - diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index f2f0f9736..057f677c0 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -893,10 +893,11 @@ TEST(carrot_core, main_address_coinbase_scan_completeness) enote.onetime_address)); } //---------------------------------------------------------------------------------------------------------------------- -static void subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(const bool alice_subaddress, +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 CarrotEnoteType alice_selfsend_type, + const bool alice_internal_selfsends) { // generate alice keys and address const mock_carrot_keys alice = mock_carrot_keys::generate(); @@ -972,11 +973,13 @@ static void subtest_2out_transfer_get_enote_output_proposals_internal_ss_complet encrypted_payment_id_t encrypted_payment_id; get_output_enote_proposals({bob_payment_proposal}, {alice_payment_proposal}, - alice.s_view_balance_dev, + 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 @@ -1061,37 +1064,73 @@ static void subtest_2out_transfer_get_enote_output_proposals_internal_ss_complet //---------------------------------------------------------------------------------------------------------------------- TEST(carrot_core, get_enote_output_proposals_internal_ss_main2main_completeness) { - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, false, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, false, CarrotEnoteType::CHANGE); + 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_enote_output_proposals_internal_ss_completeness(false, true, false, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, true, false, CarrotEnoteType::CHANGE); + 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_enote_output_proposals_internal_ss_completeness(false, false, true, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(false, false, true, CarrotEnoteType::CHANGE); + 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_enote_output_proposals_internal_ss_completeness(true, false, false, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, false, CarrotEnoteType::CHANGE); + 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_enote_output_proposals_internal_ss_completeness(true, true, false, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, true, false, CarrotEnoteType::CHANGE); + 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_enote_output_proposals_internal_ss_completeness(true, false, true, CarrotEnoteType::PAYMENT); - subtest_2out_transfer_get_enote_output_proposals_internal_ss_completeness(true, false, true, CarrotEnoteType::CHANGE); + 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); } //---------------------------------------------------------------------------------------------------------------------- From 3b5d17ab2762c1f0bc3c822e15c8ae7efeabd7c6 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 2 Dec 2024 15:47:45 -0600 Subject: [PATCH 14/14] test transfers to and from legacy addresses --- tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/carrot_core.cpp | 2 +- tests/unit_tests/carrot_legacy.cpp | 291 +++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/carrot_legacy.cpp diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index a876f632c..01385495b 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -39,6 +39,7 @@ set(unit_tests_sources bulletproofs_plus.cpp canonical_amounts.cpp carrot_core.cpp + carrot_legacy.cpp carrot_transcript_fixed.cpp chacha.cpp checkpoints.cpp diff --git a/tests/unit_tests/carrot_core.cpp b/tests/unit_tests/carrot_core.cpp index 057f677c0..3dbc1b48c 100644 --- a/tests/unit_tests/carrot_core.cpp +++ b/tests/unit_tests/carrot_core.cpp @@ -124,7 +124,7 @@ static bool can_open_fcmp_onetime_address(const crypto::secret_key &k_prove_spen // 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) + (k^o_t + k^j_subscal * k_ps) + // = (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; 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); +} +//----------------------------------------------------------------------------------------------------------------------