From 6d219a9250c10084a02ab150e420ede6eecd7aaf Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 28 May 2017 12:18:51 +0100 Subject: wallet: add multisig key generation Scheme by luigi1111 --- src/wallet/wallet2.cpp | 163 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 4 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index abd24295a..4ad527423 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2181,6 +2181,15 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); + value2.SetInt(m_multisig ? 1 :0); + json.AddMember("multisig", value2, json.GetAllocator()); + + value2.SetUint(m_multisig_threshold); + json.AddMember("multisig_threshold", value2, json.GetAllocator()); + + value2.SetUint(m_multisig_total); + json.AddMember("multisig_total", value2, json.GetAllocator()); + value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); @@ -2292,6 +2301,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ { is_old_file_format = true; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; m_always_confirm_transfers = false; m_print_ring_members = false; m_default_mixin = 0; @@ -2328,6 +2340,12 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); m_watch_only = field_watch_only; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig, int, Int, false, false); + m_multisig = field_multisig; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); + m_multisig_threshold = field_multisig_threshold; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_total, unsigned int, Uint, m_multisig, 0); + m_multisig_total = field_multisig_total; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); @@ -2394,7 +2412,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ const cryptonote::account_keys& keys = m_account.get_keys(); r = epee::serialization::load_t_from_binary(m_account, account_data); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!m_watch_only) + if(!m_watch_only && !m_multisig) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); return true; @@ -2412,7 +2430,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ */ bool wallet2::verify_password(const epee::wipeable_string& password) const { - return verify_password(m_keys_file, password, m_watch_only); + return verify_password(m_keys_file, password, m_watch_only || m_multisig); } /*! @@ -2427,7 +2445,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) +bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key) { wallet2::keys_file_data keys_file_data; std::string buf; @@ -2461,7 +2479,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip const cryptonote::account_keys& keys = account_data_check.get_keys(); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!watch_only) + if(!no_spend_key) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); return r; } @@ -2489,6 +2507,9 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; // -1 month for fluctuations in block time and machine date/time setup. // avg seconds per block @@ -2569,6 +2590,9 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.create_from_viewkey(account_public_address, viewkey); m_account_public_address = account_public_address; m_watch_only = true; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; bool r = store_keys(m_keys_file, password, true); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); @@ -2605,6 +2629,69 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.create_from_keys(account_public_address, spendkey, viewkey); m_account_public_address = account_public_address; m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_total = 0; + + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + + store(); +} + +void wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector &view_keys, + const std::vector &spend_keys, + uint32_t threshold) +{ + CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); + CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); + CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= spend_keys.size() + 1, "Invalid threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + + clear(); + + MINFO("Creating spend key..."); + rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); + if (threshold == spend_keys.size() + 1) + { + // the multisig spend public key is the sum of all spend public keys + for (const auto &k: spend_keys) + rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + } + else + { + // the multisig spend public key is the sum of keys derived from all spend public keys + const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + for (const auto &k: spend_keys) + { + rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::cn_fast_hash(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); + } + } + + // the multisig view key is shared by all, make one all can derive + MINFO("Creating view key..."); + rct::key view_skey = rct::sk2rct(get_account().get_keys().m_view_secret_key); + for (const auto &k: view_keys) + sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + sc_reduce32(view_skey.bytes); + + MINFO("Creating multisig address..."); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), + "Failed to create multisig wallet due to bad keys"); + + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + m_multisig_total = spend_keys.size() + 1; bool r = store_keys(m_keys_file, password, false); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); @@ -2620,6 +2707,74 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& store(); } +std::string wallet2::get_multisig_info() const +{ + // It's a signed package of private view key and public spend key + const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; + const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + + std::string data; + data += std::string((const char *)&skey, sizeof(crypto::secret_key)); + data += std::string((const char *)&pkey, sizeof(crypto::public_key)); + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + + return std::string("MultisigV1") + tools::base58::encode(data); +} + +bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) +{ + const size_t header_len = strlen("MultisigV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + size_t offset = 0; + skey = *(const crypto::secret_key*)(decoded.data() + offset); + offset += sizeof(skey); + pkey = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(pkey); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + return true; +} + +bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const +{ + if (!m_multisig) + return false; + if (threshold) + *threshold = m_multisig_threshold; + if (total) + *total = m_multisig_total; + return true; +} + /*! * \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) -- cgit v1.2.3 From 4c313324b1c80148dff1a8099aa26c51ab6c7e3a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 3 Jun 2017 22:34:26 +0100 Subject: Add N/N multisig tx generation and signing Scheme by luigi1111: Multisig for RingCT on Monero 2 of 2 User A (coordinator): Spendkey b,B Viewkey a,A (shared) User B: Spendkey c,C Viewkey a,A (shared) Public Address: C+B, A Both have their own watch only wallet via C+B, a A will coordinate spending process (though B could easily as well, coordinator is more needed for more participants) A and B watch for incoming outputs B creates "half" key images for discovered output D: I2_D = (Hs(aR)+c) * Hp(D) B also creates 1.5 random keypairs (one scalar and 2 pubkeys; one on base G and one on base Hp(D)) for each output, storing the scalar(k) (linked to D), and sending the pubkeys with I2_D. A also creates "half" key images: I1_D = (Hs(aR)+b) * Hp(D) Then I_D = I1_D + I2_D Having I_D allows A to check spent status of course, but more importantly allows A to actually build a transaction prefix (and thus transaction). A builds the transaction until most of the way through MLSAG_Gen, adding the 2 pubkeys (per input) provided with I2_D to his own generated ones where they are needed (secret row L, R). At this point, A has a mostly completed transaction (but with an invalid/incomplete signature). A sends over the tx and includes r, which allows B (with the recipient's address) to verify the destination and amount (by reconstructing the stealth address and decoding ecdhInfo). B then finishes the signature by computing ss[secret_index][0] = ss[secret_index][0] + k - cc[secret_index]*c (secret indices need to be passed as well). B can then broadcast the tx, or send it back to A for broadcasting. Once B has completed the signing (and verified the tx to be valid), he can add the full I_D to his cache, allowing him to verify spent status as well. NOTE: A and B *must* present key A and B to each other with a valid signature proving they know a and b respectively. Otherwise, trickery like the following becomes possible: A creates viewkey a,A, spendkey b,B, and sends a,A,B to B. B creates a fake key C = zG - B. B sends C back to A. The combined spendkey C+B then equals zG, allowing B to spend funds at any time! The signature fixes this, because B does not know a c corresponding to C (and thus can't produce a signature). 2 of 3 User A (coordinator) Shared viewkey a,A "spendkey" j,J User B "spendkey" k,K User C "spendkey" m,M A collects K and M from B and C B collects J and M from A and C C collects J and K from A and B A computes N = nG, n = Hs(jK) A computes O = oG, o = Hs(jM) B anc C compute P = pG, p = Hs(kM) || Hs(mK) B and C can also compute N and O respectively if they wish to be able to coordinate Address: N+O+P, A The rest follows as above. The coordinator possesses 2 of 3 needed keys; he can get the other needed part of the signature/key images from either of the other two. Alternatively, if secure communication exists between parties: A gives j to B B gives k to C C gives m to A Address: J+K+M, A 3 of 3 Identical to 2 of 2, except the coordinator must collect the key images from both of the others. The transaction must also be passed an additional hop: A -> B -> C (or A -> C -> B), who can then broadcast it or send it back to A. N-1 of N Generally the same as 2 of 3, except participants need to be arranged in a ring to pass their keys around (using either the secure or insecure method). For example (ignoring viewkey so letters line up): [4 of 5] User: spendkey A: a B: b C: c D: d E: e a -> B, b -> C, c -> D, d -> E, e -> A Order of signing does not matter, it just must reach n-1 users. A "remaining keys" list must be passed around with the transaction so the signers know if they should use 1 or both keys. Collecting key image parts becomes a little messy, but basically every wallet sends over both of their parts with a tag for each. Thia way the coordinating wallet can keep track of which images have been added and which wallet they come from. Reasoning: 1. The key images must be added only once (coordinator will get key images for key a from both A and B, he must add only one to get the proper key actual key image) 2. The coordinator must keep track of which helper pubkeys came from which wallet (discussed in 2 of 2 section). The coordinator must choose only one set to use, then include his choice in the "remaining keys" list so the other wallets know which of their keys to use. You can generalize it further to N-2 of N or even M of N, but I'm not sure there's legitimate demand to justify the complexity. It might also be straightforward enough to support with minimal changes from N-1 format. You basically just give each user additional keys for each additional "-1" you desire. N-2 would be 3 keys per user, N-3 4 keys, etc. The process is somewhat cumbersome: To create a N/N multisig wallet: - each participant creates a normal wallet - each participant runs "prepare_multisig", and sends the resulting string to every other participant - each participant runs "make_multisig N A B C D...", with N being the threshold and A B C D... being the strings received from other participants (the threshold must currently equal N) As txes are received, participants' wallets will need to synchronize so that those new outputs may be spent: - each participant runs "export_multisig FILENAME", and sends the FILENAME file to every other participant - each participant runs "import_multisig A B C D...", with A B C D... being the filenames received from other participants Then, a transaction may be initiated: - one of the participants runs "transfer ADDRESS AMOUNT" - this partly signed transaction will be written to the "multisig_monero_tx" file - the initiator sends this file to another participant - that other participant runs "sign_multisig multisig_monero_tx" - the resulting transaction is written to the "multisig_monero_tx" file again - if the threshold was not reached, the file must be sent to another participant, until enough have signed - the last participant to sign runs "submit_multisig multisig_monero_tx" to relay the transaction to the Monero network --- src/wallet/wallet2.cpp | 575 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 515 insertions(+), 60 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4ad527423..af48e711e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -56,6 +56,7 @@ using namespace epee; #include "mnemonics/electrum-words.h" #include "common/i18n.h" #include "common/util.h" +#include "common/apply_permutation.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" @@ -87,6 +88,7 @@ using namespace cryptonote; #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" #define SIGNED_TX_PREFIX "Monero signed tx set\004" +#define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" #define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) @@ -524,6 +526,69 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } +bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) +{ + crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + return true; +} + +bool wallet_generate_key_image_helper_old(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, bool multisig_export = false) +{ + if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + if (multisig_export) + { + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, ack.m_spend_secret_key, ki); + } + return true; +} + +crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + crypto::hash8 payment_id8 = null_hash8; + std::vector tx_extra_fields; + if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) + return payment_id8; + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); + } + } + return payment_id8; +} + +tools::wallet2::tx_construction_data get_construction_data_with_decrypted_short_payment_id(const tools::wallet2::pending_tx &ptx) +{ + tools::wallet2::tx_construction_data construction_data = ptx.construction_data; + crypto::hash8 payment_id = get_short_payment_id(ptx); + if (payment_id != null_hash8) + { + // Remove encrypted + remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); + // Add decrypted + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + THROW_WALLET_EXCEPTION_IF(!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce), + tools::error::wallet_internal_error, "Failed to add decrypted payment id to tx extra"); + LOG_PRINT_L1("Decrypted payment ID: " << payment_id); + } + return construction_data; +} + + //----------------------------------------------------------------- } //namespace namespace tools @@ -846,7 +911,15 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & //---------------------------------------------------------------------------------------------------- void wallet2::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 &tx_money_got_in_outs, std::vector &outs) { - bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r; + if (m_multisig) + { + r = wallet_generate_key_image_helper_old(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki); + } + else + { + r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + } THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -1018,7 +1091,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid; td.m_key_image = tx_scan_info[o].ki; - td.m_key_image_known = !m_watch_only; + td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_partial = m_multisig; td.m_amount = tx.vout[o].amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; @@ -1040,8 +1114,18 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_rct = false; } set_unspent(m_transfers.size()-1); - m_key_images[td.m_key_image] = m_transfers.size()-1; + if (!m_multisig) + m_key_images[td.m_key_image] = m_transfers.size()-1; m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + else + td.m_multisig_k = rct::skGen(); + } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); @@ -1090,6 +1174,15 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } + if (m_multisig) + { + THROW_WALLET_EXCEPTION_IF(!m_multisig_rescan_k && m_multisig_rescan_info, + error::wallet_internal_error, "NULL m_multisig_rescan_k"); + if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) + update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); + else + td.m_multisig_k = rct::skGen(); + } THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); @@ -2083,8 +2176,10 @@ void wallet2::detach_blockchain(uint64_t height) for(size_t i = i_start; i!= m_transfers.size();i++) { + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) + continue; auto it_ki = m_key_images.find(m_transfers[i].m_key_image); - THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); + THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found: index " + std::to_string(i) + ", ki " + epee::string_tools::pod_to_hex(m_transfers[i].m_key_image) + ", " + std::to_string(m_key_images.size()) + " key images known"); m_key_images.erase(it_ki); } @@ -2437,7 +2532,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * \brief verify password for specified wallet keys file. * \param keys_file_name Keys file to verify password for * \param password Password to verify - * \param watch_only If set = only verify view keys, otherwise also spend keys + * \param no_spend_key If set = only verify view keys, otherwise also spend keys * \return true if password is correct * * for verification only @@ -2653,8 +2748,8 @@ void wallet2::make_multisig(const epee::wipeable_string &password, { CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); - CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); + CHECK_AND_ASSERT_THROW_MES(/*threshold == spend_keys.size() ||*/ threshold == spend_keys.size() + 1, "Unsupported threshold case"); clear(); @@ -2669,19 +2764,20 @@ void wallet2::make_multisig(const epee::wipeable_string &password, else { // the multisig spend public key is the sum of keys derived from all spend public keys - const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); // WRONG for (const auto &k: spend_keys) { - rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::cn_fast_hash(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); + rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); } } // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - rct::key view_skey = rct::sk2rct(get_account().get_keys().m_view_secret_key); + crypto::hash hash; + crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); + rct::key view_skey = rct::hash2rct(hash); for (const auto &k: view_keys) sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); - sc_reduce32(view_skey.bytes); MINFO("Creating multisig address..."); CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), @@ -2712,13 +2808,14 @@ std::string wallet2::get_multisig_info() const // It's a signed package of private view key and public spend key const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + crypto::hash hash; std::string data; - data += std::string((const char *)&skey, sizeof(crypto::secret_key)); + crypto::cn_fast_hash(&skey, sizeof(crypto::secret_key), hash); + data += std::string((const char *)&hash, sizeof(crypto::hash)); data += std::string((const char *)&pkey, sizeof(crypto::public_key)); data.resize(data.size() + sizeof(crypto::signature)); - crypto::hash hash; crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); @@ -2775,6 +2872,16 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const return true; } +bool wallet2::has_multisig_partial_key_images() const +{ + if (!m_multisig) + return false; + for (const auto &td: m_transfers) + if (td.m_key_image_partial) + return true; + return false; +} + /*! * \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) @@ -3355,7 +3462,7 @@ void wallet2::rescan_spent() { transfer_details& td = m_transfers[i]; // a view wallet may not know about key images - if (!td.m_key_image_known) + if (!td.m_key_image_known || td.m_key_image_partial) continue; if (td.m_spent != (spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { @@ -3695,23 +3802,6 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const return payment_id; } -crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const -{ - crypto::hash8 payment_id8 = null_hash8; - std::vector tx_extra_fields; - if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return payment_id8; - cryptonote::tx_extra_nonce extra_nonce; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) - { - if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) - { - decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); - } - } - return payment_id8; -} - //---------------------------------------------------------------------------------------------------- // take a pending tx and actually send it to the daemon void wallet2::commit_tx(pending_tx& ptx) @@ -3779,6 +3869,10 @@ void wallet2::commit_tx(pending_tx& ptx) set_spent(idx, 0); } + // tx generated, get rid of used k values + for (size_t idx: ptx.selected_transfers) + m_transfers[idx].m_multisig_k = rct::zero(); + //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL @@ -3801,27 +3895,10 @@ bool wallet2::save_tx(const std::vector& ptx_vector, const std::stri unsigned_tx_set txs; for (auto &tx: ptx_vector) { - tx_construction_data construction_data = tx.construction_data; // Short payment id is encrypted with tx_key. // Since sign_tx() generates new tx_keys and encrypts the payment id, we need to save the decrypted payment ID - // Get decrypted payment id from pending_tx - crypto::hash8 payment_id = get_short_payment_id(tx); - if (payment_id != null_hash8) - { - // Remove encrypted - remove_field_from_tx_extra(construction_data.extra, typeid(cryptonote::tx_extra_nonce)); - // Add decrypted - std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - if (!add_extra_nonce_to_tx_extra(construction_data.extra, extra_nonce)) - { - LOG_ERROR("Failed to add decrypted payment id to tx extra"); - return false; - } - LOG_PRINT_L1("Decrypted payment ID: " << payment_id); - } // Save tx construction_data to unsigned_tx_set - txs.txes.push_back(construction_data); + txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx)); } txs.transfers = m_transfers; @@ -3942,7 +4019,8 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f bool bulletproof = sd.use_rct && !ptx.tx.rct_signatures.p.bulletproofs.empty(); crypto::secret_key tx_key; std::vector additional_tx_keys; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, bulletproof); + rct::multisig_out msout; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, bulletproof, m_multisig ? &msout : NULL); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -3978,6 +4056,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f ptx.selected_transfers = sd.selected_transfers; ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet ptx.dests = sd.dests; + ptx.msout = msout; ptx.construction_data = sd; txs.push_back(ptx); @@ -3987,7 +4066,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_txes.key_images.resize(m_transfers.size()); for (size_t i = 0; i < m_transfers.size(); ++i) { - if (!m_transfers[i].m_key_image_known) + if (!m_transfers[i].m_key_image_known || m_transfers[i].m_key_image_partial) LOG_PRINT_L0("WARNING: key image not known in signing wallet at index " << i); signed_txes.key_images[i] = m_transfers[i].m_key_image; } @@ -4113,11 +4192,12 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector& ptx_vector, const std::string &filename) +{ + multisig_tx_set txs; + txs.m_ptx = ptx_vector; + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + txs.m_signers.insert(hash); + return save_multisig_tx(txs, filename); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(filename, errcode)) + { + LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << filename); + return false; + } + const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX); + if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen)) + { + LOG_PRINT_L0("Bad magic from " << filename); + return false; + } + try + { + s = decrypt_with_view_secret_key(std::string(s, magiclen)); + } + catch (const std::exception &e) + { + LOG_PRINT_L0("Failed to decrypt " << filename << ": " << e.what()); + return 0; + } + try + { + std::istringstream iss(s); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + } + catch (...) + { + LOG_PRINT_L0("Failed to parse data from " << filename); + return false; + } + + // sanity checks + for (const auto &ptx: exported_txs.m_ptx) + { + CHECK_AND_ASSERT_MES(ptx.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched selected_transfers/vin sizes"); + for (size_t idx: ptx.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.selected_transfers.size() == ptx.tx.vin.size(), false, "Mismatched cd selected_transfers/vin sizes"); + for (size_t idx: ptx.construction_data.selected_transfers) + CHECK_AND_ASSERT_MES(idx < m_transfers.size(), false, "Transfer index out of range"); + CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes"); + } + + LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions"); + for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + + const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold; + if (is_signed) + { + for (const auto &ptx: exported_txs.m_ptx) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +{ + THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); + + txids.clear(); + + // sign the transactions + for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) + { + tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; + tools::wallet2::tx_construction_data &sd = ptx.construction_data; + LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) << + ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); + cryptonote::transaction tx; + rct::multisig_out msout = ptx.msout; + auto sources = sd.sources; + const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); + + THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), + error::wallet_internal_error, "Transaction prefix does not match data"); + + // Tests passed, sign + std::vector indices; + for (const auto &source: sources) + indices.push_back(source.real_output); + + rct::keyV k; + for (size_t idx: sd.selected_transfers) + k.push_back(get_multisig_k(idx)); + + THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, ptx.msout, rct::sk2rct(get_account().get_keys().m_spend_secret_key)), + error::wallet_internal_error, "Failed signing, transaction likely malformed"); + + const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; + if (is_last) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + } + txids.push_back(txid); + } + } + + // txes generated, get rid of used k values + for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) + for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) + m_transfers[idx].m_multisig_k = rct::zero(); + + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + exported_txs.m_signers.insert(hash); + + return save_multisig_tx(exported_txs, filename); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func) +{ + multisig_tx_set exported_txs; + if(!load_multisig_tx_from_file(filename, exported_txs)) + return false; + + crypto::hash hash; + cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(hash) != exported_txs.m_signers.end(), + error::wallet_internal_error, "Transaction already signed by this private key"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, + error::wallet_internal_error, "Transaction was signed by too many signers"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, + error::wallet_internal_error, "Transaction is already fully signed"); + + if (accept_func && !accept_func(exported_txs)) + { + LOG_PRINT_L1("Transactions rejected by callback"); + return false; + } + return sign_multisig_tx(exported_txs, filename, txids); +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const uint64_t old_multipliers[3] = {1, 2, 3}; @@ -4749,6 +5040,10 @@ void wallet2::transfer_selected(const std::vector additional_tx_keys; + rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - 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); + 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); LOG_PRINT_L2("constructed tx, r="< additional_tx_keys; + rct::multisig_out msout; LOG_PRINT_L2("constructing tx"); - 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, true, bulletproof); + auto sources_copy = sources; + 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, true, bulletproof, m_multisig ? &msout : NULL); LOG_PRINT_L2("constructed tx, r="< ins_order; + for (size_t n = 0; n < sources.size(); ++n) + { + for (size_t idx = 0; idx < sources_copy.size(); ++idx) + { + THROW_WALLET_EXCEPTION_IF((size_t)sources_copy[idx].real_output >= sources_copy[idx].outputs.size(), + error::wallet_internal_error, "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); + } + } + THROW_WALLET_EXCEPTION_IF(ins_order.size() != sources.size(), error::wallet_internal_error, "Failed to work out sources permutation"); + LOG_PRINT_L2("gathering key images"); std::string key_images; bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool @@ -4964,13 +5281,15 @@ void wallet2::transfer_selected_rct(std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; - if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) + if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -5665,7 +5984,7 @@ std::vector wallet2::create_transactions_2(std::vector>& x) { return x.first == index_minor; }; @@ -6041,7 +6360,7 @@ std::vector wallet2::create_transactions_all(uint64_t below for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (below == 0 || td.amount() < below) { @@ -6255,6 +6574,8 @@ std::vector wallet2::select_available_outputs(const std::functionm_spent) continue; + if (i->m_key_image_partial) + continue; if (!is_transfer_unlocked(*i)) continue; if (f(*i)) @@ -7235,7 +7556,7 @@ std::vector> wallet2::export_key bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && !td.m_key_image_partial && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -7350,6 +7671,7 @@ uint64_t wallet2::import_key_images(const std::vector(td.m_tx.vout[td.m_internal_output_index].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast(i)); @@ -7645,6 +7968,138 @@ size_t wallet2::import_outputs(const std::vector wallet2::export_multisig() +{ + std::vector info; + + info.resize(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + transfer_details &td = m_transfers[n]; + crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + cryptonote::keypair in_ephemeral; + crypto::key_image ki; + td.m_multisig_k = rct::skGen(); + const rct::multisig_kLRki kLRki = get_multisig_LRki(n, td.m_multisig_k); + const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + // we want to export the partial key image, not the full one, so we can't use td.m_key_image + bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, true); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, get_account().get_keys().m_spend_secret_key, ki); + info[n] = multisig_info({ki, kLRki.L, kLRki.R}); + } + + return info; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n) +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info"); + CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info"); + + MDEBUG("update_multisig_rescan_info: updating index " << n); + transfer_details &td = m_transfers[n]; + td.m_multisig_info.clear(); + for (const auto &pi: info) + { + CHECK_AND_ASSERT_THROW_MES(n < pi.size(), "Bad pi size"); + td.m_multisig_info.push_back(pi[n]); + } + m_key_images.erase(td.m_key_image); + td.m_key_image = rct::rct2ki(get_multisig_composite_LRki(n, rct::skGen()).ki); + td.m_key_image_known = true; + td.m_key_image_partial = false; + td.m_multisig_k = multisig_k[n]; + m_key_images[td.m_key_image] = n; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_multisig(std::vector> info) +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 == m_multisig_total, "Wrong number of multisig sources"); + + std::vector k; + k.reserve(m_transfers.size()); + for (const auto &td: m_transfers) + k.push_back(td.m_multisig_k); + + // how many outputs we're going to update + size_t n_outputs = m_transfers.size(); + for (const auto &pi: info) + if (pi.size() < n_outputs) + n_outputs = pi.size(); + + // trim data we don't have info for from all participants + for (auto &pi: info) + pi.resize(n_outputs); + + // first pass to determine where to detach the blockchain + for (size_t n = 0; n < n_outputs; ++n) + { + const transfer_details &td = m_transfers[n]; + if (!td.m_key_image_partial) + continue; + MINFO("Multisig info importing from block height " << td.m_block_height); + detach_blockchain(td.m_block_height); + break; + } + + MFATAL("import_multisig: updating from import"); + for (size_t n = 0; n < n_outputs && n < m_transfers.size(); ++n) + { + update_multisig_rescan_info(k, info, n); + } + + m_multisig_rescan_k = &k; + m_multisig_rescan_info = &info; + try + { + MFATAL("import_multisig: refreshing"); + refresh(); + } + catch (...) {} + m_multisig_rescan_info = NULL; + m_multisig_rescan_k = NULL; + + return n_outputs; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const { crypto::chacha8_key key; -- cgit v1.2.3 From 95a21a793d22d95868d9e12bda64ac8bed8ef93c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 30 Jun 2017 12:12:28 +0100 Subject: wallet2: allow empty wallet filename to avoid saving data Useful to speed tests up and avoid unnecessary leftover files --- src/wallet/wallet2.cpp | 86 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 29 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index af48e711e..a497f3242 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2594,9 +2594,12 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random); @@ -2618,18 +2621,23 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_refresh_from_block_height = height >= blocks_per_month ? height - blocks_per_month : 0; } - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); + return retval; } @@ -2678,9 +2686,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_viewkey(account_public_address, viewkey); m_account_public_address = account_public_address; @@ -2689,18 +2700,22 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_total = 0; - bool r = store_keys(m_keys_file, password, true); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, true); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!wallet_.empty()) + store(); } /*! @@ -2717,9 +2732,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& clear(); prepare_file_names(wallet_); - boost::system::error_code ignored_ec; - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); - THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } m_account.create_from_keys(account_public_address, spendkey, viewkey); m_account_public_address = account_public_address; @@ -2728,17 +2746,21 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig_threshold = 0; m_multisig_total = 0; - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); - store(); + if (!wallet_.empty()) + store(); } void wallet2::make_multisig(const epee::wipeable_string &password, @@ -2789,18 +2811,22 @@ void wallet2::make_multisig(const epee::wipeable_string &password, m_multisig_threshold = threshold; m_multisig_total = spend_keys.size() + 1; - bool r = store_keys(m_keys_file, password, false); - THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); - r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); - if(!r) MERROR("String with address text not saved"); + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); add_subaddress_account(tr("Primary account")); - store(); + if (!m_wallet_file.empty()) + store(); } std::string wallet2::get_multisig_info() const @@ -2889,6 +2915,8 @@ bool wallet2::has_multisig_partial_key_images() const */ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_string& password) { + if (wallet_name.empty()) + return; prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); -- cgit v1.2.3 From fff871a455a7829d2ccbceb04306365b30bd641e Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 30 Jun 2017 17:36:31 +0100 Subject: gen_multisig: generates multisig wallets if participants trust each other --- src/wallet/wallet2.cpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a497f3242..d57d538ea 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8108,7 +8108,6 @@ size_t wallet2::import_multisig(std::vector Date: Mon, 27 Nov 2017 20:09:04 +0000 Subject: multisig address generation RPC --- src/wallet/wallet2.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d57d538ea..ee5bd0441 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4234,7 +4234,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector& ptx_vector, const std::string &filename) +bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(txs); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::save_multisig_tx(const std::vector& ptx_vector) { multisig_tx_set txs; txs.m_ptx = ptx_vector; crypto::hash hash; cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); txs.m_signers.insert(hash); - return save_multisig_tx(txs, filename); + return save_multisig_tx(txs); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::save_multisig_tx(const std::vector& ptx_vector, const std::string &filename) +{ + std::string ciphertext = save_multisig_tx(ptx_vector); + if (ciphertext.empty()) + return false; + return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) -- cgit v1.2.3 From f4eda44ce35c6e1ab77566a462470deaae5376ec Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 13 Aug 2017 15:29:31 +0100 Subject: N-1/N multisig --- src/wallet/wallet2.cpp | 567 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 490 insertions(+), 77 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ee5bd0441..807248860 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1123,8 +1123,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote error::wallet_internal_error, "NULL m_multisig_rescan_k"); if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); - else - td.m_multisig_k = rct::skGen(); } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) @@ -1180,8 +1178,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote error::wallet_internal_error, "NULL m_multisig_rescan_k"); if (m_multisig_rescan_info && m_multisig_rescan_info->front().size() >= m_transfers.size()) update_multisig_rescan_info(*m_multisig_rescan_k, *m_multisig_rescan_info, m_transfers.size() - 1); - else - td.m_multisig_k = rct::skGen(); } THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); @@ -2252,6 +2248,7 @@ bool wallet2::clear() bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { std::string account_data; + std::string multisig_signers; cryptonote::account_base account = m_account; if (watch_only) @@ -2282,8 +2279,13 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_multisig_threshold); json.AddMember("multisig_threshold", value2, json.GetAllocator()); - value2.SetUint(m_multisig_total); - json.AddMember("multisig_total", value2, json.GetAllocator()); + if (m_multisig) + { + bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); + CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers"); + value.SetString(multisig_signers.c_str(), multisig_signers.length()); + json.AddMember("multisig_signers", value, json.GetAllocator()); + } value2.SetInt(m_always_confirm_transfers ? 1 :0); json.AddMember("always_confirm_transfers", value2, json.GetAllocator()); @@ -2398,7 +2400,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); m_always_confirm_transfers = false; m_print_ring_members = false; m_default_mixin = 0; @@ -2439,8 +2441,27 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_multisig = field_multisig; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_threshold, unsigned int, Uint, m_multisig, 0); m_multisig_threshold = field_multisig_threshold; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, multisig_total, unsigned int, Uint, m_multisig, 0); - m_multisig_total = field_multisig_total; + if (m_multisig) + { + if (!json.HasMember("multisig_signers")) + { + LOG_ERROR("Field multisig_signers not found in JSON"); + return false; + } + if (!json["multisig_signers"].IsString()) + { + LOG_ERROR("Field multisig_signers found in JSON, but not String"); + return false; + } + const char *field_multisig_signers = json["multisig_signers"].GetString(); + std::string multisig_signers = std::string(field_multisig_signers, field_multisig_signers + json["multisig_signers"].GetStringLength()); + r = ::serialization::parse_binary(multisig_signers, m_multisig_signers); + if (!r) + { + LOG_ERROR("Field multisig_signers found in JSON, but failed to parse"); + return false; + } + } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true); m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); @@ -2607,7 +2628,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); // -1 month for fluctuations in block time and machine date/time setup. // avg seconds per block @@ -2698,7 +2719,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_watch_only = true; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); if (!wallet_.empty()) { @@ -2744,7 +2765,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_watch_only = false; m_multisig = false; m_multisig_threshold = 0; - m_multisig_total = 0; + m_multisig_signers.clear(); if (!wallet_.empty()) { @@ -2763,7 +2784,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& store(); } -void wallet2::make_multisig(const epee::wipeable_string &password, +std::string wallet2::make_multisig(const epee::wipeable_string &password, const std::vector &view_keys, const std::vector &spend_keys, uint32_t threshold) @@ -2771,45 +2792,92 @@ void wallet2::make_multisig(const epee::wipeable_string &password, CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - CHECK_AND_ASSERT_THROW_MES(/*threshold == spend_keys.size() ||*/ threshold == spend_keys.size() + 1, "Unsupported threshold case"); + CHECK_AND_ASSERT_THROW_MES(threshold == spend_keys.size() || threshold == spend_keys.size() + 1, "Unsupported threshold case"); + + std::string extra_multisig_info; + crypto::hash hash; clear(); MINFO("Creating spend key..."); - rct::key spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); + std::vector multisig_keys; + rct::key spend_pkey, spend_skey; if (threshold == spend_keys.size() + 1) { // the multisig spend public key is the sum of all spend public keys + spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); for (const auto &k: spend_keys) rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); + multisig_keys.push_back(get_account().get_keys().m_spend_secret_key); + spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); } - else + else if (threshold == spend_keys.size()) { - // the multisig spend public key is the sum of keys derived from all spend public keys - const rct::key spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); // WRONG + spend_pkey = rct::identity(); + spend_skey = rct::zero(); + + // create all our composite private keys for (const auto &k: spend_keys) { - rct::addKeys(spend_pkey, spend_pkey, rct::scalarmultBase(rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(k), spend_skey)))); + rct::keyV data; + data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(get_account().get_keys().m_spend_secret_key))); + static const rct::key 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 } }; + data.push_back(salt); + rct::key msk = rct::hash_to_scalar(data); + multisig_keys.push_back(rct::rct2sk(msk)); + sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); } + + // We need an extra step, so we package all the composite public keys + // we know about, and make a signed string out of them + std::string data; + const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + data += std::string((const char *)&pkey, sizeof(crypto::public_key)); + const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); + data += std::string((const char *)&signer, sizeof(crypto::public_key)); + + for (const auto &msk: multisig_keys) + { + rct::key pmsk = rct::scalarmultBase(rct::sk2rct(msk)); + data += std::string((const char *)&pmsk, sizeof(crypto::public_key)); + } + + data.resize(data.size() + sizeof(crypto::signature)); + crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); + crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + + extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Unsupported threshold case"); } // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - crypto::hash hash; crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); rct::key view_skey = rct::hash2rct(hash); for (const auto &k: view_keys) sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2pk(spend_pkey)), + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), "Failed to create multisig wallet due to bad keys"); m_account_public_address = m_account.get_keys().m_account_address; m_watch_only = false; m_multisig = true; m_multisig_threshold = threshold; - m_multisig_total = spend_keys.size() + 1; + if (threshold == spend_keys.size() + 1) + { + m_multisig_signers = spend_keys; + m_multisig_signers.push_back(get_multisig_signer_public_key()); + } + else + { + m_multisig_signers = std::vector(spend_keys.size() + 1, crypto::null_pkey); + } if (!m_wallet_file.empty()) { @@ -2827,6 +2895,65 @@ void wallet2::make_multisig(const epee::wipeable_string &password, if (!m_wallet_file.empty()) store(); + + return extra_multisig_info; +} + +bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set pkeys, std::vector signers) +{ + CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); + + // add ours if not included + const crypto::public_key local_signer = get_multisig_signer_public_key(); + if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) + { + signers.push_back(local_signer); + for (const auto &msk: get_account().get_multisig_keys()) + { + pkeys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + } + } + + CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + + rct::key spend_public_key = rct::identity(); + for (const auto &pk: pkeys) + { + rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); + } + m_multisig_signers = signers; + std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); + m_account_public_address.m_spend_public_key = rct::rct2pk(spend_public_key); + m_account.finalize_multisig(m_account_public_address.m_spend_public_key); + + if (!m_wallet_file.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + m_subaddresses.clear(); + m_subaddresses_inv.clear(); + m_subaddress_labels.clear(); + add_subaddress_account(tr("Primary account")); + + if (!m_wallet_file.empty()) + store(); + + return true; +} + +bool wallet2::wallet_generate_key_image_helper_export(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, size_t multisig_key_index) const +{ + THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); + if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + return false; + // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig + crypto::generate_key_image(in_ephemeral.pub, ack.m_multisig_keys[multisig_key_index], ki); + return true; } std::string wallet2::get_multisig_info() const @@ -2887,6 +3014,57 @@ bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key & return true; } +bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set &pkeys, crypto::public_key &signer) +{ + const size_t header_len = strlen("MultisigxV1"); + if (data.size() < header_len || data.substr(0, header_len) != "MultisigxV1") + { + MERROR("Multisig info header check error"); + return false; + } + std::string decoded; + if (!tools::base58::decode(data.substr(header_len), decoded)) + { + MERROR("Multisig info decoding error"); + return false; + } + if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + { + MERROR("Multisig info is corrupt"); + return false; + } + if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) + { + MERROR("Multisig info is corrupt"); + return false; + } + + const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); + size_t offset = 0; + const crypto::public_key &pkey = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(pkey); + signer = *(const crypto::public_key*)(decoded.data() + offset); + offset += sizeof(signer); + const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); + + crypto::hash hash; + crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MERROR("Multisig info signature is invalid"); + return false; + } + + for (size_t n = 0; n < n_keys; ++n) + { + crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); + pkeys.insert(mspk); + offset += sizeof(mspk); + } + + return true; +} + bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const { if (!m_multisig) @@ -2894,7 +3072,7 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const if (threshold) *threshold = m_multisig_threshold; if (total) - *total = m_multisig_total; + *total = m_multisig_signers.size(); return true; } @@ -3899,7 +4077,7 @@ void wallet2::commit_tx(pending_tx& ptx) // tx generated, get rid of used k values for (size_t idx: ptx.selected_transfers) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL @@ -4084,7 +4262,6 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f ptx.selected_transfers = sd.selected_transfers; ptx.tx_key = rct::rct2sk(rct::identity()); // don't send it back to the untrusted view wallet ptx.dests = sd.dests; - ptx.msout = msout; ptx.construction_data = sd; txs.push_back(ptx); @@ -4241,7 +4418,7 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // txes generated, get rid of used k values for (size_t n = 0; n < txs.m_ptx.size(); ++n) for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); // zero out some data we don't want to share for (auto &ptx: txs.m_ptx) @@ -4284,9 +4461,15 @@ std::string wallet2::save_multisig_tx(const std::vector& ptx_vector) { multisig_tx_set txs; txs.m_ptx = ptx_vector; - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - txs.m_signers.insert(hash); + + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pkey = get_multisig_signing_public_key(msk); + for (auto &ptx: txs.m_ptx) for (auto &sig: ptx.multisig_sigs) sig.signing_keys.insert(pkey); + } + + txs.m_signers.insert(get_multisig_signer_public_key()); + return save_multisig_tx(txs); } //---------------------------------------------------------------------------------------------------- @@ -4378,21 +4561,24 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) { THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); + const crypto::public_key local_signer = get_multisig_signer_public_key(); + txids.clear(); // sign the transactions for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) { tools::wallet2::pending_tx &ptx = exported_txs.m_ptx[n]; + THROW_WALLET_EXCEPTION_IF(ptx.multisig_sigs.empty(), error::wallet_internal_error, "No signatures found in multisig tx"); tools::wallet2::tx_construction_data &sd = ptx.construction_data; LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, mixin " << (sd.sources[0].outputs.size()-1) << ", signed by " << exported_txs.m_signers.size() << "/" << m_multisig_threshold); cryptonote::transaction tx; - rct::multisig_out msout = ptx.msout; + rct::multisig_out msout = ptx.multisig_sigs.front().msout; auto sources = sd.sources; const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout); @@ -4406,16 +4592,51 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string for (const auto &source: sources) indices.push_back(source.real_output); - rct::keyV k; - for (size_t idx: sd.selected_transfers) - k.push_back(get_multisig_k(idx)); + for (auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer) + { + ptx.tx.rct_signatures = sig.sigs; + + rct::keyV k; + for (size_t idx: sd.selected_transfers) + k.push_back(get_multisig_k(idx, sig.used_L)); - THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, ptx.msout, rct::sk2rct(get_account().get_keys().m_spend_secret_key)), - error::wallet_internal_error, "Failed signing, transaction likely malformed"); + rct::key skey = rct::zero(); + for (const auto &msk: get_account().get_multisig_keys()) + { + crypto::public_key pmsk = get_multisig_signing_public_key(msk); + + if (sig.signing_keys.find(pmsk) == sig.signing_keys.end()) + { + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + sig.signing_keys.insert(pmsk); + } + } + THROW_WALLET_EXCEPTION_IF(!rct::signMultisig(ptx.tx.rct_signatures, indices, k, sig.msout, skey), + error::wallet_internal_error, "Failed signing, transaction likely malformed"); + + sig.sigs = ptx.tx.rct_signatures; + } + } const bool is_last = exported_txs.m_signers.size() + 1 >= m_multisig_threshold; if (is_last) { + // when the last signature on a multisig tx is made, we select the right + // signature to plug into the final tx + bool found = false; + for (const auto &sig: ptx.multisig_sigs) + { + if (sig.ignore != local_signer && exported_txs.m_signers.find(sig.ignore) == exported_txs.m_signers.end()) + { + THROW_WALLET_EXCEPTION_IF(found, error::wallet_internal_error, "More than one transaction is final"); + ptx.tx.rct_signatures = sig.sigs; + found = true; + } + } + THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, + "Final signed transaction not found: this transaction was likely made without our export data, so we cannot sign it"); const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { @@ -4429,12 +4650,18 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, const std::string // txes generated, get rid of used k values for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers) - m_transfers[idx].m_multisig_k = rct::zero(); + m_transfers[idx].m_multisig_k.clear(); - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - exported_txs.m_signers.insert(hash); + exported_txs.m_signers.insert(get_multisig_signer_public_key()); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sign_multisig_tx_from_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) +{ + bool r = sign_multisig_tx(exported_txs, txids); + if (!r) + return false; return save_multisig_tx(exported_txs, filename); } //---------------------------------------------------------------------------------------------------- @@ -4444,9 +4671,8 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto if(!load_multisig_tx_from_file(filename, exported_txs)) return false; - crypto::hash hash; - cn_fast_hash(&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(hash) != exported_txs.m_signers.end(), + const crypto::public_key signer = get_multisig_signer_public_key(); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(signer) != exported_txs.m_signers.end(), error::wallet_internal_error, "Transaction already signed by this private key"); THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, error::wallet_internal_error, "Transaction was signed by too many signers"); @@ -4458,7 +4684,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto LOG_PRINT_L1("Transactions rejected by callback"); return false; } - return sign_multisig_tx(exported_txs, filename, txids); + return sign_multisig_tx_from_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) @@ -5012,6 +5238,8 @@ void wallet2::transfer_selected(const std::vector multisig_signers; + size_t n_multisig_txes = 0; + if (m_multisig && !m_transfers.empty()) + { + const crypto::public_key local_signer = get_multisig_signer_public_key(); + size_t n_available_signers = 1; + for (const crypto::public_key &signer: m_multisig_signers) + { + if (signer == local_signer) + continue; + multisig_signers.push_front(signer); + for (const auto &i: m_transfers[0].m_multisig_info) + { + if (i.m_signer == signer) + { + multisig_signers.pop_front(); + multisig_signers.push_back(signer); + ++n_available_signers; + break; + } + } + } + multisig_signers.push_back(local_signer); + MDEBUG("We can use " << n_available_signers << "/" << m_multisig_signers.size() << " other signers"); + THROW_WALLET_EXCEPTION_IF(n_available_signers+1 < m_multisig_threshold, error::multisig_import_needed); + n_multisig_txes = n_available_signers == m_multisig_signers.size() ? m_multisig_threshold : 1; + MDEBUG("We will create " << n_multisig_txes << " txes"); + } + uint64_t found_money = 0; for(size_t idx: selected_transfers) { @@ -5207,6 +5461,7 @@ void wallet2::transfer_selected_rct(std::vector sources; + std::unordered_set used_L; for(size_t idx: selected_transfers) { sources.resize(sources.size()+1); @@ -5249,7 +5504,10 @@ void wallet2::transfer_selected_rct(std::vector multisig_sigs; + if (m_multisig) + { + crypto::public_key ignore = m_multisig_threshold == m_multisig_signers.size() ? crypto::null_pkey : multisig_signers.front(); + multisig_sigs.push_back({tx.rct_signatures, ignore, used_L, {}, msout}); + + if (m_multisig_threshold < m_multisig_signers.size()) + { + const crypto::hash prefix_hash = cryptonote::get_transaction_prefix_hash(tx); + + // create the other versions, one for every other participant (the first one's already done above) + for (size_t signer_index = 1; signer_index < n_multisig_txes; ++signer_index) + { + std::unordered_set new_used_L; + size_t src_idx = 0; + THROW_WALLET_EXCEPTION_IF(selected_transfers.size() != sources.size(), error::wallet_internal_error, "mismatched selected_transfers and sources sixes"); + for(size_t idx: selected_transfers) + { + cryptonote::tx_source_entry& src = sources[src_idx]; + src.multisig_kLRki = get_multisig_composite_kLRki(idx, multisig_signers[signer_index], used_L, new_used_L); + ++src_idx; + } + + LOG_PRINT_L2("Creating supplementary multisig transaction"); + cryptonote::transaction ms_tx; + auto sources_copy_copy = sources_copy; + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, &msout); + LOG_PRINT_L2("constructed tx, r="< bool @@ -5329,8 +5626,8 @@ void wallet2::transfer_selected_rct(std::vector &used_L) const +{ + CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "idx out of range"); + for (const auto &k: m_transfers[idx].m_multisig_k) + { + rct::key L; + rct::scalarmultBase(L, k); + if (used_L.find(L) != used_L.end()) + return k; + } + THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed); + return rct::zero(); } //---------------------------------------------------------------------------------------------------- -rct::multisig_kLRki wallet2::get_multisig_LRki(size_t n, const rct::key &k) const +rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const { + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); rct::multisig_kLRki kLRki; kLRki.k = k; rct::scalarmultBase(kLRki.L, kLRki.k); @@ -8030,27 +8363,77 @@ rct::multisig_kLRki wallet2::get_multisig_LRki(size_t n, const rct::key &k) cons return kLRki; } //---------------------------------------------------------------------------------------------------- -rct::multisig_kLRki wallet2::get_multisig_composite_LRki(size_t n, const rct::key &k) const +rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set &used_L, std::unordered_set &new_used_L) const { - rct::multisig_kLRki kLRki = get_multisig_LRki(n, k); + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index"); + + const transfer_details &td = m_transfers[n]; + rct::multisig_kLRki kLRki = get_multisig_kLRki(n, rct::skGen()); + + // pick a L/R pair from every other participant but one + size_t n_signers_used = 1; + for (const auto &p: m_transfers[n].m_multisig_info) + { + if (p.m_signer == ignore) + continue; + for (const auto &lr: p.m_LR) + { + if (used_L.find(lr.m_L) != used_L.end()) + continue; + used_L.insert(lr.m_L); + new_used_L.insert(lr.m_L); + rct::addKeys(kLRki.L, kLRki.L, lr.m_L); + rct::addKeys(kLRki.R, kLRki.R, lr.m_R); + ++n_signers_used; + break; + } + } + CHECK_AND_ASSERT_THROW_MES(n_signers_used >= m_multisig_threshold, "LR not found for enough participants"); + + return kLRki; +} +//---------------------------------------------------------------------------------------------------- +crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const +{ + CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); + const transfer_details &td = m_transfers[n]; crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); cryptonote::keypair in_ephemeral; - bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, (crypto::key_image&)kLRki.ki); + crypto::key_image ki; + bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + std::unordered_set used; + + // insert the ones we start from + for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) + { + crypto::key_image pki; + wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, pki, m); + used.insert(pki); + } + for (const auto &info: td.m_multisig_info) { - rct::addKeys(kLRki.ki, kLRki.ki, rct::ki2rct(info.m_partial_key_image)); - rct::addKeys(kLRki.L, kLRki.L, info.m_L); - rct::addKeys(kLRki.R, kLRki.R, info.m_R); + for (const auto &pki: info.m_partial_key_images) + { + // don't add duplicates again + if (used.find(pki) != used.end()) + continue; + used.insert(pki); + + rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); + } } - return kLRki; + return ki; } //---------------------------------------------------------------------------------------------------- std::vector wallet2::export_multisig() { std::vector info; + const crypto::public_key signer = get_multisig_signer_public_key(); + info.resize(m_transfers.size()); for (size_t n = 0; n < m_transfers.size(); ++n) { @@ -8058,21 +8441,33 @@ std::vector wallet2::export_multisig() crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); cryptonote::keypair in_ephemeral; crypto::key_image ki; - td.m_multisig_k = rct::skGen(); - const rct::multisig_kLRki kLRki = get_multisig_LRki(n, td.m_multisig_k); - const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); - // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = wallet_generate_key_image_helper_old(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, true); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, get_account().get_keys().m_spend_secret_key, ki); - info[n] = multisig_info({ki, kLRki.L, kLRki.R}); + td.m_multisig_k.clear(); + info[n].m_LR.clear(); + info[n].m_partial_key_images.clear(); + + for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) + { + // we want to export the partial key image, not the full one, so we can't use td.m_key_image + bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); + info[n].m_partial_key_images.push_back(ki); + } + + size_t nlr = m_multisig_threshold < m_multisig_signers.size() ? m_multisig_threshold - 1 : 1; + for (size_t m = 0; m < nlr; ++m) + { + td.m_multisig_k.push_back(rct::skGen()); + const rct::multisig_kLRki kLRki = get_multisig_kLRki(n, td.m_multisig_k.back()); + info[n].m_LR.push_back({kLRki.L, kLRki.R}); + } + + info[n].m_signer = signer; } return info; } //---------------------------------------------------------------------------------------------------- -void wallet2::update_multisig_rescan_info(const std::vector &multisig_k, const std::vector> &info, size_t n) +void wallet2::update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n) { CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad index in update_multisig_info"); CHECK_AND_ASSERT_THROW_MES(multisig_k.size() >= m_transfers.size(), "Mismatched sizes of multisig_k and info"); @@ -8086,7 +8481,7 @@ void wallet2::update_multisig_rescan_info(const std::vector &multisig_ td.m_multisig_info.push_back(pi[n]); } m_key_images.erase(td.m_key_image); - td.m_key_image = rct::rct2ki(get_multisig_composite_LRki(n, rct::skGen()).ki); + td.m_key_image = get_multisig_composite_key_image(n); td.m_key_image_known = true; td.m_key_image_partial = false; td.m_multisig_k = multisig_k[n]; @@ -8096,9 +8491,9 @@ void wallet2::update_multisig_rescan_info(const std::vector &multisig_ size_t wallet2::import_multisig(std::vector> info) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); - CHECK_AND_ASSERT_THROW_MES(info.size() + 1 == m_multisig_total, "Wrong number of multisig sources"); + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources"); - std::vector k; + std::vector> k; k.reserve(m_transfers.size()); for (const auto &td: m_transfers) k.push_back(td.m_multisig_k); @@ -8109,10 +8504,28 @@ size_t wallet2::import_multisig(std::vector &i0, const std::vector &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)); }); + } + // first pass to determine where to detach the blockchain for (size_t n = 0; n < n_outputs; ++n) { -- cgit v1.2.3 From 66e34e85b1ef3e49ea9290bd69cce2974840fc32 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 26 Sep 2017 23:16:25 +0100 Subject: add multisig core test and factor multisig building blocks --- src/wallet/wallet2.cpp | 120 +++++++++++++++---------------------------------- 1 file changed, 37 insertions(+), 83 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 807248860..2bba6f9e1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -46,6 +46,7 @@ using namespace epee; #include "rpc/core_rpc_server_commands_defs.h" #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "multisig/multisig.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -526,24 +527,9 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } -bool generate_key_image_helper_old(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) -{ - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); - - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); - - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); - return true; -} - bool wallet_generate_key_image_helper_old(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, bool multisig_export = false) { - if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) + if (!cryptonote::generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) return false; if (multisig_export) { @@ -909,6 +895,12 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- +bool wallet2::wallet_generate_key_image_helper_export(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, size_t multisig_key_index) const +{ + THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); + return cryptonote::generate_multisig_key_image(ack, tx_public_key, real_output_index, in_ephemeral, ki, multisig_key_index); +} +//---------------------------------------------------------------------------------------------------- void wallet2::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 &tx_money_got_in_outs, std::vector &outs) { bool r; @@ -2804,29 +2796,11 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, rct::key spend_pkey, spend_skey; if (threshold == spend_keys.size() + 1) { - // the multisig spend public key is the sum of all spend public keys - spend_pkey = rct::pk2rct(get_account().get_keys().m_account_address.m_spend_public_key); - for (const auto &k: spend_keys) - rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(get_account().get_keys().m_spend_secret_key); - spend_skey = rct::sk2rct(get_account().get_keys().m_spend_secret_key); + cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); } else if (threshold == spend_keys.size()) { - spend_pkey = rct::identity(); - spend_skey = rct::zero(); - - // create all our composite private keys - for (const auto &k: spend_keys) - { - rct::keyV data; - data.push_back(rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(get_account().get_keys().m_spend_secret_key))); - static const rct::key 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 } }; - data.push_back(salt); - rct::key msk = rct::hash_to_scalar(data); - multisig_keys.push_back(rct::rct2sk(msk)); - sc_add(spend_skey.bytes, spend_skey.bytes, msk.bytes); - } + cryptonote::generate_multisig_N1_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them @@ -2856,13 +2830,10 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // the multisig view key is shared by all, make one all can derive MINFO("Creating view key..."); - crypto::cn_fast_hash(&get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); - rct::key view_skey = rct::hash2rct(hash); - for (const auto &k: view_keys) - sc_add(view_skey.bytes, view_skey.bytes, rct::sk2rct(k).bytes); + crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(rct::rct2sk(view_skey), rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), "Failed to create multisig wallet due to bad keys"); m_account_public_address = m_account.get_keys().m_account_address; @@ -2916,15 +2887,12 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); - rct::key spend_public_key = rct::identity(); - for (const auto &pk: pkeys) - { - rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); - } + crypto::public_key spend_public_key = cryptonote::generate_multisig_N1_N_spend_public_key(std::vector(pkeys.begin(), pkeys.end())); + m_account_public_address.m_spend_public_key = spend_public_key; + m_account.finalize_multisig(spend_public_key); + m_multisig_signers = signers; std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); }); - m_account_public_address.m_spend_public_key = rct::rct2pk(spend_public_key); - m_account.finalize_multisig(m_account_public_address.m_spend_public_key); if (!m_wallet_file.empty()) { @@ -2946,14 +2914,20 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor return true; } -bool wallet2::wallet_generate_key_image_helper_export(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, size_t multisig_key_index) const +bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector &info) { - THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); - if (!generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, ack.m_multisig_keys[multisig_key_index], ki); - return true; + // parse all multisig info + std::unordered_set public_keys; + std::vector signers(info.size(), crypto::null_pkey); + for (size_t i = 0; i < info.size(); ++i) + { + if (!verify_extra_multisig_info(info[i], public_keys, signers[i])) + { + MERROR("Bad multisig info"); + return false; + } + } + return finalize_multisig(password, public_keys, signers); } std::string wallet2::get_multisig_info() const @@ -4657,7 +4631,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) +bool wallet2::sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids) { bool r = sign_multisig_tx(exported_txs, txids); if (!r) @@ -4684,7 +4658,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto LOG_PRINT_L1("Transactions rejected by callback"); return false; } - return sign_multisig_tx_from_file(exported_txs, filename, txids); + return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) @@ -5591,7 +5565,7 @@ void wallet2::transfer_selected_rct(std::vector used; - - // insert the ones we start from - for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) - { - crypto::key_image pki; - wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, pki, m); - used.insert(pki); - } - + std::vector pkis; for (const auto &info: td.m_multisig_info) - { for (const auto &pki: info.m_partial_key_images) - { - // don't add duplicates again - if (used.find(pki) != used.end()) - continue; - used.insert(pki); - - rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); - } - } + pkis.push_back(pki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), tx_key, td.m_internal_output_index, pkis, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } //---------------------------------------------------------------------------------------------------- -- cgit v1.2.3 From 265290388bd2134108d689818518f7d9c830292c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 1 Oct 2017 14:06:54 +0100 Subject: wallet: guard against partly initialized multisig wallet --- src/wallet/wallet2.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2bba6f9e1..9c2587f25 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3039,7 +3039,7 @@ bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered return true; } -bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const +bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const { if (!m_multisig) return false; @@ -3047,6 +3047,8 @@ bool wallet2::multisig(uint32_t *threshold, uint32_t *total) const *threshold = m_multisig_threshold; if (total) *total = m_multisig_signers.size(); + if (ready) + *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); return true; } -- cgit v1.2.3 From fa5697127f5a99ddd20311cec8180f6a89b31ceb Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 21 Oct 2017 12:14:31 +0100 Subject: make multisig work with subaddresses Thanks to kenshi84 for help getting this work --- src/wallet/wallet2.cpp | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9c2587f25..4ad6d852d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -527,18 +527,6 @@ uint8_t get_bulletproof_fork(bool testnet) return 255; // TODO } -bool wallet_generate_key_image_helper_old(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, bool multisig_export = false) -{ - if (!cryptonote::generate_key_image_helper_old(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - if (multisig_export) - { - // we got the ephemeral keypair, but the key image isn't right as it's done as per our private spend key, which is multisig - crypto::generate_key_image(in_ephemeral.pub, ack.m_spend_secret_key, ki); - } - return true; -} - crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { crypto::hash8 payment_id8 = null_hash8; @@ -895,26 +883,22 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & } } //---------------------------------------------------------------------------------------------------- -bool wallet2::wallet_generate_key_image_helper_export(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, size_t multisig_key_index) const -{ - THROW_WALLET_EXCEPTION_IF(multisig_key_index >= ack.m_multisig_keys.size(), error::wallet_internal_error, "Bad multisig_key_index"); - return cryptonote::generate_multisig_key_image(ack, tx_public_key, real_output_index, in_ephemeral, ki, multisig_key_index); -} -//---------------------------------------------------------------------------------------------------- void wallet2::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 &tx_money_got_in_outs, std::vector &outs) { - bool r; + THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); if (m_multisig) { - r = wallet_generate_key_image_helper_old(keys, tx_pub_key, i, tx_scan_info.in_ephemeral, tx_scan_info.ki); + tx_scan_info.in_ephemeral.pub = boost::get(tx.vout[i].target).key; + tx_scan_info.in_ephemeral.sec = crypto::null_skey; + tx_scan_info.ki = rct::rct2ki(rct::zero()); } else { - r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); } - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.pub != boost::get(tx.vout[i].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (tx_scan_info.money_transfered == 0) @@ -8373,13 +8357,14 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad output index"); const transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); + const std::vector additional_tx_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; std::vector pkis; for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), tx_key, td.m_internal_output_index, pkis, ki); + bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -8394,8 +8379,7 @@ std::vector wallet2::export_multisig() for (size_t n = 0; n < m_transfers.size(); ++n) { transfer_details &td = m_transfers[n]; - crypto::public_key tx_key = get_tx_pub_key_from_received_outs(td); - cryptonote::keypair in_ephemeral; + const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); crypto::key_image ki; td.m_multisig_k.clear(); info[n].m_LR.clear(); @@ -8404,7 +8388,7 @@ std::vector wallet2::export_multisig() for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = wallet_generate_key_image_helper_export(get_account().get_keys(), tx_key, td.m_internal_output_index, in_ephemeral, ki, m); + bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } -- cgit v1.2.3 From a36c261d7adc9bef61730a581fe92c0edc72ddce Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 21 Oct 2017 18:31:30 +0100 Subject: wallet2: fix slow multisig unit tests with subaddress patch While there, move the wallet2 ctor to the cpp file as it's a huge amount of init list now, and remove an unused one. --- src/wallet/wallet2.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4ad6d852d..013ad6a75 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -573,6 +573,41 @@ const size_t MAX_SPLIT_ATTEMPTS = 30; constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } +wallet2::wallet2(bool testnet, bool restricted): + m_multisig_rescan_info(NULL), + m_multisig_rescan_k(NULL), + 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(RefreshOptimizeCoinbase), + 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_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), + m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), + 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) +{ +} + bool wallet2::has_testnet_option(const boost::program_options::variables_map& vm) { return command_line::get_arg(vm, options().testnet); @@ -764,9 +799,9 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new accounts cryptonote::subaddress_index index2; - for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major) + for (index2.major = m_subaddress_labels.size(); index2.major < index.major + m_subaddress_lookahead_major; ++index2.major) { - for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -783,7 +818,7 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) { // add new subaddresses cryptonote::subaddress_index index2 = index; - for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + m_subaddress_lookahead_minor; ++index2.minor) { if (m_subaddresses_inv.count(index2) == 0) { @@ -813,6 +848,12 @@ void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, co m_subaddress_labels[index.major][index.minor] = label; } //---------------------------------------------------------------------------------------------------- +void wallet2::set_subaddress_lookahead(size_t major, size_t minor) +{ + m_subaddress_lookahead_major = major; + m_subaddress_lookahead_minor = minor; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Tells if the wallet file is deprecated. */ -- cgit v1.2.3 From e36f5b6021eb541d72fee4b2d5643ba42fd4d9dd Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 4 Nov 2017 10:39:17 +0000 Subject: Match surae's recommendation to derive multisig keys --- src/wallet/wallet2.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 013ad6a75..29ca3dd2f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2830,8 +2830,6 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them std::string data; - const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; - data += std::string((const char *)&pkey, sizeof(crypto::public_key)); const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); data += std::string((const char *)&signer, sizeof(crypto::public_key)); @@ -2844,7 +2842,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + crypto::generate_signature(hash, signer, get_multisig_blinded_secret_key(rct::rct2sk(spend_skey)), signature); extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); } @@ -2958,19 +2956,18 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std std::string wallet2::get_multisig_info() const { // It's a signed package of private view key and public spend key - const crypto::secret_key &skey = get_account().get_keys().m_view_secret_key; - const crypto::public_key &pkey = get_account().get_keys().m_account_address.m_spend_public_key; + const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); crypto::hash hash; std::string data; - crypto::cn_fast_hash(&skey, sizeof(crypto::secret_key), hash); - data += std::string((const char *)&hash, sizeof(crypto::hash)); + data += std::string((const char *)&skey, sizeof(crypto::secret_key)); data += std::string((const char *)&pkey, sizeof(crypto::public_key)); data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_account().get_keys().m_spend_secret_key, signature); + crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); return std::string("MultisigV1") + tools::base58::encode(data); } @@ -3027,28 +3024,26 @@ bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered MERROR("Multisig info decoding error"); return false; } - if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) + if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) { MERROR("Multisig info is corrupt"); return false; } - if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) + if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) { MERROR("Multisig info is corrupt"); return false; } - const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); + const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); size_t offset = 0; - const crypto::public_key &pkey = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(pkey); signer = *(const crypto::public_key*)(decoded.data() + offset); offset += sizeof(signer); const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); crypto::hash hash; crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, pkey, signature)) + if (!crypto::check_signature(hash, signer, signature)) { MERROR("Multisig info signature is invalid"); return false; @@ -8313,13 +8308,19 @@ size_t wallet2::import_outputs(const std::vector Date: Mon, 27 Nov 2017 20:09:16 +0000 Subject: wallet: add multisig sign/submit RPC --- src/wallet/wallet2.cpp | 62 +++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 26 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 29ca3dd2f..07f986e02 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4477,25 +4477,12 @@ bool wallet2::save_multisig_tx(const std::vector& ptx_vector, const return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function accept_func) { - std::string s; - boost::system::error_code errcode; - - if (!boost::filesystem::exists(filename, errcode)) - { - LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); - return false; - } - if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) - { - LOG_PRINT_L0("Failed to load from " << filename); - return false; - } const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX); if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen)) { - LOG_PRINT_L0("Bad magic from " << filename); + LOG_PRINT_L0("Bad magic from multisig tx data"); return false; } try @@ -4504,8 +4491,8 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t } catch (const std::exception &e) { - LOG_PRINT_L0("Failed to decrypt " << filename << ": " << e.what()); - return 0; + LOG_PRINT_L0("Failed to decrypt multisig tx data: " << e.what()); + return false; } try { @@ -4515,7 +4502,7 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t } catch (...) { - LOG_PRINT_L0("Failed to parse data from " << filename); + LOG_PRINT_L0("Failed to parse multisig tx data"); return false; } @@ -4557,12 +4544,43 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return true; } //---------------------------------------------------------------------------------------------------- +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +{ + std::string s; + boost::system::error_code errcode; + + if (!boost::filesystem::exists(filename, errcode)) + { + LOG_PRINT_L0("File " << filename << " does not exist: " << errcode); + return false; + } + if (!epee::file_io_utils::load_file_to_string(filename.c_str(), s)) + { + LOG_PRINT_L0("Failed to load from " << filename); + return false; + } + + if (!load_multisig_tx(s, exported_txs, accept_func)) + { + LOG_PRINT_L0("Failed to parse multisig tx data from " << filename); + return false; + } + return true; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) { THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); const crypto::public_key local_signer = get_multisig_signer_public_key(); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(local_signer) != exported_txs.m_signers.end(), + error::wallet_internal_error, "Transaction already signed by this private key"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, + error::wallet_internal_error, "Transaction was signed by too many signers"); + THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, + error::wallet_internal_error, "Transaction is already fully signed"); + txids.clear(); // sign the transactions @@ -4667,14 +4685,6 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto if(!load_multisig_tx_from_file(filename, exported_txs)) return false; - const crypto::public_key signer = get_multisig_signer_public_key(); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.find(signer) != exported_txs.m_signers.end(), - error::wallet_internal_error, "Transaction already signed by this private key"); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() > m_multisig_threshold, - error::wallet_internal_error, "Transaction was signed by too many signers"); - THROW_WALLET_EXCEPTION_IF(exported_txs.m_signers.size() == m_multisig_threshold, - error::wallet_internal_error, "Transaction is already fully signed"); - if (accept_func && !accept_func(exported_txs)) { LOG_PRINT_L1("Transactions rejected by callback"); -- cgit v1.2.3 From 31a97e761e6b7570efb5e1b58fd744a0edcad953 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 13 Nov 2017 16:24:42 +0000 Subject: wallet: use raw encrypted data in multisig import/export RPC --- src/wallet/wallet2.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 07f986e02..635264d70 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -103,6 +103,8 @@ using namespace cryptonote; #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" +#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" + namespace { // Create on-demand to prevent static initialization order fiasco issues. @@ -8421,7 +8423,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const return ki; } //---------------------------------------------------------------------------------------------------- -std::vector wallet2::export_multisig() +cryptonote::blobdata wallet2::export_multisig() { std::vector info; @@ -8456,7 +8458,19 @@ std::vector wallet2::export_multisig() info[n].m_signer = signer; } - return info; + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << info; + + std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + std::string header; + header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + header += std::string((const char *)&signer, sizeof(crypto::public_key)); + std::string ciphertext = encrypt_with_view_secret_key(header + oss.str()); + + return MULTISIG_EXPORT_FILE_MAGIC + ciphertext; } //---------------------------------------------------------------------------------------------------- void wallet2::update_multisig_rescan_info(const std::vector> &multisig_k, const std::vector> &info, size_t n) @@ -8480,9 +8494,50 @@ void wallet2::update_multisig_rescan_info(const std::vector> info) +size_t wallet2::import_multisig(std::vector blobs) { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); + + std::vector> info; + std::unordered_set seen; + for (cryptonote::blobdata &data: blobs) + { + const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); + THROW_WALLET_EXCEPTION_IF(data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen), + error::wallet_internal_error, "Bad multisig info file magic in "); + + data = decrypt_with_view_secret_key(std::string(data, magiclen)); + + const size_t headerlen = 3 * sizeof(crypto::public_key); + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, "Bad data size"); + + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; + const crypto::public_key &signer = *(const crypto::public_key*)&data[2*sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; + THROW_WALLET_EXCEPTION_IF(public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key, + error::wallet_internal_error, "Multisig info is for a different account"); + if (get_multisig_signer_public_key() == signer) + { + MINFO("Multisig info from this wallet ignored"); + continue; + } + if (seen.find(signer) != seen.end()) + { + MINFO("Duplicate multisig info ignored"); + continue; + } + seen.insert(signer); + + std::string body(data, headerlen); + std::istringstream iss(body); + std::vector i; + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + MINFO(boost::format("%u outputs found") % boost::lexical_cast(i.size())); + info.push_back(std::move(i)); + } + CHECK_AND_ASSERT_THROW_MES(info.size() + 1 <= m_multisig_signers.size() && info.size() + 1 >= m_multisig_threshold, "Wrong number of multisig sources"); std::vector> k; -- cgit v1.2.3 From 98db7ee467fb4f73ce5e748d4891326b84a4c93a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 18 Nov 2017 11:24:38 +0000 Subject: wallet: factor multisig info parsing --- src/wallet/wallet2.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 635264d70..70180e080 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2895,6 +2895,60 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, return extra_multisig_info; } +std::string wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector &info, + uint32_t threshold) +{ + // parse all multisig info + std::vector secret_keys(info.size()); + std::vector public_keys(info.size()); + for (size_t i = 0; i < info.size(); ++i) + { + THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), + error::wallet_internal_error, "Bad multisig info: " + info[i]); + } + + // remove duplicates + for (size_t i = 0; i < secret_keys.size(); ++i) + { + for (size_t j = i + 1; j < secret_keys.size(); ++j) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + MDEBUG("Duplicate key found, ignoring"); + secret_keys[j] = secret_keys.back(); + public_keys[j] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --j; + } + } + } + + // people may include their own, weed it out + const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); + const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (secret_keys[i] == local_skey) + { + MDEBUG("Local key is present, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + else + { + THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, + "Found local spend public key, but not local view secret key - something very weird"); + } + } + + return make_multisig(password, secret_keys, public_keys, threshold); +} + bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unordered_set pkeys, std::vector signers) { CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); -- cgit v1.2.3 From ceabc4f92b8b3a5c3426e4257654608cdb54797a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 29 Nov 2017 16:55:50 +0000 Subject: change the N-1/N multisig second message signer for auth --- src/wallet/wallet2.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'src/wallet/wallet2.cpp') diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 70180e080..04c6ee236 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2832,7 +2832,8 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // We need an extra step, so we package all the composite public keys // we know about, and make a signed string out of them std::string data; - const crypto::public_key signer = get_multisig_signer_public_key(rct::rct2sk(spend_skey)); + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(rct::rct2sk(spend_skey), signer), "Failed to derive public spend key"); data += std::string((const char *)&signer, sizeof(crypto::public_key)); for (const auto &msk: multisig_keys) @@ -2844,7 +2845,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, data.resize(data.size() + sizeof(crypto::signature)); crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, signer, get_multisig_blinded_secret_key(rct::rct2sk(spend_skey)), signature); + crypto::generate_signature(hash, signer, rct::rct2sk(spend_skey), signature); extra_multisig_info = std::string("MultisigxV1") + tools::base58::encode(data); } @@ -2954,7 +2955,9 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys"); // add ours if not included - const crypto::public_key local_signer = get_multisig_signer_public_key(); + crypto::public_key local_signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer), + "Failed to derive public spend key"); if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) { signers.push_back(local_signer); @@ -8381,13 +8384,9 @@ crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_ crypto::public_key wallet2::get_multisig_signer_public_key() const { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); - if (m_multisig_threshold == m_multisig_signers.size()) - { - crypto::public_key signer; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key"); - return signer; - } - return get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); + crypto::public_key signer; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, signer), "Failed to generate signer public key"); + return signer; } //---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signing_public_key(const crypto::secret_key &msk) const -- cgit v1.2.3