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/simplewallet/simplewallet.cpp | 144 +++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 2 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index bbf794c05..4349b6b92 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -587,6 +587,133 @@ bool simple_wallet::print_fee_info(const std::vector &args/* = std: return true; } +bool simple_wallet::prepare_multisig(const std::vector &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your password is incorrect."); + return true; + } + + std::string multisig_info = m_wallet->get_multisig_info(); + success_msg_writer() << multisig_info; + success_msg_writer() << "Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"; + success_msg_writer() << "This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "; + return true; +} + +bool simple_wallet::make_multisig(const std::vector &args) +{ + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is already multisig"); + return true; + } + + if(m_wallet->get_num_transfer_details()) + { + fail_msg_writer() << tr("This wallet has been used before, please use a new wallet to create a multisig wallet"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: make_multisig [...]"); + return true; + } + + // parse threshold + uint32_t threshold; + if (!string_tools::get_xtype_from_string(threshold, args[0])) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + + // parse all multisig info + std::vector secret_keys(args.size() - 1); + std::vector public_keys(args.size() - 1); + for (size_t i = 1; i < args.size(); ++i) + { + if (!tools::wallet2::verify_multisig_info(args[i], secret_keys[i - 1], public_keys[i - 1])) + { + fail_msg_writer() << tr("Bad multisig info: ") << args[i]; + return true; + } + } + + // remove duplicates + for (size_t i = 1; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[0])) + { + message_writer() << tr("Duplicate key found, ignoring"); + secret_keys[i] = secret_keys.back(); + public_keys[i] = public_keys.back(); + secret_keys.pop_back(); + public_keys.pop_back(); + --i; + } + } + + // people may include their own, weed it out + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(m_wallet->get_account().get_keys().m_view_secret_key)) + { + message_writer() << tr("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 if (rct::pk2rct(public_keys[i]) == rct::pk2rct(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key)) + { + fail_msg_writer() << "Found local spend public key, but not local view secret key - something very weird"; + return true; + } + } + + LOCK_IDLE_SCOPE(); + + try + { + m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + } + catch (const std::exception &e) + { + fail_msg_writer() << "Error creating multisig: " << e.what(); + return true; + } + + uint32_t total = secret_keys.size() + 1; + success_msg_writer() << std::to_string(threshold) << "/" << total << " multisig address: " + << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1165,10 +1292,16 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("fee", boost::bind(&simple_wallet::print_fee_info, this, _1), tr("Print the information about the current fee and transaction backlog.")); + m_cmd_binder.set_handler("prepare_multisig", boost::bind(&simple_wallet::prepare_multisig, this, _1), + tr("Export data needed to create a multisig wallet")); + m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), + tr("make_multisig [...]"), + tr("Turn this wallet into a multisig wallet")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), tr("Show the help section or the documentation about a .")); + m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector &args) @@ -2098,9 +2231,16 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) return false; } + std::string prefix; + uint32_t threshold, total; + if (m_wallet->watch_only()) + prefix = tr("Opened watch-only wallet"); + else if (m_wallet->multisig(&threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet")) % threshold % total).str(); + else + prefix = tr("Opened wallet"); message_writer(console_color_white, true) << - (m_wallet->watch_only() ? tr("Opened watch-only wallet") : tr("Opened wallet")) << ": " - << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + prefix << ": " << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); // If the wallet file is deprecated, we should ask for mnemonic language again and store // everything in the new format. // NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere. -- 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/simplewallet/simplewallet.cpp | 650 +++++++++++++++++++++++++++++++------- 1 file changed, 533 insertions(+), 117 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 4349b6b92..539301f83 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -84,6 +84,7 @@ typedef cryptonote::simple_wallet sw; #define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" +#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ @@ -340,6 +341,106 @@ namespace } return true; } + + void handle_transfer_exception(const std::exception_ptr &e) + { + try + { + std::rethrow_exception(e); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error &e) + { + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); + } + catch (const tools::error::not_enough_unlocked_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::not_enough_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + for (std::pair outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::multisig_export_needed& e) + { + LOG_ERROR("Multisig error: " << e.to_string()); + fail_msg_writer() << tr("Multisig error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + } } bool parse_priority(const std::string& arg, uint32_t& priority) @@ -417,6 +518,11 @@ bool simple_wallet::print_seed(bool encrypted) bool success = false; std::string electrum_words; + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -468,6 +574,11 @@ bool simple_wallet::encrypted_seed(const std::vector &args/* = std: bool simple_wallet::seed_set_language(const std::vector &args/* = std::vector()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and has no seed"); + return true; + } if (m_wallet->watch_only()) { fail_msg_writer() << tr("wallet is watch-only and has no seed"); @@ -594,6 +705,11 @@ bool simple_wallet::prepare_multisig(const std::vector &args) fail_msg_writer() << tr("This wallet is already multisig"); return true; } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } if(m_wallet->get_num_transfer_details()) { @@ -610,8 +726,8 @@ bool simple_wallet::prepare_multisig(const std::vector &args) std::string multisig_info = m_wallet->get_multisig_info(); success_msg_writer() << multisig_info; - success_msg_writer() << "Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"; - success_msg_writer() << "This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "; + success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"); + success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); return true; } @@ -622,6 +738,11 @@ bool simple_wallet::make_multisig(const std::vector &args) fail_msg_writer() << tr("This wallet is already multisig"); return true; } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return true; + } if(m_wallet->get_num_transfer_details()) { @@ -663,23 +784,28 @@ bool simple_wallet::make_multisig(const std::vector &args) } // remove duplicates - for (size_t i = 1; i < secret_keys.size(); ++i) + for (size_t i = 0; i < secret_keys.size(); ++i) { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[0])) + for (size_t j = i + 1; j < secret_keys.size(); ++j) { - message_writer() << tr("Duplicate key found, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; + if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) + { + message_writer() << tr("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 = m_wallet->get_account().get_keys().m_view_secret_key; + const crypto::public_key local_pkey = m_wallet->get_account().get_keys().m_account_address.m_spend_public_key; for (size_t i = 0; i < secret_keys.size(); ++i) { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(m_wallet->get_account().get_keys().m_view_secret_key)) + if (secret_keys[i] == local_skey) { message_writer() << tr("Local key is present, ignoring"); secret_keys[i] = secret_keys.back(); @@ -688,9 +814,9 @@ bool simple_wallet::make_multisig(const std::vector &args) public_keys.pop_back(); --i; } - else if (rct::pk2rct(public_keys[i]) == rct::pk2rct(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key)) + else if (public_keys[i] == local_pkey) { - fail_msg_writer() << "Found local spend public key, but not local view secret key - something very weird"; + fail_msg_writer() << tr("Found local spend public key, but not local view secret key - something very weird"); return true; } } @@ -703,17 +829,315 @@ bool simple_wallet::make_multisig(const std::vector &args) } catch (const std::exception &e) { - fail_msg_writer() << "Error creating multisig: " << e.what(); + fail_msg_writer() << tr("Error creating multisig: ") << e.what(); return true; } uint32_t total = secret_keys.size() + 1; - success_msg_writer() << std::to_string(threshold) << "/" << total << " multisig address: " + success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); return true; } +bool simple_wallet::export_multisig(const std::vector &args) +{ + if (!m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_multisig_info "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + const std::string filename = args[0]; + try + { + std::vector outs = m_wallet->export_multisig(); + + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << outs; + + std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = m_wallet->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)); + crypto::hash hash; + cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); + header += std::string((const char *)&hash, sizeof(crypto::hash)); + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); + bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting multisig info: " << e.what()); + fail_msg_writer() << tr("Error exporting multisig info: ") << e.what(); + return true; + } + + success_msg_writer() << tr("Multisig info exported to ") << filename; + return true; +} + +bool simple_wallet::import_multisig(const std::vector &args) +{ + uint32_t threshold, total; + if (!m_wallet->multisig(&threshold, &total)) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + if (args.size() < threshold - 1) + { + fail_msg_writer() << tr("usage: import_multisig_info [...] - one for each other participant"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) + return true; + + std::vector> info; + std::unordered_set seen; + for (size_t n = 0; n < args.size(); ++n) + { + const std::string filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << tr("Bad multisig info file magic in ") << filename; + return true; + } + + try + { + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to decrypt ") << filename << ": " << e.what(); + return true; + } + + const size_t headerlen = 3 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + fail_msg_writer() << tr("Bad data size from file ") << filename; + return true; + } + 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::hash &hash = *(const crypto::hash*)&data[2*sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); + return true; + } + crypto::hash this_hash; + cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&this_hash); + if (this_hash == hash) + { + message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); + continue; + } + if (seen.find(hash) != seen.end()) + { + message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); + continue; + } + seen.insert(hash); + + try + { + std::string body(data, headerlen); + std::istringstream iss(body); + std::vector i; + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + message_writer() << (boost::format(tr("%u outputs found in %s")) % boost::lexical_cast(i.size()) % filename).str(); + info.push_back(std::move(i)); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); + return true; + } + } + + LOCK_IDLE_SCOPE(); + + // all read and parsed, actually import + try + { + size_t n_outputs = m_wallet->import_multisig(info); + // Clear line "Height xxx of xxx" + std::cout << "\r \r"; + success_msg_writer() << tr("Multisig info imported"); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); + return true; + } + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + message_writer() << tr("Failed to update spent status after importing multisig info: ") << e.what(); + } + } + else + { + message_writer() << tr("Untrusted daemon, spent status may be incorrect. Use a trusted daemon and run \"rescan_spent\""); + } + return true; +} + +bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) +{ + std::string extra_message; + return accept_loaded_tx([&txs](){return txs.m_ptx.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.m_ptx[n].construction_data;}, extra_message); +} + +bool simple_wallet::sign_multisig(const std::vector &args) +{ + if(!m_wallet->multisig()) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: sign_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + std::vector txids; + uint32_t signers = 0; + try + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } + } + catch (const tools::error::multisig_export_needed& e) + { + fail_msg_writer() << tr("Multisig error: ") << e.what(); + return true; + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to sign multisig transaction: ") << e.what(); + return true; + } + + if (txids.empty()) + { + uint32_t threshold; + m_wallet->multisig(&threshold); + uint32_t signers_needed = threshold - signers - 1; + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " + << signers_needed << " more signer(s) needed"; + return true; + } + else + { + std::string txids_as_text; + for (const auto &txid: txids) + { + if (!txids_as_text.empty()) + txids_as_text += (", "); + txids_as_text += epee::string_tools::pod_to_hex(txid); + } + success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", txid " << txids_as_text; + success_msg_writer(true) << tr("It may be relayed to the network with submit_multisig"); + } + return true; +} + +bool simple_wallet::submit_multisig(const std::vector &args) +{ + uint32_t threshold; + if (!m_wallet->multisig(&threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: submit_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + if (!try_connect_to_daemon()) + return true; + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // actually commit the transactions + for (auto &ptx: txs.m_ptx) + { + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL + << tr("You can check its status by using the `show_transfers` command."); + } + } + catch (const std::exception &e) + { + handle_transfer_exception(std::current_exception()); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1297,11 +1721,26 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), tr("make_multisig [...]"), tr("Turn this wallet into a multisig wallet")); + m_cmd_binder.set_handler("export_multisig_info", + boost::bind(&simple_wallet::export_multisig, this, _1), + tr("export_multisig "), + tr("Export multisig info for other participants")); + m_cmd_binder.set_handler("import_multisig_info", + boost::bind(&simple_wallet::import_multisig, this, _1), + tr("import_multisig [...]"), + tr("Import multisig info from other participants")); + m_cmd_binder.set_handler("sign_multisig", + boost::bind(&simple_wallet::sign_multisig, this, _1), + tr("sign_multisig "), + tr("Sign a multisig transaction from a file")); + m_cmd_binder.set_handler("submit_multisig", + boost::bind(&simple_wallet::submit_multisig, this, _1), + tr("submit_multisig "), + tr("Submit a signed multisig transaction from a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), tr("Show the help section or the documentation about a .")); - m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_variable(const std::vector &args) @@ -2345,6 +2784,12 @@ bool simple_wallet::save(const std::vector &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::save_watch_only(const std::vector &args/* = std::vector()*/) { + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("wallet is multisig and cannot save a watch-only version"); + return true; + } + const auto pwd_container = tools::password_container::prompt(true, tr("Password for new watch-only wallet")); if (!pwd_container) @@ -2638,9 +3083,12 @@ bool simple_wallet::refresh(const std::vector& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance_unlocked(bool detailed) { + std::string extra; + if (m_wallet->has_multisig_partial_key_images()) + extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)); + << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; std::map balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); std::map unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); if (!detailed || balance_per_subaddress.empty()) @@ -2734,7 +3182,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args } std::string verbose_string; if (verbose) - verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); + verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str(); message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % print_money(td.amount()) % @@ -2998,101 +3446,6 @@ bool simple_wallet::print_ring_members(const std::vector outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } - catch (const std::exception& e) - { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); - } -} -//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector &args_) { // "transfer [index=[,,...]] [] []
[]" @@ -3419,7 +3772,19 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vectorwatch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3515,7 +3880,19 @@ bool simple_wallet::sweep_unmixable(const std::vector &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3734,7 +4111,19 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector &a } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -3925,7 +4314,19 @@ bool simple_wallet::sweep_single(const std::vector &args_) } // actually commit the transactions - if (m_wallet->watch_only()) + if (m_wallet->multisig()) + { + bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx"; + } + } + else if (m_wallet->watch_only()) { bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); if (!r) @@ -4014,6 +4415,11 @@ bool simple_wallet::sweep_single(const std::vector &args_) LOG_ERROR("unknown transfer error: " << e.to_string()); fail_msg_writer() << tr("unknown transfer error: ") << e.what(); } + catch (const tools::error::multisig_export_needed& e) + { + LOG_ERROR("Multisig error: " << e.to_string()); + fail_msg_writer() << tr("Multisig error: ") << e.what(); + } catch (const tools::error::wallet_internal_error& e) { LOG_ERROR("internal error: " << e.to_string()); @@ -4226,6 +4632,11 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::signed_tx_set &txs) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sign_transfer(const std::vector &args_) { + if(m_wallet->multisig()) + { + fail_msg_writer() << tr("This is a multisig wallet, it can only sign with sign_multisig"); + return true; + } if(m_wallet->watch_only()) { fail_msg_writer() << tr("This is a watch only wallet"); @@ -5519,6 +5930,11 @@ bool simple_wallet::sign(const std::vector &args) fail_msg_writer() << tr("wallet is watch-only and cannot sign"); return true; } + if (m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is multisig and cannot sign"); + return true; + } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } std::string filename = args[0]; std::string data; -- 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/simplewallet/simplewallet.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 539301f83..09dbb1373 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6396,6 +6396,7 @@ int main(int argc, char* argv[]) const auto vm = wallet_args::main( argc, argv, "monero-wallet-cli [--wallet-file=|--generate-new-wallet=] []", + sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly."), desc_params, positional_options, [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, -- 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/simplewallet/simplewallet.cpp | 81 +++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 11 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 09dbb1373..ab09ace91 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -825,7 +825,14 @@ bool simple_wallet::make_multisig(const std::vector &args) try { - m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + if (!multisig_extra_info.empty()) + { + success_msg_writer() << tr("Another step is needed"); + success_msg_writer() << multisig_extra_info; + success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig [...] with others' multisig info"); + return true; + } } catch (const std::exception &e) { @@ -840,6 +847,57 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } +bool simple_wallet::finalize_multisig(const std::vector &args) +{ + if (!m_wallet->multisig()) + { + fail_msg_writer() << tr("This wallet is not multisig"); + return true; + } + + const auto orig_pwd_container = get_and_verify_password(); + if(orig_pwd_container == boost::none) + { + fail_msg_writer() << tr("Your original password was incorrect."); + return true; + } + + if (args.size() < 2) + { + fail_msg_writer() << tr("usage: finalize_multisig [...]"); + return true; + } + + // parse all multisig info + std::unordered_set public_keys; + std::vector signers(args.size(), crypto::null_pkey); + for (size_t i = 0; i < args.size(); ++i) + { + if (!tools::wallet2::verify_extra_multisig_info(args[i], public_keys, signers[i])) + { + fail_msg_writer() << tr("Bad multisig info: ") << args[i]; + return true; + } + } + + // we have all pubkeys now + try + { + if (!m_wallet->finalize_multisig(orig_pwd_container->password(), public_keys, signers)) + { + fail_msg_writer() << tr("Failed to finalize multisig"); + return true; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); + return true; + } + + return true; +} + bool simple_wallet::export_multisig(const std::vector &args) { if (!m_wallet->multisig()) @@ -869,9 +927,8 @@ bool simple_wallet::export_multisig(const std::vector &args) 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)); - crypto::hash hash; - cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&hash); - header += std::string((const char *)&hash, sizeof(crypto::hash)); + crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); + header += std::string((const char *)&signer, sizeof(crypto::public_key)); std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); if (!r) @@ -908,7 +965,7 @@ bool simple_wallet::import_multisig(const std::vector &args) return true; std::vector> info; - std::unordered_set seen; + std::unordered_set seen; for (size_t n = 0; n < args.size(); ++n) { const std::string filename = args[n]; @@ -944,26 +1001,24 @@ bool simple_wallet::import_multisig(const std::vector &args) } 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::hash &hash = *(const crypto::hash*)&data[2*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 = m_wallet->get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); return true; } - crypto::hash this_hash; - cn_fast_hash(&m_wallet->get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (char*)&this_hash); - if (this_hash == hash) + if (m_wallet->get_multisig_signer_public_key() == signer) { message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); continue; } - if (seen.find(hash) != seen.end()) + if (seen.find(signer) != seen.end()) { message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); continue; } - seen.insert(hash); + seen.insert(signer); try { @@ -1721,6 +1776,10 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::make_multisig, this, _1), tr("make_multisig [...]"), tr("Turn this wallet into a multisig wallet")); + m_cmd_binder.set_handler("finalize_multisig", + boost::bind(&simple_wallet::finalize_multisig, this, _1), + tr("finalize_multisig [...]"), + tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("export_multisig_info", boost::bind(&simple_wallet::export_multisig, this, _1), tr("export_multisig "), -- 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/simplewallet/simplewallet.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ab09ace91..b0aec186c 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -868,22 +868,9 @@ bool simple_wallet::finalize_multisig(const std::vector &args) return true; } - // parse all multisig info - std::unordered_set public_keys; - std::vector signers(args.size(), crypto::null_pkey); - for (size_t i = 0; i < args.size(); ++i) - { - if (!tools::wallet2::verify_extra_multisig_info(args[i], public_keys, signers[i])) - { - fail_msg_writer() << tr("Bad multisig info: ") << args[i]; - return true; - } - } - - // we have all pubkeys now try { - if (!m_wallet->finalize_multisig(orig_pwd_container->password(), public_keys, signers)) + if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args)) { fail_msg_writer() << tr("Failed to finalize multisig"); return true; -- 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/simplewallet/simplewallet.cpp | 47 ++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b0aec186c..40226ec34 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -849,11 +849,17 @@ bool simple_wallet::make_multisig(const std::vector &args) bool simple_wallet::finalize_multisig(const std::vector &args) { - if (!m_wallet->multisig()) + bool ready; + if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (ready) + { + fail_msg_writer() << tr("This wallet is already finalized"); + return true; + } const auto orig_pwd_container = get_and_verify_password(); if(orig_pwd_container == boost::none) @@ -887,11 +893,17 @@ bool simple_wallet::finalize_multisig(const std::vector &args) bool simple_wallet::export_multisig(const std::vector &args) { - if (!m_wallet->multisig()) + bool ready; + if (!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: export_multisig_info "); @@ -937,12 +949,18 @@ bool simple_wallet::export_multisig(const std::vector &args) bool simple_wallet::import_multisig(const std::vector &args) { + bool ready; uint32_t threshold, total; - if (!m_wallet->multisig(&threshold, &total)) + if (!m_wallet->multisig(&ready, &threshold, &total)) { fail_msg_writer() << tr("This wallet is not multisig"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() < threshold - 1) { fail_msg_writer() << tr("usage: import_multisig_info [...] - one for each other participant"); @@ -1065,11 +1083,17 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector &args) { - if(!m_wallet->multisig()) + bool ready; + if(!m_wallet->multisig(&ready)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: sign_multisig "); @@ -1103,7 +1127,7 @@ bool simple_wallet::sign_multisig(const std::vector &args) if (txids.empty()) { uint32_t threshold; - m_wallet->multisig(&threshold); + m_wallet->multisig(NULL, &threshold); uint32_t signers_needed = threshold - signers - 1; success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " << signers_needed << " more signer(s) needed"; @@ -1126,12 +1150,18 @@ bool simple_wallet::sign_multisig(const std::vector &args) bool simple_wallet::submit_multisig(const std::vector &args) { + bool ready; uint32_t threshold; - if (!m_wallet->multisig(&threshold)) + if (!m_wallet->multisig(&ready, &threshold)) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } if (args.size() != 1) { fail_msg_writer() << tr("usage: submit_multisig "); @@ -2717,11 +2747,12 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) } std::string prefix; + bool ready; uint32_t threshold, total; if (m_wallet->watch_only()) prefix = tr("Opened watch-only wallet"); - else if (m_wallet->multisig(&threshold, &total)) - prefix = (boost::format(tr("Opened %u/%u multisig wallet")) % threshold % total).str(); + else if (m_wallet->multisig(&ready, &threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); else prefix = tr("Opened wallet"); message_writer(console_color_white, true) << -- cgit v1.2.3 From 7f4c220b70b5ef46bd80e4530a3581063d6cdbd5 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 6 Oct 2017 11:24:46 +0100 Subject: simplewallet: add multisig to wallet type in wallet_info output --- src/simplewallet/simplewallet.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 40226ec34..505237027 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -5987,10 +5987,20 @@ bool simple_wallet::status(const std::vector &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::wallet_info(const std::vector &args) { + bool ready; + uint32_t threshold, total; + message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); message_writer() << tr("Description: ") << m_wallet->get_description(); message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); - message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); + std::string type; + if (m_wallet->watch_only()) + type = tr("Watch only"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else + type = tr("Normal"); + message_writer() << tr("Type: ") << type; message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); return true; } -- cgit v1.2.3 From dffa0dceaf54e00266525d2f3edd18cce05d0e16 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 7 Oct 2017 21:20:28 +0100 Subject: simplewallet: add export_raw_multisig command It exports raw transactions, so they may be used by other tools, for instance to be relayed to the network externally. --- src/simplewallet/simplewallet.cpp | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 505237027..98d7ea14f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1210,6 +1210,75 @@ bool simple_wallet::submit_multisig(const std::vector &args) return true; } +bool simple_wallet::export_raw_multisig(const std::vector &args) +{ + bool ready; + uint32_t threshold; + if (!m_wallet->multisig(&ready, &threshold)) + { + fail_msg_writer() << tr("This is not a multisig wallet"); + return true; + } + if (!ready) + { + fail_msg_writer() << tr("This multisig wallet is not yet finalized"); + return true; + } + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_raw_multisig "); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + std::string filename = args[0]; + try + { + tools::wallet2::multisig_tx_set txs; + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } + if (txs.m_signers.size() < threshold) + { + fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) + % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + return true; + } + + // save the transactions + std::string filenames; + for (auto &ptx: txs.m_ptx) + { + const crypto::hash txid = cryptonote::get_transaction_hash(ptx.tx); + const std::string filename = std::string("raw_multisig_monero_tx_") + epee::string_tools::pod_to_hex(txid); + if (!filenames.empty()) + filenames += ", "; + filenames += filename; + if (!epee::file_io_utils::save_string_to_file(filename, cryptonote::tx_to_blob(ptx.tx))) + { + fail_msg_writer() << tr("Failed to export multisig transaction to file ") << filename; + return true; + } + } + success_msg_writer() << tr("Saved exported multisig transaction file(s): ") << filenames; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector &args/* = std::vector()*/) { const auto pwd_container = get_and_verify_password(); @@ -1813,6 +1882,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::submit_multisig, this, _1), tr("submit_multisig "), tr("Submit a signed multisig transaction from a file")); + m_cmd_binder.set_handler("export_raw_multisig_tx", + boost::bind(&simple_wallet::export_raw_multisig, this, _1), + tr("export_raw_multisig "), + tr("Export a signed multisig transaction to a file")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("help []"), -- 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/simplewallet/simplewallet.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 98d7ea14f..145d90d91 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -60,6 +60,7 @@ #include "rapidjson/document.h" #include "common/json_util.h" #include "ringct/rctSigs.h" +#include "multisig/multisig.h" #include "wallet/wallet_args.h" #include @@ -801,8 +802,8 @@ bool simple_wallet::make_multisig(const std::vector &args) } // people may include their own, weed it out - const crypto::secret_key local_skey = m_wallet->get_account().get_keys().m_view_secret_key; - const crypto::public_key local_pkey = m_wallet->get_account().get_keys().m_account_address.m_spend_public_key; + const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(m_wallet->get_account().get_keys().m_view_secret_key); + const crypto::public_key local_pkey = m_wallet->get_multisig_signer_public_key(m_wallet->get_account().get_keys().m_spend_secret_key); for (size_t i = 0; i < secret_keys.size(); ++i) { if (secret_keys[i] == local_skey) -- 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/simplewallet/simplewallet.cpp | 80 ++------------------------------------- 1 file changed, 4 insertions(+), 76 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 145d90d91..5c94f1704 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -85,7 +85,6 @@ typedef cryptonote::simple_wallet sw; #define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" -#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ @@ -916,21 +915,9 @@ bool simple_wallet::export_multisig(const std::vector &args) const std::string filename = args[0]; try { - std::vector outs = m_wallet->export_multisig(); + cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << outs; - - std::string magic(MULTISIG_EXPORT_FILE_MAGIC, strlen(MULTISIG_EXPORT_FILE_MAGIC)); - const cryptonote::account_public_address &keys = m_wallet->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)); - crypto::public_key signer = m_wallet->get_multisig_signer_public_key(); - header += std::string((const char *)&signer, sizeof(crypto::public_key)); - std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str()); - bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext); + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); if (!r) { fail_msg_writer() << tr("failed to save file ") << filename; @@ -970,8 +957,7 @@ bool simple_wallet::import_multisig(const std::vector &args) if (m_wallet->ask_password() && !get_and_verify_password()) return true; - std::vector> info; - std::unordered_set seen; + std::vector info; for (size_t n = 0; n < args.size(); ++n) { const std::string filename = args[n]; @@ -982,65 +968,7 @@ bool simple_wallet::import_multisig(const std::vector &args) fail_msg_writer() << tr("failed to read file ") << filename; return true; } - const size_t magiclen = strlen(MULTISIG_EXPORT_FILE_MAGIC); - if (data.size() < magiclen || memcmp(data.data(), MULTISIG_EXPORT_FILE_MAGIC, magiclen)) - { - fail_msg_writer() << tr("Bad multisig info file magic in ") << filename; - return true; - } - - try - { - data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to decrypt ") << filename << ": " << e.what(); - return true; - } - - const size_t headerlen = 3 * sizeof(crypto::public_key); - if (data.size() < headerlen) - { - fail_msg_writer() << tr("Bad data size from file ") << filename; - return true; - } - 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 = m_wallet->get_account().get_keys().m_account_address; - if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) - { - fail_msg_writer() << (boost::format(tr("Multisig info from %s is for a different account")) % filename).str(); - return true; - } - if (m_wallet->get_multisig_signer_public_key() == signer) - { - message_writer() << (boost::format(tr("Multisig info from %s is from this wallet, ignored")) % filename).str(); - continue; - } - if (seen.find(signer) != seen.end()) - { - message_writer() << (boost::format(tr("Multisig info from %s already seen, ignored")) % filename).str(); - continue; - } - seen.insert(signer); - - try - { - std::string body(data, headerlen); - std::istringstream iss(body); - std::vector i; - boost::archive::portable_binary_iarchive ar(iss); - ar >> i; - message_writer() << (boost::format(tr("%u outputs found in %s")) % boost::lexical_cast(i.size()) % filename).str(); - info.push_back(std::move(i)); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); - return true; - } + info.push_back(std::move(data)); } LOCK_IDLE_SCOPE(); -- 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/simplewallet/simplewallet.cpp | 57 ++++----------------------------------- 1 file changed, 5 insertions(+), 52 deletions(-) (limited to 'src/simplewallet/simplewallet.cpp') diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 5c94f1704..64e665fb3 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -771,61 +771,13 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } - // parse all multisig info - std::vector secret_keys(args.size() - 1); - std::vector public_keys(args.size() - 1); - for (size_t i = 1; i < args.size(); ++i) - { - if (!tools::wallet2::verify_multisig_info(args[i], secret_keys[i - 1], public_keys[i - 1])) - { - fail_msg_writer() << tr("Bad multisig info: ") << args[i]; - return true; - } - } - - // 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])) - { - message_writer() << tr("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(m_wallet->get_account().get_keys().m_view_secret_key); - const crypto::public_key local_pkey = m_wallet->get_multisig_signer_public_key(m_wallet->get_account().get_keys().m_spend_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (secret_keys[i] == local_skey) - { - message_writer() << tr("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 if (public_keys[i] == local_pkey) - { - fail_msg_writer() << tr("Found local spend public key, but not local view secret key - something very weird"); - return true; - } - } - LOCK_IDLE_SCOPE(); try { - std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), secret_keys, public_keys, threshold); + auto local_args = args; + local_args.erase(local_args.begin()); + std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); if (!multisig_extra_info.empty()) { success_msg_writer() << tr("Another step is needed"); @@ -840,7 +792,8 @@ bool simple_wallet::make_multisig(const std::vector &args) return true; } - uint32_t total = secret_keys.size() + 1; + uint32_t total; + m_wallet->multisig(NULL, &threshold, &total); success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); -- cgit v1.2.3