From acc775c5547a94fa5ad12ecb0bdaeefdc285d853 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 29 Apr 2013 19:50:02 +0200 Subject: [PATCH 1/3] Add ExtractAffectedKeys to script This function finds all keys affected by a particular output script, supporting everything ExtractDestinations supports (pay-to-pubkey, pay-to-pubkeyhash, multisig) and recurses into subscripts (P2SH). --- src/script.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/script.h | 1 + 2 files changed, 37 insertions(+) diff --git a/src/script.cpp b/src/script.cpp index cf6eeaf3..14fe80e2 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1474,6 +1474,42 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vecto return true; } +class CAffectedKeysVisitor : public boost::static_visitor { +private: + const CKeyStore &keystore; + std::vector &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + BOOST_FOREACH(const CTxDestination &dest, vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination &none) {} +}; + +void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector &vKeys) { + CAffectedKeysVisitor(keystore, vKeys).Process(scriptPubKey); +} + bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, unsigned int flags, int nHashType) { diff --git a/src/script.h b/src/script.h index f963467c..03afe8b6 100644 --- a/src/script.h +++ b/src/script.h @@ -674,6 +674,7 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector &vKeys); bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); From 434e4273b96cb9204fea346c3fbc65583b01c55b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 29 Apr 2013 19:50:40 +0200 Subject: [PATCH 2/3] Add GetKeyBirthTimes to wallet Compute safe lower bounds on the birth times of all wallet keys. For pool keys or keys with metadata, the actually stored birth time is used. For all others, the birth times are inferred from the wallet transactions. --- src/wallet.cpp | 52 +++++++++++++++++++++++++++++++++++++++++++++++- src/wallet.h | 4 +++- src/walletdb.cpp | 9 +++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/wallet.cpp b/src/wallet.cpp index 1087db63..dcedf86f 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1846,7 +1846,7 @@ void CReserveKey::ReturnKey() vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(set& setAddress) +void CWallet::GetAllReserveKeys(set& setAddress) const { setAddress.clear(); @@ -1908,3 +1908,53 @@ void CWallet::ListLockedCoins(std::vector& vOutpts) } } +void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { + mapKeyBirth.clear(); + + // get birth times for keys with metadata + for (std::map::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) + if (it->second.nCreateTime) + mapKeyBirth[it->first] = it->second.nCreateTime; + + // map in which we'll infer heights of other keys + CBlockIndex *pindexMax = FindBlockByHeight(std::max(0, nBestHeight - 144)); // the tip can be reorganised; use a 144-block safety margin + std::map mapKeyFirstBlock; + std::set setKeys; + GetKeys(setKeys); + BOOST_FOREACH(const CKeyID &keyid, setKeys) { + if (mapKeyBirth.count(keyid) == 0) + mapKeyFirstBlock[keyid] = pindexMax; + } + setKeys.clear(); + + // if there are no such keys, we're done + if (mapKeyFirstBlock.empty()) + return; + + // find first block that affects those keys, if there are any left + std::vector vAffected; + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { + // iterate over all wallet transactions... + const CWalletTx &wtx = (*it).second; + std::map::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); + if (blit != mapBlockIndex.end() && blit->second->IsInMainChain()) { + // ... which are already in a block + int nHeight = blit->second->nHeight; + BOOST_FOREACH(const CTxOut &txout, wtx.vout) { + // iterate over all their outputs + ::ExtractAffectedKeys(*this, txout.scriptPubKey, vAffected); + BOOST_FOREACH(const CKeyID &keyid, vAffected) { + // ... and all their affected keys + std::map::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) + rit->second = blit->second; + } + vAffected.clear(); + } + } + } + + // Extract block timestamps for those keys + for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) + mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off +} diff --git a/src/wallet.h b/src/wallet.h index 48bd5119..36b3608f 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -159,6 +159,8 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); + void GetKeyBirthTimes(std::map &mapKeyBirth) const; + /** Increment the next transaction order id @return next transaction order id */ @@ -200,7 +202,7 @@ public: void ReturnKey(int64 nIndex); bool GetKeyFromPool(CPubKey &key, bool fAllowReuse=true); int64 GetOldestKeyPoolTime(); - void GetAllReserveKeys(std::set& setAddress); + void GetAllReserveKeys(std::set& setAddress) const; std::set< std::set > GetAddressGroupings(); std::map GetAddressBalances(); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index bf23357f..702e219a 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -365,7 +365,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { int64 nIndex; ssKey >> nIndex; + CKeyPool keypool; + ssValue >> keypool; pwallet->setKeyPool.insert(nIndex); + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (pwallet->mapKeyMetadata.count(keyid) == 0) + pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); } else if (strType == "version") { From 4e534aa9d892dd6db64afda979ba1f8d44371389 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 29 Apr 2013 19:50:56 +0200 Subject: [PATCH 3/3] 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 febb475d..9b7917ff 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 44c657f8..8201dffe 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 d46309ea..dcfb023f 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; +}