From cd64c7990ce1b6a9d88ff24e749fb906154c95dc Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 27 Nov 2017 20:09:04 +0000 Subject: [PATCH] multisig address generation RPC --- src/wallet/wallet2.cpp | 26 ++- src/wallet/wallet2.h | 4 +- src/wallet/wallet_rpc_server.cpp | 234 +++++++++++++++++++ src/wallet/wallet_rpc_server.h | 10 + src/wallet/wallet_rpc_server_commands_defs.h | 126 ++++++++++ src/wallet/wallet_rpc_server_error_codes.h | 6 + 6 files changed, 400 insertions(+), 6 deletions(-) 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) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 97abb1476..8567edc9a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -592,7 +592,9 @@ namespace tools void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector& ptx_vector); bool save_tx(const std::vector& ptx_vector, const std::string &filename); - bool save_multisig_tx(multisig_tx_set txs, const std::string &filename); + std::string save_multisig_tx(multisig_tx_set txs); + bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); + std::string save_multisig_tx(const std::vector& ptx_vector); bool save_multisig_tx(const std::vector& ptx_vector, const std::string &filename); // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL, bool export_raw = false); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index aa521c9aa..e2bb0a5c6 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2339,6 +2339,240 @@ namespace tools } } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + res.multisig = m_wallet->multisig(&res.threshold, &res.total); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + res.multisig_info = m_wallet->get_multisig_info(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is already multisig"; + return false; + } + if (m_wallet->watch_only()) + { + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } + + // parse all multisig info + std::vector secret_keys(req.multisig_info.size()); + std::vector public_keys(req.multisig_info.size()); + for (size_t i = 0; i < req.multisig_info.size(); ++i) + { + if (!m_wallet->verify_multisig_info(req.multisig_info[i], secret_keys[i], public_keys[i])) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; + er.message = "Bad multisig info: " + req.multisig_info[i]; + return false; + } + } + + // remove duplicates + for (size_t i = 1; 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])) + { + 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 + crypto::hash hash; + crypto::cn_fast_hash(&m_wallet->get_account().get_keys().m_view_secret_key, sizeof(crypto::secret_key), hash); + for (size_t i = 0; i < secret_keys.size(); ++i) + { + if (rct::sk2rct(secret_keys[i]) == rct::hash2rct(hash)) + { + 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] == m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO; + er.message = "Found local spend public key, but not local view secret key - something very weird"; + return false; + } + } + + try + { + m_wallet->make_multisig(req.password, secret_keys, public_keys, req.threshold); + res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (!m_wallet->multisig()) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + + std::vector info; + try + { + info = m_wallet->export_multisig(); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + + res.info.resize(info.size()); + for (size_t n = 0; n < info.size(); ++n) + { + res.info[n].partial_key_image = epee::string_tools::pod_to_hex(info[n].m_partial_key_image); + res.info[n].L = epee::string_tools::pod_to_hex(info[n].m_L); + res.info[n].R = epee::string_tools::pod_to_hex(info[n].m_R); + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + uint32_t threshold, total; + if (!m_wallet->multisig(&threshold, &total)) + { + er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; + er.message = "This wallet is not multisig"; + return false; + } + + if (req.info.size() < threshold - 1) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig export info from more participants"; + return false; + } + + std::vector> info; + info.resize(req.info.size()); + for (size_t n = 0; n < info.size(); ++n) + { + info[n].resize(req.info[n].info.size()); + for (size_t i = 0; i < info[n].size(); ++i) + { + if (!epee::string_tools::hex_to_pod(req.info[n].info[i].partial_key_image, info[n][i].m_partial_key_image)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Failed to parse partial key image from multisig info"; + return false; + } + if (!epee::string_tools::hex_to_pod(req.info[n].info[i].L, info[n][i].m_L) || !epee::string_tools::hex_to_pod(req.info[n].info[i].R, info[n][i].m_R)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_LR; + er.message = "Failed to parse L/R info from hex"; + return false; + } + } + } + + try + { + res.n_outputs = m_wallet->import_multisig(info); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Error calling import_multisig"; + return false; + } + + if (m_trusted_daemon) + { + try + { + m_wallet->rescan_spent(); + } + catch (const std::exception &e) + { + er.message = std::string("Success, but failed to update spent status after import multisig info: ") + e.what(); + } + } + else + { + er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted"; + } + + return true; + } } int main(int argc, char** argv) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 9455c4769..10b2e9dbd 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -117,6 +117,11 @@ namespace tools MAP_JON_RPC_WE("get_languages", on_get_languages, wallet_rpc::COMMAND_RPC_GET_LANGUAGES) MAP_JON_RPC_WE("create_wallet", on_create_wallet, wallet_rpc::COMMAND_RPC_CREATE_WALLET) MAP_JON_RPC_WE("open_wallet", on_open_wallet, wallet_rpc::COMMAND_RPC_OPEN_WALLET) + MAP_JON_RPC_WE("is_multisig", on_is_multisig, wallet_rpc::COMMAND_RPC_IS_MULTISIG) + MAP_JON_RPC_WE("prepare_multisig", on_prepare_multisig, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG) + MAP_JON_RPC_WE("make_multisig", on_make_multisig, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG) + MAP_JON_RPC_WE("export_multisig_info", on_export_multisig, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG) + MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG) END_JSON_RPC_MAP() END_URI_MAP2() @@ -171,6 +176,11 @@ namespace tools bool on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er); bool on_create_wallet(const wallet_rpc::COMMAND_RPC_CREATE_WALLET::request& req, wallet_rpc::COMMAND_RPC_CREATE_WALLET::response& res, epee::json_rpc::error& er); bool on_open_wallet(const wallet_rpc::COMMAND_RPC_OPEN_WALLET::request& req, wallet_rpc::COMMAND_RPC_OPEN_WALLET::response& res, epee::json_rpc::error& er); + bool on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_prepare_multisig(const wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_PREPARE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_make_multisig(const wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_MAKE_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index cf35cc48d..806704d6e 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1487,5 +1487,131 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_IS_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool multisig; + uint32_t threshold; + uint32_t total; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig) + KV_SERIALIZE(threshold) + KV_SERIALIZE(total) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_PREPARE_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_MAKE_MULTISIG + { + struct request + { + std::vector multisig_info; + uint32_t threshold; + std::string password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + KV_SERIALIZE(threshold) + KV_SERIALIZE(password) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + }; + + struct multisig_info_entry + { + std::string partial_key_image; + std::string L; + std::string R; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(partial_key_image) + KV_SERIALIZE(L) + KV_SERIALIZE(R) + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_EXPORT_MULTISIG + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_IMPORT_MULTISIG + { + struct participant_entry + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct request + { + std::vector info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t n_outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(n_outputs) + END_KV_SERIALIZE_MAP() + }; + }; } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index c3f3e20d1..139fcf8ed 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -58,3 +58,9 @@ #define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 #define WALLET_RPC_ERROR_CODE_BAD_HEX -26 #define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 +#define WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG -28 +#define WALLET_RPC_ERROR_CODE_WATCH_ONLY -29 +#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_INFO -30 +#define WALLET_RPC_ERROR_CODE_NOT_MULTISIG -31 +#define WALLET_RPC_ERROR_CODE_WRONG_LR -32 +#define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED -33