Fix issue 4793 - M/N multisig transaction signature

This commit is contained in:
naughtyfox 2018-11-12 20:46:00 +03:00
parent 8534f71eed
commit 6732fc7fde
5 changed files with 213 additions and 20 deletions

View file

@ -43,7 +43,8 @@ set(common_sources
spawn.cpp spawn.cpp
threadpool.cpp threadpool.cpp
updates.cpp updates.cpp
aligned.c) aligned.c
combinator.cpp)
if (STACK_TRACE) if (STACK_TRACE)
list(APPEND common_sources stack_trace.cpp) list(APPEND common_sources stack_trace.cpp)
@ -77,7 +78,8 @@ set(common_private_headers
stack_trace.h stack_trace.h
threadpool.h threadpool.h
updates.h updates.h
aligned.h) aligned.h
combinator.h)
monero_private_headers(common monero_private_headers(common
${common_private_headers}) ${common_private_headers})

50
src/common/combinator.cpp Normal file
View file

@ -0,0 +1,50 @@
// Copyright (c) 2018, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "combinator.h"
namespace tools {
uint64_t combinations_count(uint32_t k, uint32_t n)
{
if (k > n) {
throw std::runtime_error("k must not be greater than n");
}
uint64_t c = 1;
for (uint64_t i = 1; i <= k; ++i) {
c *= n--;
c /= i;
}
return c;
}
}

96
src/common/combinator.h Normal file
View file

@ -0,0 +1,96 @@
// Copyright (c) 2018, 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.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#pragma once
#include <iostream>
#include <vector>
namespace tools {
uint64_t combinations_count(uint32_t k, uint32_t n);
template<typename T>
class Combinator {
public:
Combinator(const std::vector<T>& v) : origin(v) { }
std::vector<std::vector<T>> combine(size_t k);
private:
void doCombine(size_t from, size_t k);
std::vector<T> origin;
std::vector<std::vector<T>> combinations;
std::vector<size_t> current;
};
template<typename T>
std::vector<std::vector<T>> Combinator<T>::combine(size_t k)
{
if (k > origin.size())
{
throw std::runtime_error("k must be smaller than elements number");
}
if (k == 0)
{
throw std::runtime_error("k must be greater than zero");
}
combinations.clear();
doCombine(0, k);
return combinations;
}
template<typename T>
void Combinator<T>::doCombine(size_t from, size_t k)
{
current.push_back(0);
for (size_t i = from; i <= origin.size() - k; ++i)
{
current.back() = i;
if (k > 1) {
doCombine(i + 1, k - 1);
} else {
std::vector<T> comb;
for (auto ind: current) {
comb.push_back(origin[ind]);
}
combinations.push_back(comb);
}
}
current.pop_back();
}
} //namespace tools

View file

@ -67,6 +67,7 @@ using namespace epee;
#include "common/json_util.h" #include "common/json_util.h"
#include "memwipe.h" #include "memwipe.h"
#include "common/base58.h" #include "common/base58.h"
#include "common/combinator.h"
#include "common/dns_utils.h" #include "common/dns_utils.h"
#include "common/notify.h" #include "common/notify.h"
#include "common/perf_timer.h" #include "common/perf_timer.h"
@ -177,6 +178,20 @@ namespace
return public_keys; return public_keys;
} }
bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2)
{
if (s1.empty() || s2.empty())
return false;
for (const auto& e: s1)
{
if (s2.find(e) != s2.end())
return true;
}
return false;
}
} }
namespace namespace
@ -6045,7 +6060,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
for (auto &sig: ptx.multisig_sigs) for (auto &sig: ptx.multisig_sigs)
{ {
if (sig.ignore != local_signer) if (sig.ignore.find(local_signer) == sig.ignore.end())
{ {
ptx.tx.rct_signatures = sig.sigs; ptx.tx.rct_signatures = sig.sigs;
@ -6079,7 +6094,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
bool found = false; bool found = false;
for (const auto &sig: ptx.multisig_sigs) for (const auto &sig: ptx.multisig_sigs)
{ {
if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end()) if (sig.ignore.find(local_signer) == sig.ignore.end() && !keys_intersect(sig.ignore, exported_txs.m_signers))
{ {
THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final");
ptx.tx.rct_signatures = sig.sigs; ptx.tx.rct_signatures = sig.sigs;
@ -7512,30 +7527,56 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
// if this is a multisig wallet, create a list of multisig signers we can use // if this is a multisig wallet, create a list of multisig signers we can use
std::deque<crypto::public_key> multisig_signers; std::deque<crypto::public_key> multisig_signers;
size_t n_multisig_txes = 0; size_t n_multisig_txes = 0;
std::vector<std::unordered_set<crypto::public_key>> ignore_sets;
if (m_multisig && !m_transfers.empty()) if (m_multisig && !m_transfers.empty())
{ {
const crypto::public_key local_signer = get_multisig_signer_public_key(); const crypto::public_key local_signer = get_multisig_signer_public_key();
size_t n_available_signers = 1; size_t n_available_signers = 1;
// At this step we need to define set of participants available for signature,
// i.e. those of them who exchanged with multisig info's
for (const crypto::public_key &signer: m_multisig_signers) for (const crypto::public_key &signer: m_multisig_signers)
{ {
if (signer == local_signer) if (signer == local_signer)
continue; continue;
multisig_signers.push_front(signer);
for (const auto &i: m_transfers[0].m_multisig_info) for (const auto &i: m_transfers[0].m_multisig_info)
{ {
if (i.m_signer == signer) if (i.m_signer == signer)
{ {
multisig_signers.pop_front();
multisig_signers.push_back(signer); multisig_signers.push_back(signer);
++n_available_signers; ++n_available_signers;
break; break;
} }
} }
} }
multisig_signers.push_back(local_signer); // n_available_signers includes the transaction creator, but multisig_signers doesn't
MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers"); MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers");
THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed); THROW_WALLET_EXCEPTION_IF(n_available_signers < m_multisig_threshold, error::multisig_import_needed);
n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1; if (n_available_signers > m_multisig_threshold)
{
// If there more potential signers (those who exchanged with multisig info)
// than threshold needed some of them should be skipped since we don't know
// who will sign tx and who won't. Hence we don't contribute their LR pairs to the signature.
// We create as many transactions as many combinations of excluded signers may be.
// For example, if we have 2/4 wallet and wallets are: A, B, C and D. Let A be
// transaction creator, so we need just 1 signature from set of B, C, D.
// Using "excluding" logic here we have to exclude 2-of-3 wallets. Combinations go as follows:
// BC, BD, and CD. We save these sets to use later and counting the number of required txs.
tools::Combinator<crypto::public_key> c(std::vector<crypto::public_key>(multisig_signers.begin(), multisig_signers.end()));
auto ignore_combinations = c.combine(multisig_signers.size() + 1 - m_multisig_threshold);
for (const auto& combination: ignore_combinations)
{
ignore_sets.push_back(std::unordered_set<crypto::public_key>(combination.begin(), combination.end()));
}
n_multisig_txes = ignore_sets.size();
}
else
{
// If we have exact count of signers just to fit in threshold we don't exclude anyone and create 1 transaction
n_multisig_txes = 1;
}
MDEBUG("We will create " << n_multisig_txes << " txes"); MDEBUG("We will create " << n_multisig_txes << " txes");
} }
@ -7603,8 +7644,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.mask = td.m_mask; src.mask = td.m_mask;
if (m_multisig) if (m_multisig)
{ {
crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L); src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L);
} }
else else
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()}); src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
@ -7666,7 +7707,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
std::vector<tools::wallet2::multisig_sig> multisig_sigs; std::vector<tools::wallet2::multisig_sig> multisig_sigs;
if (m_multisig) if (m_multisig)
{ {
crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); auto ignore = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout}); multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, std::unordered_set<crypto::public_key>(), msout});
if (m_multisig_threshold < m_multisig_signers.size()) if (m_multisig_threshold < m_multisig_signers.size())
@ -7674,7 +7715,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx); const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx);
// create the other versions, one for every other participant (the first one's already done above) // create the other versions, one for every other participant (the first one's already done above)
for (size_t signer_index = 1; signer_index < n_multisig_txes; ++signer_index) for (size_t ignore_index = 1; ignore_index < ignore_sets.size(); ++ignore_index)
{ {
std::unordered_set<rct::key> new_used_L; std::unordered_set<rct::key> new_used_L;
size_t src_idx = 0; size_t src_idx = 0;
@ -7682,7 +7723,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
for(size_t idx: selected_transfers) for(size_t idx: selected_transfers)
{ {
cryptonote::tx_source_entry& src = sources_copy[src_idx]; cryptonote::tx_source_entry& src = sources_copy[src_idx];
src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L); src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_sets[ignore_index], used_L, new_used_L);
++src_idx; ++src_idx;
} }
@ -7694,7 +7735,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit);
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix"); THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_prefix_hash(ms_tx) != prefix_hash, error::wallet_internal_error, "Multisig txes do not share prefix");
multisig_sigs.push_back({ms_tx.rct_signatures, multisig_signers[signer_index], new_used_L, std::unordered_set<crypto::public_key>(), msout}); multisig_sigs.push_back({ms_tx.rct_signatures, ignore_sets[ignore_index], new_used_L, std::unordered_set<crypto::public_key>(), msout});
ms_tx.rct_signatures = tx.rct_signatures; ms_tx.rct_signatures = tx.rct_signatures;
THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures"); THROW_WALLET_EXCEPTION_IF(cryptonote::get_transaction_hash(ms_tx) != cryptonote::get_transaction_hash(tx), error::wallet_internal_error, "Multisig txes differ by more than the signatures");
@ -11265,7 +11306,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con
return kLRki; return kLRki;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const
{ {
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index"); CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index");
@ -11276,8 +11317,9 @@ rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto
size_t n_signers_used = 1; size_t n_signers_used = 1;
for (const auto &p: m_transfers[n].m_multisig_info) for (const auto &p: m_transfers[n].m_multisig_info)
{ {
if (p.m_signer == ignore) if (ignore_set.find(p.m_signer) != ignore_set.end())
continue; continue;
for (const auto &lr: p.m_LR) for (const auto &lr: p.m_LR)
{ {
if (used_L.find(lr.m_L) != used_L.end()) if (used_L.find(lr.m_L) != used_L.end())
@ -11336,7 +11378,10 @@ cryptonote::blobdata wallet2::export_multisig()
info[n].m_partial_key_images.push_back(ki); info[n].m_partial_key_images.push_back(ki);
} }
size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1; // Wallet tries to create as many transactions as many signers combinations. We calculate the maximum number here as follows:
// if we have 2/4 wallet with signers: A, B, C, D and A is a transaction creator it will need to pick up 1 signer from 3 wallets left.
// That means counting combinations for excluding 2-of-3 wallets (k = total signers count - threshold, n = total signers count - 1).
size_t nlr = tools::combinations_count(m_multisig_signers.size() - m_multisig_threshold, m_multisig_signers.size() - 1);
for (size_t m = 0; m < nlr; ++m) for (size_t m = 0; m < nlr; ++m)
{ {
td.m_multisig_k.push_back(rct::skGen()); td.m_multisig_k.push_back(rct::skGen());

View file

@ -374,7 +374,7 @@ namespace tools
struct multisig_sig struct multisig_sig
{ {
rct::rctSig sigs; rct::rctSig sigs;
crypto::public_key ignore; std::unordered_set<crypto::public_key> ignore;
std::unordered_set<rct::key> used_L; std::unordered_set<rct::key> used_L;
std::unordered_set<crypto::public_key> signing_keys; std::unordered_set<crypto::public_key> signing_keys;
rct::multisig_out msout; rct::multisig_out msout;
@ -1256,7 +1256,7 @@ namespace tools
void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs); void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
void trim_hashchain(); void trim_hashchain();
crypto::key_image get_multisig_composite_key_image(size_t n) const; crypto::key_image get_multisig_composite_key_image(size_t n) const;
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const; rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const;
rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const; rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const;
void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n); void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);