From da99d88bce88813e6d6999386ab05348253641de Mon Sep 17 00:00:00 2001 From: Some Random Crypto Guy Date: Tue, 13 May 2025 12:57:05 +0100 Subject: [PATCH] initial import of SPARC integration into latest Carrot --- src/carrot_core/CMakeLists.txt | 3 +- src/carrot_core/carrot_enote_types.cpp | 4 +- src/carrot_core/carrot_enote_types.h | 4 + src/carrot_core/config.h | 5 + src/carrot_core/core_types.cpp | 10 ++ src/carrot_core/core_types.h | 12 +++ src/carrot_core/device.h | 11 ++ src/carrot_core/device_ram_borrowed.cpp | 7 ++ src/carrot_core/device_ram_borrowed.h | 4 + src/carrot_core/enote_utils.cpp | 20 ++++ src/carrot_core/enote_utils.h | 24 +++++ src/carrot_core/sparc.cpp | 135 ++++++++++++++++++++++++ src/carrot_core/sparc.h | 59 +++++++++++ 13 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 src/carrot_core/sparc.cpp create mode 100644 src/carrot_core/sparc.h diff --git a/src/carrot_core/CMakeLists.txt b/src/carrot_core/CMakeLists.txt index e4e145c60..9f6da606d 100644 --- a/src/carrot_core/CMakeLists.txt +++ b/src/carrot_core/CMakeLists.txt @@ -39,7 +39,8 @@ set(carrot_core_sources output_set_finalization.cpp payment_proposal.cpp scan.cpp - scan_unsafe.cpp) + scan_unsafe.cpp + sparc.cpp) monero_find_all_headers(carrot_core_headers, "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/carrot_core/carrot_enote_types.cpp b/src/carrot_core/carrot_enote_types.cpp index dfc71dc34..64fe9b274 100644 --- a/src/carrot_core/carrot_enote_types.cpp +++ b/src/carrot_core/carrot_enote_types.cpp @@ -57,6 +57,8 @@ bool operator==(const CarrotEnoteV1 &a, const CarrotEnoteV1 &b) a.anchor_enc == b.anchor_enc && a.view_tag == b.view_tag && a.tx_first_key_image == b.tx_first_key_image && + a.return_enc == b.return_enc && + a.asset_type == b.asset_type && memcmp(a.enote_ephemeral_pubkey.data, b.enote_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0; } //------------------------------------------------------------------------------------------------------------------- @@ -70,4 +72,4 @@ bool operator==(const CarrotCoinbaseEnoteV1 &a, const CarrotCoinbaseEnoteV1 &b) memcmp(a.enote_ephemeral_pubkey.data, b.enote_ephemeral_pubkey.data, sizeof(mx25519_pubkey)) == 0; } //------------------------------------------------------------------------------------------------------------------- -} //namespace carrot \ No newline at end of file +} //namespace carrot diff --git a/src/carrot_core/carrot_enote_types.h b/src/carrot_core/carrot_enote_types.h index 6cfc99b9e..a61602168 100644 --- a/src/carrot_core/carrot_enote_types.h +++ b/src/carrot_core/carrot_enote_types.h @@ -71,6 +71,10 @@ struct CarrotEnoteV1 final mx25519_pubkey enote_ephemeral_pubkey; /// L_0 crypto::key_image tx_first_key_image; + /// return_enc + encrypted_return_pubkey_t return_enc; + /// asset_type + std::string asset_type; }; /// equality operators diff --git a/src/carrot_core/config.h b/src/carrot_core/config.h index 9cc9b3d9c..dbcca0757 100644 --- a/src/carrot_core/config.h +++ b/src/carrot_core/config.h @@ -71,4 +71,9 @@ static constexpr const unsigned int CARROT_MIN_TX_OUTPUTS = 2; static constexpr const unsigned int CARROT_MAX_TX_OUTPUTS = 8; static constexpr const unsigned int CARROT_MIN_TX_INPUTS = 1; static constexpr const unsigned int CARROT_MAX_TX_INPUTS = 8; + +// SPARC addressing protocol domain separators +static constexpr const unsigned char SPARC_DOMAIN_SEP_RETURN_PUBKEY_ENCRYPTION_MASK[] = "SPARC return pubkey encryption mask"; +static constexpr const unsigned char SPARC_DOMAIN_SEP_RETURN_ADDRESS_SCALAR[] = "SPARC return address scalar"; + } //namespace carrot diff --git a/src/carrot_core/core_types.cpp b/src/carrot_core/core_types.cpp index 137b6a952..5be0c9ebf 100644 --- a/src/carrot_core/core_types.cpp +++ b/src/carrot_core/core_types.cpp @@ -82,6 +82,16 @@ encrypted_amount_t operator^(const encrypted_amount_t &a, const encrypted_amount return xor_bytes(a, b); } //------------------------------------------------------------------------------------------------------------------- +bool operator==(const encrypted_return_pubkey_t &a, const encrypted_return_pubkey_t &b) +{ + return memcmp(&a, &b, sizeof(encrypted_return_pubkey_t)) == 0; +} +//------------------------------------------------------------------------------------------------------------------- +encrypted_return_pubkey_t operator^(const encrypted_return_pubkey_t &a, const encrypted_return_pubkey_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; diff --git a/src/carrot_core/core_types.h b/src/carrot_core/core_types.h index 98b096201..70f1266ff 100644 --- a/src/carrot_core/core_types.h +++ b/src/carrot_core/core_types.h @@ -96,6 +96,13 @@ struct input_context_t final unsigned char bytes[INPUT_CONTEXT_BYTES]; }; +// SPARC encrypted return public key +constexpr std::size_t ENCRYPTED_RETURN_PUBKEY_BYTES{32}; +struct encrypted_return_pubkey_t final +{ + unsigned char bytes[ENCRYPTED_RETURN_PUBKEY_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); } @@ -119,6 +126,11 @@ static inline bool operator!=(const input_context_t &a, const input_context_t &b 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); } +/// overloaded operators: encrypted return pubkey +bool operator==(const encrypted_return_pubkey_t &a, const encrypted_return_pubkey_t &b); +static inline bool operator!=(const encrypted_return_pubkey_t &a, const encrypted_return_pubkey_t &b) { return !(a == b); } +encrypted_return_pubkey_t operator^(const encrypted_return_pubkey_t &a, const encrypted_return_pubkey_t &b); + /// generate a random janus anchor janus_anchor_t gen_janus_anchor(); /// generate a random (non-zero) payment ID diff --git a/src/carrot_core/device.h b/src/carrot_core/device.h index 52c36d57d..04c146fea 100644 --- a/src/carrot_core/device.h +++ b/src/carrot_core/device.h @@ -160,6 +160,17 @@ struct view_balance_secret_device virtual void make_internal_sender_receiver_secret(const mx25519_pubkey &enote_ephemeral_pubkey, const input_context_t &input_context, crypto::hash &s_sender_receiver_out) const = 0; + + /** + * brief: make_internal_return_privkey - make internal return private key, given non-secret data + * k_return = H_32(s_vb || input_context || Ko) + * param: input_context - input_context + * param: onetime_address - Ko + * outparam: return_privkey_out - k_return + */ + virtual void make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const = 0; virtual ~view_balance_secret_device() = default; }; diff --git a/src/carrot_core/device_ram_borrowed.cpp b/src/carrot_core/device_ram_borrowed.cpp index ab6d55783..569ad7d5f 100644 --- a/src/carrot_core/device_ram_borrowed.cpp +++ b/src/carrot_core/device_ram_borrowed.cpp @@ -86,6 +86,13 @@ void view_balance_secret_ram_borrowed_device::make_internal_sender_receiver_secr s_sender_receiver_out); } //------------------------------------------------------------------------------------------------------------------- +void view_balance_secret_ram_borrowed_device::make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const +{ + make_sparc_return_privkey(to_bytes(m_s_view_balance), input_context, onetime_address, return_privkey_out); +} +//------------------------------------------------------------------------------------------------------------------- void generate_address_secret_ram_borrowed_device::make_index_extension_generator( const std::uint32_t major_index, const std::uint32_t minor_index, diff --git a/src/carrot_core/device_ram_borrowed.h b/src/carrot_core/device_ram_borrowed.h index 9d7f0c425..21fe3cae4 100644 --- a/src/carrot_core/device_ram_borrowed.h +++ b/src/carrot_core/device_ram_borrowed.h @@ -78,6 +78,10 @@ public: const input_context_t &input_context, crypto::hash &s_sender_receiver_out) const override; + void make_internal_return_privkey(const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) const override; + protected: const crypto::secret_key &m_s_view_balance; }; diff --git a/src/carrot_core/enote_utils.cpp b/src/carrot_core/enote_utils.cpp index 47df16df4..800064b96 100644 --- a/src/carrot_core/enote_utils.cpp +++ b/src/carrot_core/enote_utils.cpp @@ -190,6 +190,26 @@ void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], derive_bytes_3(transcript.data(), transcript.size(), s_sender_receiver_unctx, &view_tag_out); } //------------------------------------------------------------------------------------------------------------------- +void make_sparc_return_privkey(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out) +{ + // k_return = H_32(s_sr || input_context || Ko) + const auto transcript = sp::make_fixed_transcript(input_context, onetime_address); + derive_scalar(transcript.data(), transcript.size(), s_sender_receiver_unctx, &return_privkey_out); +} +//------------------------------------------------------------------------------------------------------------------- +void make_sparc_return_pubkey_encryption_mask(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + encrypted_return_pubkey_t &return_pubkey_mask_out) +{ + // m_return = H_32(s_sr || input_context || Ko) + const auto transcript = sp::make_fixed_transcript(input_context, onetime_address); + derive_bytes_32(transcript.data(), transcript.size(), s_sender_receiver_unctx, &return_pubkey_mask_out); +} +//------------------------------------------------------------------------------------------------------------------- input_context_t make_carrot_input_context_coinbase(const std::uint64_t block_index) { // input_context = "C" || IntToBytes256(block_index) diff --git a/src/carrot_core/enote_utils.h b/src/carrot_core/enote_utils.h index c4a68a38b..a85eac482 100644 --- a/src/carrot_core/enote_utils.h +++ b/src/carrot_core/enote_utils.h @@ -125,6 +125,30 @@ void make_carrot_view_tag(const unsigned char s_sender_receiver_unctx[32], const crypto::public_key &onetime_address, view_tag_t &view_tag_out); /** +* brief: make_sparc_return_privkey - return private key, given non-secret data +* k_return = H_32(s_sr || input_context || Ko) +* param: s_sender_receiver_unctx - s_sr +* param: input_context - input_context +* param: onetime_address - Ko +* outparam: return_privkey_out - k_return +*/ +void make_sparc_return_privkey(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + crypto::secret_key &return_privkey_out); +/** +* brief: make_sparc_return_pubkey_encryption_mask - used for hiding return pubkey +* vt = H_32(s_sr || input_context || Ko) +* param: s_sender_receiver_unctx - s_sr +* param: input_context - input_context +* param: onetime_address - Ko +* outparam: return_pubkey_mask_out - m_return +*/ +void make_sparc_return_pubkey_encryption_mask(const unsigned char s_sender_receiver_unctx[32], + const input_context_t &input_context, + const crypto::public_key &onetime_address, + encrypted_return_pubkey_t &return_pubkey_mask_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 diff --git a/src/carrot_core/sparc.cpp b/src/carrot_core/sparc.cpp new file mode 100644 index 000000000..3b609a597 --- /dev/null +++ b/src/carrot_core/sparc.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2024, Salvium (author: SRCG) +// +// 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 'return address' is a proposal to facilitate pseudonymous transfers of received funds +// back to the originating wallet, as per the previously-published "Return Address Scheme" +// published by knaccc at https://github.com/monero-project/research-lab/issues/53 +// This code is designed to implement the F point management and zero-knowledge proofs +// required to support the "Return Address Scheme" in Carrot. +// Carrot: Cryptonote Address For Rerandomizable-RingCT-Output Transactions + +//paired header +#include "sparc.h" + +//local headers +#include "crypto/crypto.h" +#include "crypto/generators.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" +#include "crypto/hash.h" +#include "address_utils.h" +#include "misc_log_ex.h" + +#include +#include + +namespace carrot { + + // Optimized function to hash a vector of keys into a scalar + rct::key hash_to_scalar(std::vector& keys) { + + // Create a fixed-size buffer large enough to hold all keys and a domain separator + const char* domain_separator = "sparc_spend_authority_proof"; + const size_t domain_separator_length = strlen(domain_separator); + size_t total_size = keys.size() * sizeof(rct::key) + domain_separator_length; + std::vector data(total_size); + + // Copy the keys into the buffer + size_t offset = 0; + for (const auto& key : keys) { + std::memcpy(data.data() + offset, key.bytes, sizeof(rct::key)); + offset += sizeof(rct::key); + } + + // Add the domain separator "sparc_spend_authority_proof" at the _end_ of the buffer + std::memcpy(data.data() + offset, domain_separator, domain_separator_length); + + // Hash the concatenated data into a fixed-size hash + rct::key hash_output; + keccak((const uint8_t *)data.data(), total_size, hash_output.bytes, sizeof(rct::key)); + sc_reduce32(hash_output.bytes); // Reduce to valid scalar + + return hash_output; + } + + // Function to generate the zero-knowledge proof + void make_sparc_spend_authority_proof(const rct::key &x, const rct::key &y, const rct::key &K_o, rct::zk_proof &proof_out) { + + // Step 1: Generate random scalars r_x and r_y + rct::key r_x = rct::skGen(); // Random scalar for G commitment + rct::key r_y = rct::skGen(); // Random scalar for T commitment + + // Step 2: Calculate commitment by summing terms for G and T + rct::key commitment; + rct::key commitment_G = rct::scalarmultBase(r_x); // r_x * G + rct::key commitment_T = rct::scalarmultKey(rct::pk2rct(crypto::get_T()), r_y); // r_y * T (using T generator) + commitment = rct::addKeys(commitment_G, commitment_T); // R = r_xG + r_yT + + // Step 3: Calculate the challenge scalar + std::vector keys{commitment, K_o}; + rct::key challenge = rct::hash_to_scalar(keys); // c = H(R || K_o) + + // Step 4: Calculate responses + rct::key response_x; + sc_muladd(response_x.bytes, challenge.bytes, x.bytes, r_x.bytes); // z_x = r_x + c * x + sc_reduce32(response_x.bytes); + rct::key response_y; + sc_muladd(response_y.bytes, challenge.bytes, y.bytes, r_y.bytes); // z_y = r_y + c * y + sc_reduce32(response_y.bytes); + + // Step 5: Construct and return the proof + proof_out.R = commitment; + proof_out.z1 = response_x; + proof_out.z2 = response_y; + + // Step 6: Sanity checks + rct::key resC = rct::addKeys(commitment, rct::scalarmultKey(K_o, challenge)); + rct::key resZ = rct::addKeys(rct::scalarmultBase(response_x), rct::scalarmultKey(rct::pk2rct(crypto::get_T()), response_y)); + if (!rct::equalKeys(resZ, resC)) assert(false); + } + + // Function to verify the zero-knowledge proof + bool verify_sparc_spend_authority_proof(const rct::zk_proof &proof, const rct::key &K_o) { + + // Step 1: calculate the challenge + std::vector keys{proof.R, K_o}; + rct::key recomputed_challenge = rct::hash_to_scalar(keys); + + // Step 2: Calculate z_xG + x_yT + rct::key z_xG = rct::scalarmultBase(proof.z1); // z1 * G + rct::key x_yT = rct::scalarmultKey(rct::pk2rct(crypto::get_T()), proof.z2); // z2 * T + rct::key resZ = rct::addKeys(z_xG, x_yT); // z_xG + x_yT + + // Step 3: Calculate R + cK_o + rct::key resC = rct::addKeys(proof.R, rct::scalarmultKey(K_o, recomputed_challenge)); // R + cK_o + + // Step 4: verify z_xG + x_yT ?= R + cK_o + return rct::equalKeys(resZ, resC); + } + +} // namespace carrot diff --git a/src/carrot_core/sparc.h b/src/carrot_core/sparc.h new file mode 100644 index 000000000..2cf9e1221 --- /dev/null +++ b/src/carrot_core/sparc.h @@ -0,0 +1,59 @@ +// Copyright (c) 2024, Salvium (author: SRCG) +// +// 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 'return address' is a proposal to facilitate pseudonymous transfers of received funds +// back to the originating wallet, as per the previously-published "Return Address Scheme" +// published by knaccc at https://github.com/monero-project/research-lab/issues/53 +// This code is designed to implement the F point management and zero-knowledge proofs +// required to support the "Return Address Scheme" in Carrot. +// Carrot: Cryptonote Address For Rerandomizable-RingCT-Output Transactions + +#pragma once + +#include "ringct/rctTypes.h" + +namespace carrot +{ + /** + * brief: make_sparc_spend_authority_proof - generate the zero-knowledge proof for spend authority for SPARC output pubkey + * param: x - G-term for the given output key + * param: y - T-term for the given output key + * param: K_o - output key to generate spend authority proof for + * outparam: proof_out - the complete spend authority proof + */ + void make_sparc_spend_authority_proof(const rct::key &x, const rct::key &y, const rct::key &K_o, rct::zk_proof &proof_out); + + /** + * brief: verify_sparc_spend_authority_proof - verify the zero-knowledge proof for spend authority for SPARC output pubkey + * param: proof - the zero-knowledge proof to verify + * param: K_o - output key to verify the spend authority proof for + * return: true if proof is valid, false otherwise + */ + bool verify_sparc_spend_authority_proof(const rct::zk_proof &proof, const rct::key &K_o); + +} //namespace carrot