mirror of
https://github.com/monero-project/monero.git
synced 2025-08-14 06:35:35 -04:00
Merge pull request #2134
ceabc4f9
change the N-1/N multisig second message signer for auth (moneromooo-monero)55c2845d
core_tests: multisig test now tests multiple inputs (moneromooo-monero)98db7ee4
wallet: factor multisig info parsing (moneromooo-monero)31a97e76
wallet: use raw encrypted data in multisig import/export RPC (moneromooo-monero)2fa707d1
wallet: add multisig sign/submit RPC (moneromooo-monero)e36f5b60
Match surae's recommendation to derive multisig keys (moneromooo-monero)a36c261d
wallet2: fix slow multisig unit tests with subaddress patch (moneromooo-monero)fa569712
make multisig work with subaddresses (moneromooo-monero)dffa0dce
simplewallet: add export_raw_multisig command (moneromooo-monero)7f4c220b
simplewallet: add multisig to wallet type in wallet_info output (moneromooo-monero)26529038
wallet: guard against partly initialized multisig wallet (moneromooo-monero)66e34e85
add multisig core test and factor multisig building blocks (moneromooo-monero)f4eda44c
N-1/N multisig (moneromooo-monero)cd64c799
multisig address generation RPC (moneromooo-monero)fff871a4
gen_multisig: generates multisig wallets if participants trust each other (moneromooo-monero)95a21a79
wallet2: allow empty wallet filename to avoid saving data (moneromooo-monero)b84b3565
tests: add multisig unit tests (moneromooo-monero)4c313324
Add N/N multisig tx generation and signing (moneromooo-monero)6d219a92
wallet: add multisig key generation (moneromooo-monero)
This commit is contained in:
commit
1cc7451130
41 changed files with 4828 additions and 383 deletions
|
@ -59,6 +59,7 @@ target_link_libraries(cryptonote_core
|
|||
common
|
||||
cncrypto
|
||||
blockchain_db
|
||||
multisig
|
||||
ringct
|
||||
${Boost_DATE_TIME_LIBRARY}
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
|
|
|
@ -40,11 +40,38 @@ using namespace epee;
|
|||
#include "crypto/crypto.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "ringct/rctSigs.h"
|
||||
#include "multisig/multisig.h"
|
||||
|
||||
using namespace crypto;
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
//---------------------------------------------------------------
|
||||
void classify_addresses(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr, size_t &num_stdaddresses, size_t &num_subaddresses, account_public_address &single_dest_subaddress)
|
||||
{
|
||||
num_stdaddresses = 0;
|
||||
num_subaddresses = 0;
|
||||
std::unordered_set<cryptonote::account_public_address> unique_dst_addresses;
|
||||
for(const tx_destination_entry& dst_entr: destinations)
|
||||
{
|
||||
if (change_addr && dst_entr.addr == change_addr)
|
||||
continue;
|
||||
if (unique_dst_addresses.count(dst_entr.addr) == 0)
|
||||
{
|
||||
unique_dst_addresses.insert(dst_entr.addr);
|
||||
if (dst_entr.is_subaddress)
|
||||
{
|
||||
++num_subaddresses;
|
||||
single_dest_subaddress = dst_entr.addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
++num_stdaddresses;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << " subaddresses");
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs, uint8_t hard_fork_version) {
|
||||
tx.vin.clear();
|
||||
|
@ -161,19 +188,21 @@ namespace cryptonote
|
|||
return destinations[0].addr.m_view_public_key;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, bool bulletproof)
|
||||
bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout)
|
||||
{
|
||||
std::vector<rct::key> amount_keys;
|
||||
tx.set_null();
|
||||
amount_keys.clear();
|
||||
if (msout)
|
||||
{
|
||||
msout->c.clear();
|
||||
}
|
||||
|
||||
tx.version = rct ? 2 : 1;
|
||||
tx.unlock_time = unlock_time;
|
||||
|
||||
tx.extra = extra;
|
||||
keypair txkey;
|
||||
txkey.sec = rct::rct2sk(rct::skGen());
|
||||
tx_key = txkey.sec;
|
||||
crypto::public_key txkey_pub;
|
||||
|
||||
// if we have a stealth payment id, find it and encrypt it with the tx key now
|
||||
std::vector<tx_extra_field> tx_extra_fields;
|
||||
|
@ -193,7 +222,7 @@ namespace cryptonote
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec))
|
||||
if (!encrypt_payment_id(payment_id, view_key_pub, tx_key))
|
||||
{
|
||||
LOG_ERROR("Failed to encrypt payment id");
|
||||
return false;
|
||||
|
@ -247,8 +276,8 @@ namespace cryptonote
|
|||
return false;
|
||||
}
|
||||
|
||||
//check that derivated key is equal with real output key
|
||||
if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
|
||||
//check that derivated key is equal with real output key (if non multisig)
|
||||
if(!msout && !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
|
||||
{
|
||||
LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:"
|
||||
<< string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:"
|
||||
|
@ -261,7 +290,7 @@ namespace cryptonote
|
|||
//put key image into tx input
|
||||
txin_to_key input_to_key;
|
||||
input_to_key.amount = src_entr.amount;
|
||||
input_to_key.k_image = img;
|
||||
input_to_key.k_image = msout ? rct::rct2ki(src_entr.multisig_kLRki.ki) : img;
|
||||
|
||||
//fill outputs array and use relative offsets
|
||||
for(const tx_source_entry::output_entry& out_entry: src_entr.outputs)
|
||||
|
@ -293,47 +322,29 @@ namespace cryptonote
|
|||
// figure out if we need to make additional tx pubkeys
|
||||
size_t num_stdaddresses = 0;
|
||||
size_t num_subaddresses = 0;
|
||||
std::unordered_set<cryptonote::account_public_address> unique_dst_addresses;
|
||||
account_public_address single_dest_subaddress;
|
||||
for(const tx_destination_entry& dst_entr: destinations)
|
||||
{
|
||||
if (change_addr && dst_entr.addr == *change_addr)
|
||||
continue;
|
||||
if (unique_dst_addresses.count(dst_entr.addr) == 0)
|
||||
{
|
||||
unique_dst_addresses.insert(dst_entr.addr);
|
||||
if (dst_entr.is_subaddress)
|
||||
{
|
||||
++num_subaddresses;
|
||||
single_dest_subaddress = dst_entr.addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
++num_stdaddresses;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses");
|
||||
classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress);
|
||||
|
||||
// if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D
|
||||
if (num_stdaddresses == 0 && num_subaddresses == 1)
|
||||
{
|
||||
txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec)));
|
||||
txkey_pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(tx_key)));
|
||||
}
|
||||
else
|
||||
{
|
||||
txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(txkey.sec)));
|
||||
txkey_pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(tx_key)));
|
||||
}
|
||||
remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
|
||||
add_tx_pub_key_to_extra(tx, txkey.pub);
|
||||
add_tx_pub_key_to_extra(tx, txkey_pub);
|
||||
|
||||
std::vector<crypto::public_key> additional_tx_public_keys;
|
||||
additional_tx_keys.clear();
|
||||
|
||||
// we don't need to include additional tx keys if:
|
||||
// - all the destinations are standard addresses
|
||||
// - there's only one destination which is a subaddress
|
||||
bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1);
|
||||
if (need_additional_txkeys)
|
||||
CHECK_AND_ASSERT_MES(destinations.size() == additional_tx_keys.size(), false, "Wrong amount of additional tx keys");
|
||||
|
||||
uint64_t summary_outs_money = 0;
|
||||
//fill outputs
|
||||
|
@ -348,7 +359,7 @@ namespace cryptonote
|
|||
keypair additional_txkey;
|
||||
if (need_additional_txkeys)
|
||||
{
|
||||
additional_txkey.sec = rct::rct2sk(rct::skGen());
|
||||
additional_txkey.sec = additional_tx_keys[output_index];
|
||||
if (dst_entr.is_subaddress)
|
||||
additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec)));
|
||||
else
|
||||
|
@ -359,20 +370,19 @@ namespace cryptonote
|
|||
if (change_addr && dst_entr.addr == *change_addr)
|
||||
{
|
||||
// sending change to yourself; derivation = a*R
|
||||
r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")");
|
||||
r = crypto::generate_key_derivation(txkey_pub, sender_account_keys.m_view_secret_key, derivation);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey_pub << ", " << sender_account_keys.m_view_secret_key << ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
// sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
|
||||
r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")");
|
||||
r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key, derivation);
|
||||
CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : tx_key) << ")");
|
||||
}
|
||||
|
||||
if (need_additional_txkeys)
|
||||
{
|
||||
additional_tx_public_keys.push_back(additional_txkey.pub);
|
||||
additional_tx_keys.push_back(additional_txkey.sec);
|
||||
}
|
||||
|
||||
if (tx.version > 1)
|
||||
|
@ -393,10 +403,11 @@ namespace cryptonote
|
|||
output_index++;
|
||||
summary_outs_money += dst_entr.amount;
|
||||
}
|
||||
CHECK_AND_ASSERT_MES(additional_tx_public_keys.size() == additional_tx_keys.size(), false, "Internal error creating additional public keys");
|
||||
|
||||
remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys));
|
||||
|
||||
LOG_PRINT_L2("tx pubkey: " << txkey.pub);
|
||||
LOG_PRINT_L2("tx pubkey: " << txkey_pub);
|
||||
if (need_additional_txkeys)
|
||||
{
|
||||
LOG_PRINT_L2("additional tx pubkeys: ");
|
||||
|
@ -492,6 +503,7 @@ namespace cryptonote
|
|||
rct::keyV destinations;
|
||||
std::vector<uint64_t> inamounts, outamounts;
|
||||
std::vector<unsigned int> index;
|
||||
std::vector<rct::multisig_kLRki> kLRki;
|
||||
for (size_t i = 0; i < sources.size(); ++i)
|
||||
{
|
||||
rct::ctkey ctkey;
|
||||
|
@ -504,6 +516,10 @@ namespace cryptonote
|
|||
inSk.push_back(ctkey);
|
||||
// inPk: (public key, commitment)
|
||||
// will be done when filling in mixRing
|
||||
if (msout)
|
||||
{
|
||||
kLRki.push_back(sources[i].multisig_kLRki);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < tx.vout.size(); ++i)
|
||||
{
|
||||
|
@ -553,9 +569,9 @@ namespace cryptonote
|
|||
get_transaction_prefix_hash(tx, tx_prefix_hash);
|
||||
rct::ctkeyV outSk;
|
||||
if (use_simple_rct)
|
||||
tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, index, outSk, bulletproof);
|
||||
tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, bulletproof);
|
||||
else
|
||||
tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, sources[0].real_output, outSk, bulletproof); // same index assumption
|
||||
tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, amount_keys, msout ? &kLRki[0] : NULL, msout, sources[0].real_output, outSk, bulletproof); // same index assumption
|
||||
|
||||
CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout");
|
||||
|
||||
|
@ -567,13 +583,34 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, bool bulletproof, rct::multisig_out *msout)
|
||||
{
|
||||
keypair txkey = keypair::generate();
|
||||
tx_key = txkey.sec;
|
||||
|
||||
// figure out if we need to make additional tx pubkeys
|
||||
size_t num_stdaddresses = 0;
|
||||
size_t num_subaddresses = 0;
|
||||
account_public_address single_dest_subaddress;
|
||||
classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress);
|
||||
bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1);
|
||||
if (need_additional_txkeys)
|
||||
{
|
||||
additional_tx_keys.clear();
|
||||
for (const auto &d: destinations)
|
||||
additional_tx_keys.push_back(keypair::generate().sec);
|
||||
}
|
||||
|
||||
return construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, bulletproof, msout);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time)
|
||||
{
|
||||
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
|
||||
subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
|
||||
crypto::secret_key tx_key;
|
||||
std::vector<crypto::secret_key> additional_tx_keys;
|
||||
return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
|
||||
return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, NULL);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool generate_genesis_block(
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace cryptonote
|
|||
uint64_t amount; //money
|
||||
bool rct; //true if the output is rct
|
||||
rct::key mask; //ringct amount mask
|
||||
rct::multisig_kLRki multisig_kLRki; //multisig info
|
||||
|
||||
void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); }
|
||||
|
||||
|
@ -63,6 +64,7 @@ namespace cryptonote
|
|||
FIELD(amount)
|
||||
FIELD(rct)
|
||||
FIELD(mask)
|
||||
FIELD(multisig_kLRki)
|
||||
|
||||
if (real_output >= outputs.size())
|
||||
return false;
|
||||
|
@ -87,8 +89,9 @@ namespace cryptonote
|
|||
|
||||
//---------------------------------------------------------------
|
||||
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys);
|
||||
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
|
||||
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false);
|
||||
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
|
||||
bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL);
|
||||
bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, bool bulletproof = false, rct::multisig_out *msout = NULL);
|
||||
|
||||
bool generate_genesis_block(
|
||||
block& bl
|
||||
|
@ -98,7 +101,7 @@ namespace cryptonote
|
|||
|
||||
}
|
||||
|
||||
BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0)
|
||||
BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 1)
|
||||
BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1)
|
||||
|
||||
namespace boost
|
||||
|
@ -115,6 +118,10 @@ namespace boost
|
|||
a & x.amount;
|
||||
a & x.rct;
|
||||
a & x.mask;
|
||||
if (ver < 1)
|
||||
return;
|
||||
a & x.multisig_kLRki;
|
||||
a & x.real_out_additional_tx_keys;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue