aboutsummaryrefslogtreecommitdiff
path: root/src/multisig
diff options
context:
space:
mode:
authoranon <anon [at] nowhere>2021-12-06 10:25:01 +0000
committerkoe <ukoe@protonmail.com>2022-06-30 12:56:40 -0500
commitc7b2944f8960c208ceddeb3075a673630ae000cd (patch)
tree5ab335821b3dd9398a7a0ec645b4ad79d38e750b /src/multisig
parentMerge pull request #8340 (diff)
downloadmonero-c7b2944f8960c208ceddeb3075a673630ae000cd.tar.xz
multisig: fix critical vulnerabilities in signing
Diffstat (limited to 'src/multisig')
-rw-r--r--src/multisig/CMakeLists.txt5
-rw-r--r--src/multisig/multisig_clsag_context.cpp257
-rw-r--r--src/multisig/multisig_clsag_context.h137
-rw-r--r--src/multisig/multisig_tx_builder_ringct.cpp943
-rw-r--r--src/multisig/multisig_tx_builder_ringct.h119
5 files changed, 1460 insertions, 1 deletions
diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt
index 294a1721f..61e658a39 100644
--- a/src/multisig/CMakeLists.txt
+++ b/src/multisig/CMakeLists.txt
@@ -30,7 +30,9 @@ set(multisig_sources
multisig.cpp
multisig_account.cpp
multisig_account_kex_impl.cpp
- multisig_kex_msg.cpp)
+ multisig_clsag_context.cpp
+ multisig_kex_msg.cpp
+ multisig_tx_builder_ringct.cpp)
set(multisig_headers)
@@ -48,6 +50,7 @@ target_link_libraries(multisig
PUBLIC
ringct
cryptonote_basic
+ cryptonote_core
common
cncrypto
PRIVATE
diff --git a/src/multisig/multisig_clsag_context.cpp b/src/multisig/multisig_clsag_context.cpp
new file mode 100644
index 000000000..e3417b896
--- /dev/null
+++ b/src/multisig/multisig_clsag_context.cpp
@@ -0,0 +1,257 @@
+// Copyright (c) 2021, 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 "multisig_clsag_context.h"
+
+#include "int-util.h"
+
+#include "crypto/crypto.h"
+#include "cryptonote_config.h"
+#include "ringct/rctOps.h"
+#include "ringct/rctTypes.h"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig {
+
+namespace signing {
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+template<std::size_t N>
+static rct::key string_to_key(const unsigned char (&str)[N]) {
+ rct::key tmp{};
+ static_assert(sizeof(tmp.bytes) >= N, "");
+ std::memcpy(tmp.bytes, str, N);
+ return tmp;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void encode_int_to_key_le(const unsigned int i, rct::key &k_out)
+{
+ static_assert(sizeof(unsigned int) <= sizeof(std::uint64_t), "unsigned int max too large");
+ static_assert(sizeof(std::uint64_t) <= sizeof(rct::key), "");
+ std::uint64_t temp_i{SWAP64LE(i)};
+ std::memcpy(k_out.bytes, &temp_i, sizeof(temp_i));
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::init(
+ const rct::keyV& P,
+ const rct::keyV& C_nonzero,
+ const rct::key& C_offset,
+ const rct::key& message,
+ const rct::key& I,
+ const rct::key& D,
+ const unsigned int l,
+ const rct::keyV& s,
+ const std::size_t num_alpha_components
+)
+{
+ initialized = false;
+
+ n = P.size();
+ if (n <= 0)
+ return false;
+ if (C_nonzero.size() != n)
+ return false;
+ if (s.size() != n)
+ return false;
+ if (l >= n)
+ return false;
+
+ c_params.clear();
+ c_params.reserve(n * 2 + 5);
+ b_params.clear();
+ b_params.reserve(n * 3 + 2 * num_alpha_components + 7);
+
+ c_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND));
+ b_params.push_back(string_to_key(config::HASH_KEY_CLSAG_ROUND_MULTISIG));
+ c_params.insert(c_params.end(), P.begin(), P.end());
+ b_params.insert(b_params.end(), P.begin(), P.end());
+ c_params.insert(c_params.end(), C_nonzero.begin(), C_nonzero.end());
+ b_params.insert(b_params.end(), C_nonzero.begin(), C_nonzero.end());
+ c_params.emplace_back(C_offset);
+ b_params.emplace_back(C_offset);
+ c_params.emplace_back(message);
+ b_params.emplace_back(message);
+ c_params_L_offset = c_params.size();
+ b_params_L_offset = b_params.size();
+ c_params.resize(c_params.size() + 1); //this is where L will be inserted later
+ b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for L will be inserted here later
+ c_params_R_offset = c_params.size();
+ b_params_R_offset = b_params.size();
+ c_params.resize(c_params.size() + 1); //this is where R will be inserted later
+ b_params.resize(b_params.size() + num_alpha_components); //multisig aggregate public nonces for R will be inserted here later
+ b_params.emplace_back(I);
+ b_params.emplace_back(D);
+ b_params.insert(b_params.end(), s.begin(), s.begin() + l); //fake responses before 'l'
+ b_params.insert(b_params.end(), s.begin() + l + 1, s.end()); //fake responses after 'l'
+ b_params.emplace_back();
+ encode_int_to_key_le(l, b_params.back()); //real signing index 'l'
+ b_params.emplace_back();
+ encode_int_to_key_le(num_alpha_components, b_params.back()); //number of parallel nonces
+ b_params.emplace_back();
+ encode_int_to_key_le(n, b_params.back()); //number of ring members
+
+ rct::keyV mu_P_params;
+ rct::keyV mu_C_params;
+ mu_P_params.reserve(n * 2 + 4);
+ mu_C_params.reserve(n * 2 + 4);
+
+ mu_P_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_0));
+ mu_C_params.push_back(string_to_key(config::HASH_KEY_CLSAG_AGG_1));
+ mu_P_params.insert(mu_P_params.end(), P.begin(), P.end());
+ mu_C_params.insert(mu_C_params.end(), P.begin(), P.end());
+ mu_P_params.insert(mu_P_params.end(), C_nonzero.begin(), C_nonzero.end());
+ mu_C_params.insert(mu_C_params.end(), C_nonzero.begin(), C_nonzero.end());
+ mu_P_params.emplace_back(I);
+ mu_C_params.emplace_back(I);
+ mu_P_params.emplace_back(scalarmultKey(D, rct::INV_EIGHT));
+ mu_C_params.emplace_back(mu_P_params.back());
+ mu_P_params.emplace_back(C_offset);
+ mu_C_params.emplace_back(C_offset);
+ mu_P = hash_to_scalar(mu_P_params);
+ mu_C = hash_to_scalar(mu_C_params);
+
+ rct::geDsmp I_precomp;
+ rct::geDsmp D_precomp;
+ rct::precomp(I_precomp.k, I);
+ rct::precomp(D_precomp.k, D);
+ rct::key wH_l;
+ rct::addKeys3(wH_l, mu_P, I_precomp.k, mu_C, D_precomp.k);
+ rct::precomp(wH_l_precomp.k, wH_l);
+ W_precomp.resize(n);
+ H_precomp.resize(n);
+ for (std::size_t i = 0; i < n; ++i) {
+ rct::geDsmp P_precomp;
+ rct::geDsmp C_precomp;
+ rct::key C;
+ rct::subKeys(C, C_nonzero[i], C_offset);
+ rct::precomp(P_precomp.k, P[i]);
+ rct::precomp(C_precomp.k, C);
+ rct::key W;
+ rct::addKeys3(W, mu_P, P_precomp.k, mu_C, C_precomp.k);
+ rct::precomp(W_precomp[i].k, W);
+ ge_p3 Hi_p3;
+ rct::hash_to_p3(Hi_p3, P[i]);
+ ge_dsm_precomp(H_precomp[i].k, &Hi_p3);
+ }
+ rct::precomp(G_precomp.k, rct::G);
+ this->l = l;
+ this->s = s;
+ this->num_alpha_components = num_alpha_components;
+
+ initialized = true;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::combine_alpha_and_compute_challenge(
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& alpha_combined,
+ rct::key& c_0,
+ rct::key& c
+)
+{
+ if (not initialized)
+ return false;
+
+ if (num_alpha_components != total_alpha_G.size())
+ return false;
+ if (num_alpha_components != total_alpha_H.size())
+ return false;
+ if (num_alpha_components != alpha.size())
+ return false;
+
+ // insert aggregate public nonces for L and R components
+ for (std::size_t i = 0; i < num_alpha_components; ++i) {
+ b_params[b_params_L_offset + i] = total_alpha_G[i];
+ b_params[b_params_R_offset + i] = total_alpha_H[i];
+ }
+
+ // musig2-style combination factor 'b'
+ const rct::key b = rct::hash_to_scalar(b_params);
+
+ // 1) store combined public nonces in the 'L' and 'R' slots for computing the initial challenge
+ // - L = sum_i(b^i total_alpha_G[i])
+ // - R = sum_i(b^i total_alpha_H[i])
+ // 2) compute the local signer's combined private nonce
+ // - alpha_combined = sum_i(b^i * alpha[i])
+ rct::key& L_l = c_params[c_params_L_offset];
+ rct::key& R_l = c_params[c_params_R_offset];
+ rct::key b_i = rct::identity();
+ L_l = rct::identity();
+ R_l = rct::identity();
+ alpha_combined = rct::zero();
+ for (std::size_t i = 0; i < num_alpha_components; ++i) {
+ rct::addKeys(L_l, L_l, rct::scalarmultKey(total_alpha_G[i], b_i));
+ rct::addKeys(R_l, R_l, rct::scalarmultKey(total_alpha_H[i], b_i));
+ sc_muladd(alpha_combined.bytes, alpha[i].bytes, b_i.bytes, alpha_combined.bytes);
+ sc_mul(b_i.bytes, b_i.bytes, b.bytes);
+ }
+
+ // compute initial challenge from real spend components
+ c = rct::hash_to_scalar(c_params);
+
+ // 1) c_0: find the CLSAG's challenge for index '0', which will be stored in the proof
+ // note: in the CLSAG implementation in ringct/rctSigs, c_0 is denoted 'c1' (a notation error)
+ // 2) c: find the final challenge for the multisig signers to respond to
+ for (std::size_t i = (l + 1) % n; i != l; i = (i + 1) % n) {
+ if (i == 0)
+ c_0 = c;
+ rct::addKeys3(c_params[c_params_L_offset], s[i], G_precomp.k, c, W_precomp[i].k);
+ rct::addKeys3(c_params[c_params_R_offset], s[i], H_precomp[i].k, c, wH_l_precomp.k);
+ c = rct::hash_to_scalar(c_params);
+ }
+ if (l == 0)
+ c_0 = c;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool CLSAG_context_t::get_mu(
+ rct::key& mu_P,
+ rct::key& mu_C
+) const
+{
+ if (not initialized)
+ return false;
+ mu_P = this->mu_P;
+ mu_C = this->mu_C;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_clsag_context.h b/src/multisig/multisig_clsag_context.h
new file mode 100644
index 000000000..5017e8688
--- /dev/null
+++ b/src/multisig/multisig_clsag_context.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2021, 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.
+
+////
+// References
+// - CLSAG (base signature scheme): https://eprint.iacr.org/2019/654
+// - MuSig2 (style for multisig signing): https://eprint.iacr.org/2020/1261
+///
+
+
+#pragma once
+
+#include "ringct/rctTypes.h"
+
+#include <vector>
+
+
+namespace multisig {
+
+namespace signing {
+
+class CLSAG_context_t final {
+private:
+ // is the CLSAG context initialized?
+ bool initialized;
+ // challenge components: c = H(domain-separator, {P}, {C}, C_offset, message, L, R)
+ rct::keyV c_params;
+ // indices in c_params where L and R will be
+ std::size_t c_params_L_offset;
+ std::size_t c_params_R_offset;
+ // musig2-style nonce combination factor components for multisig signing
+ // b = H(domain-separator, {P}, {C}, C_offset, message, {L_combined_alphas}, {R_combined_alphas}, I, D, {s_non_l}, l, k, n)
+ // - {P} = ring of one-time addresses
+ // - {C} = ring of amount commitments (1:1 with one-time addresses)
+ // - C_offset = pseudo-output commitment to offset all amount commitments with
+ // - message = message the CLSAG will sign
+ // - {L_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's L component
+ // - {R_combined_alphas} = set of summed-together public nonces from all multisig signers for this CLSAG's R component
+ // - I = key image for one-time address at {P}[l]
+ // - D = auxiliary key image for the offsetted amount commitment '{C}[l] - C_offset'
+ // - {s_non_l} = fake responses for this proof
+ // - l = real signing index in {P} and '{C} - C_offset'
+ // - k = number of parallel nonces that each participant provides
+ // - n = number of ring members
+ rct::keyV b_params;
+ // indices in b_params where L and R 'alpha' components will be
+ std::size_t b_params_L_offset;
+ std::size_t b_params_R_offset;
+ // CLSAG 'concise' coefficients for {P} and '{C} - C_offset'
+ // mu_x = H(domain-separator, {P}, {C}, I, (1/8)*D, C_offset)
+ // - note: 'D' is stored in the form '(1/8)*D' in transaction data
+ rct::key mu_P;
+ rct::key mu_C;
+ // ring size
+ std::size_t n;
+ // aggregate key image: mu_P*I + mu_C*D
+ rct::geDsmp wH_l_precomp;
+ // aggregate ring members: mu_P*P_i + mu_C*(C_i - C_offset)
+ std::vector<rct::geDsmp> W_precomp;
+ // key image component base keys: H_p(P_i)
+ std::vector<rct::geDsmp> H_precomp;
+ // cache for later: generator 'G' in 'precomp' representation
+ rct::geDsmp G_precomp;
+ // real signing index in this CLSAG
+ std::size_t l;
+ // signature responses
+ rct::keyV s;
+ // number of signing nonces expected per signer
+ std::size_t num_alpha_components;
+public:
+ CLSAG_context_t() : initialized{false} {}
+
+ // prepare CLSAG challenge context
+ bool init(
+ const rct::keyV& P,
+ const rct::keyV& C_nonzero,
+ const rct::key& C_offset,
+ const rct::key& message,
+ const rct::key& I,
+ const rct::key& D,
+ const unsigned int l,
+ const rct::keyV& s,
+ const std::size_t num_alpha_components
+ );
+
+ // get the local signer's combined musig2-style private nonce and compute the CLSAG challenge
+ bool combine_alpha_and_compute_challenge(
+ // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's L component
+ const rct::keyV& total_alpha_G,
+ // set of summed-together musig2-style public nonces from all multisig signers for this CLSAG's R component
+ const rct::keyV& total_alpha_H,
+ // local signer's private musig2-style nonces
+ const rct::keyV& alpha,
+ // local signer's final private nonce, using musig2-style combination with factor 'b'
+ // alpha_combined = sum_i(b^i * alpha[i])
+ rct::key& alpha_combined,
+ // CLSAG challenge to store in the proof
+ rct::key& c_0,
+ // final CLSAG challenge to respond to (need this to make multisig partial signatures)
+ rct::key& c
+ );
+
+ // getter for CLSAG 'concise' coefficients
+ bool get_mu(
+ rct::key& mu_P,
+ rct::key& mu_C
+ ) const;
+};
+
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp
new file mode 100644
index 000000000..cbc556b71
--- /dev/null
+++ b/src/multisig/multisig_tx_builder_ringct.cpp
@@ -0,0 +1,943 @@
+// Copyright (c) 2021, 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 "multisig_tx_builder_ringct.h"
+
+#include "int-util.h"
+#include "memwipe.h"
+
+#include "cryptonote_basic/cryptonote_basic.h"
+#include "cryptonote_basic/account.h"
+#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "cryptonote_core/cryptonote_tx_utils.h"
+#include "device/device.hpp"
+#include "multisig_clsag_context.h"
+#include "ringct/bulletproofs.h"
+#include "ringct/bulletproofs_plus.h"
+#include "ringct/rctSigs.h"
+
+#include <boost/multiprecision/cpp_int.hpp>
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
+
+namespace multisig {
+
+namespace signing {
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+bool view_tag_required(const int bp_version)
+{
+ // view tags were introduced at the same time as BP+, so they are needed after BP+ (v4 and later)
+ if (bp_version <= 3)
+ return false;
+ else
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void sort_sources(
+ std::vector<cryptonote::tx_source_entry>& sources
+)
+{
+ std::sort(sources.begin(), sources.end(), [](const auto& lhs, const auto& rhs){
+ const rct::key& ki0 = lhs.multisig_kLRki.ki;
+ const rct::key& ki1 = rhs.multisig_kLRki.ki;
+ return memcmp(&ki0, &ki1, sizeof(rct::key)) > 0;
+ });
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_keys_for_sources(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ rct::keyV& input_secret_keys
+)
+{
+ const std::size_t num_sources = sources.size();
+ hw::device& hwdev = account_keys.get_device();
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+ for (const std::uint32_t minor_index: subaddr_minor_indices) {
+ subaddresses[hwdev.get_subaddress_spend_public_key(
+ account_keys,
+ {subaddr_account, minor_index}
+ )] = {subaddr_account, minor_index};
+ }
+ input_secret_keys.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const auto& src = sources[i];
+ crypto::key_image tmp_key_image;
+ cryptonote::keypair tmp_keys;
+ if (src.real_output >= src.outputs.size())
+ return false;
+ if (not cryptonote::generate_key_image_helper(
+ account_keys,
+ subaddresses,
+ rct::rct2pk(src.outputs[src.real_output].second.dest),
+ src.real_out_tx_key,
+ src.real_out_additional_tx_keys,
+ src.real_output_in_tx_index,
+ tmp_keys,
+ tmp_key_image,
+ hwdev
+ )) {
+ return false;
+ }
+ input_secret_keys[i] = rct::sk2rct(tmp_keys.sec);
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void shuffle_destinations(
+ std::vector<cryptonote::tx_destination_entry>& destinations
+)
+{
+ std::shuffle(destinations.begin(), destinations.end(), crypto::random_device{});
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_extra(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const crypto::secret_key& tx_secret_key,
+ const crypto::public_key& tx_public_key,
+ const std::vector<crypto::public_key>& tx_aux_public_keys,
+ const std::vector<std::uint8_t>& extra,
+ cryptonote::transaction& tx
+)
+{
+ hw::device &hwdev = account_keys.get_device();
+ tx.extra = extra;
+ // if we have a stealth payment id, find it and encrypt it with the tx key now
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ if (cryptonote::parse_tx_extra(tx.extra, tx_extra_fields))
+ {
+ bool add_dummy_payment_id = true;
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash payment_id = crypto::null_hash;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ if (cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ LOG_PRINT_L2("Encrypting payment id " << payment_id8);
+ crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr);
+ if (view_key_pub == crypto::null_pkey)
+ {
+ // valid combinations:
+ // - 1 output with encrypted payment ID, dummy change output (0 amount)
+ // - 0 outputs, 1 change output with encrypted payment ID
+ // - 1 output with encrypted payment ID, 1 change output
+ LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids");
+ return false;
+ }
+
+ if (!hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key))
+ {
+ LOG_ERROR("Failed to encrypt payment id");
+ return false;
+ }
+
+ std::string extra_nonce_updated;
+ cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8);
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_nonce));
+ if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated))
+ {
+ LOG_ERROR("Failed to add encrypted payment id to tx extra");
+ return false;
+ }
+ LOG_PRINT_L1("Encrypted payment ID: " << payment_id8);
+ add_dummy_payment_id = false;
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ add_dummy_payment_id = false;
+ }
+ }
+
+ // we don't add one if we've got more than the usual 1 destination plus change
+ if (destinations.size() > 2)
+ add_dummy_payment_id = false;
+
+ if (add_dummy_payment_id)
+ {
+ // if we have neither long nor short payment id, add a dummy short one,
+ // this should end up being the vast majority of txes as time goes on
+ std::string extra_nonce_updated;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ crypto::public_key view_key_pub = cryptonote::get_destination_view_key_pub(destinations, change.addr);
+ if (view_key_pub == crypto::null_pkey)
+ {
+ LOG_ERROR("Failed to get key to encrypt dummy payment id with");
+ }
+ else
+ {
+ hwdev.encrypt_payment_id(payment_id8, view_key_pub, tx_secret_key);
+ cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce_updated, payment_id8);
+ if (!cryptonote::add_extra_nonce_to_tx_extra(tx.extra, extra_nonce_updated))
+ {
+ LOG_ERROR("Failed to add dummy encrypted payment id to tx extra");
+ // continue anyway
+ }
+ }
+ }
+ }
+ else
+ {
+ MWARNING("Failed to parse tx extra");
+ tx_extra_fields.clear();
+ }
+
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_pub_key));
+ cryptonote::add_tx_pub_key_to_extra(tx.extra, tx_public_key);
+ cryptonote::remove_field_from_tx_extra(tx.extra, typeid(cryptonote::tx_extra_additional_pub_keys));
+ LOG_PRINT_L2("tx pubkey: " << tx_public_key);
+ if (tx_aux_public_keys.size())
+ {
+ LOG_PRINT_L2("additional tx pubkeys: ");
+ for (size_t i = 0; i < tx_aux_public_keys.size(); ++i)
+ LOG_PRINT_L2(tx_aux_public_keys[i]);
+ cryptonote::add_additional_tx_pub_keys_to_extra(tx.extra, tx_aux_public_keys);
+ }
+ if (not cryptonote::sort_tx_extra(tx.extra, tx.extra))
+ return false;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_keys_for_destinations(
+ const cryptonote::account_keys& account_keys,
+ const std::uint32_t subaddr_account,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const std::vector<std::uint8_t>& extra,
+ const bool use_view_tags,
+ const bool reconstruction,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ rct::keyV& output_public_keys,
+ rct::keyV& output_amount_secret_keys,
+ std::vector<crypto::view_tag>& view_tags,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ hw::device &hwdev = account_keys.get_device();
+
+ // check non-zero change amount case
+ if (change.amount > 0)
+ {
+ // the change output must be directed to the local account
+ if (change.addr != hwdev.get_subaddress(account_keys, {subaddr_account}))
+ return false;
+
+ // expect the change destination to be in the destination set
+ if (std::find_if(destinations.begin(), destinations.end(),
+ [&change](const auto &destination) -> bool
+ {
+ return destination.addr == change.addr;
+ }) == destinations.end())
+ return false;
+ }
+
+ // collect non-change recipients into normal/subaddress buckets
+ std::unordered_set<cryptonote::account_public_address> unique_subbaddr_recipients;
+ std::unordered_set<cryptonote::account_public_address> unique_std_recipients;
+ for(const auto& dst_entr: destinations) {
+ if (dst_entr.addr == change.addr)
+ continue;
+ if (dst_entr.is_subaddress)
+ unique_subbaddr_recipients.insert(dst_entr.addr);
+ else
+ unique_std_recipients.insert(dst_entr.addr);
+ }
+
+ if (not reconstruction) {
+ tx_secret_key = rct::rct2sk(rct::skGen());
+ }
+
+ // tx pub key: R
+ crypto::public_key tx_public_key;
+ if (unique_std_recipients.empty() && unique_subbaddr_recipients.size() == 1) {
+ // if there is exactly 1 non-change recipient, and it's to a subaddress, then the tx pubkey = r*Ksi_nonchange_recipient
+ tx_public_key = rct::rct2pk(
+ hwdev.scalarmultKey(
+ rct::pk2rct(unique_subbaddr_recipients.begin()->m_spend_public_key),
+ rct::sk2rct(tx_secret_key)
+ ));
+ }
+ else {
+ // otherwise, the tx pub key = r*G
+ // - if there are > 1 non-change recipients, with at least one to a subaddress, then the tx pubkey is not used
+ // (additional tx keys will be used instead)
+ // - if all non-change recipients are to normal addresses, then the tx pubkey will be used by all recipients
+ // (including change recipient, even if change is to a subaddress)
+ tx_public_key = rct::rct2pk(hwdev.scalarmultBase(rct::sk2rct(tx_secret_key)));
+ }
+
+ // additional tx pubkeys: R_t
+ // - add if there are > 1 non-change recipients, with at least one to a subaddress
+ const std::size_t num_destinations = destinations.size();
+
+ const bool need_tx_aux_keys = unique_subbaddr_recipients.size() + bool(unique_std_recipients.size()) > 1;
+ if (not reconstruction and need_tx_aux_keys) {
+ tx_aux_secret_keys.clear();
+ tx_aux_secret_keys.reserve(num_destinations);
+ for(std::size_t i = 0; i < num_destinations; ++i)
+ tx_aux_secret_keys.push_back(rct::rct2sk(rct::skGen()));
+ }
+
+ output_public_keys.resize(num_destinations);
+ view_tags.resize(num_destinations);
+ std::vector<crypto::public_key> tx_aux_public_keys;
+ crypto::public_key temp_output_public_key;
+
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ if (not hwdev.generate_output_ephemeral_keys(
+ unsigned_tx.version,
+ account_keys,
+ tx_public_key,
+ tx_secret_key,
+ destinations[i],
+ change.addr,
+ i,
+ need_tx_aux_keys,
+ tx_aux_secret_keys,
+ tx_aux_public_keys,
+ output_amount_secret_keys,
+ temp_output_public_key,
+ use_view_tags,
+ view_tags[i] //unused variable if use_view_tags is not set
+ )) {
+ return false;
+ }
+ output_public_keys[i] = rct::pk2rct(temp_output_public_key);
+ }
+
+ if (num_destinations != output_amount_secret_keys.size())
+ return false;
+
+ CHECK_AND_ASSERT_MES(
+ tx_aux_public_keys.size() == tx_aux_secret_keys.size(),
+ false,
+ "Internal error creating additional public keys"
+ );
+
+ if (not set_tx_extra(account_keys, destinations, change, tx_secret_key, tx_public_key, tx_aux_public_keys, extra, unsigned_tx))
+ return false;
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void set_tx_inputs(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ const std::size_t num_sources = sources.size();
+ unsigned_tx.vin.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ std::vector<std::uint64_t> offsets;
+ offsets.reserve(sources[i].outputs.size());
+ for (const auto& e: sources[i].outputs)
+ offsets.emplace_back(e.first);
+ unsigned_tx.vin[i] = cryptonote::txin_to_key{
+ .amount = 0,
+ .key_offsets = cryptonote::absolute_output_offsets_to_relative(offsets),
+ .k_image = rct::rct2ki(sources[i].multisig_kLRki.ki),
+ };
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool onetime_addresses_are_unique(const rct::keyV& output_public_keys)
+{
+ for (auto addr_it = output_public_keys.begin(); addr_it != output_public_keys.end(); ++addr_it)
+ {
+ if (std::find(output_public_keys.begin(), addr_it, *addr_it) != addr_it)
+ return false;
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_outputs(const rct::keyV& output_public_keys, cryptonote::transaction& unsigned_tx)
+{
+ // sanity check: all onetime addresses should be unique
+ if (not onetime_addresses_are_unique(output_public_keys))
+ return false;
+
+ // set the tx outputs
+ const std::size_t num_destinations = output_public_keys.size();
+ unsigned_tx.vout.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), false, crypto::view_tag{}, unsigned_tx.vout[i]);
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_outputs_with_view_tags(
+ const rct::keyV& output_public_keys,
+ const std::vector<crypto::view_tag>& view_tags,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ // sanity check: all onetime addresses should be unique
+ if (not onetime_addresses_are_unique(output_public_keys))
+ return false;
+
+ // set the tx outputs (with view tags)
+ const std::size_t num_destinations = output_public_keys.size();
+ CHECK_AND_ASSERT_MES(view_tags.size() == num_destinations, false,
+ "multisig signing protocol: internal error, view tag size mismatch.");
+ unsigned_tx.vout.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ cryptonote::set_tx_out(0, rct::rct2pk(output_public_keys[i]), true, view_tags[i], unsigned_tx.vout[i]);
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static void make_new_range_proofs(const int bp_version,
+ const std::vector<std::uint64_t>& output_amounts,
+ const rct::keyV& output_amount_masks,
+ rct::rctSigPrunable& sigs)
+{
+ sigs.bulletproofs.clear();
+ sigs.bulletproofs_plus.clear();
+
+ if (bp_version == 3)
+ sigs.bulletproofs.push_back(rct::bulletproof_PROVE(output_amounts, output_amount_masks));
+ else if (bp_version == 4)
+ sigs.bulletproofs_plus.push_back(rct::bulletproof_plus_PROVE(output_amounts, output_amount_masks));
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool try_reconstruct_range_proofs(const int bp_version,
+ const rct::rctSigPrunable& original_sigs,
+ const std::size_t num_destinations,
+ const rct::ctkeyV& output_public_keys,
+ rct::rctSigPrunable& reconstructed_sigs)
+{
+ auto try_reconstruct_range_proofs =
+ [&](const auto &original_range_proofs, auto &new_range_proofs) -> bool
+ {
+ if (original_range_proofs.size() != 1)
+ return false;
+
+ new_range_proofs = original_range_proofs;
+ new_range_proofs[0].V.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i)
+ new_range_proofs[0].V[i] = rct::scalarmultKey(output_public_keys[i].mask, rct::INV_EIGHT);
+
+ return true;
+ };
+
+ if (bp_version == 3)
+ {
+ if (not try_reconstruct_range_proofs(original_sigs.bulletproofs, reconstructed_sigs.bulletproofs))
+ return false;
+ return rct::bulletproof_VERIFY(reconstructed_sigs.bulletproofs);
+ }
+ else if (bp_version == 4)
+ {
+ if (not try_reconstruct_range_proofs(original_sigs.bulletproofs_plus, reconstructed_sigs.bulletproofs_plus))
+ return false;
+ return rct::bulletproof_plus_VERIFY(reconstructed_sigs.bulletproofs_plus);
+ }
+
+ return false;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool set_tx_rct_signatures(
+ const std::uint64_t fee,
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ const rct::keyV& input_secret_keys,
+ const rct::keyV& output_public_keys,
+ const rct::keyV& output_amount_secret_keys,
+ const rct::RCTConfig& rct_config,
+ const bool reconstruction,
+ cryptonote::transaction& unsigned_tx,
+ std::vector<CLSAG_context_t>& CLSAG_contexts,
+ rct::keyV& cached_w
+)
+{
+ if (rct_config.bp_version != 3 &&
+ rct_config.bp_version != 4)
+ return false;
+ if (rct_config.range_proof_type != rct::RangeProofPaddedBulletproof)
+ return false;
+
+ const std::size_t num_destinations = destinations.size();
+ const std::size_t num_sources = sources.size();
+
+ // rct_signatures component of tx
+ rct::rctSig rv{};
+
+ // set misc. fields
+ if (rct_config.bp_version == 3)
+ rv.type = rct::RCTTypeCLSAG;
+ else if (rct_config.bp_version == 4)
+ rv.type = rct::RCTTypeBulletproofPlus;
+ else
+ return false;
+ rv.txnFee = fee;
+ rv.message = rct::hash2rct(cryptonote::get_transaction_prefix_hash(unsigned_tx));
+
+ // define outputs
+ std::vector<std::uint64_t> output_amounts(num_destinations);
+ rct::keyV output_amount_masks(num_destinations);
+ rv.ecdhInfo.resize(num_destinations);
+ rv.outPk.resize(num_destinations);
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ rv.outPk[i].dest = output_public_keys[i];
+ output_amounts[i] = destinations[i].amount;
+ output_amount_masks[i] = genCommitmentMask(output_amount_secret_keys[i]);
+ rv.ecdhInfo[i].amount = rct::d2h(output_amounts[i]);
+ rct::addKeys2(
+ rv.outPk[i].mask,
+ output_amount_masks[i],
+ rv.ecdhInfo[i].amount,
+ rct::H
+ );
+ rct::ecdhEncode(rv.ecdhInfo[i], output_amount_secret_keys[i], true);
+ }
+
+ // output range proofs
+ if (not reconstruction) {
+ make_new_range_proofs(rct_config.bp_version, output_amounts, output_amount_masks, rv.p);
+ }
+ else {
+ if (not try_reconstruct_range_proofs(rct_config.bp_version,
+ unsigned_tx.rct_signatures.p,
+ num_destinations,
+ rv.outPk,
+ rv.p))
+ return false;
+ }
+
+ // prepare rings for input CLSAGs
+ rv.mixRing.resize(num_sources);
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = sources[i].outputs.size();
+ rv.mixRing[i].resize(ring_size);
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ rv.mixRing[i][j].dest = sources[i].outputs[j].second.dest;
+ rv.mixRing[i][j].mask = sources[i].outputs[j].second.mask;
+ }
+ }
+
+ // make pseudo-output commitments
+ rct::keyV a; //pseudo-output commitment blinding factors
+ auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(a.data()), a.size() * sizeof(rct::key));
+ });
+ if (not reconstruction) {
+ a.resize(num_sources);
+ rv.p.pseudoOuts.resize(num_sources);
+ a[num_sources - 1] = rct::zero();
+ for (std::size_t i = 0; i < num_destinations; ++i) {
+ sc_add(
+ a[num_sources - 1].bytes,
+ a[num_sources - 1].bytes,
+ output_amount_masks[i].bytes
+ );
+ }
+ for (std::size_t i = 0; i < num_sources - 1; ++i) {
+ rct::skGen(a[i]);
+ sc_sub(
+ a[num_sources - 1].bytes,
+ a[num_sources - 1].bytes,
+ a[i].bytes
+ );
+ rct::genC(rv.p.pseudoOuts[i], a[i], sources[i].amount);
+ }
+ rct::genC(
+ rv.p.pseudoOuts[num_sources - 1],
+ a[num_sources - 1],
+ sources[num_sources - 1].amount
+ );
+ }
+ // check balance if reconstructing the tx
+ else {
+ rv.p.pseudoOuts = unsigned_tx.rct_signatures.p.pseudoOuts;
+ if (num_sources != rv.p.pseudoOuts.size())
+ return false;
+ rct::key balance_accumulator = rct::scalarmultH(rct::d2h(fee));
+ for (const auto& e: rv.outPk)
+ rct::addKeys(balance_accumulator, balance_accumulator, e.mask);
+ for (const auto& pseudoOut: rv.p.pseudoOuts)
+ rct::subKeys(balance_accumulator, balance_accumulator, pseudoOut);
+ if (not (balance_accumulator == rct::identity()))
+ return false;
+ }
+
+ // prepare input CLSAGs for signing
+ const rct::key message = get_pre_mlsag_hash(rv, hw::get_device("default"));
+
+ rv.p.CLSAGs.resize(num_sources);
+ if (reconstruction) {
+ if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size())
+ return false;
+ }
+
+ CLSAG_contexts.resize(num_sources);
+ if (not reconstruction)
+ cached_w.resize(num_sources);
+
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = rv.mixRing[i].size();
+ const rct::key& I = sources[i].multisig_kLRki.ki;
+ const std::size_t l = sources[i].real_output;
+ if (l >= ring_size)
+ return false;
+ rct::keyV& s = rv.p.CLSAGs[i].s;
+ const rct::key& C_offset = rv.p.pseudoOuts[i];
+ rct::keyV P(ring_size);
+ rct::keyV C_nonzero(ring_size);
+
+ if (not reconstruction) {
+ s.resize(ring_size);
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ if (j != l)
+ s[j] = rct::skGen(); //make fake responses
+ }
+ }
+ else {
+ if (ring_size != unsigned_tx.rct_signatures.p.CLSAGs[i].s.size())
+ return false;
+ s = unsigned_tx.rct_signatures.p.CLSAGs[i].s;
+ }
+
+ for (std::size_t j = 0; j < ring_size; ++j) {
+ P[j] = rv.mixRing[i][j].dest;
+ C_nonzero[j] = rv.mixRing[i][j].mask;
+ }
+
+ rct::key D;
+ rct::key z;
+ auto z_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&z), sizeof(rct::key));
+ });
+ if (not reconstruction) {
+ sc_sub(z.bytes, sources[i].mask.bytes, a[i].bytes); //commitment to zero privkey
+ ge_p3 H_p3;
+ rct::hash_to_p3(H_p3, rv.mixRing[i][l].dest);
+ rct::key H_l;
+ ge_p3_tobytes(H_l.bytes, &H_p3);
+ D = rct::scalarmultKey(H_l, z); //auxilliary key image (for commitment to zero)
+ rv.p.CLSAGs[i].D = rct::scalarmultKey(D, rct::INV_EIGHT);
+ rv.p.CLSAGs[i].I = I;
+ }
+ else {
+ rv.p.CLSAGs[i].D = unsigned_tx.rct_signatures.p.CLSAGs[i].D;
+ rv.p.CLSAGs[i].I = I;
+ D = rct::scalarmultKey(rv.p.CLSAGs[i].D, rct::EIGHT);
+ }
+
+ if (not CLSAG_contexts[i].init(P, C_nonzero, C_offset, message, I, D, l, s, kAlphaComponents))
+ return false;
+
+ if (not reconstruction) {
+ rct::key mu_P;
+ rct::key mu_C;
+ if (not CLSAG_contexts[i].get_mu(mu_P, mu_C))
+ return false;
+ sc_mul(cached_w[i].bytes, mu_P.bytes, input_secret_keys[i].bytes);
+ sc_muladd(cached_w[i].bytes, mu_C.bytes, z.bytes, cached_w[i].bytes);
+ }
+ }
+ unsigned_tx.rct_signatures = std::move(rv);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+static bool compute_tx_fee(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const std::vector<cryptonote::tx_destination_entry>& destinations,
+ std::uint64_t& fee
+)
+{
+ boost::multiprecision::uint128_t in_amount = 0;
+ for (const auto& src: sources)
+ in_amount += src.amount;
+
+ boost::multiprecision::uint128_t out_amount = 0;
+ for (const auto& dst: destinations)
+ out_amount += dst.amount;
+
+ if (out_amount > in_amount)
+ return false;
+
+ if (in_amount - out_amount > std::numeric_limits<std::uint64_t>::max())
+ return false;
+
+ fee = static_cast<std::uint64_t>(in_amount - out_amount);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+tx_builder_ringct_t::tx_builder_ringct_t(): initialized(false) {}
+//----------------------------------------------------------------------------------------------------------------------
+tx_builder_ringct_t::~tx_builder_ringct_t()
+{
+ memwipe(static_cast<rct::key *>(cached_w.data()), cached_w.size() * sizeof(rct::key));
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::init(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<std::uint8_t>& extra,
+ const std::uint64_t unlock_time,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ std::vector<cryptonote::tx_source_entry>& sources,
+ std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const rct::RCTConfig& rct_config,
+ const bool use_rct,
+ const bool reconstruction,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ initialized = false;
+ this->reconstruction = reconstruction;
+ if (not use_rct)
+ return false;
+ if (sources.empty())
+ return false;
+
+ if (not reconstruction)
+ unsigned_tx.set_null();
+
+ std::uint64_t fee;
+ if (not compute_tx_fee(sources, destinations, fee))
+ return false;
+
+ // decide if view tags are needed
+ const bool use_view_tags{view_tag_required(rct_config.bp_version)};
+
+ // misc. fields
+ unsigned_tx.version = 2; //rct = 2
+ unsigned_tx.unlock_time = unlock_time;
+
+ // sort inputs
+ sort_sources(sources);
+
+ // get secret keys for signing input CLSAGs (multisig: or for the initial partial signature)
+ rct::keyV input_secret_keys;
+ auto input_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(input_secret_keys.data()), input_secret_keys.size() * sizeof(rct::key));
+ });
+ if (not compute_keys_for_sources(account_keys, sources, subaddr_account, subaddr_minor_indices, input_secret_keys))
+ return false;
+
+ // randomize output order
+ if (not reconstruction)
+ shuffle_destinations(destinations);
+
+ // prepare outputs
+ rct::keyV output_public_keys;
+ rct::keyV output_amount_secret_keys;
+ std::vector<crypto::view_tag> view_tags;
+ auto output_amount_secret_keys_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(output_amount_secret_keys.data()), output_amount_secret_keys.size() * sizeof(rct::key));
+ });
+ if (not compute_keys_for_destinations(account_keys,
+ subaddr_account,
+ destinations,
+ change,
+ extra,
+ use_view_tags,
+ reconstruction,
+ tx_secret_key,
+ tx_aux_secret_keys,
+ output_public_keys,
+ output_amount_secret_keys,
+ view_tags,
+ unsigned_tx))
+ return false;
+
+ // add inputs to tx
+ set_tx_inputs(sources, unsigned_tx);
+
+ // add output one-time addresses to tx
+ bool set_tx_outputs_result{false};
+ if (use_view_tags)
+ set_tx_outputs_result = set_tx_outputs_with_view_tags(output_public_keys, view_tags, unsigned_tx);
+ else
+ set_tx_outputs_result = set_tx_outputs(output_public_keys, unsigned_tx);
+
+ if (not set_tx_outputs_result)
+ return false;
+
+ // prepare input signatures
+ if (not set_tx_rct_signatures(fee, sources, destinations, input_secret_keys, output_public_keys, output_amount_secret_keys,
+ rct_config, reconstruction, unsigned_tx, CLSAG_contexts, cached_w))
+ return false;
+
+ initialized = true;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::first_partial_sign(
+ const std::size_t source,
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& c_0,
+ rct::key& s
+)
+{
+ if (not initialized or reconstruction)
+ return false;
+ const std::size_t num_sources = CLSAG_contexts.size();
+ if (source >= num_sources)
+ return false;
+ rct::key c;
+ rct::key alpha_combined;
+ auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key));
+ });
+ if (not CLSAG_contexts[source].combine_alpha_and_compute_challenge(
+ total_alpha_G,
+ total_alpha_H,
+ alpha,
+ alpha_combined,
+ c_0,
+ c
+ )) {
+ return false;
+ }
+
+ // initial partial response:
+ // s = alpha_combined_local - challenge*[mu_P*(local keys and sender-receiver secret and subaddress material) +
+ // mu_C*(commitment-to-zero secret)]
+ sc_mulsub(s.bytes, c.bytes, cached_w[source].bytes, alpha_combined.bytes);
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::next_partial_sign(
+ const rct::keyM& total_alpha_G,
+ const rct::keyM& total_alpha_H,
+ const rct::keyM& alpha,
+ const rct::key& x,
+ rct::keyV& c_0,
+ rct::keyV& s
+)
+{
+ if (not initialized or not reconstruction)
+ return false;
+ const std::size_t num_sources = CLSAG_contexts.size();
+ if (num_sources != total_alpha_G.size())
+ return false;
+ if (num_sources != total_alpha_H.size())
+ return false;
+ if (num_sources != alpha.size())
+ return false;
+ if (num_sources != c_0.size())
+ return false;
+ if (num_sources != s.size())
+ return false;
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ rct::key c;
+ rct::key alpha_combined;
+ auto alpha_combined_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&alpha_combined), sizeof(rct::key));
+ });
+ if (not CLSAG_contexts[i].combine_alpha_and_compute_challenge(
+ total_alpha_G[i],
+ total_alpha_H[i],
+ alpha[i],
+ alpha_combined,
+ c_0[i],
+ c
+ )) {
+ return false;
+ }
+ rct::key mu_P;
+ rct::key mu_C;
+ if (not CLSAG_contexts[i].get_mu(mu_P, mu_C))
+ return false;
+ rct::key w;
+ auto w_wiper = epee::misc_utils::create_scope_leave_handler([&]{
+ memwipe(static_cast<rct::key *>(&w), sizeof(rct::key));
+ });
+ sc_mul(w.bytes, mu_P.bytes, x.bytes);
+
+ // include local signer's response:
+ // s += alpha_combined_local - challenge*[mu_P*(local keys)]
+ sc_add(s[i].bytes, s[i].bytes, alpha_combined.bytes);
+ sc_mulsub(s[i].bytes, c.bytes, w.bytes, s[i].bytes);
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool tx_builder_ringct_t::finalize_tx(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const rct::keyV& c_0,
+ const rct::keyV& s,
+ cryptonote::transaction& unsigned_tx
+)
+{
+ const std::size_t num_sources = sources.size();
+ if (num_sources != unsigned_tx.rct_signatures.p.CLSAGs.size())
+ return false;
+ if (num_sources != c_0.size())
+ return false;
+ if (num_sources != s.size())
+ return false;
+ for (std::size_t i = 0; i < num_sources; ++i) {
+ const std::size_t ring_size = unsigned_tx.rct_signatures.p.CLSAGs[i].s.size();
+ if (sources[i].real_output >= ring_size)
+ return false;
+ unsigned_tx.rct_signatures.p.CLSAGs[i].s[sources[i].real_output] = s[i];
+ unsigned_tx.rct_signatures.p.CLSAGs[i].c1 = c_0[i];
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------------------------
+} //namespace signing
+
+} //namespace multisig
diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h
new file mode 100644
index 000000000..67ef9e065
--- /dev/null
+++ b/src/multisig/multisig_tx_builder_ringct.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, 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
+
+#include "ringct/rctTypes.h"
+
+#include <set>
+#include <vector>
+
+namespace cryptonote {
+
+class transaction;
+struct tx_source_entry;
+struct tx_destination_entry;
+struct account_keys;
+
+}
+
+namespace multisig {
+
+namespace signing {
+
+class CLSAG_context_t;
+
+// number of parallel signing nonces to use per signer (2 nonces as in musig2 and FROST)
+constexpr std::size_t kAlphaComponents = 2;
+
+class tx_builder_ringct_t final {
+private:
+ // the tx builder has been initialized
+ bool initialized;
+ // the tx builder is 'reconstructing' a tx that has already been created using this object
+ bool reconstruction;
+ // cached: mu_P*(local keys and sender-receiver secret and subaddress material) + mu_C*(commitment-to-zero secret)
+ // - these are only used for the initial building of a tx (not reconstructions)
+ rct::keyV cached_w;
+ // contexts for making CLSAG challenges with multisig nonces
+ std::vector<CLSAG_context_t> CLSAG_contexts;
+public:
+ tx_builder_ringct_t();
+ ~tx_builder_ringct_t();
+
+ // prepare an unsigned transaction (and get tx privkeys for outputs)
+ bool init(
+ const cryptonote::account_keys& account_keys,
+ const std::vector<std::uint8_t>& extra,
+ const std::uint64_t unlock_time,
+ const std::uint32_t subaddr_account,
+ const std::set<std::uint32_t>& subaddr_minor_indices,
+ std::vector<cryptonote::tx_source_entry>& sources,
+ std::vector<cryptonote::tx_destination_entry>& destinations,
+ const cryptonote::tx_destination_entry& change,
+ const rct::RCTConfig& rct_config,
+ const bool use_rct,
+ const bool reconstruction,
+ crypto::secret_key& tx_secret_key,
+ std::vector<crypto::secret_key>& tx_aux_secret_keys,
+ cryptonote::transaction& unsigned_tx
+ );
+
+ // get the first partial signature for the specified input ('source')
+ bool first_partial_sign(
+ const std::size_t source,
+ const rct::keyV& total_alpha_G,
+ const rct::keyV& total_alpha_H,
+ const rct::keyV& alpha,
+ rct::key& c_0,
+ rct::key& s
+ );
+
+ // get intermediate partial signatures for all the inputs
+ bool next_partial_sign(
+ const rct::keyM& total_alpha_G,
+ const rct::keyM& total_alpha_H,
+ const rct::keyM& alpha,
+ const rct::key& x,
+ rct::keyV& c_0,
+ rct::keyV& s
+ );
+
+ // finalize an unsigned transaction (add challenges and real responses to incomplete CLSAG signatures)
+ static bool finalize_tx(
+ const std::vector<cryptonote::tx_source_entry>& sources,
+ const rct::keyV& c_0,
+ const rct::keyV& s,
+ cryptonote::transaction& unsigned_tx
+ );
+};
+
+} //namespace signing
+
+} //namespace multisig