From 4e534aa9d892dd6db64afda979ba1f8d44371389 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 29 Apr 2013 19:50:56 +0200 Subject: [PATCH] Add dumpwallet and importwallet RPCs dumpwallet: produce a dump of all keys in a wallet, in a format compatible with Bitcoin Wallet for Android and Multibit. importwallet: import such a dump --- src/bitcoinrpc.cpp | 2 + src/bitcoinrpc.h | 3 + src/rpcdump.cpp | 206 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 195 insertions(+), 16 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index febb475db..9b7917ffe 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -243,7 +243,9 @@ static const CRPCCommand vRPCCommands[] = { "submitblock", &submitblock, false, false }, { "listsinceblock", &listsinceblock, false, false }, { "dumpprivkey", &dumpprivkey, true, false }, + { "dumpwallet", &dumpwallet, true, false }, { "importprivkey", &importprivkey, false, false }, + { "importwallet", &importwallet, false, false }, { "listunspent", &listunspent, false, false }, { "getrawtransaction", &getrawtransaction, false, false }, { "createrawtransaction", &createrawtransaction, false, false }, diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 44c657f8d..8201dffe9 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -145,8 +145,11 @@ extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, b extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value addnode(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getaddednodeinfo(const json_spirit::Array& params, bool fHelp); + extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getgenerate(const json_spirit::Array& params, bool fHelp); // in rpcmining.cpp extern json_spirit::Value setgenerate(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index d46309eaa..dcfb023f3 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -2,35 +2,68 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include + #include "init.h" // for pwalletMain #include "bitcoinrpc.h" #include "ui_interface.h" #include "base58.h" +#include #include +#include +#include #define printf OutputDebugStringF using namespace json_spirit; using namespace std; -class CTxDump -{ -public: - CBlockIndex *pindex; - int64 nValue; - bool fSpent; - CWalletTx* ptx; - int nOut; - CTxDump(CWalletTx* ptx = NULL, int nOut = -1) - { - pindex = NULL; - nValue = 0; - fSpent = false; - this->ptx = ptx; - this->nOut = nOut; +void EnsureWalletIsUnlocked(); + +std::string static EncodeDumpTime(int64 nTime) { + return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); +} + +int64 static DecodeDumpTime(const std::string &str) { + static const boost::posix_time::time_input_facet facet("%Y-%m-%dT%H:%M:%SZ"); + static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); + const std::locale loc(std::locale::classic(), &facet); + std::istringstream iss(str); + iss.imbue(loc); + boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); + iss >> ptime; + if (ptime.is_not_a_date_time()) + return 0; + return (ptime - epoch).total_seconds(); +} + +std::string static EncodeDumpString(const std::string &str) { + std::stringstream ret; + BOOST_FOREACH(unsigned char c, str) { + if (c <= 32 || c >= 128 || c == '%') { + ret << '%' << HexStr(&c, &c + 1); + } else { + ret << c; + } + } + return ret.str(); +} + +std::string DecodeDumpString(const std::string &str) { + std::stringstream ret; + for (unsigned int pos = 0; pos < str.length(); pos++) { + unsigned char c = str[pos]; + if (c == '%' && pos+2 < str.length()) { + c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | + ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); + pos += 2; + } + ret << c; } -}; + return ret.str(); +} Value importprivkey(const Array& params, bool fHelp) { @@ -63,6 +96,10 @@ Value importprivkey(const Array& params, bool fHelp) pwalletMain->MarkDirty(); pwalletMain->SetAddressBookName(vchAddress, strLabel); + // Don't throw error in case a key is already there + if (pwalletMain->HaveKey(vchAddress)) + return Value::null; + if (!pwalletMain->AddKeyPubKey(key, pubkey)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); @@ -75,6 +112,86 @@ Value importprivkey(const Array& params, bool fHelp) return Value::null; } +Value importwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "importwallet \n" + "Imports keys from a wallet dump file (see dumpwallet)."); + + EnsureWalletIsUnlocked(); + + ifstream file; + file.open(params[0].get_str().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + int64 nTimeBegin = pindexBest->nTime; + + bool fGood = true; + + while (file.good()) { + std::string line; + std::getline(file, line); + if (line.empty() || line[0] == '#') + continue; + + std::vector vstr; + boost::split(vstr, line, boost::is_any_of(" ")); + if (vstr.size() < 2) + continue; + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(vstr[0])) + continue; + CKey key = vchSecret.GetKey(); + CPubKey pubkey = key.GetPubKey(); + CKeyID keyid = pubkey.GetID(); + if (pwalletMain->HaveKey(keyid)) { + printf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString().c_str()); + continue; + } + int64 nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + if (boost::algorithm::starts_with(vstr[nStr], "#")) + break; + if (vstr[nStr] == "change=1") + fLabel = false; + if (vstr[nStr] == "reserve=1") + fLabel = false; + if (boost::algorithm::starts_with(vstr[nStr], "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } + } + printf("Importing %s...\n", CBitcoinAddress(keyid).ToString().c_str()); + if (!pwalletMain->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; + } + pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) + pwalletMain->SetAddressBookName(keyid, strLabel); + nTimeBegin = std::min(nTimeBegin, nTime); + } + file.close(); + + CBlockIndex *pindex = pindexBest; + while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200) + pindex = pindex->pprev; + + printf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1); + pwalletMain->ScanForWalletTransactions(pindex); + pwalletMain->ReacceptWalletTransactions(); + pwalletMain->MarkDirty(); + + if (!fGood) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); + + return Value::null; +} + Value dumpprivkey(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -82,6 +199,8 @@ Value dumpprivkey(const Array& params, bool fHelp) "dumpprivkey \n" "Reveals the private key corresponding to ."); + EnsureWalletIsUnlocked(); + string strAddress = params[0].get_str(); CBitcoinAddress address; if (!address.SetString(strAddress)) @@ -94,3 +213,58 @@ Value dumpprivkey(const Array& params, bool fHelp) throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); return CBitcoinSecret(vchSecret).ToString(); } + + +Value dumpwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "dumpwallet \n" + "Dumps all wallet keys in a human-readable format."); + + EnsureWalletIsUnlocked(); + + ofstream file; + file.open(params[0].get_str().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + std::map mapKeyBirth; + std::set setKeyPool; + pwalletMain->GetKeyBirthTimes(mapKeyBirth); + pwalletMain->GetAllReserveKeys(setKeyPool); + + // sort time/key pairs + std::vector > vKeyBirth; + for (std::map::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) { + vKeyBirth.push_back(std::make_pair(it->second, it->first)); + } + mapKeyBirth.clear(); + std::sort(vKeyBirth.begin(), vKeyBirth.end()); + + // produce output + file << strprintf("# Wallet dump created by Bitcoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str()); + file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str()); + file << strprintf("# mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str()); + file << "\n"; + for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { + const CKeyID &keyid = it->second; + std::string strTime = EncodeDumpTime(it->first); + std::string strAddr = CBitcoinAddress(keyid).ToString(); + CKey key; + if (pwalletMain->GetKey(keyid, key)) { + if (pwalletMain->mapAddressBook.count(keyid)) { + file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid]).c_str(), strAddr.c_str()); + } else if (setKeyPool.count(keyid)) { + file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), strAddr.c_str()); + } else { + file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString().c_str(), strTime.c_str(), strAddr.c_str()); + } + } + } + file << "\n"; + file << "# End of dump\n"; + file.close(); + return Value::null; +}