From 2fa707d1a54c2e2c00c542edb3757ae553b5b84a Mon Sep 17 00:00:00 2001
From: moneromooo-monero <moneromooo-monero@users.noreply.github.com>
Date: Mon, 27 Nov 2017 20:09:16 +0000
Subject: [PATCH] wallet: add multisig sign/submit RPC

---
 src/wallet/wallet2.cpp                       |  62 +--
 src/wallet/wallet2.h                         |   6 +
 src/wallet/wallet_rpc_server.cpp             | 453 +++++++++++++++----
 src/wallet/wallet_rpc_server.h               |   5 +
 src/wallet/wallet_rpc_server_commands_defs.h |  54 +++
 src/wallet/wallet_rpc_server_error_codes.h   |   3 +
 6 files changed, 470 insertions(+), 113 deletions(-)

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<pending_tx>& 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<bool(const multisig_tx_set&)> accept_func)
+bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> 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<bool(const multisig_tx_set&)> 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<crypto::hash> &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");
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 83a4fd2e1..7a8becb3c 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -395,6 +395,11 @@ namespace tools
     {
       std::vector<pending_tx> m_ptx;
       std::unordered_set<crypto::public_key> m_signers;
+
+      BEGIN_SERIALIZE_OBJECT()
+        FIELD(m_ptx)
+        FIELD(m_signers)
+      END_SERIALIZE()
     };
 
     struct keys_file_data
@@ -640,6 +645,7 @@ namespace tools
     std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
     std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
     std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
+    bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
     bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
     bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
     bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids);
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index e6e5445b2..0157a6f71 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -610,11 +610,6 @@ namespace tools
         return false;
       }
 
-      if (!req.do_not_relay)
-        m_wallet->commit_tx(ptx_vector);
-
-      // populate response with tx hash
-      res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
       if (req.get_tx_key)
       {
         res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key);
@@ -623,18 +618,45 @@ namespace tools
       }
       res.fee = ptx_vector.back().fee;
 
-      if (req.get_tx_hex)
+      if (m_wallet->multisig())
       {
-        cryptonote::blobdata blob;
-        tx_to_blob(ptx_vector.back().tx, blob);
-        res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+        res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+        if (res.multisig_txset.empty())
+        {
+          er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+          er.message = "Failed to save multisig tx set after creation";
+          return false;
+        }
       }
-      if (req.get_tx_metadata)
+      else
       {
-        std::ostringstream oss;
-        binary_archive<true> ar(oss);
-        ::serialization::serialize(ar, ptx_vector.back());
-        res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+        if (!req.do_not_relay)
+          m_wallet->commit_tx(ptx_vector);
+
+        // populate response with tx hash
+        res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx));
+        if (req.get_tx_hex)
+        {
+          cryptonote::blobdata blob;
+          tx_to_blob(ptx_vector.back().tx, blob);
+          res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+        }
+        if (req.get_tx_metadata)
+        {
+          std::ostringstream oss;
+          boost::archive::portable_binary_oarchive ar(oss);
+          try
+          {
+            ar << ptx_vector.back();
+          }
+          catch (...)
+          {
+            er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+            er.message = "Failed to save multisig tx set after creation";
+            return false;
+          }
+          res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+        }
       }
       return true;
     }
@@ -675,17 +697,9 @@ namespace tools
       ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
       LOG_PRINT_L2("on_transfer_split called create_transactions_2");
 
-      if (!req.do_not_relay)
-      {
-        LOG_PRINT_L2("on_transfer_split calling commit_tx");
-        m_wallet->commit_tx(ptx_vector);
-        LOG_PRINT_L2("on_transfer_split called commit_tx");
-      }
-
       // populate response with tx hashes
       for (const auto & ptx : ptx_vector)
       {
-        res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
         if (req.get_tx_keys)
         {
           res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
@@ -699,19 +713,56 @@ namespace tools
         res.amount_list.push_back(ptx_amount);
 
         res.fee_list.push_back(ptx.fee);
+      }
 
-        if (req.get_tx_hex)
+      if (m_wallet->multisig())
+      {
+        res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+        if (res.multisig_txset.empty())
         {
-          cryptonote::blobdata blob;
-          tx_to_blob(ptx.tx, blob);
-          res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+          er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+          er.message = "Failed to save multisig tx set after creation";
+          return false;
         }
-        if (req.get_tx_metadata)
+      }
+
+      // populate response with tx hashes
+      for (const auto & ptx : ptx_vector)
+      {
+        if (!req.do_not_relay)
         {
-          std::ostringstream oss;
-          binary_archive<true> ar(oss);
-          ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
-          res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          LOG_PRINT_L2("on_transfer_split calling commit_tx");
+          m_wallet->commit_tx(ptx_vector);
+          LOG_PRINT_L2("on_transfer_split called commit_tx");
+        }
+
+        // populate response with tx hashes
+        for (auto & ptx : ptx_vector)
+        {
+          res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+
+          if (req.get_tx_hex)
+          {
+            cryptonote::blobdata blob;
+            tx_to_blob(ptx.tx, blob);
+            res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+          }
+          if (req.get_tx_metadata)
+          {
+            std::ostringstream oss;
+            boost::archive::portable_binary_oarchive ar(oss);
+            try
+            {
+              ar << ptx;
+            }
+            catch (...)
+            {
+              er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+              er.message = "Failed to save multisig tx set after creation";
+              return false;
+            }
+            res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          }
         }
       }
 
@@ -739,30 +790,65 @@ namespace tools
     {
       std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
 
-      if (!req.do_not_relay)
-        m_wallet->commit_tx(ptx_vector);
-
-      // populate response with tx hashes
       for (const auto & ptx : ptx_vector)
       {
-        res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
         if (req.get_tx_keys)
         {
           res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
         }
         res.fee_list.push_back(ptx.fee);
-        if (req.get_tx_hex)
-        {
-          cryptonote::blobdata blob;
-          tx_to_blob(ptx.tx, blob);
-          res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
-        }
-        if (req.get_tx_metadata)
+      }
+
+      if (m_wallet->multisig())
+      {
+        for (tools::wallet2::pending_tx &ptx: ptx_vector)
         {
           std::ostringstream oss;
-          binary_archive<true> ar(oss);
-          ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
-          res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          boost::archive::portable_binary_oarchive ar(oss);
+          try
+          {
+            ar << ptx;
+          }
+          catch (...)
+          {
+            er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+            er.message = "Failed to save multisig tx set after creation";
+            return false;
+          }
+          res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+        }
+      }
+      else
+      {
+        if (!req.do_not_relay)
+          m_wallet->commit_tx(ptx_vector);
+
+        // populate response with tx hashes
+        for (auto & ptx : ptx_vector)
+        {
+          res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+          if (req.get_tx_hex)
+          {
+            cryptonote::blobdata blob;
+            tx_to_blob(ptx.tx, blob);
+            res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+          }
+          if (req.get_tx_metadata)
+          {
+            std::ostringstream oss;
+            boost::archive::portable_binary_oarchive ar(oss);
+            try
+            {
+              ar << ptx;
+            }
+            catch (...)
+            {
+              er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+              er.message = "Failed to save multisig tx set after creation";
+              return false;
+            }
+            res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          }
         }
       }
 
@@ -804,29 +890,64 @@ namespace tools
       uint64_t mixin = m_wallet->adjust_mixin(req.mixin);
       std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
 
-      if (!req.do_not_relay)
-        m_wallet->commit_tx(ptx_vector);
-
-      // populate response with tx hashes
       for (const auto & ptx : ptx_vector)
       {
-        res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
         if (req.get_tx_keys)
         {
           res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
         }
-        if (req.get_tx_hex)
-        {
-          cryptonote::blobdata blob;
-          tx_to_blob(ptx.tx, blob);
-          res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
-        }
-        if (req.get_tx_metadata)
+      }
+
+      if (m_wallet->multisig())
+      {
+        for (tools::wallet2::pending_tx &ptx: ptx_vector)
         {
           std::ostringstream oss;
-          binary_archive<true> ar(oss);
-          ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
-          res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          boost::archive::portable_binary_oarchive ar(oss);
+          try
+          {
+            ar << ptx;
+          }
+          catch (...)
+          {
+            er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+            er.message = "Failed to save multisig tx set after creation";
+            return false;
+          }
+          res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+        }
+      }
+      else
+      {
+        if (!req.do_not_relay)
+          m_wallet->commit_tx(ptx_vector);
+
+        // populate response with tx hashes
+        for (auto & ptx : ptx_vector)
+        {
+          res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+          if (req.get_tx_hex)
+          {
+            cryptonote::blobdata blob;
+            tx_to_blob(ptx.tx, blob);
+            res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob));
+          }
+          if (req.get_tx_metadata)
+          {
+            std::ostringstream oss;
+            boost::archive::portable_binary_oarchive ar(oss);
+            try
+            {
+              ar << ptx;
+            }
+            catch (...)
+            {
+              er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+              er.message = "Failed to save multisig tx set after creation";
+              return false;
+            }
+            res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str()));
+          }
         }
       }
 
@@ -888,37 +1009,59 @@ namespace tools
         er.message = "Multiple transactions are created, which is not supposed to happen";
         return false;
       }
-      if (ptx_vector[0].selected_transfers.size() > 1)
+      const wallet2::pending_tx &ptx = ptx_vector[0];
+      if (ptx.selected_transfers.size() > 1)
       {
         er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
         er.message = "The transaction uses multiple inputs, which is not supposed to happen";
         return false;
       }
 
-      if (!req.do_not_relay)
-        m_wallet->commit_tx(ptx_vector);
-
-      // populate response with tx hashes
-      const wallet2::pending_tx &ptx = ptx_vector[0];
-      res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
       if (req.get_tx_key)
       {
         res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key);
       }
-      if (req.get_tx_hex)
-      {
-        cryptonote::blobdata blob;
-        tx_to_blob(ptx.tx, blob);
-        res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
-      }
-      if (req.get_tx_metadata)
-      {
-        std::ostringstream oss;
-        binary_archive<true> ar(oss);
-        ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx));
-        res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
-      }
 
+      if (m_wallet->multisig())
+      {
+        res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector));
+        if (res.multisig_txset.empty())
+        {
+          er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+          er.message = "Failed to save multisig tx set after creation";
+          return false;
+        }
+      }
+      else
+      {
+        if (!req.do_not_relay)
+          m_wallet->commit_tx(ptx_vector);
+
+        // populate response with tx hashes
+        res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
+        if (req.get_tx_hex)
+        {
+          cryptonote::blobdata blob;
+          tx_to_blob(ptx.tx, blob);
+          res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
+        }
+        if (req.get_tx_metadata)
+        {
+          std::ostringstream oss;
+          boost::archive::portable_binary_oarchive ar(oss);
+          try
+          {
+            ar << ptx;
+          }
+          catch (...)
+          {
+            er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+            er.message = "Failed to save multisig tx set after creation";
+            return false;
+          }
+          res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str());
+        }
+      }
       return true;
     }
     catch (const tools::error::daemon_busy& e)
@@ -954,13 +1097,14 @@ namespace tools
       return false;
     }
 
-    std::stringstream ss;
-    ss << blob;
-    binary_archive<false> ba(ss);
-
     tools::wallet2::pending_tx ptx;
-    bool r = ::serialization::serialize(ba, ptx);
-    if (!r)
+    try
+    {
+      std::istringstream iss(blob);
+      boost::archive::portable_binary_iarchive ar(iss);
+      ar >> ptx;
+    }
+    catch (...)
     {
       er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA;
       er.message = "Failed to parse tx metadata.";
@@ -2663,6 +2807,141 @@ namespace tools
     return true;
   }
   //------------------------------------------------------------------------------------------------------------------------------
+  bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er)
+  {
+    if (!m_wallet) return not_open(er);
+    if (m_wallet->restricted())
+    {
+      er.code = WALLET_RPC_ERROR_CODE_DENIED;
+      er.message = "Command unavailable in restricted mode.";
+      return false;
+    }
+    bool ready;
+    uint32_t threshold, total;
+    if (!m_wallet->multisig(&ready, &threshold, &total))
+    {
+      er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+      er.message = "This wallet is not multisig";
+      return false;
+    }
+    if (!ready)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+      er.message = "This wallet is multisig, but not yet finalized";
+      return false;
+    }
+
+    cryptonote::blobdata blob;
+    if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
+    {
+      er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+      er.message = "Failed to parse hex.";
+      return false;
+    }
+
+    tools::wallet2::multisig_tx_set txs;
+    bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
+    if (!r)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
+      er.message = "Failed to parse multisig tx data.";
+      return false;
+    }
+
+    std::vector<crypto::hash> txids;
+    try
+    {
+      bool r = m_wallet->sign_multisig_tx(txs, txids);
+      if (!r)
+      {
+        er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
+        er.message = "Failed to sign multisig tx";
+        return false;
+      }
+    }
+    catch (const std::exception &e)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE;
+      er.message = std::string("Failed to sign multisig tx: ") + e.what();
+      return false;
+    }
+
+    res.tx_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(txs));
+    if (!txids.empty())
+    {
+      for (const crypto::hash &txid: txids)
+        res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(txid));
+    }
+
+    return true;
+  }
+  //------------------------------------------------------------------------------------------------------------------------------
+  bool wallet_rpc_server::on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er)
+  {
+    if (!m_wallet) return not_open(er);
+    if (m_wallet->restricted())
+    {
+      er.code = WALLET_RPC_ERROR_CODE_DENIED;
+      er.message = "Command unavailable in restricted mode.";
+      return false;
+    }
+    bool ready;
+    uint32_t threshold, total;
+    if (!m_wallet->multisig(&ready, &threshold, &total))
+    {
+      er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+      er.message = "This wallet is not multisig";
+      return false;
+    }
+    if (!ready)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
+      er.message = "This wallet is multisig, but not yet finalized";
+      return false;
+    }
+
+    cryptonote::blobdata blob;
+    if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
+    {
+      er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+      er.message = "Failed to parse hex.";
+      return false;
+    }
+
+    tools::wallet2::multisig_tx_set txs;
+    bool r = m_wallet->load_multisig_tx(blob, txs, NULL);
+    if (!r)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
+      er.message = "Failed to parse multisig tx data.";
+      return false;
+    }
+
+    if (txs.m_signers.size() < threshold)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
+      er.message = "Not enough signers signed this transaction.";
+      return false;
+    }
+
+    try
+    {
+      for (auto &ptx: txs.m_ptx)
+      {
+        m_wallet->commit_tx(ptx);
+        res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+      }
+    }
+    catch (const std::exception &e)
+    {
+      er.code = WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION;
+      er.message = std::string("Failed to submit multisig tx: ") + e.what();
+      return false;
+    }
+
+    return true;
+  }
+  //------------------------------------------------------------------------------------------------------------------------------
 }
 
 int main(int argc, char** argv) {
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 22eb8964e..79f589623 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -122,6 +122,9 @@ namespace tools
         MAP_JON_RPC_WE("make_multisig",      on_make_multisig,      wallet_rpc::COMMAND_RPC_MAKE_MULTISIG)
         MAP_JON_RPC_WE("export_multisig_info", on_export_multisig,  wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG)
         MAP_JON_RPC_WE("import_multisig_info", on_import_multisig,  wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG)
+        MAP_JON_RPC_WE("finalize_multisig",  on_finalize_multisig,  wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG)
+        MAP_JON_RPC_WE("sign_multisig",      on_sign_multisig,      wallet_rpc::COMMAND_RPC_SIGN_MULTISIG)
+        MAP_JON_RPC_WE("submit_multisig",    on_submit_multisig,    wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG)
       END_JSON_RPC_MAP()
     END_URI_MAP2()
 
@@ -182,6 +185,8 @@ namespace tools
       bool on_export_multisig(const wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_EXPORT_MULTISIG::response& res, epee::json_rpc::error& er);
       bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er);
       bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er);
+      bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er);
+      bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er);
 
       //json rpc v2
       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 bff4fdd7c..07917a683 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -318,6 +318,7 @@ namespace wallet_rpc
       uint64_t fee;
       std::string tx_blob;
       std::string tx_metadata;
+      std::string multisig_txset;
 
       BEGIN_KV_SERIALIZE_MAP()
         KV_SERIALIZE(tx_hash)
@@ -326,6 +327,7 @@ namespace wallet_rpc
         KV_SERIALIZE(fee)
         KV_SERIALIZE(tx_blob)
         KV_SERIALIZE(tx_metadata)
+        KV_SERIALIZE(multisig_txset)
       END_KV_SERIALIZE_MAP()
     };
   };
@@ -378,6 +380,7 @@ namespace wallet_rpc
       std::list<uint64_t> fee_list;
       std::list<std::string> tx_blob_list;
       std::list<std::string> tx_metadata_list;
+      std::string multisig_txset;
 
       BEGIN_KV_SERIALIZE_MAP()
         KV_SERIALIZE(tx_hash_list)
@@ -386,6 +389,7 @@ namespace wallet_rpc
         KV_SERIALIZE(fee_list)
         KV_SERIALIZE(tx_blob_list)
         KV_SERIALIZE(tx_metadata_list)
+        KV_SERIALIZE(multisig_txset)
       END_KV_SERIALIZE_MAP()
     };
   };
@@ -423,6 +427,7 @@ namespace wallet_rpc
       std::list<uint64_t> fee_list;
       std::list<std::string> tx_blob_list;
       std::list<std::string> tx_metadata_list;
+      std::list<std::string> multisig_txset;
 
       BEGIN_KV_SERIALIZE_MAP()
         KV_SERIALIZE(tx_hash_list)
@@ -430,6 +435,7 @@ namespace wallet_rpc
         KV_SERIALIZE(fee_list)
         KV_SERIALIZE(tx_blob_list)
         KV_SERIALIZE(tx_metadata_list)
+        KV_SERIALIZE(multisig_txset)
       END_KV_SERIALIZE_MAP()
     };
   };
@@ -483,6 +489,7 @@ namespace wallet_rpc
       std::list<uint64_t> fee_list;
       std::list<std::string> tx_blob_list;
       std::list<std::string> tx_metadata_list;
+      std::list<std::string> multisig_txset;
 
       BEGIN_KV_SERIALIZE_MAP()
         KV_SERIALIZE(tx_hash_list)
@@ -490,6 +497,7 @@ namespace wallet_rpc
         KV_SERIALIZE(fee_list)
         KV_SERIALIZE(tx_blob_list)
         KV_SERIALIZE(tx_metadata_list)
+        KV_SERIALIZE(multisig_txset)
       END_KV_SERIALIZE_MAP()
     };
   };
@@ -530,6 +538,7 @@ namespace wallet_rpc
       uint64_t fee;
       std::string tx_blob;
       std::string tx_metadata;
+      std::string multisig_txset;
 
       BEGIN_KV_SERIALIZE_MAP()
         KV_SERIALIZE(tx_hash)
@@ -537,6 +546,7 @@ namespace wallet_rpc
         KV_SERIALIZE(fee)
         KV_SERIALIZE(tx_blob)
         KV_SERIALIZE(tx_metadata)
+        KV_SERIALIZE(multisig_txset)
       END_KV_SERIALIZE_MAP()
     };
   };
@@ -1652,5 +1662,49 @@ namespace wallet_rpc
     };
   };
 
+  struct COMMAND_RPC_SIGN_MULTISIG
+  {
+    struct request
+    {
+      std::string tx_data_hex;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(tx_data_hex)
+      END_KV_SERIALIZE_MAP()
+    };
+
+    struct response
+    {
+      std::string tx_data_hex;
+      std::list<std::string> tx_hash_list;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(tx_data_hex)
+        KV_SERIALIZE(tx_hash_list)
+      END_KV_SERIALIZE_MAP()
+    };
+  };
+
+  struct COMMAND_RPC_SUBMIT_MULTISIG
+  {
+    struct request
+    {
+      std::string tx_data_hex;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(tx_data_hex)
+      END_KV_SERIALIZE_MAP()
+    };
+
+    struct response
+    {
+      std::list<std::string> tx_hash_list;
+
+      BEGIN_KV_SERIALIZE_MAP()
+        KV_SERIALIZE(tx_hash_list)
+      END_KV_SERIALIZE_MAP()
+    };
+  };
+
 }
 }
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index 139fcf8ed..578413e38 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -64,3 +64,6 @@
 #define WALLET_RPC_ERROR_CODE_NOT_MULTISIG           -31
 #define WALLET_RPC_ERROR_CODE_WRONG_LR               -32
 #define WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED  -33
+#define WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA   -34
+#define WALLET_RPC_ERROR_CODE_MULTISIG_SIGNATURE     -35
+#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION    -36