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:
Riccardo Spagni 2017-12-17 21:05:16 +02:00
commit 1cc7451130
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
41 changed files with 4828 additions and 383 deletions

View file

@ -114,6 +114,7 @@ add_subdirectory(ringct)
add_subdirectory(checkpoints) add_subdirectory(checkpoints)
add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_basic)
add_subdirectory(cryptonote_core) add_subdirectory(cryptonote_core)
add_subdirectory(multisig)
if(NOT IOS) if(NOT IOS)
add_subdirectory(blockchain_db) add_subdirectory(blockchain_db)
endif() endif()
@ -129,6 +130,7 @@ endif()
add_subdirectory(cryptonote_protocol) add_subdirectory(cryptonote_protocol)
if(NOT IOS) if(NOT IOS)
add_subdirectory(simplewallet) add_subdirectory(simplewallet)
add_subdirectory(gen_multisig)
add_subdirectory(daemonizer) add_subdirectory(daemonizer)
add_subdirectory(daemon) add_subdirectory(daemon)
add_subdirectory(blockchain_utilities) add_subdirectory(blockchain_utilities)

View file

@ -64,6 +64,7 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_base::forget_spend_key() void account_base::forget_spend_key()
{ {
m_keys.m_spend_secret_key = crypto::secret_key(); m_keys.m_spend_secret_key = crypto::secret_key();
m_keys.m_multisig_keys.clear();
} }
//----------------------------------------------------------------- //-----------------------------------------------------------------
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random)
@ -123,6 +124,20 @@ DISABLE_VS_WARNINGS(4244 4345)
create_from_keys(address, fake, viewkey); create_from_keys(address, fake, viewkey);
} }
//----------------------------------------------------------------- //-----------------------------------------------------------------
bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys)
{
m_keys.m_account_address.m_spend_public_key = spend_public_key;
m_keys.m_view_secret_key = view_secret_key;
m_keys.m_spend_secret_key = spend_secret_key;
m_keys.m_multisig_keys = multisig_keys;
return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key);
}
//-----------------------------------------------------------------
void account_base::finalize_multisig(const crypto::public_key &spend_public_key)
{
m_keys.m_account_address.m_spend_public_key = spend_public_key;
}
//-----------------------------------------------------------------
const account_keys& account_base::get_keys() const const account_keys& account_base::get_keys() const
{ {
return m_keys; return m_keys;

View file

@ -42,11 +42,13 @@ namespace cryptonote
account_public_address m_account_address; account_public_address m_account_address;
crypto::secret_key m_spend_secret_key; crypto::secret_key m_spend_secret_key;
crypto::secret_key m_view_secret_key; crypto::secret_key m_view_secret_key;
std::vector<crypto::secret_key> m_multisig_keys;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(m_account_address) KV_SERIALIZE(m_account_address)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
@ -60,6 +62,8 @@ namespace cryptonote
crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false);
void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey);
bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys);
void finalize_multisig(const crypto::public_key &spend_public_key);
const account_keys& get_keys() const; const account_keys& get_keys() const;
std::string get_public_address_str(bool testnet) const; std::string get_public_address_str(bool testnet) const;
std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const;
@ -71,6 +75,7 @@ namespace cryptonote
bool store(const std::string& file_path); bool store(const std::string& file_path);
void forget_spend_key(); void forget_spend_key();
const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; }
template <class t_archive> template <class t_archive>
inline void serialize(t_archive &a, const unsigned int /*ver*/) inline void serialize(t_archive &a, const unsigned int /*ver*/)

View file

@ -252,6 +252,21 @@ namespace boost
// a & x.senderPk; // not serialized, as we do not use it in monero currently // a & x.senderPk; // not serialized, as we do not use it in monero currently
} }
template <class Archive>
inline void serialize(Archive &a, rct::multisig_kLRki &x, const boost::serialization::version_type ver)
{
a & x.k;
a & x.L;
a & x.R;
a & x.ki;
}
template <class Archive>
inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver)
{
a & x.c;
}
template <class Archive> template <class Archive>
inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) inline typename std::enable_if<Archive::is_loading::value, void>::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver)
{ {

View file

@ -80,6 +80,31 @@ static std::atomic<uint64_t> tx_hashes_cached_count(0);
static std::atomic<uint64_t> block_hashes_calculated_count(0); static std::atomic<uint64_t> block_hashes_calculated_count(0);
static std::atomic<uint64_t> block_hashes_cached_count(0); static std::atomic<uint64_t> block_hashes_cached_count(0);
#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}}
namespace cryptonote
{
static inline unsigned char *operator &(ec_point &point) {
return &reinterpret_cast<unsigned char &>(point);
}
static inline const unsigned char *operator &(const ec_point &point) {
return &reinterpret_cast<const unsigned char &>(point);
}
// a copy of rct::addKeys, since we can't link to libringct to avoid circular dependencies
static void add_public_key(crypto::public_key &AB, const crypto::public_key &A, const crypto::public_key &B) {
ge_p3 B2, A2;
CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, &B) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, &A) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
ge_cached tmp2;
ge_p3_to_cached(&tmp2, &B2);
ge_p1p1 tmp3;
ge_add(&tmp3, &A2, &tmp2);
ge_p1p1_to_p3(&A2, &tmp3);
ge_p3_tobytes(&AB, &A2);
}
}
namespace cryptonote namespace cryptonote
{ {
//--------------------------------------------------------------- //---------------------------------------------------------------
@ -182,6 +207,7 @@ namespace cryptonote
crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b
// step 2: add Hs(a || index_major || index_minor) // step 2: add Hs(a || index_major || index_minor)
crypto::secret_key subaddr_sk;
crypto::secret_key scalar_step2; crypto::secret_key scalar_step2;
if (received_index.is_zero()) if (received_index.is_zero())
{ {
@ -189,13 +215,32 @@ namespace cryptonote
} }
else else
{ {
crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index); subaddr_sk = get_subaddress_secret_key(ack.m_view_secret_key, received_index);
sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m); sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&subaddr_sk);
} }
in_ephemeral.sec = scalar_step2; in_ephemeral.sec = scalar_step2;
crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub);
CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one"); if (ack.m_multisig_keys.empty())
{
// when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase
CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub), false, "Failed to derive public key");
}
else
{
// when in multisig, we only know the partial spend secret key. but we do know the full spend public key, so the output pubkey can be obtained by using the standard CN key derivation
CHECK_AND_ASSERT_MES(crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub), false, "Failed to derive public key");
// and don't forget to add the contribution from the subaddress part
if (!received_index.is_zero())
{
crypto::public_key subaddr_pk;
CHECK_AND_ASSERT_MES(crypto::secret_key_to_public_key(subaddr_sk, subaddr_pk), false, "Failed to derive public key");
add_public_key(in_ephemeral.pub, in_ephemeral.pub, subaddr_pk);
}
}
CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key,
false, "key image helper precomp: given output pubkey doesn't match the derived one");
} }
crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki);

View file

@ -59,6 +59,7 @@ target_link_libraries(cryptonote_core
common common
cncrypto cncrypto
blockchain_db blockchain_db
multisig
ringct ringct
${Boost_DATE_TIME_LIBRARY} ${Boost_DATE_TIME_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY}

View file

@ -40,11 +40,38 @@ using namespace epee;
#include "crypto/crypto.h" #include "crypto/crypto.h"
#include "crypto/hash.h" #include "crypto/hash.h"
#include "ringct/rctSigs.h" #include "ringct/rctSigs.h"
#include "multisig/multisig.h"
using namespace crypto; using namespace crypto;
namespace cryptonote 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) { 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(); tx.vin.clear();
@ -161,19 +188,21 @@ namespace cryptonote
return destinations[0].addr.m_view_public_key; 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; std::vector<rct::key> amount_keys;
tx.set_null(); tx.set_null();
amount_keys.clear(); amount_keys.clear();
if (msout)
{
msout->c.clear();
}
tx.version = rct ? 2 : 1; tx.version = rct ? 2 : 1;
tx.unlock_time = unlock_time; tx.unlock_time = unlock_time;
tx.extra = extra; tx.extra = extra;
keypair txkey; crypto::public_key txkey_pub;
txkey.sec = rct::rct2sk(rct::skGen());
tx_key = txkey.sec;
// if we have a stealth payment id, find it and encrypt it with the tx key now // 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; std::vector<tx_extra_field> tx_extra_fields;
@ -193,7 +222,7 @@ namespace cryptonote
return false; 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"); LOG_ERROR("Failed to encrypt payment id");
return false; return false;
@ -247,8 +276,8 @@ namespace cryptonote
return false; return false;
} }
//check that derivated key is equal with real output key //check that derivated key is equal with real output key (if non multisig)
if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) 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:" 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:" << 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 //put key image into tx input
txin_to_key input_to_key; txin_to_key input_to_key;
input_to_key.amount = src_entr.amount; 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 //fill outputs array and use relative offsets
for(const tx_source_entry::output_entry& out_entry: src_entr.outputs) 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 // figure out if we need to make additional tx pubkeys
size_t num_stdaddresses = 0; size_t num_stdaddresses = 0;
size_t num_subaddresses = 0; size_t num_subaddresses = 0;
std::unordered_set<cryptonote::account_public_address> unique_dst_addresses;
account_public_address single_dest_subaddress; account_public_address single_dest_subaddress;
for(const tx_destination_entry& dst_entr: destinations) classify_addresses(destinations, change_addr, num_stdaddresses, num_subaddresses, single_dest_subaddress);
{
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");
// if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D // 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) 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 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)); 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; std::vector<crypto::public_key> additional_tx_public_keys;
additional_tx_keys.clear();
// we don't need to include additional tx keys if: // we don't need to include additional tx keys if:
// - all the destinations are standard addresses // - all the destinations are standard addresses
// - there's only one destination which is a subaddress // - there's only one destination which is a subaddress
bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); 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; uint64_t summary_outs_money = 0;
//fill outputs //fill outputs
@ -348,7 +359,7 @@ namespace cryptonote
keypair additional_txkey; keypair additional_txkey;
if (need_additional_txkeys) if (need_additional_txkeys)
{ {
additional_txkey.sec = rct::rct2sk(rct::skGen()); additional_txkey.sec = additional_tx_keys[output_index];
if (dst_entr.is_subaddress) 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))); additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec)));
else else
@ -359,20 +370,19 @@ namespace cryptonote
if (change_addr && dst_entr.addr == *change_addr) if (change_addr && dst_entr.addr == *change_addr)
{ {
// sending change to yourself; derivation = a*R // sending change to yourself; derivation = a*R
r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation); 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 << ")"); CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey_pub << ", " << sender_account_keys.m_view_secret_key << ")");
} }
else else
{ {
// sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) // 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); 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 : txkey.sec) << ")"); 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) if (need_additional_txkeys)
{ {
additional_tx_public_keys.push_back(additional_txkey.pub); additional_tx_public_keys.push_back(additional_txkey.pub);
additional_tx_keys.push_back(additional_txkey.sec);
} }
if (tx.version > 1) if (tx.version > 1)
@ -393,10 +403,11 @@ namespace cryptonote
output_index++; output_index++;
summary_outs_money += dst_entr.amount; 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)); 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) if (need_additional_txkeys)
{ {
LOG_PRINT_L2("additional tx pubkeys: "); LOG_PRINT_L2("additional tx pubkeys: ");
@ -492,6 +503,7 @@ namespace cryptonote
rct::keyV destinations; rct::keyV destinations;
std::vector<uint64_t> inamounts, outamounts; std::vector<uint64_t> inamounts, outamounts;
std::vector<unsigned int> index; std::vector<unsigned int> index;
std::vector<rct::multisig_kLRki> kLRki;
for (size_t i = 0; i < sources.size(); ++i) for (size_t i = 0; i < sources.size(); ++i)
{ {
rct::ctkey ctkey; rct::ctkey ctkey;
@ -504,6 +516,10 @@ namespace cryptonote
inSk.push_back(ctkey); inSk.push_back(ctkey);
// inPk: (public key, commitment) // inPk: (public key, commitment)
// will be done when filling in mixRing // 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) for (size_t i = 0; i < tx.vout.size(); ++i)
{ {
@ -553,9 +569,9 @@ namespace cryptonote
get_transaction_prefix_hash(tx, tx_prefix_hash); get_transaction_prefix_hash(tx, tx_prefix_hash);
rct::ctkeyV outSk; rct::ctkeyV outSk;
if (use_simple_rct) 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 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"); CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout");
@ -567,13 +583,34 @@ namespace cryptonote
return true; 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) 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; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
crypto::secret_key tx_key; crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys; 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( bool generate_genesis_block(

View file

@ -51,6 +51,7 @@ namespace cryptonote
uint64_t amount; //money uint64_t amount; //money
bool rct; //true if the output is rct bool rct; //true if the output is rct
rct::key mask; //ringct amount mask 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)}))); } 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(amount)
FIELD(rct) FIELD(rct)
FIELD(mask) FIELD(mask)
FIELD(multisig_kLRki)
if (real_output >= outputs.size()) if (real_output >= outputs.size())
return false; 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); 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(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_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( bool generate_genesis_block(
block& bl 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) BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1)
namespace boost namespace boost
@ -115,6 +118,10 @@ namespace boost
a & x.amount; a & x.amount;
a & x.rct; a & x.rct;
a & x.mask; a & x.mask;
if (ver < 1)
return;
a & x.multisig_kLRki;
a & x.real_out_additional_tx_keys;
} }
template <class Archive> template <class Archive>

View file

@ -0,0 +1,54 @@
# Copyright (c) 2017, 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.
set(gen_multisig_sources
gen_multisig.cpp)
monero_add_executable(gen_multisig
${gen_multisig_sources})
target_link_libraries(gen_multisig
PRIVATE
wallet
cryptonote_core
cncrypto
common
epee
${EPEE_READLINE}
${Boost_CHRONO_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${Readline_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
add_dependencies(gen_multisig
version)
set_property(TARGET gen_multisig
PROPERTY
OUTPUT_NAME "monero-gen-trusted-multisig")
install(TARGETS gen_multisig DESTINATION bin)

View file

@ -0,0 +1,241 @@
// Copyright (c) 2017, 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
/*!
* \file gen_multisig.cpp
*
* \brief Generates a set of multisig wallets
*/
#include <iostream>
#include <sstream>
#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include "include_base_utils.h"
#include "crypto/crypto.h" // for crypto::secret_key definition
#include "common/i18n.h"
#include "common/command_line.h"
#include "common/util.h"
#include "common/scoped_message_writer.h"
#include "wallet/wallet_args.h"
#include "wallet/wallet2.h"
using namespace std;
using namespace epee;
using namespace cryptonote;
using boost::lexical_cast;
namespace po = boost::program_options;
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.gen_multisig"
namespace genms
{
const char* tr(const char* str)
{
return i18n_translate(str, "tools::gen_multisig");
}
}
namespace
{
const command_line::arg_descriptor<std::string> arg_filename_base = {"filename-base", genms::tr("Base filename (-1, -2, etc suffixes will be appended as needed)"), ""};
const command_line::arg_descriptor<std::string> arg_scheme = {"scheme", genms::tr("Give threshold and participants at once as M/N"), ""};
const command_line::arg_descriptor<uint32_t> arg_participants = {"participants", genms::tr("How many participants wil share parts of the multisig wallet"), 0};
const command_line::arg_descriptor<uint32_t> arg_threshold = {"threshold", genms::tr("How many signers are required to sign a valid transaction"), 0};
const command_line::arg_descriptor<bool, false> arg_testnet = {"testnet", genms::tr("Create testnet multisig wallets"), false};
const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""};
}
static bool generate_multisig(uint32_t threshold, uint32_t total, const std::string &basename, bool testnet)
{
tools::msg_writer() << (boost::format(genms::tr("Generating %u %u/%u multisig wallets")) % total % threshold % total).str();
const auto pwd_container = tools::password_container::prompt(true, "Enter password for new multisig wallets");
try
{
// create M wallets first
std::vector<boost::shared_ptr<tools::wallet2>> wallets(total);
for (size_t n = 0; n < total; ++n)
{
std::string name = basename + "-" + std::to_string(n + 1);
wallets[n].reset(new tools::wallet2(testnet));
wallets[n]->init("");
wallets[n]->generate(name, pwd_container->password(), rct::rct2sk(rct::skGen()), false, false);
}
// gather the keys
std::vector<crypto::secret_key> sk(total);
std::vector<crypto::public_key> pk(total);
for (size_t n = 0; n < total; ++n)
{
if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
{
tools::fail_msg_writer() << tr("Failed to verify multisig info");
return false;
}
}
// make the wallets multisig
std::vector<std::string> extra_info(total);
std::stringstream ss;
for (size_t n = 0; n < total; ++n)
{
std::string name = basename + "-" + std::to_string(n + 1);
std::vector<crypto::secret_key> skn;
std::vector<crypto::public_key> pkn;
for (size_t k = 0; k < total; ++k)
{
if (k != n)
{
skn.push_back(sk[k]);
pkn.push_back(pk[k]);
}
}
extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold);
ss << " " << name << std::endl;
}
// finalize step if needed
if (!extra_info[0].empty())
{
std::unordered_set<crypto::public_key> pkeys;
std::vector<crypto::public_key> signers(total);
for (size_t n = 0; n < total; ++n)
{
if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n]))
{
tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info");
return false;
}
}
for (size_t n = 0; n < total; ++n)
{
if (!wallets[n]->finalize_multisig(pwd_container->password(), pkeys, signers))
{
tools::fail_msg_writer() << genms::tr("Error finalizing multisig");
return false;
}
}
}
std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->testnet());
tools::success_msg_writer() << genms::tr("Generated multisig wallets for address ") << address << std::endl << ss.str();
}
catch (const std::exception &e)
{
tools::fail_msg_writer() << genms::tr("Error creating multisig wallets: ") << e.what();
return false;
}
return true;
}
int main(int argc, char* argv[])
{
po::options_description desc_params(wallet_args::tr("Wallet options"));
command_line::add_arg(desc_params, arg_filename_base);
command_line::add_arg(desc_params, arg_scheme);
command_line::add_arg(desc_params, arg_threshold);
command_line::add_arg(desc_params, arg_participants);
command_line::add_arg(desc_params, arg_testnet);
const auto vm = wallet_args::main(
argc, argv,
"monero-gen-multisig [--testnet] [--filename-base=<filename>] [--scheme=M/N] [--threshold=M] [--participants=N]",
genms::tr("This program generates a set of multisig wallets - use this simpler scheme only if all the participants trust each other"),
desc_params,
boost::program_options::positional_options_description(),
[](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; },
"monero-gen-multisig.log"
);
if (!vm)
return 1;
bool testnet;
uint32_t threshold = 0, total = 0;
std::string basename;
testnet = command_line::get_arg(*vm, arg_testnet);
if (command_line::has_arg(*vm, arg_scheme))
{
if (sscanf(command_line::get_arg(*vm, arg_scheme).c_str(), "%u/%u", &threshold, &total) != 2)
{
tools::fail_msg_writer() << genms::tr("Error: expected N/M, but got: ") << command_line::get_arg(*vm, arg_scheme);
return 1;
}
}
if (!(*vm)["threshold"].defaulted())
{
if (threshold)
{
tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given");
return 1;
}
threshold = command_line::get_arg(*vm, arg_threshold);
}
if (!(*vm)["participants"].defaulted())
{
if (total)
{
tools::fail_msg_writer() << genms::tr("Error: either --scheme or both of --threshold and --participants may be given");
return 1;
}
total = command_line::get_arg(*vm, arg_participants);
}
if (threshold <= 1 || threshold > total)
{
tools::fail_msg_writer() << (boost::format(genms::tr("Error: expected N > 1 and N <= M, but got N==%u and M==%d")) % threshold % total).str();
return 1;
}
if (!(*vm)["filename-base"].defaulted() && !command_line::get_arg(*vm, arg_filename_base).empty())
{
basename = command_line::get_arg(*vm, arg_filename_base);
}
else
{
tools::fail_msg_writer() << genms::tr("Error: --filename-base is required");
return 1;
}
if (threshold != total-1 && threshold != total)
{
tools::fail_msg_writer() << genms::tr("Error: unsupported scheme: only N/N and N-1/N are supported");
return 1;
}
if (!generate_multisig(threshold, total, basename, testnet))
return 1;
return 0;
//CATCH_ENTRY_L0("main", 1);
}

View file

@ -0,0 +1,52 @@
# Copyright (c) 2017, 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.
set(multisig_sources
multisig.cpp)
set(multisig_headers)
set(multisig_private_headers
multisig.h)
monero_private_headers(multisig
${multisig_private_headers})
monero_add_library(multisig
${multisig_sources}
${multisig_headers}
${multisig_private_headers})
target_link_libraries(multisig
PUBLIC
ringct
cryptonote_basic
common
cncrypto
PRIVATE
${EXTRA_LIBRARIES})

141
src/multisig/multisig.cpp Normal file
View file

@ -0,0 +1,141 @@
// Copyright (c) 2017, 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 <unordered_set>
#include "include_base_utils.h"
#include "crypto/crypto.h"
#include "ringct/rctOps.h"
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "multisig.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
using namespace std;
static const rct::key multisig_salt = { {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
namespace cryptonote
{
//-----------------------------------------------------------------
crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key)
{
rct::keyV data;
data.push_back(rct::sk2rct(key));
data.push_back(multisig_salt);
return rct::rct2sk(rct::hash_to_scalar(data));
}
//-----------------------------------------------------------------
void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
{
// the multisig spend public key is the sum of all spend public keys
multisig_keys.clear();
const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key");
for (const auto &k: spend_keys)
rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k));
multisig_keys.push_back(spend_secret_key);
spend_skey = rct::sk2rct(spend_secret_key);
}
//-----------------------------------------------------------------
void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
{
multisig_keys.clear();
spend_pkey = rct::identity();
spend_skey = rct::zero();
// create all our composite private keys
crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
for (const auto &k: spend_keys)
{
rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk));
multisig_keys.push_back(msk);
sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data);
}
}
//-----------------------------------------------------------------
crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys)
{
rct::key view_skey = rct::sk2rct(get_multisig_blinded_secret_key(skey));
for (const auto &k: skeys)
sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes);
return rct::rct2sk(view_skey);
}
//-----------------------------------------------------------------
crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys)
{
rct::key spend_public_key = rct::identity();
for (const auto &pk: pkeys)
{
rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk));
}
return rct::rct2pk(spend_public_key);
}
//-----------------------------------------------------------------
bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki)
{
if (multisig_key_index >= keys.m_multisig_keys.size())
return false;
crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki);
return true;
}
//-----------------------------------------------------------------
void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R)
{
rct::scalarmultBase((rct::key&)L, rct::sk2rct(k));
crypto::generate_key_image(pkey, k, (crypto::key_image&)R);
}
//-----------------------------------------------------------------
bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki)
{
cryptonote::keypair in_ephemeral;
if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki))
return false;
std::unordered_set<crypto::key_image> used;
for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m)
{
crypto::key_image pki;
bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki);
if (!r)
return false;
used.insert(pki);
}
for (const auto &pki: pkis)
{
if (used.find(pki) == used.end())
{
used.insert(pki);
rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki));
}
}
return true;
}
//-----------------------------------------------------------------
}

49
src/multisig/multisig.h Normal file
View file

@ -0,0 +1,49 @@
// Copyright (c) 2017, 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 <vector>
#include <unordered_map>
#include "crypto/crypto.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "ringct/rctTypes.h"
namespace cryptonote
{
struct account_keys;
crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key);
void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys);
crypto::public_key generate_multisig_N1_N_spend_public_key(const std::vector<crypto::public_key> &pkeys);
bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki);
void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R);
bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki);
}

View file

@ -122,7 +122,7 @@ namespace rct {
// Gen creates a signature which proves that for some column in the keymatrix "pk" // Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column // the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly // Ver verifies that the MG sig was created correctly
mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows) {
mgSig rv; mgSig rv;
size_t cols = pk.size(); size_t cols = pk.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!");
@ -134,6 +134,8 @@ namespace rct {
} }
CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size"); CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size");
CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size"); CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size");
CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
CHECK_AND_ASSERT_THROW_MES(!kLRki || dsRows == 1, "Multisig requires exactly 1 dsRows");
size_t i = 0, j = 0, ii = 0; size_t i = 0, j = 0, ii = 0;
key c, c_old, L, R, Hi; key c, c_old, L, R, Hi;
@ -148,13 +150,22 @@ namespace rct {
toHash[0] = message; toHash[0] = message;
DP("here1"); DP("here1");
for (i = 0; i < dsRows; i++) { for (i = 0; i < dsRows; i++) {
skpkGen(alpha[i], aG[i]); //need to save alphas for later..
Hi = hashToPoint(pk[index][i]);
aHP[i] = scalarmultKey(Hi, alpha[i]);
toHash[3 * i + 1] = pk[index][i]; toHash[3 * i + 1] = pk[index][i];
if (kLRki) {
// multisig
alpha[i] = kLRki->k;
toHash[3 * i + 2] = kLRki->L;
toHash[3 * i + 3] = kLRki->R;
rv.II[i] = kLRki->ki;
}
else {
Hi = hashToPoint(pk[index][i]);
skpkGen(alpha[i], aG[i]); //need to save alphas for later..
aHP[i] = scalarmultKey(Hi, alpha[i]);
toHash[3 * i + 2] = aG[i]; toHash[3 * i + 2] = aG[i];
toHash[3 * i + 3] = aHP[i]; toHash[3 * i + 3] = aHP[i];
rv.II[i] = scalarmultKey(Hi, xx[i]); rv.II[i] = scalarmultKey(Hi, xx[i]);
}
precomp(Ip[i].k, rv.II[i]); precomp(Ip[i].k, rv.II[i]);
} }
size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper)
@ -199,6 +210,8 @@ namespace rct {
for (j = 0; j < rows; j++) { for (j = 0; j < rows; j++) {
sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes); sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes);
} }
if (mscout)
*mscout = c;
return rv; return rv;
} }
@ -393,7 +406,7 @@ namespace rct {
// this shows that sum inputs = sum outputs // this shows that sum inputs = sum outputs
//Ver: //Ver:
// verifies the above sig is created corretly // verifies the above sig is created corretly
mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, key txnFeeKey) { mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, key txnFeeKey) {
mgSig mg; mgSig mg;
//setup vars //setup vars
size_t cols = pubs.size(); size_t cols = pubs.size();
@ -405,6 +418,7 @@ namespace rct {
} }
CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size"); CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size");
CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size"); CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size");
CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV sk(rows + 1); keyV sk(rows + 1);
keyV tmp(rows + 1); keyV tmp(rows + 1);
@ -437,7 +451,7 @@ namespace rct {
for (size_t j = 0; j < outPk.size(); j++) { for (size_t j = 0; j < outPk.size(); j++) {
sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row..
} }
return MLSAG_Gen(message, M, sk, index, rows); return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows);
} }
@ -448,12 +462,13 @@ namespace rct {
// inSk is x, a_in corresponding to signing index // inSk is x, a_in corresponding to signing index
// a_out, Cout is for the output commitment // a_out, Cout is for the output commitment
// index is the signing index.. // index is the signing index..
mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index) { mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index) {
mgSig mg; mgSig mg;
//setup vars //setup vars
size_t rows = 1; size_t rows = 1;
size_t cols = pubs.size(); size_t cols = pubs.size();
CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
keyV tmp(rows + 1); keyV tmp(rows + 1);
keyV sk(rows + 1); keyV sk(rows + 1);
size_t i; size_t i;
@ -464,7 +479,7 @@ namespace rct {
sk[0] = copy(inSk.dest); sk[0] = copy(inSk.dest);
sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
} }
return MLSAG_Gen(message, M, sk, index, rows); return MLSAG_Gen(message, M, sk, kLRki, mscout, index, rows);
} }
@ -598,13 +613,14 @@ namespace rct {
// must know the destination private key to find the correct amount, else will return a random number // must know the destination private key to find the correct amount, else will return a random number
// Note: For txn fees, the last index in the amounts vector should contain that // Note: For txn fees, the last index in the amounts vector should contain that
// Thus the amounts vector will be "one" longer than the destinations vectort // Thus the amounts vector will be "one" longer than the destinations vectort
rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof) { rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof) {
CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations");
CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations"); CHECK_AND_ASSERT_THROW_MES(amount_keys.size() == destinations.size(), "Different number of amount_keys/destinations");
CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing");
for (size_t n = 0; n < mixRing.size(); ++n) { for (size_t n = 0; n < mixRing.size(); ++n) {
CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size");
} }
CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present");
rctSig rv; rctSig rv;
rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull; rv.type = bulletproof ? RCTTypeFullBulletproof : RCTTypeFull;
@ -653,21 +669,23 @@ namespace rct {
key txnFeeKey = scalarmultH(d2h(rv.txnFee)); key txnFeeKey = scalarmultH(d2h(rv.txnFee));
rv.mixRing = mixRing; rv.mixRing = mixRing;
rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey)); if (msout)
msout->c.resize(1);
rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, kLRki, msout ? &msout->c[0] : NULL, index, txnFeeKey));
return rv; return rv;
} }
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin) { rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin) {
unsigned int index; unsigned int index;
ctkeyM mixRing; ctkeyM mixRing;
ctkeyV outSk; ctkeyV outSk;
tie(mixRing, index) = populateFromBlockchain(inPk, mixin); tie(mixRing, index) = populateFromBlockchain(inPk, mixin);
return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk, false); return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, kLRki, msout, index, outSk, false);
} }
//RCT simple //RCT simple
//for post-rct only //for post-rct only
rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof) { rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof) {
CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts");
CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk");
CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations");
@ -677,6 +695,10 @@ namespace rct {
for (size_t n = 0; n < mixRing.size(); ++n) { for (size_t n = 0; n < mixRing.size(); ++n) {
CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing");
} }
CHECK_AND_ASSERT_THROW_MES((kLRki && msout) || (!kLRki && !msout), "Only one of kLRki/msout is present");
if (kLRki && msout) {
CHECK_AND_ASSERT_THROW_MES(kLRki->size() == inamounts.size(), "Mismatched kLRki/inamounts sizes");
}
rctSig rv; rctSig rv;
rv.type = bulletproof ? RCTTypeSimpleBulletproof : RCTTypeSimple; rv.type = bulletproof ? RCTTypeSimpleBulletproof : RCTTypeSimple;
@ -736,13 +758,15 @@ namespace rct {
DP(rv.pseudoOuts[i]); DP(rv.pseudoOuts[i]);
key full_message = get_pre_mlsag_hash(rv); key full_message = get_pre_mlsag_hash(rv);
if (msout)
msout->c.resize(inamounts.size());
for (i = 0 ; i < inamounts.size(); i++) { for (i = 0 ; i < inamounts.size(); i++) {
rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], index[i]); rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i]);
} }
return rv; return rv;
} }
rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin) { rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin) {
std::vector<unsigned int> index; std::vector<unsigned int> index;
index.resize(inPk.size()); index.resize(inPk.size());
ctkeyM mixRing; ctkeyM mixRing;
@ -752,7 +776,7 @@ namespace rct {
mixRing[i].resize(mixin+1); mixRing[i].resize(mixin+1);
index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin);
} }
return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk, false); return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, kLRki, msout, index, outSk, false);
} }
//RingCT protocol //RingCT protocol
@ -822,8 +846,14 @@ namespace rct {
return true; return true;
} }
catch(...) catch (const std::exception &e)
{ {
LOG_PRINT_L1("Error in verRct: " << e.what());
return false;
}
catch (...)
{
LOG_PRINT_L1("Error in verRct, but not an actual exception");
return false; return false;
} }
} }
@ -920,7 +950,16 @@ namespace rct {
return true; return true;
} }
// we can get deep throws from ge_frombytes_vartime if input isn't valid // we can get deep throws from ge_frombytes_vartime if input isn't valid
catch (...) { return false; } catch (const std::exception &e)
{
LOG_PRINT_L1("Error in verRct: " << e.what());
return false;
}
catch (...)
{
LOG_PRINT_L1("Error in verRct, but not an actual exception");
return false;
}
} }
//RingCT protocol //RingCT protocol
@ -988,4 +1027,27 @@ namespace rct {
key mask; key mask;
return decodeRctSimple(rv, sk, i, mask); return decodeRctSimple(rv, sk, i, mask);
} }
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeFullBulletproof || rv.type == RCTTypeSimpleBulletproof,
false, "unsupported rct type");
CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size");
CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
if (rv.type == RCTTypeFull || rv.type == RCTTypeFullBulletproof)
{
CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element");
}
for (size_t n = 0; n < indices.size(); ++n) {
CHECK_AND_ASSERT_MES(indices[n] < rv.p.MGs[n].ss.size(), false, "Index out of range");
CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line");
}
for (size_t n = 0; n < indices.size(); ++n) {
rct::key diff;
sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes);
sc_add(rv.p.MGs[n].ss[indices[n]][0].bytes, rv.p.MGs[n].ss[indices[n]][0].bytes, diff.bytes);
}
return true;
}
} }

View file

@ -73,7 +73,7 @@ namespace rct {
// the signer knows a secret key for each row in that column // the signer knows a secret key for each row in that column
// Ver verifies that the MG sig was created correctly // Ver verifies that the MG sig was created correctly
keyV keyImageV(const keyV &xx); keyV keyImageV(const keyV &xx);
mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows);
bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows); bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
//mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
@ -95,8 +95,8 @@ namespace rct {
// this shows that sum inputs = sum outputs // this shows that sum inputs = sum outputs
//Ver: //Ver:
// verifies the above sig is created corretly // verifies the above sig is created corretly
mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, key txnFee, const key &message); mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, const multisig_kLRki *kLRki, key *mscout, unsigned int index, key txnFee, const key &message);
mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index); mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, const multisig_kLRki *kLRki, key *mscout, unsigned int index);
bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message); bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message);
bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C);
@ -118,10 +118,10 @@ namespace rct {
//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1)
// uses the attached ecdh info to find the amounts represented by each output commitment // uses the attached ecdh info to find the amounts represented by each output commitment
// must know the destination private key to find the correct amount, else will return a random number // must know the destination private key to find the correct amount, else will return a random number
rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk, bool bulletproof); rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, unsigned int index, ctkeyV &outSk, bool bulletproof);
rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin); rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const multisig_kLRki *kLRki, multisig_out *msout, const int mixin);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, xmr_amount txnFee, unsigned int mixin);
rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof); rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, bool bulletproof);
bool verRct(const rctSig & rv, bool semantics); bool verRct(const rctSig & rv, bool semantics);
static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); }
bool verRctSimple(const rctSig & rv, bool semantics); bool verRctSimple(const rctSig & rv, bool semantics);
@ -130,6 +130,8 @@ namespace rct {
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i); xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i);
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key);
} }
#endif /* RCTSIGS_H */ #endif /* RCTSIGS_H */

View file

@ -99,6 +99,22 @@ namespace rct {
typedef std::vector<ctkey> ctkeyV; typedef std::vector<ctkey> ctkeyV;
typedef std::vector<ctkeyV> ctkeyM; typedef std::vector<ctkeyV> ctkeyM;
//used for multisig data
struct multisig_kLRki {
key k;
key L;
key R;
key ki;
};
struct multisig_out {
std::vector<key> c; // for all inputs
BEGIN_SERIALIZE_OBJECT()
FIELD(c)
END_SERIALIZE()
};
//data for passing the amount to the receiver secretly //data for passing the amount to the receiver secretly
// If the pedersen commitment to an amount is C = aG + bH, // If the pedersen commitment to an amount is C = aG + bH,
// "mask" contains a 32 byte key a // "mask" contains a 32 byte key a
@ -501,9 +517,15 @@ inline std::ostream &operator <<(std::ostream &o, const rct::key &v) {
} }
namespace std
{
template<> struct hash<rct::key> { std::size_t operator()(const rct::key &k) const { return reinterpret_cast<const std::size_t&>(k); } };
}
BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key);
BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::key64);
BLOB_SERIALIZER(rct::ctkey); BLOB_SERIALIZER(rct::ctkey);
BLOB_SERIALIZER(rct::multisig_kLRki);
BLOB_SERIALIZER(rct::boroSig); BLOB_SERIALIZER(rct::boroSig);
VARIANT_TAG(debug_archive, rct::key, "rct::key"); VARIANT_TAG(debug_archive, rct::key, "rct::key");
@ -519,6 +541,8 @@ VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig");
VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig"); VARIANT_TAG(debug_archive, rct::boroSig, "rct::boroSig");
VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig"); VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig");
VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof");
VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki");
VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out");
VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key, 0x90);
VARIANT_TAG(binary_archive, rct::key64, 0x91); VARIANT_TAG(binary_archive, rct::key64, 0x91);
@ -533,6 +557,8 @@ VARIANT_TAG(binary_archive, rct::rangeSig, 0x99);
VARIANT_TAG(binary_archive, rct::boroSig, 0x9a); VARIANT_TAG(binary_archive, rct::boroSig, 0x9a);
VARIANT_TAG(binary_archive, rct::rctSig, 0x9b); VARIANT_TAG(binary_archive, rct::rctSig, 0x9b);
VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c);
VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d);
VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e);
VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key, "rct_key");
VARIANT_TAG(json_archive, rct::key64, "rct_key64"); VARIANT_TAG(json_archive, rct::key64, "rct_key64");
@ -547,5 +573,7 @@ VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig");
VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig"); VARIANT_TAG(json_archive, rct::boroSig, "rct_boroSig");
VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig"); VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig");
VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof");
VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR");
VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out");
#endif /* RCTTYPES_H */ #endif /* RCTTYPES_H */

View file

@ -60,6 +60,7 @@
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "common/json_util.h" #include "common/json_util.h"
#include "ringct/rctSigs.h" #include "ringct/rctSigs.h"
#include "multisig/multisig.h"
#include "wallet/wallet_args.h" #include "wallet/wallet_args.h"
#include <stdexcept> #include <stdexcept>
@ -340,6 +341,106 @@ namespace
} }
return true; return true;
} }
void handle_transfer_exception(const std::exception_ptr &e)
{
try
{
std::rethrow_exception(e);
}
catch (const tools::error::daemon_busy&)
{
fail_msg_writer() << tr("daemon is busy. Please try again later.");
}
catch (const tools::error::no_connection_to_daemon&)
{
fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running.");
}
catch (const tools::error::wallet_rpc_error& e)
{
LOG_ERROR("RPC error: " << e.to_string());
fail_msg_writer() << tr("RPC error: ") << e.what();
}
catch (const tools::error::get_random_outs_error &e)
{
fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what();
}
catch (const tools::error::not_enough_unlocked_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
}
catch (const tools::error::not_enough_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
}
catch (const tools::error::tx_not_possible& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee()));
fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
auto writer = fail_msg_writer();
writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs())
{
writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second;
}
}
catch (const tools::error::tx_not_constructed&)
{
fail_msg_writer() << tr("transaction was not constructed");
}
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
std::string reason = e.reason();
if (!reason.empty())
fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
fail_msg_writer() << e.what();
}
catch (const tools::error::zero_destination&)
{
fail_msg_writer() << tr("one of destinations is zero");
}
catch (const tools::error::tx_too_big& e)
{
fail_msg_writer() << tr("failed to find a suitable way to split transactions");
}
catch (const tools::error::transfer_error& e)
{
LOG_ERROR("unknown transfer error: " << e.to_string());
fail_msg_writer() << tr("unknown transfer error: ") << e.what();
}
catch (const tools::error::multisig_export_needed& e)
{
LOG_ERROR("Multisig error: " << e.to_string());
fail_msg_writer() << tr("Multisig error: ") << e.what();
}
catch (const tools::error::wallet_internal_error& e)
{
LOG_ERROR("internal error: " << e.to_string());
fail_msg_writer() << tr("internal error: ") << e.what();
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << tr("unexpected error: ") << e.what();
}
}
} }
bool parse_priority(const std::string& arg, uint32_t& priority) bool parse_priority(const std::string& arg, uint32_t& priority)
@ -417,6 +518,11 @@ bool simple_wallet::print_seed(bool encrypted)
bool success = false; bool success = false;
std::string electrum_words; std::string electrum_words;
if (m_wallet->multisig())
{
fail_msg_writer() << tr("wallet is multisig and has no seed");
return true;
}
if (m_wallet->watch_only()) if (m_wallet->watch_only())
{ {
fail_msg_writer() << tr("wallet is watch-only and has no seed"); fail_msg_writer() << tr("wallet is watch-only and has no seed");
@ -468,6 +574,11 @@ bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std:
bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{ {
if (m_wallet->multisig())
{
fail_msg_writer() << tr("wallet is multisig and has no seed");
return true;
}
if (m_wallet->watch_only()) if (m_wallet->watch_only())
{ {
fail_msg_writer() << tr("wallet is watch-only and has no seed"); fail_msg_writer() << tr("wallet is watch-only and has no seed");
@ -587,6 +698,469 @@ bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std:
return true; return true;
} }
bool simple_wallet::prepare_multisig(const std::vector<std::string> &args)
{
if (m_wallet->multisig())
{
fail_msg_writer() << tr("This wallet is already multisig");
return true;
}
if (m_wallet->watch_only())
{
fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig");
return true;
}
if(m_wallet->get_num_transfer_details())
{
fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet");
return true;
}
const auto orig_pwd_container = get_and_verify_password();
if(orig_pwd_container == boost::none)
{
fail_msg_writer() << tr("Your password is incorrect.");
return true;
}
std::string multisig_info = m_wallet->get_multisig_info();
success_msg_writer() << multisig_info;
success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info");
success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants ");
return true;
}
bool simple_wallet::make_multisig(const std::vector<std::string> &args)
{
if (m_wallet->multisig())
{
fail_msg_writer() << tr("This wallet is already multisig");
return true;
}
if (m_wallet->watch_only())
{
fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig");
return true;
}
if(m_wallet->get_num_transfer_details())
{
fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet");
return true;
}
const auto orig_pwd_container = get_and_verify_password();
if(orig_pwd_container == boost::none)
{
fail_msg_writer() << tr("Your original password was incorrect.");
return true;
}
if (args.size() < 2)
{
fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]");
return true;
}
// parse threshold
uint32_t threshold;
if (!string_tools::get_xtype_from_string(threshold, args[0]))
{
fail_msg_writer() << tr("Invalid threshold");
return true;
}
LOCK_IDLE_SCOPE();
try
{
auto local_args = args;
local_args.erase(local_args.begin());
std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold);
if (!multisig_extra_info.empty())
{
success_msg_writer() << tr("Another step is needed");
success_msg_writer() << multisig_extra_info;
success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig <info1> [<info2>...] with others' multisig info");
return true;
}
}
catch (const std::exception &e)
{
fail_msg_writer() << tr("Error creating multisig: ") << e.what();
return true;
}
uint32_t total;
m_wallet->multisig(NULL, &threshold, &total);
success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ")
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
return true;
}
bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
{
bool ready;
if (!m_wallet->multisig(&ready))
{
fail_msg_writer() << tr("This wallet is not multisig");
return true;
}
if (ready)
{
fail_msg_writer() << tr("This wallet is already finalized");
return true;
}
const auto orig_pwd_container = get_and_verify_password();
if(orig_pwd_container == boost::none)
{
fail_msg_writer() << tr("Your original password was incorrect.");
return true;
}
if (args.size() < 2)
{
fail_msg_writer() << tr("usage: finalize_multisig <multisiginfo1> [<multisiginfo2>...]");
return true;
}
try
{
if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args))
{
fail_msg_writer() << tr("Failed to finalize multisig");
return true;
}
}
catch (const std::exception &e)
{
fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what();
return true;
}
return true;
}
bool simple_wallet::export_multisig(const std::vector<std::string> &args)
{
bool ready;
if (!m_wallet->multisig(&ready))
{
fail_msg_writer() << tr("This wallet is not multisig");
return true;
}
if (!ready)
{
fail_msg_writer() << tr("This multisig wallet is not yet finalized");
return true;
}
if (args.size() != 1)
{
fail_msg_writer() << tr("usage: export_multisig_info <filename>");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password())
return true;
const std::string filename = args[0];
try
{
cryptonote::blobdata ciphertext = m_wallet->export_multisig();
bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext);
if (!r)
{
fail_msg_writer() << tr("failed to save file ") << filename;
return true;
}
}
catch (const std::exception &e)
{
LOG_ERROR("Error exporting multisig info: " << e.what());
fail_msg_writer() << tr("Error exporting multisig info: ") << e.what();
return true;
}
success_msg_writer() << tr("Multisig info exported to ") << filename;
return true;
}
bool simple_wallet::import_multisig(const std::vector<std::string> &args)
{
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total))
{
fail_msg_writer() << tr("This wallet is not multisig");
return true;
}
if (!ready)
{
fail_msg_writer() << tr("This multisig wallet is not yet finalized");
return true;
}
if (args.size() < threshold - 1)
{
fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password())
return true;
std::vector<cryptonote::blobdata> info;
for (size_t n = 0; n < args.size(); ++n)
{
const std::string filename = args[n];
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to read file ") << filename;
return true;
}
info.push_back(std::move(data));
}
LOCK_IDLE_SCOPE();
// all read and parsed, actually import
try
{
size_t n_outputs = m_wallet->import_multisig(info);
// Clear line "Height xxx of xxx"
std::cout << "\r \r";
success_msg_writer() << tr("Multisig info imported");
}
catch (const std::exception &e)
{
fail_msg_writer() << tr("Failed to import multisig info: ") << e.what();
return true;
}
if (m_trusted_daemon)
{
try
{
m_wallet->rescan_spent();
}
catch (const std::exception &e)
{
message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what();
}
}
else
{
message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\"");
}
return true;
}
bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs)
{
std::string extra_message;
return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message);
}
bool simple_wallet::sign_multisig(const std::vector<std::string> &args)
{
bool ready;
if(!m_wallet->multisig(&ready))
{
fail_msg_writer() << tr("This is not a multisig wallet");
return true;
}
if (!ready)
{
fail_msg_writer() << tr("This multisig wallet is not yet finalized");
return true;
}
if (args.size() != 1)
{
fail_msg_writer() << tr("usage: sign_multisig <filename>");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
std::string filename = args[0];
std::vector<crypto::hash> txids;
uint32_t signers = 0;
try
{
bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); });
if (!r)
{
fail_msg_writer() << tr("Failed to sign multisig transaction");
return true;
}
}
catch (const tools::error::multisig_export_needed& e)
{
fail_msg_writer() << tr("Multisig error: ") << e.what();
return true;
}
catch (const std::exception &e)
{
fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what();
return true;
}
if (txids.empty())
{
uint32_t threshold;
m_wallet->multisig(NULL, &threshold);
uint32_t signers_needed = threshold - signers - 1;
success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", "
<< signers_needed << " more signer(s) needed";
return true;
}
else
{
std::string txids_as_text;
for (const auto &txid: txids)
{
if (!txids_as_text.empty())
txids_as_text += (", ");
txids_as_text += epee::string_tools::pod_to_hex(txid);
}
success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", txid " << txids_as_text;
success_msg_writer(true) << tr("It may be relayed to the network with submit_multisig");
}
return true;
}
bool simple_wallet::submit_multisig(const std::vector<std::string> &args)
{
bool ready;
uint32_t threshold;
if (!m_wallet->multisig(&ready, &threshold))
{
fail_msg_writer() << tr("This is not a multisig wallet");
return true;
}
if (!ready)
{
fail_msg_writer() << tr("This multisig wallet is not yet finalized");
return true;
}
if (args.size() != 1)
{
fail_msg_writer() << tr("usage: submit_multisig <filename>");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
std::string filename = args[0];
try
{
tools::wallet2::multisig_tx_set txs;
bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); });
if (!r)
{
fail_msg_writer() << tr("Failed to load multisig transaction from file");
return true;
}
if (txs.m_signers.size() < threshold)
{
fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures"))
% txs.m_signers.size() % (threshold - txs.m_signers.size())).str();
return true;
}
// actually commit the transactions
for (auto &ptx: txs.m_ptx)
{
m_wallet->commit_tx(ptx);
success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL
<< tr("You can check its status by using the `show_transfers` command.");
}
}
catch (const std::exception &e)
{
handle_transfer_exception(std::current_exception());
}
catch (...)
{
LOG_ERROR("unknown error");
fail_msg_writer() << tr("unknown error");
}
return true;
}
bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
{
bool ready;
uint32_t threshold;
if (!m_wallet->multisig(&ready, &threshold))
{
fail_msg_writer() << tr("This is not a multisig wallet");
return true;
}
if (!ready)
{
fail_msg_writer() << tr("This multisig wallet is not yet finalized");
return true;
}
if (args.size() != 1)
{
fail_msg_writer() << tr("usage: export_raw_multisig <filename>");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
std::string filename = args[0];
try
{
tools::wallet2::multisig_tx_set txs;
bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); });
if (!r)
{
fail_msg_writer() << tr("Failed to load multisig transaction from file");
return true;
}
if (txs.m_signers.size() < threshold)
{
fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures"))
% txs.m_signers.size() % (threshold - txs.m_signers.size())).str();
return true;
}
// save the transactions
std::string filenames;
for (auto &ptx: txs.m_ptx)
{
const crypto::hash txid = cryptonote::get_transaction_hash(ptx.tx);
const std::string filename = std::string("raw_multisig_monero_tx_") + epee::string_tools::pod_to_hex(txid);
if (!filenames.empty())
filenames += ", ";
filenames += filename;
if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx)))
{
fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename;
return true;
}
}
success_msg_writer() << tr("Saved exported multisig transaction file(s): ") << filenames;
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << tr("unexpected error: ") << e.what();
}
catch (...)
{
LOG_ERROR("Unknown error");
fail_msg_writer() << tr("unknown error");
}
return true;
}
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{ {
const auto pwd_container = get_and_verify_password(); const auto pwd_container = get_and_verify_password();
@ -1165,6 +1739,35 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("fee", m_cmd_binder.set_handler("fee",
boost::bind(&simple_wallet::print_fee_info, this, _1), boost::bind(&simple_wallet::print_fee_info, this, _1),
tr("Print the information about the current fee and transaction backlog.")); tr("Print the information about the current fee and transaction backlog."));
m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1),
tr("Export data needed to create a multisig wallet"));
m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1),
tr("make_multisig <threshold> <string1> [<string>...]"),
tr("Turn this wallet into a multisig wallet"));
m_cmd_binder.set_handler("finalize_multisig",
boost::bind(&simple_wallet::finalize_multisig, this, _1),
tr("finalize_multisig <string> [<string>...]"),
tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
m_cmd_binder.set_handler("export_multisig_info",
boost::bind(&simple_wallet::export_multisig, this, _1),
tr("export_multisig <filename>"),
tr("Export multisig info for other participants"));
m_cmd_binder.set_handler("import_multisig_info",
boost::bind(&simple_wallet::import_multisig, this, _1),
tr("import_multisig <filename> [<filename>...]"),
tr("Import multisig info from other participants"));
m_cmd_binder.set_handler("sign_multisig",
boost::bind(&simple_wallet::sign_multisig, this, _1),
tr("sign_multisig <filename>"),
tr("Sign a multisig transaction from a file"));
m_cmd_binder.set_handler("submit_multisig",
boost::bind(&simple_wallet::submit_multisig, this, _1),
tr("submit_multisig <filename>"),
tr("Submit a signed multisig transaction from a file"));
m_cmd_binder.set_handler("export_raw_multisig_tx",
boost::bind(&simple_wallet::export_raw_multisig, this, _1),
tr("export_raw_multisig <filename>"),
tr("Export a signed multisig transaction to a file"));
m_cmd_binder.set_handler("help", m_cmd_binder.set_handler("help",
boost::bind(&simple_wallet::help, this, _1), boost::bind(&simple_wallet::help, this, _1),
tr("help [<command>]"), tr("help [<command>]"),
@ -2098,9 +2701,17 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
return false; return false;
} }
std::string prefix;
bool ready;
uint32_t threshold, total;
if (m_wallet->watch_only())
prefix = tr("Opened watch-only wallet");
else if (m_wallet->multisig(&ready, &threshold, &total))
prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str();
else
prefix = tr("Opened wallet");
message_writer(console_color_white, true) << message_writer(console_color_white, true) <<
(m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": " prefix << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet());
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
// If the wallet file is deprecated, we should ask for mnemonic language again and store // If the wallet file is deprecated, we should ask for mnemonic language again and store
// everything in the new format. // everything in the new format.
// NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere. // NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere.
@ -2205,6 +2816,12 @@ bool simple_wallet::save(const std::vector<std::string> &args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/) bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{ {
if (m_wallet->multisig())
{
fail_msg_writer() << tr("wallet is multisig and cannot save a watch-only version");
return true;
}
const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet")); const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet"));
if (!pwd_container) if (!pwd_container)
@ -2498,9 +3115,12 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool simple_wallet::show_balance_unlocked(bool detailed) bool simple_wallet::show_balance_unlocked(bool detailed)
{ {
std::string extra;
if (m_wallet->has_multisig_partial_key_images())
extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)");
success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0});
success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", "
<< tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)); << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra;
std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account);
std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account);
if (!detailed || balance_per_subaddress.empty()) if (!detailed || balance_per_subaddress.empty())
@ -2594,7 +3214,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
} }
std::string verbose_string; std::string verbose_string;
if (verbose) if (verbose)
verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str();
message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << message_writer(td.m_spent ? console_color_magenta : console_color_green, false) <<
boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % boost::format("%21s%8s%12s%8s%16u%68s%16u%s") %
print_money(td.amount()) % print_money(td.amount()) %
@ -2858,101 +3478,6 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
return true; return true;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
static void handle_transfer_exception(const std::exception_ptr &e)
{
try
{
std::rethrow_exception(e);
}
catch (const tools::error::daemon_busy&)
{
fail_msg_writer() << tr("daemon is busy. Please try again later.");
}
catch (const tools::error::no_connection_to_daemon&)
{
fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running.");
}
catch (const tools::error::wallet_rpc_error& e)
{
LOG_ERROR("RPC error: " << e.to_string());
fail_msg_writer() << tr("RPC error: ") << e.what();
}
catch (const tools::error::get_random_outs_error &e)
{
fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what();
}
catch (const tools::error::not_enough_unlocked_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
}
catch (const tools::error::not_enough_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
}
catch (const tools::error::tx_not_possible& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee()));
fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
auto writer = fail_msg_writer();
writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs())
{
writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second;
}
}
catch (const tools::error::tx_not_constructed&)
{
fail_msg_writer() << tr("transaction was not constructed");
}
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
std::string reason = e.reason();
if (!reason.empty())
fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
fail_msg_writer() << e.what();
}
catch (const tools::error::zero_destination&)
{
fail_msg_writer() << tr("one of destinations is zero");
}
catch (const tools::error::tx_too_big& e)
{
fail_msg_writer() << tr("failed to find a suitable way to split transactions");
}
catch (const tools::error::transfer_error& e)
{
LOG_ERROR("unknown transfer error: " << e.to_string());
fail_msg_writer() << tr("unknown transfer error: ") << e.what();
}
catch (const tools::error::wallet_internal_error& e)
{
LOG_ERROR("internal error: " << e.to_string());
fail_msg_writer() << tr("internal error: ") << e.what();
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << tr("unexpected error: ") << e.what();
}
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_)
{ {
// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"
@ -3279,7 +3804,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
} }
// actually commit the transactions // actually commit the transactions
if (m_wallet->watch_only()) if (m_wallet->multisig())
{
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
fail_msg_writer() << tr("Failed to write transaction(s) to file");
}
else
{
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
else if (m_wallet->watch_only())
{ {
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
if (!r) if (!r)
@ -3375,7 +3912,19 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
} }
// actually commit the transactions // actually commit the transactions
if (m_wallet->watch_only()) if (m_wallet->multisig())
{
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
fail_msg_writer() << tr("Failed to write transaction(s) to file");
}
else
{
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
else if (m_wallet->watch_only())
{ {
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
if (!r) if (!r)
@ -3594,7 +4143,19 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
} }
// actually commit the transactions // actually commit the transactions
if (m_wallet->watch_only()) if (m_wallet->multisig())
{
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
fail_msg_writer() << tr("Failed to write transaction(s) to file");
}
else
{
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
else if (m_wallet->watch_only())
{ {
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
if (!r) if (!r)
@ -3785,7 +4346,19 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
} }
// actually commit the transactions // actually commit the transactions
if (m_wallet->watch_only()) if (m_wallet->multisig())
{
bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx");
if (!r)
{
fail_msg_writer() << tr("Failed to write transaction(s) to file");
}
else
{
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
else if (m_wallet->watch_only())
{ {
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
if (!r) if (!r)
@ -3874,6 +4447,11 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
LOG_ERROR("unknown transfer error: " << e.to_string()); LOG_ERROR("unknown transfer error: " << e.to_string());
fail_msg_writer() << tr("unknown transfer error: ") << e.what(); fail_msg_writer() << tr("unknown transfer error: ") << e.what();
} }
catch (const tools::error::multisig_export_needed& e)
{
LOG_ERROR("Multisig error: " << e.to_string());
fail_msg_writer() << tr("Multisig error: ") << e.what();
}
catch (const tools::error::wallet_internal_error& e) catch (const tools::error::wallet_internal_error& e)
{ {
LOG_ERROR("internal error: " << e.to_string()); LOG_ERROR("internal error: " << e.to_string());
@ -4086,6 +4664,11 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
{ {
if(m_wallet->multisig())
{
fail_msg_writer() << tr("This is a multisig wallet, it can only sign with sign_multisig");
return true;
}
if(m_wallet->watch_only()) if(m_wallet->watch_only())
{ {
fail_msg_writer() << tr("This is a watch only wallet"); fail_msg_writer() << tr("This is a watch only wallet");
@ -5359,10 +5942,20 @@ bool simple_wallet::status(const std::vector<std::string> &args)
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool simple_wallet::wallet_info(const std::vector<std::string> &args) bool simple_wallet::wallet_info(const std::vector<std::string> &args)
{ {
bool ready;
uint32_t threshold, total;
message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); message_writer() << tr("Filename: ") << m_wallet->get_wallet_file();
message_writer() << tr("Description: ") << m_wallet->get_description(); message_writer() << tr("Description: ") << m_wallet->get_description();
message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet());
message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); std::string type;
if (m_wallet->watch_only())
type = tr("Watch only");
else if (m_wallet->multisig(&ready, &threshold, &total))
type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str();
else
type = tr("Normal");
message_writer() << tr("Type: ") << type;
message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No"));
return true; return true;
} }
@ -5379,6 +5972,11 @@ bool simple_wallet::sign(const std::vector<std::string> &args)
fail_msg_writer() << tr("wallet is watch-only and cannot sign"); fail_msg_writer() << tr("wallet is watch-only and cannot sign");
return true; return true;
} }
if (m_wallet->multisig())
{
fail_msg_writer() << tr("This wallet is multisig and cannot sign");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
std::string filename = args[0]; std::string filename = args[0];
std::string data; std::string data;
@ -5840,6 +6438,7 @@ int main(int argc, char* argv[])
const auto vm = wallet_args::main( const auto vm = wallet_args::main(
argc, argv, argc, argv,
"monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]",
sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly."),
desc_params, desc_params,
positional_options, positional_options,
[](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; },

View file

@ -188,6 +188,15 @@ namespace cryptonote
bool change_password(const std::vector<std::string>& args); bool change_password(const std::vector<std::string>& args);
bool payment_id(const std::vector<std::string> &args); bool payment_id(const std::vector<std::string> &args);
bool print_fee_info(const std::vector<std::string> &args); bool print_fee_info(const std::vector<std::string> &args);
bool prepare_multisig(const std::vector<std::string>& args);
bool make_multisig(const std::vector<std::string>& args);
bool finalize_multisig(const std::vector<std::string> &args);
bool export_multisig(const std::vector<std::string>& args);
bool import_multisig(const std::vector<std::string>& args);
bool accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs);
bool sign_multisig(const std::vector<std::string>& args);
bool submit_multisig(const std::vector<std::string>& args);
bool export_raw_multisig(const std::vector<std::string>& args);
uint64_t get_daemon_blockchain_height(std::string& err); uint64_t get_daemon_blockchain_height(std::string& err);
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);

View file

@ -51,6 +51,7 @@ monero_add_library(wallet
${wallet_private_headers}) ${wallet_private_headers})
target_link_libraries(wallet target_link_libraries(wallet
PUBLIC PUBLIC
multisig
common common
cryptonote_core cryptonote_core
mnemonics mnemonics
@ -104,6 +105,7 @@ if (BUILD_GUI_DEPS)
set(libs_to_merge set(libs_to_merge
wallet_api wallet_api
wallet wallet
multisig
cryptonote_core cryptonote_core
cryptonote_basic cryptonote_basic
mnemonics mnemonics

View file

@ -812,10 +812,10 @@ struct WalletManager
* @brief verifyWalletPassword - check if the given filename is the wallet * @brief verifyWalletPassword - check if the given filename is the wallet
* @param keys_file_name - location of keys file * @param keys_file_name - location of keys file
* @param password - password to verify * @param password - password to verify
* @param watch_only - verify only view keys? * @param no_spend_key - verify only view keys?
* @return - true if password is correct * @return - true if password is correct
*/ */
virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const = 0; virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0;
/*! /*!
* \brief findWallets - searches for the wallet files by given path name recursively * \brief findWallets - searches for the wallet files by given path name recursively

View file

@ -127,9 +127,9 @@ bool WalletManagerImpl::walletExists(const std::string &path)
return false; return false;
} }
bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const
{ {
return tools::wallet2::verify_password(keys_file_name, password, watch_only); return tools::wallet2::verify_password(keys_file_name, password, no_spend_key);
} }
std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path) std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path)

View file

@ -50,7 +50,7 @@ public:
const std::string &spendKeyString = ""); const std::string &spendKeyString = "");
virtual bool closeWallet(Wallet *wallet, bool store = true); virtual bool closeWallet(Wallet *wallet, bool store = true);
bool walletExists(const std::string &path); bool walletExists(const std::string &path);
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const;
std::vector<std::string> findWallets(const std::string &path); std::vector<std::string> findWallets(const std::string &path);
std::string errorString() const; std::string errorString() const;
void setDaemonAddress(const std::string &address); void setDaemonAddress(const std::string &address);

File diff suppressed because it is too large Load diff

View file

@ -144,10 +144,6 @@ namespace tools
RefreshDefault = RefreshOptimizeCoinbase, RefreshDefault = RefreshOptimizeCoinbase,
}; };
private:
wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {}
public:
static const char* tr(const char* str); static const char* tr(const char* str);
static bool has_testnet_option(const boost::program_options::variables_map& vm); static bool has_testnet_option(const boost::program_options::variables_map& vm);
@ -166,9 +162,33 @@ namespace tools
//! Just parses variables. //! Just parses variables.
static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only); static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key);
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} wallet2(bool testnet = false, bool restricted = false);
struct multisig_info
{
struct LR
{
rct::key m_L;
rct::key m_R;
BEGIN_SERIALIZE_OBJECT()
FIELD(m_L)
FIELD(m_R)
END_SERIALIZE()
};
crypto::public_key m_signer;
std::vector<LR> m_LR;
std::vector<crypto::key_image> m_partial_key_images; // one per key the participant has
BEGIN_SERIALIZE_OBJECT()
FIELD(m_signer)
FIELD(m_LR)
FIELD(m_partial_key_images)
END_SERIALIZE()
};
struct tx_scan_info_t struct tx_scan_info_t
{ {
@ -199,6 +219,9 @@ namespace tools
bool m_key_image_known; bool m_key_image_known;
size_t m_pk_index; size_t m_pk_index;
cryptonote::subaddress_index m_subaddr_index; cryptonote::subaddress_index m_subaddr_index;
bool m_key_image_partial;
std::vector<rct::key> m_multisig_k;
std::vector<multisig_info> m_multisig_info; // one per other participant
bool is_rct() const { return m_rct; } bool is_rct() const { return m_rct; }
uint64_t amount() const { return m_amount; } uint64_t amount() const { return m_amount; }
@ -219,6 +242,9 @@ namespace tools
FIELD(m_key_image_known) FIELD(m_key_image_known)
FIELD(m_pk_index) FIELD(m_pk_index)
FIELD(m_subaddr_index) FIELD(m_subaddr_index)
FIELD(m_key_image_partial)
FIELD(m_multisig_k)
FIELD(m_multisig_info)
END_SERIALIZE() END_SERIALIZE()
}; };
@ -308,6 +334,15 @@ namespace tools
typedef std::vector<transfer_details> transfer_container; typedef std::vector<transfer_details> transfer_container;
typedef std::unordered_multimap<crypto::hash, payment_details> payment_container; typedef std::unordered_multimap<crypto::hash, payment_details> payment_container;
struct multisig_sig
{
rct::rctSig sigs;
crypto::public_key ignore;
std::unordered_set<rct::key> used_L;
std::unordered_set<crypto::public_key> signing_keys;
rct::multisig_out msout;
};
// The convention for destinations is: // The convention for destinations is:
// dests does not include change // dests does not include change
// splitted_dsts (in construction_data) does // splitted_dsts (in construction_data) does
@ -322,6 +357,7 @@ namespace tools
crypto::secret_key tx_key; crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys; std::vector<crypto::secret_key> additional_tx_keys;
std::vector<cryptonote::tx_destination_entry> dests; std::vector<cryptonote::tx_destination_entry> dests;
std::vector<multisig_sig> multisig_sigs;
tx_construction_data construction_data; tx_construction_data construction_data;
@ -337,6 +373,7 @@ namespace tools
FIELD(additional_tx_keys) FIELD(additional_tx_keys)
FIELD(dests) FIELD(dests)
FIELD(construction_data) FIELD(construction_data)
FIELD(multisig_sigs)
END_SERIALIZE() END_SERIALIZE()
}; };
@ -354,6 +391,17 @@ namespace tools
std::vector<crypto::key_image> key_images; std::vector<crypto::key_image> key_images;
}; };
struct multisig_tx_set
{
std::vector<pending_tx> m_ptx;
std::unordered_set<crypto::public_key> m_signers;
BEGIN_SERIALIZE_OBJECT()
FIELD(m_ptx)
FIELD(m_signers)
END_SERIALIZE()
};
struct keys_file_data struct keys_file_data
{ {
crypto::chacha8_iv iv; crypto::chacha8_iv iv;
@ -418,6 +466,53 @@ namespace tools
void generate(const std::string& wallet, const epee::wipeable_string& password, void generate(const std::string& wallet, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address, const cryptonote::account_public_address &account_public_address,
const crypto::secret_key& viewkey = crypto::secret_key()); const crypto::secret_key& viewkey = crypto::secret_key());
/*!
* \brief Creates a multisig wallet
* \return empty if done, non empty if we need to send another string
* to other participants
*/
std::string make_multisig(const epee::wipeable_string &password,
const std::vector<std::string> &info,
uint32_t threshold);
/*!
* \brief Creates a multisig wallet
* \return empty if done, non empty if we need to send another string
* to other participants
*/
std::string make_multisig(const epee::wipeable_string &password,
const std::vector<crypto::secret_key> &view_keys,
const std::vector<crypto::public_key> &spend_keys,
uint32_t threshold);
/*!
* \brief Finalizes creation of a multisig wallet
*/
bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info);
/*!
* \brief Finalizes creation of a multisig wallet
*/
bool finalize_multisig(const epee::wipeable_string &password, std::unordered_set<crypto::public_key> pkeys, std::vector<crypto::public_key> signers);
/*!
* Get a packaged multisig information string
*/
std::string get_multisig_info() const;
/*!
* Verifies and extracts keys from a packaged multisig information string
*/
static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey);
/*!
* Verifies and extracts keys from a packaged multisig information string
*/
static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer);
/*!
* Export multisig info
* This will generate and remember new k values
*/
cryptonote::blobdata export_multisig();
/*!
* Import a set of multisig info from multisig partners
* \return the number of inputs which were imported
*/
size_t import_multisig(std::vector<cryptonote::blobdata> info);
/*! /*!
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there) * \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
* \param wallet_name Name of wallet file (should exist) * \param wallet_name Name of wallet file (should exist)
@ -497,6 +592,7 @@ namespace tools
void expand_subaddresses(const cryptonote::subaddress_index& index); void expand_subaddresses(const cryptonote::subaddress_index& index);
std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; std::string get_subaddress_label(const cryptonote::subaddress_index& index) const;
void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label);
void set_subaddress_lookahead(size_t major, size_t minor);
/*! /*!
* \brief Tells if the wallet file is deprecated. * \brief Tells if the wallet file is deprecated.
*/ */
@ -512,6 +608,8 @@ namespace tools
bool testnet() const { return m_testnet; } bool testnet() const { return m_testnet; }
bool restricted() const { return m_restricted; } bool restricted() const { return m_restricted; }
bool watch_only() const { return m_watch_only; } bool watch_only() const { return m_watch_only; }
bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
bool has_multisig_partial_key_images() const;
// locked & unlocked balance of given or current subaddress account // locked & unlocked balance of given or current subaddress account
uint64_t balance(uint32_t subaddr_index_major) const; uint64_t balance(uint32_t subaddr_index_major) const;
@ -539,6 +637,10 @@ namespace tools
void commit_tx(pending_tx& ptx_vector); void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
std::string save_multisig_tx(multisig_tx_set txs);
bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename);
std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector);
bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename);
// load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet
bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false);
// sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI
@ -551,6 +653,11 @@ namespace tools
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids);
bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids);
std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000);
void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_transfers(wallet2::transfer_container& incoming_transfers) const;
@ -724,6 +831,7 @@ namespace tools
bool delete_address_book_row(std::size_t row_id); bool delete_address_book_row(std::size_t row_id);
uint64_t get_num_rct_outputs(); uint64_t get_num_rct_outputs();
size_t get_num_transfer_details() const { return m_transfers.size(); }
const transfer_details &get_transfer_details(size_t idx) const; const transfer_details &get_transfer_details(size_t idx) const;
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
@ -829,6 +937,11 @@ namespace tools
void set_attribute(const std::string &key, const std::string &value); void set_attribute(const std::string &key, const std::string &value);
std::string get_attribute(const std::string &key) const; std::string get_attribute(const std::string &key) const;
crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const;
crypto::public_key get_multisig_signer_public_key() const;
crypto::public_key get_multisig_signing_public_key(size_t idx) const;
crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const;
private: private:
/*! /*!
* \brief Stores wallet information to wallet file. * \brief Stores wallet information to wallet file.
@ -864,7 +977,6 @@ namespace tools
void check_genesis(const crypto::hash& genesis_hash) const; //throws void check_genesis(const crypto::hash& genesis_hash) const; //throws
bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const;
crypto::hash get_payment_id(const pending_tx &ptx) const; crypto::hash get_payment_id(const pending_tx &ptx) const;
crypto::hash8 get_short_payment_id(const pending_tx &ptx) const;
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
uint64_t get_upper_transaction_size_limit(); uint64_t get_upper_transaction_size_limit();
@ -876,12 +988,16 @@ namespace tools
void set_unspent(size_t idx); void set_unspent(size_t idx);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
void scan_output(const cryptonote::account_keys &keys, 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::account_keys &keys, 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;
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_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);
cryptonote::account_base m_account; cryptonote::account_base m_account;
boost::optional<epee::net_utils::http::login> m_daemon_login; boost::optional<epee::net_utils::http::login> m_daemon_login;
@ -910,6 +1026,8 @@ namespace tools
std::unordered_map<std::string, std::string> m_attributes; std::unordered_map<std::string, std::string> m_attributes;
std::vector<tools::wallet2::address_book_row> m_address_book; std::vector<tools::wallet2::address_book_row> m_address_book;
uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value
const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info;
const std::vector<std::vector<rct::key>> *m_multisig_rescan_k;
std::atomic<bool> m_run; std::atomic<bool> m_run;
@ -921,6 +1039,9 @@ namespace tools
std::string seed_language; /*!< Language of the mnemonics (seed). */ std::string seed_language; /*!< Language of the mnemonics (seed). */
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
bool m_watch_only; /*!< no spend key */ bool m_watch_only; /*!< no spend key */
bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */
uint32_t m_multisig_threshold;
std::vector<crypto::public_key> m_multisig_signers;
bool m_always_confirm_transfers; bool m_always_confirm_transfers;
bool m_print_ring_members; bool m_print_ring_members;
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
@ -939,6 +1060,7 @@ namespace tools
bool m_is_initialized; bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy; NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
// Light wallet // Light wallet
bool m_light_wallet; /* sends view key to daemon for scanning */ bool m_light_wallet; /* sends view key to daemon for scanning */
@ -956,7 +1078,10 @@ namespace tools
}; };
} }
BOOST_CLASS_VERSION(tools::wallet2, 22) BOOST_CLASS_VERSION(tools::wallet2, 22)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
@ -965,7 +1090,8 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 2) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
namespace boost namespace boost
{ {
@ -1003,6 +1129,12 @@ namespace boost
{ {
x.m_subaddr_index = {}; x.m_subaddr_index = {};
} }
if (ver < 9)
{
x.m_key_image_partial = false;
x.m_multisig_k.clear();
x.m_multisig_info.clear();
}
} }
template <class Archive> template <class Archive>
@ -1076,6 +1208,36 @@ namespace boost
return; return;
} }
a & x.m_subaddr_index; a & x.m_subaddr_index;
if (ver < 9)
{
initialize_transfer_details(a, x, ver);
return;
}
a & x.m_multisig_info;
a & x.m_multisig_k;
a & x.m_key_image_partial;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::multisig_info::LR &x, const boost::serialization::version_type ver)
{
a & x.m_L;
a & x.m_R;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::multisig_info &x, const boost::serialization::version_type ver)
{
a & x.m_signer;
a & x.m_LR;
a & x.m_partial_key_images;
}
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::multisig_tx_set &x, const boost::serialization::version_type ver)
{
a & x.m_ptx;
a & x.m_signers;
} }
template <class Archive> template <class Archive>
@ -1253,6 +1415,16 @@ namespace boost
a & x.selected_transfers; a & x.selected_transfers;
} }
template <class Archive>
inline void serialize(Archive &a, tools::wallet2::multisig_sig &x, const boost::serialization::version_type ver)
{
a & x.sigs;
a & x.ignore;
a & x.used_L;
a & x.signing_keys;
a & x.msout;
}
template <class Archive> template <class Archive>
inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver) inline void serialize(Archive &a, tools::wallet2::pending_tx &x, const boost::serialization::version_type ver)
{ {
@ -1281,6 +1453,9 @@ namespace boost
if (ver < 2) if (ver < 2)
return; return;
a & x.selected_transfers; a & x.selected_transfers;
if (ver < 3)
return;
a & x.multisig_sigs;
} }
} }
} }
@ -1356,6 +1531,8 @@ namespace tools
// throw if attempting a transaction with no destinations // throw if attempting a transaction with no destinations
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
THROW_WALLET_EXCEPTION_IF(m_multisig, error::wallet_internal_error, "Multisig wallets cannot spend non rct outputs");
uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit(); uint64_t upper_transaction_size_limit = get_upper_transaction_size_limit();
uint64_t needed_money = fee; uint64_t needed_money = fee;
@ -1458,6 +1635,7 @@ namespace tools
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
src.real_output = interted_it - src.outputs.begin(); src.real_output = interted_it - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index; src.real_output_in_tx_index = td.m_internal_output_index;
src.multisig_kLRki = rct::multisig_kLRki({rct::zero(), rct::zero(), rct::zero(), rct::zero()});
detail::print_source_entry(src); detail::print_source_entry(src);
++i; ++i;
} }
@ -1484,7 +1662,8 @@ namespace tools
crypto::secret_key tx_key; crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys; std::vector<crypto::secret_key> additional_tx_keys;
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys); rct::multisig_out msout;
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, false, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);

View file

@ -85,6 +85,7 @@ namespace wallet_args
boost::optional<boost::program_options::variables_map> main( boost::optional<boost::program_options::variables_map> main(
int argc, char** argv, int argc, char** argv,
const char* const usage, const char* const usage,
const char* const notice,
boost::program_options::options_description desc_params, boost::program_options::options_description desc_params,
const boost::program_options::positional_options_description& positional_options, const boost::program_options::positional_options_description& positional_options,
const std::function<void(const std::string&, bool)> &print, const std::function<void(const std::string&, bool)> &print,
@ -179,6 +180,9 @@ namespace wallet_args
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
} }
if (notice)
Print(print) << notice << ENDL;
if (!command_line::is_arg_defaulted(vm, arg_max_concurrency)) if (!command_line::is_arg_defaulted(vm, arg_max_concurrency))
tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency));

View file

@ -48,6 +48,7 @@ namespace wallet_args
boost::optional<boost::program_options::variables_map> main( boost::optional<boost::program_options::variables_map> main(
int argc, char** argv, int argc, char** argv,
const char* const usage, const char* const usage,
const char* const notice,
boost::program_options::options_description desc_params, boost::program_options::options_description desc_params,
const boost::program_options::positional_options_description& positional_options, const boost::program_options::positional_options_description& positional_options,
const std::function<void(const std::string&, bool)> &print, const std::function<void(const std::string&, bool)> &print,

View file

@ -50,6 +50,8 @@ namespace tools
// wallet_internal_error // wallet_internal_error
// unexpected_txin_type // unexpected_txin_type
// wallet_not_initialized // wallet_not_initialized
// multisig_export_needed
// multisig_import_needed
// std::logic_error // std::logic_error
// wallet_logic_error * // wallet_logic_error *
// file_exists // file_exists
@ -186,7 +188,22 @@ namespace tools
{ {
} }
}; };
//----------------------------------------------------------------------------------------------------
struct multisig_export_needed : public wallet_runtime_error
{
explicit multisig_export_needed(std::string&& loc)
: wallet_runtime_error(std::move(loc), "This signature was made with stale data: export fresh multisig data, which other participants must then use")
{
}
};
//----------------------------------------------------------------------------------------------------
struct multisig_import_needed : public wallet_runtime_error
{
explicit multisig_import_needed(std::string&& loc)
: wallet_runtime_error(std::move(loc), "Not enough multisig data was found to sign: import multisig data from more other participants")
{
}
};
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
const char* const file_error_messages[] = { const char* const file_error_messages[] = {
"file already exists", "file already exists",

View file

@ -41,6 +41,7 @@ using namespace epee;
#include "common/i18n.h" #include "common/i18n.h"
#include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/account.h" #include "cryptonote_basic/account.h"
#include "multisig/multisig.h"
#include "wallet_rpc_server_commands_defs.h" #include "wallet_rpc_server_commands_defs.h"
#include "misc_language.h" #include "misc_language.h"
#include "string_coding.h" #include "string_coding.h"
@ -324,6 +325,7 @@ namespace tools
{ {
res.balance = m_wallet->balance(req.account_index); res.balance = m_wallet->balance(req.account_index);
res.unlocked_balance = m_wallet->unlocked_balance(req.account_index); res.unlocked_balance = m_wallet->unlocked_balance(req.account_index);
res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images();
std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index); std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index);
std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index);
std::vector<tools::wallet2::transfer_details> transfers; std::vector<tools::wallet2::transfer_details> transfers;
@ -608,11 +610,6 @@ namespace tools
return false; return false;
} }
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hash
res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
if (req.get_tx_key) if (req.get_tx_key)
{ {
res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key);
@ -621,6 +618,23 @@ namespace tools
} }
res.fee = ptx_vector.back().fee; res.fee = ptx_vector.back().fee;
if (m_wallet->multisig())
{
res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
if (res.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
}
else
{
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hash
res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
if (req.get_tx_hex) if (req.get_tx_hex)
{ {
cryptonote::blobdata blob; cryptonote::blobdata blob;
@ -630,10 +644,20 @@ namespace tools
if (req.get_tx_metadata) if (req.get_tx_metadata)
{ {
std::ostringstream oss; std::ostringstream oss;
binary_archive<true> ar(oss); boost::archive::portable_binary_oarchive ar(oss);
::serialization::serialize(ar, ptx_vector.back()); try
{
ar << ptx_vector.back();
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
} }
}
return true; return true;
} }
catch (const std::exception& e) catch (const std::exception& e)
@ -673,17 +697,9 @@ namespace tools
ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
LOG_PRINT_L2("on_transfer_split called create_transactions_2"); LOG_PRINT_L2("on_transfer_split called create_transactions_2");
if (!req.do_not_relay)
{
LOG_PRINT_L2("on_transfer_split calling commit_tx");
m_wallet->commit_tx(ptx_vector);
LOG_PRINT_L2("on_transfer_split called commit_tx");
}
// populate response with tx hashes // populate response with tx hashes
for (const auto & ptx : ptx_vector) for (const auto & ptx : ptx_vector)
{ {
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys) if (req.get_tx_keys)
{ {
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
@ -697,6 +713,33 @@ namespace tools
res.amount_list.push_back(ptx_amount); res.amount_list.push_back(ptx_amount);
res.fee_list.push_back(ptx.fee); res.fee_list.push_back(ptx.fee);
}
if (m_wallet->multisig())
{
res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
if (res.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
}
// populate response with tx hashes
for (const auto & ptx : ptx_vector)
{
if (!req.do_not_relay)
{
LOG_PRINT_L2("on_transfer_split calling commit_tx");
m_wallet->commit_tx(ptx_vector);
LOG_PRINT_L2("on_transfer_split called commit_tx");
}
// populate response with tx hashes
for (auto & ptx : ptx_vector)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_hex) if (req.get_tx_hex)
{ {
@ -707,11 +750,21 @@ namespace tools
if (req.get_tx_metadata) if (req.get_tx_metadata)
{ {
std::ostringstream oss; std::ostringstream oss;
binary_archive<true> ar(oss); boost::archive::portable_binary_oarchive ar(oss);
::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
} }
} }
}
return true; return true;
} }
@ -737,18 +790,43 @@ namespace tools
{ {
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
for (const auto & ptx : ptx_vector) for (const auto & ptx : ptx_vector)
{ {
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys) if (req.get_tx_keys)
{ {
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
} }
res.fee_list.push_back(ptx.fee); res.fee_list.push_back(ptx.fee);
}
if (m_wallet->multisig())
{
for (tools::wallet2::pending_tx &ptx: ptx_vector)
{
std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
}
}
else
{
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
for (auto & ptx : ptx_vector)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_hex) if (req.get_tx_hex)
{ {
cryptonote::blobdata blob; cryptonote::blobdata blob;
@ -758,11 +836,21 @@ namespace tools
if (req.get_tx_metadata) if (req.get_tx_metadata)
{ {
std::ostringstream oss; std::ostringstream oss;
binary_archive<true> ar(oss); boost::archive::portable_binary_oarchive ar(oss);
::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
} }
} }
}
return true; return true;
} }
@ -802,17 +890,42 @@ namespace tools
uint64_t mixin = m_wallet->adjust_mixin(req.mixin); uint64_t mixin = m_wallet->adjust_mixin(req.mixin);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
for (const auto & ptx : ptx_vector) for (const auto & ptx : ptx_vector)
{ {
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys) if (req.get_tx_keys)
{ {
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
} }
}
if (m_wallet->multisig())
{
for (tools::wallet2::pending_tx &ptx: ptx_vector)
{
std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss);
try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
}
}
else
{
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
for (auto & ptx : ptx_vector)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_hex) if (req.get_tx_hex)
{ {
cryptonote::blobdata blob; cryptonote::blobdata blob;
@ -822,11 +935,21 @@ namespace tools
if (req.get_tx_metadata) if (req.get_tx_metadata)
{ {
std::ostringstream oss; std::ostringstream oss;
binary_archive<true> ar(oss); boost::archive::portable_binary_oarchive ar(oss);
::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
} }
} }
}
return true; return true;
} }
@ -886,23 +1009,36 @@ namespace tools
er.message = "Multiple transactions are created, which is not supposed to happen"; er.message = "Multiple transactions are created, which is not supposed to happen";
return false; return false;
} }
if (ptx_vector[0].selected_transfers.size() > 1) const wallet2::pending_tx &ptx = ptx_vector[0];
if (ptx.selected_transfers.size() > 1)
{ {
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "The transaction uses multiple inputs, which is not supposed to happen"; er.message = "The transaction uses multiple inputs, which is not supposed to happen";
return false; return false;
} }
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
const wallet2::pending_tx &ptx = ptx_vector[0];
res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
if (req.get_tx_key) if (req.get_tx_key)
{ {
res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key);
} }
if (m_wallet->multisig())
{
res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
if (res.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
}
else
{
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
if (req.get_tx_hex) if (req.get_tx_hex)
{ {
cryptonote::blobdata blob; cryptonote::blobdata blob;
@ -912,11 +1048,20 @@ namespace tools
if (req.get_tx_metadata) if (req.get_tx_metadata)
{ {
std::ostringstream oss; std::ostringstream oss;
binary_archive<true> ar(oss); boost::archive::portable_binary_oarchive ar(oss);
::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); try
{
ar << ptx;
}
catch (...)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to save multisig tx set after creation";
return false;
}
res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
} }
}
return true; return true;
} }
catch (const tools::error::daemon_busy& e) catch (const tools::error::daemon_busy& e)
@ -952,13 +1097,14 @@ namespace tools
return false; return false;
} }
std::stringstream ss;
ss << blob;
binary_archive<false> ba(ss);
tools::wallet2::pending_tx ptx; tools::wallet2::pending_tx ptx;
bool r = ::serialization::serialize(ba, ptx); try
if (!r) {
std::istringstream iss(blob);
boost::archive::portable_binary_iarchive ar(iss);
ar >> ptx;
}
catch (...)
{ {
er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA;
er.message = "Failed to parse tx metadata."; er.message = "Failed to parse tx metadata.";
@ -2338,6 +2484,375 @@ namespace tools
} }
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
res.multisig = m_wallet->multisig(&res.ready, &res.threshold, &res.total);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
if (m_wallet->multisig())
{
er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
er.message = "This wallet is already multisig";
return false;
}
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
res.multisig_info = m_wallet->get_multisig_info();
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
if (m_wallet->multisig())
{
er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
er.message = "This wallet is already multisig";
return false;
}
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
er.message = "wallet is watch-only and cannot be made multisig";
return false;
}
try
{
res.multisig_info = m_wallet->make_multisig(req.password, req.multisig_info, req.threshold);
res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = e.what();
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
bool ready;
if (!m_wallet->multisig(&ready))
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is not multisig";
return false;
}
if (!ready)
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
cryptonote::blobdata info;
try
{
info = m_wallet->export_multisig();
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = e.what();
return false;
}
res.info = epee::string_tools::buff_to_hex_nodelimer(info);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total))
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is not multisig";
return false;
}
if (!ready)
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
if (req.info.size() < threshold - 1)
{
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
er.message = "Needs multisig export info from more participants";
return false;
}
std::vector<cryptonote::blobdata> info;
info.resize(req.info.size());
for (size_t n = 0; n < info.size(); ++n)
{
if (!epee::string_tools::parse_hexstr_to_binbuff(req.info[n], info[n]))
{
er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
er.message = "Failed to parse hex.";
return false;
}
}
try
{
res.n_outputs = m_wallet->import_multisig(info);
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Error calling import_multisig";
return false;
}
if (m_trusted_daemon)
{
try
{
m_wallet->rescan_spent();
}
catch (const std::exception &e)
{
er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what();
}
}
else
{
er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted";
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total))
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is not multisig";
return false;
}
if (ready)
{
er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
er.message = "This wallet is multisig, and already finalized";
return false;
}
if (req.multisig_info.size() < threshold - 1)
{
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
er.message = "Needs multisig info from more participants";
return false;
}
try
{
if (!m_wallet->finalize_multisig(req.password, req.multisig_info))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Error calling finalize_multisig";
return false;
}
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = std::string("Error calling finalize_multisig: ") + e.what();
return false;
}
res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet());
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total))
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is not multisig";
return false;
}
if (!ready)
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
{
er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
er.message = "Failed to parse hex.";
return false;
}
tools::wallet2::multisig_tx_set txs;
bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
if (!r)
{
er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
er.message = "Failed to parse multisig tx data.";
return false;
}
std::vector<crypto::hash> txids;
try
{
bool r = m_wallet->sign_multisig_tx(txs, txids);
if (!r)
{
er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
er.message = "Failed to sign multisig tx";
return false;
}
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
er.message = std::string("Failed to sign multisig tx: ") + e.what();
return false;
}
res.tx_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(txs));
if (!txids.empty())
{
for (const crypto::hash &txid: txids)
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(txid));
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
bool ready;
uint32_t threshold, total;
if (!m_wallet->multisig(&ready, &threshold, &total))
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is not multisig";
return false;
}
if (!ready)
{
er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
er.message = "This wallet is multisig, but not yet finalized";
return false;
}
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
{
er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
er.message = "Failed to parse hex.";
return false;
}
tools::wallet2::multisig_tx_set txs;
bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
if (!r)
{
er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
er.message = "Failed to parse multisig tx data.";
return false;
}
if (txs.m_signers.size() < threshold)
{
er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
er.message = "Not enough signers signed this transaction.";
return false;
}
try
{
for (auto &ptx: txs.m_ptx)
{
m_wallet->commit_tx(ptx);
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
}
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION;
er.message = std::string("Failed to submit multisig tx: ") + e.what();
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
@ -2360,6 +2875,7 @@ int main(int argc, char** argv) {
const auto vm = wallet_args::main( const auto vm = wallet_args::main(
argc, argv, argc, argv,
"monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]",
tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."),
desc_params, desc_params,
po::positional_options_description(), po::positional_options_description(),
[](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); }, [](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); },

View file

@ -117,6 +117,14 @@ namespace tools
MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES)
MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET)
MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET)
MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG)
MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG)
MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG)
MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
END_JSON_RPC_MAP() END_JSON_RPC_MAP()
END_URI_MAP2() END_URI_MAP2()
@ -171,6 +179,14 @@ namespace tools
bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er);
bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er);
bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er);
bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er);
bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er);
//json rpc v2 //json rpc v2
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er);

View file

@ -78,11 +78,13 @@ namespace wallet_rpc
{ {
uint64_t balance; uint64_t balance;
uint64_t unlocked_balance; uint64_t unlocked_balance;
bool multisig_import_needed;
std::vector<per_subaddress_info> per_subaddress; std::vector<per_subaddress_info> per_subaddress;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(balance) KV_SERIALIZE(balance)
KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(unlocked_balance)
KV_SERIALIZE(multisig_import_needed)
KV_SERIALIZE(per_subaddress) KV_SERIALIZE(per_subaddress)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
@ -316,6 +318,7 @@ namespace wallet_rpc
uint64_t fee; uint64_t fee;
std::string tx_blob; std::string tx_blob;
std::string tx_metadata; std::string tx_metadata;
std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_hash)
@ -324,6 +327,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee) KV_SERIALIZE(fee)
KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata) KV_SERIALIZE(tx_metadata)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -376,6 +380,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list; std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list; std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list; std::list<std::string> tx_metadata_list;
std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_hash_list)
@ -384,6 +389,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list) KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -421,6 +427,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list; std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list; std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list; std::list<std::string> tx_metadata_list;
std::list<std::string> multisig_txset;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_hash_list)
@ -428,6 +435,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list) KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -481,6 +489,7 @@ namespace wallet_rpc
std::list<uint64_t> fee_list; std::list<uint64_t> fee_list;
std::list<std::string> tx_blob_list; std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list; std::list<std::string> tx_metadata_list;
std::list<std::string> multisig_txset;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_hash_list)
@ -488,6 +497,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee_list) KV_SERIALIZE(fee_list)
KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -528,6 +538,7 @@ namespace wallet_rpc
uint64_t fee; uint64_t fee;
std::string tx_blob; std::string tx_blob;
std::string tx_metadata; std::string tx_metadata;
std::string multisig_txset;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_hash)
@ -535,6 +546,7 @@ namespace wallet_rpc
KV_SERIALIZE(fee) KV_SERIALIZE(fee)
KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata) KV_SERIALIZE(tx_metadata)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
@ -1485,5 +1497,181 @@ namespace wallet_rpc
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
}; };
struct COMMAND_RPC_IS_MULTISIG
{
struct request
{
BEGIN_KV_SERIALIZE_MAP()
END_KV_SERIALIZE_MAP()
};
struct response
{
bool multisig;
bool ready;
uint32_t threshold;
uint32_t total;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(multisig)
KV_SERIALIZE(ready)
KV_SERIALIZE(threshold)
KV_SERIALIZE(total)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_PREPARE_MULTISIG
{
struct request
{
BEGIN_KV_SERIALIZE_MAP()
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string multisig_info;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(multisig_info)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_MAKE_MULTISIG
{
struct request
{
std::vector<std::string> multisig_info;
uint32_t threshold;
std::string password;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(multisig_info)
KV_SERIALIZE(threshold)
KV_SERIALIZE(password)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string address;
std::string multisig_info;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
KV_SERIALIZE(multisig_info)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_EXPORT_MULTISIG
{
struct request
{
BEGIN_KV_SERIALIZE_MAP()
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string info;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(info)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_IMPORT_MULTISIG
{
struct request
{
std::vector<std::string> info;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(info)
END_KV_SERIALIZE_MAP()
};
struct response
{
uint64_t n_outputs;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(n_outputs)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_FINALIZE_MULTISIG
{
struct request
{
std::string password;
std::vector<std::string> multisig_info;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(password)
KV_SERIALIZE(multisig_info)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string address;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_SIGN_MULTISIG
{
struct request
{
std::string tx_data_hex;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_data_hex)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string tx_data_hex;
std::list<std::string> tx_hash_list;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_data_hex)
KV_SERIALIZE(tx_hash_list)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_SUBMIT_MULTISIG
{
struct request
{
std::string tx_data_hex;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_data_hex)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::list<std::string> tx_hash_list;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
END_KV_SERIALIZE_MAP()
};
};
} }
} }

View file

@ -58,3 +58,12 @@
#define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 #define WALLET_RPC_ERROR_CODE_WRONG_KEY -25
#define WALLET_RPC_ERROR_CODE_BAD_HEX -26 #define WALLET_RPC_ERROR_CODE_BAD_HEX -26
#define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 #define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27
#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28
#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29
#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30
#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31
#define WALLET_RPC_ERROR_CODE_WRONG_LR -32
#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33
#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA -34
#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE -35
#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36

View file

@ -36,6 +36,7 @@ set(core_tests_sources
chaingen_main.cpp chaingen_main.cpp
double_spend.cpp double_spend.cpp
integer_overflow.cpp integer_overflow.cpp
multisig.cpp
ring_signature_1.cpp ring_signature_1.cpp
transaction_tests.cpp transaction_tests.cpp
tx_validation.cpp tx_validation.cpp
@ -52,6 +53,7 @@ set(core_tests_headers
double_spend.h double_spend.h
double_spend.inl double_spend.inl
integer_overflow.h integer_overflow.h
multisig.h
ring_signature_1.h ring_signature_1.h
transaction_tests.h transaction_tests.h
tx_validation.h tx_validation.h
@ -63,6 +65,7 @@ add_executable(core_tests
${core_tests_headers}) ${core_tests_headers})
target_link_libraries(core_tests target_link_libraries(core_tests
PRIVATE PRIVATE
multisig
cryptonote_core cryptonote_core
p2p p2p
version version

View file

@ -505,6 +505,56 @@ inline bool do_replay_file(const std::string& filename)
cryptonote::account_base account; \ cryptonote::account_base account; \
account.generate(); account.generate();
#define GENERATE_MULTISIG_ACCOUNT(account, threshold, total) \
CHECK_AND_ASSERT_MES(threshold >= 2 && threshold <= total, false, "Invalid multisig scheme"); \
std::vector<cryptonote::account_base> account(total); \
do \
{ \
for (size_t msidx = 0; msidx < total; ++msidx) \
account[msidx].generate(); \
std::unordered_set<crypto::public_key> all_multisig_keys; \
std::vector<std::vector<crypto::secret_key>> view_keys(total); \
std::vector<std::vector<crypto::public_key>> spend_keys(total); \
for (size_t msidx = 0; msidx < total; ++msidx) \
{ \
for (size_t msidx_inner = 0; msidx_inner < total; ++msidx_inner) \
{ \
if (msidx_inner != msidx) \
{ \
crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_view_secret_key); \
view_keys[msidx].push_back(vkh); \
crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx_inner].get_keys().m_spend_secret_key); \
crypto::public_key pskh; \
crypto::secret_key_to_public_key(skh, pskh); \
spend_keys[msidx].push_back(pskh); \
} \
} \
} \
for (size_t msidx = 0; msidx < total; ++msidx) \
{ \
std::vector<crypto::secret_key> multisig_keys; \
crypto::secret_key spend_skey; \
crypto::public_key spend_pkey; \
if (threshold == total) \
cryptonote::generate_multisig_N_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \
else \
cryptonote::generate_multisig_N1_N(account[msidx].get_keys(), spend_keys[msidx], multisig_keys, (rct::key&)spend_skey, (rct::key&)spend_pkey); \
crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, view_keys[msidx]); \
account[msidx].make_multisig(view_skey, spend_skey, spend_pkey, multisig_keys); \
for (const auto &k: multisig_keys) \
all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k)))); \
} \
if (threshold < total) \
{ \
std::vector<crypto::public_key> spend_public_keys; \
for (const auto &k: all_multisig_keys) \
spend_public_keys.push_back(k); \
crypto::public_key spend_pkey = cryptonote::generate_multisig_N1_N_spend_public_key(spend_public_keys); \
for (size_t msidx = 0; msidx < total; ++msidx) \
account[msidx].finalize_multisig(spend_pkey); \
} \
} while(0)
#define MAKE_ACCOUNT(VEC_EVENTS, account) \ #define MAKE_ACCOUNT(VEC_EVENTS, account) \
cryptonote::account_base account; \ cryptonote::account_base account; \
account.generate(); \ account.generate(); \

View file

@ -199,6 +199,25 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra);
GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra);
GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2);
GENERATE_AND_PLAY(gen_multisig_tx_valid_22_1_2_many_inputs);
GENERATE_AND_PLAY(gen_multisig_tx_valid_22_2_1);
GENERATE_AND_PLAY(gen_multisig_tx_valid_33_1_23);
GENERATE_AND_PLAY(gen_multisig_tx_valid_33_3_21);
GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_2);
GENERATE_AND_PLAY(gen_multisig_tx_valid_23_1_3);
GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_1);
GENERATE_AND_PLAY(gen_multisig_tx_valid_23_2_3);
GENERATE_AND_PLAY(gen_multisig_tx_valid_45_1_234);
GENERATE_AND_PLAY(gen_multisig_tx_valid_45_4_135_many_inputs);
GENERATE_AND_PLAY(gen_multisig_tx_valid_89_3_1245789);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_23_1__no_threshold);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_45_5_23_no_threshold);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_22_1__no_threshold);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1__no_threshold);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_2_no_threshold);
GENERATE_AND_PLAY(gen_multisig_tx_invalid_33_1_3_no_threshold);
el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error); el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error);
MLOG(level, "\nREPORT:"); MLOG(level, "\nREPORT:");
MLOG(level, " Test run: " << tests_count); MLOG(level, " Test run: " << tests_count);

View file

@ -41,6 +41,7 @@
#include "tx_validation.h" #include "tx_validation.h"
#include "v2_tests.h" #include "v2_tests.h"
#include "rct.h" #include "rct.h"
#include "multisig.h"
/************************************************************************/ /************************************************************************/
/* */ /* */
/************************************************************************/ /************************************************************************/

View file

@ -0,0 +1,523 @@
// Copyright (c) 2017, 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 "ringct/rctSigs.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "multisig/multisig.h"
#include "common/apply_permutation.h"
#include "chaingen.h"
#include "multisig.h"
using namespace epee;
using namespace crypto;
using namespace cryptonote;
//#define NO_MULTISIG
//----------------------------------------------------------------------------------------------------------------------
// Tests
bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
size_t inputs, size_t mixin, uint64_t amount_paid, bool valid,
size_t threshold, size_t total, size_t creator, std::vector<size_t> signers,
const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx,
const std::function<void(transaction &tx)> &post_tx) const
{
uint64_t ts_start = 1338224400;
bool r;
CHECK_AND_ASSERT_MES(total >= 2, false, "Bad scheme");
CHECK_AND_ASSERT_MES(threshold <= total, false, "Bad scheme");
CHECK_AND_ASSERT_MES(threshold >= total - 1, false, "Unsupported scheme");
#ifdef NO_MULTISIG
CHECK_AND_ASSERT_MES(total <= 5, false, "Unsupported scheme");
#endif
CHECK_AND_ASSERT_MES(inputs >= 1 && inputs <= 8, false, "Inputs should between 1 and 8");
// given as 1 based for clarity
--creator;
for (size_t &signer: signers)
--signer;
CHECK_AND_ASSERT_MES(creator < total, false, "invalid creator");
for (size_t signer: signers)
CHECK_AND_ASSERT_MES(signer < total, false, "invalid signer");
#ifdef NO_MULTISIG
GENERATE_ACCOUNT(acc0);
GENERATE_ACCOUNT(acc1);
GENERATE_ACCOUNT(acc2);
GENERATE_ACCOUNT(acc3);
GENERATE_ACCOUNT(acc4);
account_base miner_account[5] = {acc0, acc1, acc2, acc3, acc4};
#else
GENERATE_MULTISIG_ACCOUNT(miner_account, threshold, total);
#endif
MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start);
// create 8 miner accounts, and have them mine the next 8 blocks
// they will have a coinbase with a single out that's pseudo rct
constexpr size_t n_coinbases = 8;
cryptonote::account_base miner_accounts[n_coinbases];
const cryptonote::block *prev_block = &blk_0;
cryptonote::block blocks[n_coinbases];
for (size_t n = 0; n < n_coinbases; ++n) {
// the first block goes to the multisig account
miner_accounts[n].generate();
account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n];
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account,
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
4, 4, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block");
events.push_back(blocks[n]);
prev_block = blocks + n;
LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx));
LOG_PRINT_L0("in block: " << obj_to_json_str(blocks[n]));
}
// rewind
cryptonote::block blk_r, blk_last;
{
blk_last = blocks[n_coinbases - 1];
for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i)
{
cryptonote::block blk;
CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0],
test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
false, "Failed to generate block");
events.push_back(blk);
blk_last = blk;
}
blk_r = blk_last;
}
cryptonote::keypair in_ephemeral;
crypto::public_key tx_pub_key[n_coinbases];
crypto::public_key output_pub_key[n_coinbases];
for (size_t n = 0; n < n_coinbases; ++n)
{
tx_pub_key[n] = get_tx_pub_key_from_extra(blocks[n].miner_tx);
MDEBUG("tx_pub_key: " << tx_pub_key);
output_pub_key[n] = boost::get<txout_to_key>(blocks[n].miner_tx.vout[0].target).key;
MDEBUG("output_pub_key: " << output_pub_key);
}
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[miner_account[0].get_keys().m_account_address.m_spend_public_key] = {0,0};
#ifndef NO_MULTISIG
// create k/L/R/ki for that output we're going to spend
std::vector<std::vector<std::vector<crypto::secret_key>>> account_k(total);
std::vector<std::vector<std::vector<crypto::public_key>>> account_L(total);
std::vector<std::vector<std::vector<crypto::public_key>>> account_R(total);
std::vector<std::vector<std::vector<crypto::key_image>>> account_ki(total);
std::vector<crypto::public_key> additional_tx_keys;
for (size_t msidx = 0; msidx < total; ++msidx)
{
CHECK_AND_ASSERT_MES(miner_account[msidx].get_keys().m_account_address.m_spend_public_key == miner_account[0].get_keys().m_account_address.m_spend_public_key,
false, "Mismatched spend public keys");
size_t nlr = threshold < total ? threshold - 1 : 1;
account_k[msidx].resize(inputs);
account_L[msidx].resize(inputs);
account_R[msidx].resize(inputs);
account_ki[msidx].resize(inputs);
for (size_t tdidx = 0; tdidx < inputs; ++tdidx)
{
account_L[msidx][tdidx].resize(nlr);
account_R[msidx][tdidx].resize(nlr);
for (size_t n = 0; n < nlr; ++n)
{
account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen()));
cryptonote::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]);
}
size_t numki = miner_account[msidx].get_multisig_keys().size();
account_ki[msidx][tdidx].resize(numki);
for (size_t kiidx = 0; kiidx < numki; ++kiidx)
{
r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image");
}
MDEBUG("Party " << msidx << ":");
MDEBUG("spend: sec " << miner_account[msidx].get_keys().m_spend_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_spend_public_key);
MDEBUG("view: sec " << miner_account[msidx].get_keys().m_view_secret_key << ", pub " << miner_account[msidx].get_keys().m_account_address.m_view_public_key);
for (const auto &k: miner_account[msidx].get_multisig_keys())
MDEBUG("msk: " << k);
for (size_t n = 0; n < account_k[msidx][tdidx].size(); ++n)
{
MDEBUG("k: " << account_k[msidx][tdidx][n]);
MDEBUG("L: " << account_L[msidx][tdidx][n]);
MDEBUG("R: " << account_R[msidx][tdidx][n]);
}
for (const auto &ki: account_ki[msidx][tdidx])
MDEBUG("ki: " << ki);
}
}
#endif
// create kLRki
std::vector<rct::multisig_kLRki> kLRkis;
std::unordered_set<crypto::public_key> used_L;
for (size_t tdidx = 0; tdidx < inputs; ++tdidx)
{
kLRkis.push_back(rct::multisig_kLRki());
rct::multisig_kLRki &kLRki = kLRkis.back();
#ifdef NO_MULTISIG
kLRki = {rct::zero(), rct::zero(), rct::zero(), rct::zero()};
#else
kLRki.k = rct::sk2rct(account_k[creator][tdidx][0]);
kLRki.L = rct::pk2rct(account_L[creator][tdidx][0]);
kLRki.R = rct::pk2rct(account_R[creator][tdidx][0]);
MDEBUG("Starting with k " << kLRki.k);
MDEBUG("Starting with L " << kLRki.L);
MDEBUG("Starting with R " << kLRki.R);
for (size_t msidx = 0; msidx < total; ++msidx)
{
if (msidx == creator)
continue;
if (std::find(signers.begin(), signers.end(), msidx) == signers.end())
continue;
for (size_t lr = 0; lr < account_L[msidx][tdidx].size(); ++lr)
{
if (used_L.find(account_L[msidx][tdidx][lr]) == used_L.end())
{
used_L.insert(account_L[msidx][tdidx][lr]);
MDEBUG("Adding L " << account_L[msidx][tdidx][lr] << " (for k " << account_k[msidx][tdidx][lr] << ")");
MDEBUG("Adding R " << account_R[msidx][tdidx][lr]);
rct::addKeys((rct::key&)kLRki.L, kLRki.L, rct::pk2rct(account_L[msidx][tdidx][lr]));
rct::addKeys((rct::key&)kLRki.R, kLRki.R, rct::pk2rct(account_R[msidx][tdidx][lr]));
break;
}
}
}
std::vector<crypto::key_image> pkis;
for (size_t msidx = 0; msidx < total; ++msidx)
for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n)
pkis.push_back(account_ki[msidx][tdidx][n]);
r = cryptonote::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image");
MDEBUG("composite ki: " << kLRki.ki);
MDEBUG("L: " << kLRki.L);
MDEBUG("R: " << kLRki.R);
for (size_t n = 1; n < total; ++n)
{
rct::key ki;
r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image");
CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match");
}
}
#endif
// create a tx: we have 8 outputs, all from coinbase, so "fake" rct - use 2
std::vector<tx_source_entry> sources;
for (size_t n = 0; n < inputs; ++n)
{
sources.resize(sources.size() + 1);
tx_source_entry& src = sources.back();
src.real_output = n;
src.amount = blocks[n].miner_tx.vout[0].amount;
src.real_out_tx_key = tx_pub_key[n];
src.real_output_in_tx_index = 0;
src.mask = rct::identity();
src.rct = true;
src.multisig_kLRki = kLRkis[n];
for (size_t m = 0; m <= mixin; ++m)
{
rct::ctkey ctkey;
ctkey.dest = rct::pk2rct(boost::get<txout_to_key>(blocks[m].miner_tx.vout[0].target).key);
MDEBUG("using " << (m == n ? "real" : "fake") << " input " << ctkey.dest);
ctkey.mask = rct::commit(blocks[m].miner_tx.vout[0].amount, rct::identity()); // since those are coinbases, the masks are known
src.outputs.push_back(std::make_pair(m, ctkey));
}
}
//fill outputs entry
tx_destination_entry td;
td.addr = miner_account[creator].get_keys().m_account_address;
td.amount = amount_paid;
std::vector<tx_destination_entry> destinations;
destinations.push_back(td);
if (pre_tx)
pre_tx(sources, destinations);
transaction tx;
crypto::secret_key tx_key;
#ifdef NO_MULTISIG
rct::multisig_out *msoutp = NULL;
#else
rct::multisig_out msout;
rct::multisig_out *msoutp = &msout;
#endif
std::vector<crypto::secret_key> additional_tx_secret_keys;
auto sources_copy = sources;
r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, false, msoutp);
CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
#ifndef NO_MULTISIG
// work out the permutation done on sources
std::vector<size_t> ins_order;
for (size_t n = 0; n < sources.size(); ++n)
{
for (size_t idx = 0; idx < sources_copy.size(); ++idx)
{
CHECK_AND_ASSERT_MES((size_t)sources_copy[idx].real_output < sources_copy[idx].outputs.size(),
false, "Invalid real_output");
if (sources_copy[idx].outputs[sources_copy[idx].real_output].second.dest == sources[n].outputs[sources[n].real_output].second.dest)
ins_order.push_back(idx);
}
}
CHECK_AND_ASSERT_MES(ins_order.size() == sources.size(), false, "Failed to work out sources permutation");
#endif
#ifndef NO_MULTISIG
// sign
std::unordered_set<crypto::secret_key> used_keys;
const std::vector<crypto::secret_key> &msk0 = miner_account[creator].get_multisig_keys();
for (const auto &sk: msk0)
used_keys.insert(sk);
for (size_t signer: signers)
{
rct::key skey = rct::zero();
const std::vector<crypto::secret_key> &msk1 = miner_account[signer].get_multisig_keys();
for (size_t n = 0; n < msk1.size(); ++n)
{
const crypto::secret_key &sk1 = msk1[n];
if (used_keys.find(sk1) == used_keys.end())
{
used_keys.insert(sk1);
sc_add(skey.bytes, skey.bytes, rct::sk2rct(sk1).bytes);
}
}
CHECK_AND_ASSERT_MES(!(skey == rct::zero()), false, "failed to find secret multisig key to sign transaction");
std::vector<unsigned int> indices;
for (const auto &src: sources_copy)
indices.push_back(src.real_output);
rct::keyV k;
for (size_t tdidx = 0; tdidx < inputs; ++tdidx)
{
k.push_back(rct::zero());
for (size_t n = 0; n < account_k[signer][tdidx].size(); ++n)
{
crypto::public_key L;
rct::scalarmultBase((rct::key&)L, rct::sk2rct(account_k[signer][tdidx][n]));
if (used_L.find(L) != used_L.end())
{
sc_add(k.back().bytes, k.back().bytes, rct::sk2rct(account_k[signer][tdidx][n]).bytes);
}
}
CHECK_AND_ASSERT_MES(!(k.back() == rct::zero()), false, "failed to find k to sign transaction");
}
tools::apply_permutation(ins_order, indices);
tools::apply_permutation(ins_order, k);
MDEBUG("signing with k size " << k.size());
MDEBUG("signing with k " << k.back());
MDEBUG("signing with sk " << skey);
for (const auto &sk: used_keys)
MDEBUG(" created with sk " << sk);
MDEBUG("signing with c size " << msout.c.size());
MDEBUG("signing with c " << msout.c.back());
r = rct::signMultisig(tx.rct_signatures, indices, k, msout, skey);
CHECK_AND_ASSERT_MES(r, false, "failed to sign transaction");
}
#endif
// verify this tx is really to the expected address
const crypto::public_key tx_pub_key2 = get_tx_pub_key_from_extra(tx, 0);
crypto::key_derivation derivation;
r = crypto::generate_key_derivation(tx_pub_key2, miner_account[creator].get_keys().m_view_secret_key, derivation);
CHECK_AND_ASSERT_MES(r, false, "Failed to generate derivation");
uint64_t n_outs = 0, amount = 0;
std::vector<crypto::key_derivation> additional_derivations;
for (size_t n = 0; n < tx.vout.size(); ++n)
{
CHECK_AND_ASSERT_MES(typeid(txout_to_key) == tx.vout[n].target.type(), false, "Unexpected tx out type");
if (is_out_to_acc_precomp(subaddresses, boost::get<txout_to_key>(tx.vout[n].target).key, derivation, additional_derivations, n))
{
++n_outs;
CHECK_AND_ASSERT_MES(tx.vout[n].amount == 0, false, "Destination amount is not zero");
rct::key Ctmp;
crypto::secret_key scalar1;
crypto::derivation_to_scalar(derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1));
rct::key C = tx.rct_signatures.outPk[n].mask;
rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
amount += rct::h2d(ecdh_info.amount);
}
}
CHECK_AND_ASSERT_MES(n_outs == 1, false, "Not exactly 1 output was received");
CHECK_AND_ASSERT_MES(amount == amount_paid, false, "Amount paid was not the expected amount");
if (post_tx)
post_tx(tx);
if (!valid)
DO_CALLBACK(events, "mark_invalid_tx");
events.push_back(tx);
LOG_PRINT_L0("Test tx: " << obj_to_json_str(tx));
return true;
}
bool gen_multisig_tx_valid_22_1_2::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
}
bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
}
bool gen_multisig_tx_valid_22_2_1::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL);
}
bool gen_multisig_tx_valid_33_1_23::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL);
}
bool gen_multisig_tx_valid_33_3_21::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL);
}
bool gen_multisig_tx_valid_23_1_2::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL);
}
bool gen_multisig_tx_valid_23_1_3::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL);
}
bool gen_multisig_tx_valid_23_2_1::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL);
}
bool gen_multisig_tx_valid_23_2_3::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL);
}
bool gen_multisig_tx_valid_45_1_234::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL);
}
bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL);
}
bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL);
}
bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL);
}
bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL);
}
bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL);
}
bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL);
}
bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL);
}
bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector<test_event_entry>& events) const
{
const size_t mixin = 4;
const uint64_t amount_paid = 10000;
return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL);
}

199
tests/core_tests/multisig.h Normal file
View file

@ -0,0 +1,199 @@
// Copyright (c) 2017, 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 "chaingen.h"
struct gen_multisig_tx_validation_base : public test_chain_unit_base
{
gen_multisig_tx_validation_base()
: m_invalid_tx_index(0)
, m_invalid_block_index(0)
{
REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_tx);
REGISTER_CALLBACK_METHOD(gen_multisig_tx_validation_base, mark_invalid_block);
}
bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/)
{
if (m_invalid_tx_index == event_idx)
return tvc.m_verifivation_failed;
else
return !tvc.m_verifivation_failed && tx_added;
}
bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/)
{
if (m_invalid_block_index == event_idx)
return bvc.m_verifivation_failed;
else
return !bvc.m_verifivation_failed;
}
bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_invalid_block_index = ev_index + 1;
return true;
}
bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
{
m_invalid_tx_index = ev_index + 1;
return true;
}
bool generate_with(std::vector<test_event_entry>& events, size_t inputs, size_t mixin,
uint64_t amount_paid, bool valid,
size_t threshold, size_t total, size_t creator, std::vector<size_t> signers,
const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx,
const std::function<void(cryptonote::transaction &tx)> &post_tx) const;
private:
size_t m_invalid_tx_index;
size_t m_invalid_block_index;
};
template<>
struct get_test_options<gen_multisig_tx_validation_base> {
const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)};
const cryptonote::test_options test_options = {
hard_forks
};
};
// valid
struct gen_multisig_tx_valid_22_1_2: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_22_1_2>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_22_1_2_many_inputs: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_22_1_2_many_inputs>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_22_2_1: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_22_2_1>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_33_1_23: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_33_1_23>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_33_3_21: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_33_3_21>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_23_1_2: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_23_1_2>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_23_1_3: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_23_1_3>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_23_2_1: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_23_2_1>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_23_2_3: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_23_2_3>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_45_1_234: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_45_1_234>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_45_4_135_many_inputs: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_45_4_135_many_inputs>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_valid_89_3_1245789: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_valid_89_3_1245789>: public get_test_options<gen_multisig_tx_validation_base> {};
// invalid
struct gen_multisig_tx_invalid_22_1__no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_22_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_invalid_33_1__no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_33_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_invalid_33_1_2_no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_33_1_2_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_invalid_33_1_3_no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_33_1_3_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_invalid_23_1__no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_23_1__no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};
struct gen_multisig_tx_invalid_45_5_23_no_threshold: public gen_multisig_tx_validation_base
{
bool generate(std::vector<test_event_entry>& events) const;
};
template<> struct get_test_options<gen_multisig_tx_invalid_45_5_23_no_threshold>: public get_test_options<gen_multisig_tx_validation_base> {};

View file

@ -53,6 +53,7 @@ set(unit_tests_sources
memwipe.cpp memwipe.cpp
mnemonics.cpp mnemonics.cpp
mul_div.cpp mul_div.cpp
multisig.cpp
parse_amount.cpp parse_amount.cpp
serialization.cpp serialization.cpp
sha256.cpp sha256.cpp

View file

@ -0,0 +1,188 @@
// Copyright (c) 2017, 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 "gtest/gtest.h"
#include <cstdint>
#include "wallet/wallet2.h"
static const struct
{
const char *address;
const char *spendkey;
} test_addresses[] =
{
{
"9uvjbU54ZJb8j7Dcq1h3F1DnBRkxXdYUX4pbJ7mE3ghM8uF3fKzqRKRNAKYZXcNLqMg7MxjVVD2wKC2PALUwEveGSC3YSWD",
"2dd6e34a234c3e8b5d29a371789e4601e96dee4ea6f7ef79224d1a2d91164c01"
},
{
"9ywDBAyDbb6QKFiZxDJ4hHZqZEQXXCR5EaYNcndUpqPDeE7rEgs6neQdZnhcDrWbURYK8xUjhuG2mVjJdmknrZbcG7NnbaB",
"fac47aecc948ce9d3531aa042abb18235b1df632087c55a361b632ffdd6ede0c"
},
{
"9t6Hn946u3eah5cuncH1hB5hGzsTUoevtf4SY7MHN5NgJZh2SFWsyVt3vUhuHyRKyrCQvr71Lfc1AevG3BXE11PQFoXDtD8",
"bbd3175ef9fd9f5eefdc43035f882f74ad14c4cf1799d8b6f9001bc197175d02"
}
};
static void make_wallet(unsigned int idx, tools::wallet2 &wallet)
{
ASSERT_TRUE(idx < sizeof(test_addresses) / sizeof(test_addresses[0]));
crypto::secret_key spendkey;
epee::string_tools::hex_to_pod(test_addresses[idx].spendkey, spendkey);
try
{
wallet.init("");
wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false);
ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(true));
}
catch (const std::exception &e)
{
MFATAL("Error creating test wallet: " << e.what());
ASSERT_TRUE(0);
}
}
static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, unsigned int M)
{
ASSERT_TRUE(M <= 2);
make_wallet(0, wallet0);
make_wallet(1, wallet1);
std::vector<crypto::secret_key> sk0(1), sk1(1);
std::vector<crypto::public_key> pk0(1), pk1(1);
std::string mi0 = wallet0.get_multisig_info();
std::string mi1 = wallet1.get_multisig_info();
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0]));
ASSERT_FALSE(wallet0.multisig() || wallet1.multisig());
wallet0.make_multisig("", sk0, pk0, M);
wallet1.make_multisig("", sk1, pk1, M);
ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true));
bool ready;
uint32_t threshold, total;
ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total));
ASSERT_TRUE(ready);
ASSERT_TRUE(threshold == M);
ASSERT_TRUE(total == 2);
ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total));
ASSERT_TRUE(ready);
ASSERT_TRUE(threshold == M);
ASSERT_TRUE(total == 2);
}
static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, tools::wallet2 &wallet2, unsigned int M)
{
ASSERT_TRUE(M <= 3);
make_wallet(0, wallet0);
make_wallet(1, wallet1);
make_wallet(2, wallet2);
std::vector<crypto::secret_key> sk0(2), sk1(2), sk2(2);
std::vector<crypto::public_key> pk0(2), pk1(2), pk2(2);
std::string mi0 = wallet0.get_multisig_info();
std::string mi1 = wallet1.get_multisig_info();
std::string mi2 = wallet2.get_multisig_info();
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk0[1], pk0[1]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk1[1], pk1[1]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk2[0], pk2[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk2[1], pk2[1]));
ASSERT_FALSE(wallet0.multisig() || wallet1.multisig() || wallet2.multisig());
std::string mxi0 = wallet0.make_multisig("", sk0, pk0, M);
std::string mxi1 = wallet1.make_multisig("", sk1, pk1, M);
std::string mxi2 = wallet2.make_multisig("", sk2, pk2, M);
const size_t nset = !mxi0.empty() + !mxi1.empty() + !mxi2.empty();
ASSERT_TRUE((M < 3 && nset == 3) || (M == 3 && nset == 0));
if (nset > 0)
{
std::unordered_set<crypto::public_key> pkeys;
std::vector<crypto::public_key> signers(3, crypto::null_pkey);
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi0, pkeys, signers[0]));
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi1, pkeys, signers[1]));
ASSERT_TRUE(tools::wallet2::verify_extra_multisig_info(mxi2, pkeys, signers[2]));
ASSERT_TRUE(pkeys.size() == 3);
ASSERT_TRUE(wallet0.finalize_multisig("", pkeys, signers));
ASSERT_TRUE(wallet1.finalize_multisig("", pkeys, signers));
ASSERT_TRUE(wallet2.finalize_multisig("", pkeys, signers));
}
ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet1.get_account().get_public_address_str(true));
ASSERT_TRUE(wallet0.get_account().get_public_address_str(true) == wallet2.get_account().get_public_address_str(true));
bool ready;
uint32_t threshold, total;
ASSERT_TRUE(wallet0.multisig(&ready, &threshold, &total));
ASSERT_TRUE(ready);
ASSERT_TRUE(threshold == M);
ASSERT_TRUE(total == 3);
ASSERT_TRUE(wallet1.multisig(&ready, &threshold, &total));
ASSERT_TRUE(ready);
ASSERT_TRUE(threshold == M);
ASSERT_TRUE(total == 3);
ASSERT_TRUE(wallet2.multisig(&ready, &threshold, &total));
ASSERT_TRUE(ready);
ASSERT_TRUE(threshold == M);
ASSERT_TRUE(total == 3);
}
TEST(multisig, make_2_2)
{
tools::wallet2 wallet0, wallet1;
make_M_2_wallet(wallet0, wallet1, 2);
}
TEST(multisig, make_3_3)
{
tools::wallet2 wallet0, wallet1, wallet2;
make_M_3_wallet(wallet0, wallet1, wallet2, 3);
}
TEST(multisig, make_2_3)
{
tools::wallet2 wallet0, wallet1, wallet2;
make_M_3_wallet(wallet0, wallet1, wallet2, 2);
}

View file

@ -111,7 +111,7 @@ TEST(ringct, MG_sigs)
sk[j] = xm[ind][j]; sk[j] = xm[ind][j];
} }
key message = identity(); key message = identity();
mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R); mgSig IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R);
ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R));
//#MG sig: false one //#MG sig: false one
@ -132,7 +132,7 @@ TEST(ringct, MG_sigs)
sk[j] = xx[ind][j]; sk[j] = xx[ind][j];
} }
sk[2] = skGen();//asume we don't know one of the private keys.. sk[2] = skGen();//asume we don't know one of the private keys..
IIccss = MLSAG_Gen(message, P, sk, ind, R); IIccss = MLSAG_Gen(message, P, sk, NULL, NULL, ind, R);
ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R));
} }
@ -171,7 +171,7 @@ TEST(ringct, range_proofs)
destinations.push_back(Pk); destinations.push_back(Pk);
//compute rct data with mixin 500 //compute rct data with mixin 500
rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);
//verify rct data //verify rct data
ASSERT_TRUE(verRct(s)); ASSERT_TRUE(verRct(s));
@ -188,7 +188,7 @@ TEST(ringct, range_proofs)
//compute rct data with mixin 500 //compute rct data with mixin 500
s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);
//verify rct data //verify rct data
ASSERT_FALSE(verRct(s)); ASSERT_FALSE(verRct(s));
@ -235,7 +235,7 @@ TEST(ringct, range_proofs_with_fee)
destinations.push_back(Pk); destinations.push_back(Pk);
//compute rct data with mixin 500 //compute rct data with mixin 500
rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);
//verify rct data //verify rct data
ASSERT_TRUE(verRct(s)); ASSERT_TRUE(verRct(s));
@ -252,7 +252,7 @@ TEST(ringct, range_proofs_with_fee)
//compute rct data with mixin 500 //compute rct data with mixin 500
s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);
//verify rct data //verify rct data
ASSERT_FALSE(verRct(s)); ASSERT_FALSE(verRct(s));
@ -310,7 +310,7 @@ TEST(ringct, simple)
//compute sig with mixin 2 //compute sig with mixin 2
xmr_amount txnfee = 1; xmr_amount txnfee = 1;
rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2); rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, NULL, NULL, txnfee, 2);
//verify ring ct signature //verify ring ct signature
ASSERT_TRUE(verRctSimple(s)); ASSERT_TRUE(verRctSimple(s));
@ -344,7 +344,7 @@ static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amount
} }
} }
return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3);; return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);;
} }
static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], uint64_t fee) static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], uint64_t fee)
@ -370,7 +370,7 @@ static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input
destinations.push_back(Pk); destinations.push_back(Pk);
} }
return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3);; return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, NULL, NULL, fee, 3);;
} }
static bool range_proof_test(bool expected_valid, static bool range_proof_test(bool expected_valid,

View file

@ -591,7 +591,7 @@ TEST(Serialization, serializes_ringct_types)
rct::skpkGen(Sk, Pk); rct::skpkGen(Sk, Pk);
destinations.push_back(Pk); destinations.push_back(Pk);
//compute rct data with mixin 500 //compute rct data with mixin 500
s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, NULL, NULL, 3);
mg0 = s0.p.MGs[0]; mg0 = s0.p.MGs[0];
ASSERT_TRUE(serialization::dump_binary(mg0, blob)); ASSERT_TRUE(serialization::dump_binary(mg0, blob));