Fix issue 4793 - M/N multisig transaction signature
This commit is contained in:
parent
8534f71eed
commit
6732fc7fde
@ -43,7 +43,8 @@ set(common_sources
|
||||
spawn.cpp
|
||||
threadpool.cpp
|
||||
updates.cpp
|
||||
aligned.c)
|
||||
aligned.c
|
||||
combinator.cpp)
|
||||
|
||||
if (STACK_TRACE)
|
||||
list(APPEND common_sources stack_trace.cpp)
|
||||
@ -77,7 +78,8 @@ set(common_private_headers
|
||||
stack_trace.h
|
||||
threadpool.h
|
||||
updates.h
|
||||
aligned.h)
|
||||
aligned.h
|
||||
combinator.h)
|
||||
|
||||
monero_private_headers(common
|
||||
${common_private_headers})
|
||||
|
50
src/common/combinator.cpp
Normal file
50
src/common/combinator.cpp
Normal 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
96
src/common/combinator.h
Normal 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
|
@ -67,6 +67,7 @@ using namespace epee;
|
||||
#include "common/json_util.h"
|
||||
#include "memwipe.h"
|
||||
#include "common/base58.h"
|
||||
#include "common/combinator.h"
|
||||
#include "common/dns_utils.h"
|
||||
#include "common/notify.h"
|
||||
#include "common/perf_timer.h"
|
||||
@ -177,6 +178,20 @@ namespace
|
||||
|
||||
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
|
||||
@ -6045,7 +6060,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
|
||||
|
||||
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;
|
||||
|
||||
@ -6079,7 +6094,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
|
||||
bool found = false;
|
||||
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");
|
||||
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
|
||||
std::deque<crypto::public_key> multisig_signers;
|
||||
size_t n_multisig_txes = 0;
|
||||
std::vector<std::unordered_set<crypto::public_key>> ignore_sets;
|
||||
if (m_multisig && !m_transfers.empty())
|
||||
{
|
||||
const crypto::public_key local_signer = get_multisig_signer_public_key();
|
||||
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)
|
||||
{
|
||||
if (signer == local_signer)
|
||||
continue;
|
||||
multisig_signers.push_front(signer);
|
||||
for (const auto &i: m_transfers[0].m_multisig_info)
|
||||
{
|
||||
if (i.m_signer == signer)
|
||||
{
|
||||
multisig_signers.pop_front();
|
||||
multisig_signers.push_back(signer);
|
||||
++n_available_signers;
|
||||
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");
|
||||
THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed);
|
||||
n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1;
|
||||
THROW_WALLET_EXCEPTION_IF(n_available_signers < m_multisig_threshold, error::multisig_import_needed);
|
||||
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");
|
||||
}
|
||||
|
||||
@ -7603,8 +7644,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
src.mask = td.m_mask;
|
||||
if (m_multisig)
|
||||
{
|
||||
crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front();
|
||||
src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore, used_L, used_L);
|
||||
auto ignore_set = ignore_sets.empty() ? std::unordered_set<crypto::public_key>() : ignore_sets.front();
|
||||
src.multisig_kLRki = get_multisig_composite_kLRki(idx, ignore_set, used_L, used_L);
|
||||
}
|
||||
else
|
||||
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;
|
||||
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});
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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(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");
|
||||
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;
|
||||
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;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
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");
|
||||
|
||||
@ -11276,8 +11317,9 @@ rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto
|
||||
size_t n_signers_used = 1;
|
||||
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;
|
||||
|
||||
for (const auto &lr: p.m_LR)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
td.m_multisig_k.push_back(rct::skGen());
|
||||
|
@ -374,7 +374,7 @@ namespace tools
|
||||
struct multisig_sig
|
||||
{
|
||||
rct::rctSig sigs;
|
||||
crypto::public_key ignore;
|
||||
std::unordered_set<crypto::public_key> ignore;
|
||||
std::unordered_set<rct::key> used_L;
|
||||
std::unordered_set<crypto::public_key> signing_keys;
|
||||
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 trim_hashchain();
|
||||
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::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);
|
||||
|
Loading…
Reference in New Issue
Block a user