parent
9750e1fa10
commit
c7b2944f89
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in new issue