changeset 29686:d39665a7ebb8

wallet-utility: extract addresses and private keys usage: ./wallet-utility -datadir=<directory> help: ./wallet-utility -h
author Chethan Krishna <chethan@bitpay.com>
date Thu, 30 Jun 2016 18:00:50 -0400
parents c778c74e4adb
children 521c8602e897
files .gitignore Makefile.am configure.ac src/Makefile.am src/Makefile.test.include src/test/data/wallet.dat src/test/test_bitcoin.h src/test/walletutil_tests.cpp src/wallet-utility.cpp
diffstat 9 files changed, 448 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/.gitignore	Thu Jun 16 10:08:08 2016 -0400
+++ b/.gitignore	Thu Jun 30 18:00:50 2016 -0400
@@ -114,3 +114,4 @@
 /doc/doxygen/
 
 libbitcoinconsensus.pc
+wallet-utility
--- a/Makefile.am	Thu Jun 16 10:08:08 2016 -0400
+++ b/Makefile.am	Thu Jun 30 18:00:50 2016 -0400
@@ -12,6 +12,7 @@
 BITCOIND_BIN=$(top_builddir)/src/bitcoind$(EXEEXT)
 BITCOIN_QT_BIN=$(top_builddir)/src/qt/bitcoin-qt$(EXEEXT)
 BITCOIN_CLI_BIN=$(top_builddir)/src/bitcoin-cli$(EXEEXT)
+WALLET_UTILITY_BIN=$(top_builddir)/src/wallet-utility$(EXEEXT)
 BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT)
 
 OSX_APP=Bitcoin-Qt.app
@@ -63,6 +64,7 @@
 	STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release
 	STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_QT_BIN) $(top_builddir)/release
 	STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_CLI_BIN) $(top_builddir)/release
+	STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(WALLET_UTILITY_BIN) $(top_builddir)/release
 	@test -f $(MAKENSIS) && $(MAKENSIS) -V2 $(top_builddir)/share/setup.nsi || \
 	  echo error: could not build $@
 	@echo built $@
@@ -145,6 +147,9 @@
 $(BITCOIN_CLI_BIN): FORCE
 	$(MAKE) -C src $(@F)
 
+$(WALLET_UTILITY_BIN): FORCE
+	$(MAKE) -C src $(@F)
+
 if USE_LCOV
 
 baseline.info:
--- a/configure.ac	Thu Jun 16 10:08:08 2016 -0400
+++ b/configure.ac	Thu Jun 30 18:00:50 2016 -0400
@@ -191,7 +191,7 @@
 
 AC_ARG_WITH([utils],
   [AS_HELP_STRING([--with-utils],
-  [build bitcoin-cli bitcoin-tx (default=yes)])],
+  [build bitcoin-cli bitcoin-tx wallet-utility (default=yes)])],
   [build_bitcoin_utils=$withval],
   [build_bitcoin_utils=yes])
 
@@ -766,7 +766,7 @@
 AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes])
 AC_MSG_RESULT($build_bitcoind)
 
-AC_MSG_CHECKING([whether to build utils (bitcoin-cli bitcoin-tx)])
+AC_MSG_CHECKING([whether to build utils (bitcoin-cli bitcoin-tx wallet-utility)])
 AM_CONDITIONAL([BUILD_BITCOIN_UTILS], [test x$build_bitcoin_utils = xyes])
 AC_MSG_RESULT($build_bitcoin_utils)
 
--- a/src/Makefile.am	Thu Jun 16 10:08:08 2016 -0400
+++ b/src/Makefile.am	Thu Jun 30 18:00:50 2016 -0400
@@ -74,6 +74,9 @@
 
 if BUILD_BITCOIN_UTILS
   bin_PROGRAMS += bitcoin-cli bitcoin-tx
+if ENABLE_WALLET
+  bin_PROGRAMS += wallet-utility
+endif
 endif
 
 .PHONY: FORCE check-symbols check-security
@@ -367,6 +370,14 @@
 bitcoin_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
 bitcoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
 
+# wallet-utility binary #
+if ENABLE_WALLET
+wallet_utility_SOURCES = wallet-utility.cpp
+wallet_utility_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAG)
+wallet_utility_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+endif
+
 if TARGET_WINDOWS
 bitcoin_cli_SOURCES += bitcoin-cli-res.rc
 endif
@@ -377,6 +388,10 @@
   $(LIBBITCOIN_UTIL)
 
 bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS)
+if ENABLE_WALLET
+wallet_utility_LDADD = libbitcoin_wallet.a $(LIBBITCOIN_COMMON) $(LIBBITCOIN_CRYPTO) $(LIBSECP256K1) $(LIBBITCOIN_UTIL) $(BOOST_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS)
+endif
+
 #
 
 # bitcoin-tx binary #
--- a/src/Makefile.test.include	Thu Jun 16 10:08:08 2016 -0400
+++ b/src/Makefile.test.include	Thu Jun 30 18:00:50 2016 -0400
@@ -92,7 +92,8 @@
 BITCOIN_TESTS += \
   test/accounting_tests.cpp \
   wallet/test/wallet_tests.cpp \
-  test/rpc_wallet_tests.cpp
+  test/rpc_wallet_tests.cpp \
+  test/walletutil_tests.cpp
 endif
 
 test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
Binary file src/test/data/wallet.dat has changed
--- a/src/test/test_bitcoin.h	Thu Jun 16 10:08:08 2016 -0400
+++ b/src/test/test_bitcoin.h	Thu Jun 30 18:00:50 2016 -0400
@@ -33,6 +33,17 @@
     ~TestingSetup();
 };
 
+/** Wallet setup that configures a complete environment.
+ * Included are data directory, coins database, script check threads
+ * and wallet with 5 unused keys.
+ */
+struct WalletSetup: public BasicTestingSetup {
+	boost::filesystem::path pathTemp;
+
+	WalletSetup(const std::string& chainName = CBaseChainParams::MAIN);
+	~WalletSetup();
+};
+
 class CBlock;
 struct CMutableTransaction;
 class CScript;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/walletutil_tests.cpp	Thu Jun 30 18:00:50 2016 -0400
@@ -0,0 +1,73 @@
+#include "main.h"
+#include "test/test_bitcoin.h"
+#include <boost/test/unit_test.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#ifdef ENABLE_WALLET
+#include "wallet/db.h"
+#include "wallet/wallet.h"
+#endif
+
+using namespace std;
+
+BOOST_FIXTURE_TEST_SUITE(walletutil_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(walletutil_test)
+{
+	/*
+	 * addresses and private keys in test/data/wallet.dat
+	 */
+	string expected_addr = "[ \"13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av\", \"1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8\", \"13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F\" ]\n";
+	string expected_addr_pkeys = "[ {\"addr\" : \"13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av\", \"pkey\" : \"5Jz5BWE2WQxp1hGqDZeisQFV1mRFR2AVBAgiXCbNcZyXNjD9aUd\"}, {\"addr\" : \"1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8\", \"pkey\" : \"5HsX2b3v2GjngYQ5ZM4mLp2b2apw6aMNVaPELV1YmpiYR1S4jzc\"}, {\"addr\" : \"13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F\", \"pkey\" : \"5KCWAs1wX2ESiL4PfDR8XYVSSETHFd2jaRGxt1QdanBFTit4XcH\"} ]\n";
+
+#ifdef WIN32
+	string strCmd = "wallet-utility -datadir=test/data/ > test/data/op.txt";
+#else
+	string strCmd = "./wallet-utility -datadir=test/data/ > test/data/op.txt";
+#endif
+	int ret = system(strCmd.c_str());
+	BOOST_CHECK(ret == 0);
+
+	boost::filesystem::path opPath = "test/data/op.txt";
+	boost::filesystem::ifstream fIn;
+	fIn.open(opPath, std::ios::in);
+
+	if (!fIn)
+	{
+		std::cerr << "Could not open the output file" << std::endl;
+	}
+
+	stringstream ss_addr;
+	ss_addr << fIn.rdbuf();
+	fIn.close();
+	boost::filesystem::remove(opPath);
+
+	string obtained = ss_addr.str();
+	BOOST_CHECK_EQUAL(obtained, expected_addr);
+
+#ifdef WIN32
+	strCmd = "wallet-utility -datadir=test/data/ -dumppass > test/data/op.txt";
+#else
+	strCmd = "./wallet-utility -datadir=test/data/ -dumppass > test/data/op.txt";
+#endif
+
+	ret = system(strCmd.c_str());
+	BOOST_CHECK(ret == 0);
+
+	fIn.open(opPath, std::ios::in);
+
+	if (!fIn)
+	{
+		std::cerr << "Could not open the output file" << std::endl;
+	}
+
+	stringstream ss_addr_pkeys;
+	ss_addr_pkeys << fIn.rdbuf();
+	fIn.close();
+	boost::filesystem::remove(opPath);
+
+	obtained = ss_addr_pkeys.str();
+	BOOST_CHECK_EQUAL(obtained, expected_addr_pkeys);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wallet-utility.cpp	Thu Jun 30 18:00:50 2016 -0400
@@ -0,0 +1,339 @@
+#include <iostream>
+#include <string>
+
+// Include local headers
+#include "wallet/walletdb.h"
+#include "util.h"
+#include "base58.h"
+#include "wallet/crypter.h"
+#include <boost/foreach.hpp>
+
+
+void show_help()
+{
+	std::cout <<
+		"This program outputs Bitcoin addresses and private keys from a wallet.dat file" << std::endl
+		<< std::endl
+		<< "Usage and options: "
+		<< std::endl
+		<< " -datadir=<directory> to tell the program where your wallet is"
+		<< std::endl
+		<< " -wallet=<name> (Optional) if your wallet is not named wallet.dat"
+		<< std::endl
+		<< " -regtest or -testnet (Optional) dumps addresses from regtest/testnet"
+		<< std::endl
+		<< " -dumppass (Optional)if you want to extract private keys associated with addresses"
+		<< std::endl
+		<< "    -pass=<walletpassphrase> if you have encrypted private keys stored in your wallet"
+		<< std::endl;
+}
+
+
+class WalletUtilityDB : public CDB
+{
+	private:
+		typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
+		MasterKeyMap mapMasterKeys;
+		unsigned int nMasterKeyMaxID;
+		SecureString mPass;
+		std::vector<CKeyingMaterial> vMKeys;
+
+	public:
+		WalletUtilityDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose)
+	{
+		nMasterKeyMaxID = 0;
+		mPass.reserve(100);
+	}
+
+		std::string getAddress(CDataStream ssKey);
+		std::string getKey(CDataStream ssKey, CDataStream ssValue);
+		std::string getCryptedKey(CDataStream ssKey, CDataStream ssValue, std::string masterPass);
+		bool updateMasterKeys(CDataStream ssKey, CDataStream ssValue);
+		bool parseKeys(bool dumppriv, std::string masterPass);
+
+		bool DecryptSecret(const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext);
+		bool Unlock();
+		bool DecryptKey(const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key);
+};
+
+
+/*
+ * Address from a public key in base58
+ */
+std::string WalletUtilityDB::getAddress(CDataStream ssKey)
+{
+	CPubKey vchPubKey;
+	ssKey >> vchPubKey;
+	CKeyID id = vchPubKey.GetID();
+	std::string strAddr = CBitcoinAddress(id).ToString();
+
+	return strAddr;
+}
+
+
+/*
+ * Non encrypted private key in WIF
+ */
+std::string WalletUtilityDB::getKey(CDataStream ssKey, CDataStream ssValue)
+{
+	std::string strKey;
+	CPubKey vchPubKey;
+	ssKey >> vchPubKey;
+	CPrivKey pkey;
+	CKey key;
+
+	ssValue >> pkey;
+	if (key.Load(pkey, vchPubKey, true))
+		strKey = CBitcoinSecret(key).ToString();
+
+	return strKey;
+}
+
+
+bool WalletUtilityDB::DecryptSecret(const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
+{
+	CCrypter cKeyCrypter;
+	std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
+	memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
+
+	BOOST_FOREACH(const CKeyingMaterial vMKey, vMKeys)
+	{
+		if(!cKeyCrypter.SetKey(vMKey, chIV))
+			continue;
+		if (cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)))
+			return true;
+	}
+	return false;
+}
+
+
+bool WalletUtilityDB::Unlock()
+{
+	CCrypter crypter;
+	CKeyingMaterial vMasterKey;
+
+	BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
+	{
+		if(!crypter.SetKeyFromPassphrase(mPass, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
+			return false;
+		if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
+			continue; // try another master key
+		vMKeys.push_back(vMasterKey);
+	}
+	return true;
+}
+
+
+bool WalletUtilityDB::DecryptKey(const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key)
+{
+	CKeyingMaterial vchSecret;
+	if(!DecryptSecret(vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
+		return false;
+
+	if (vchSecret.size() != 32)
+		return false;
+
+	key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed());
+	return true;
+}
+
+
+/*
+ * Encrypted private key in WIF format
+ */
+std::string WalletUtilityDB::getCryptedKey(CDataStream ssKey, CDataStream ssValue, std::string masterPass)
+{
+	mPass = masterPass.c_str();
+	CPubKey vchPubKey;
+	ssKey >> vchPubKey;
+	CKey key;
+
+	std::vector<unsigned char> vKey;
+	ssValue >> vKey;
+
+	if (!Unlock())
+		return "";
+
+	if(!DecryptKey(vKey, vchPubKey, key))
+		return "";
+
+	std::string strKey = CBitcoinSecret(key).ToString();
+	return strKey;
+}
+
+
+/*
+ * Master key derivation
+ */
+bool WalletUtilityDB::updateMasterKeys(CDataStream ssKey, CDataStream ssValue)
+{
+	unsigned int nID;
+	ssKey >> nID;
+	CMasterKey kMasterKey;
+	ssValue >> kMasterKey;
+	if (mapMasterKeys.count(nID) != 0)
+	{
+		std::cout << "Error reading wallet database: duplicate CMasterKey id " << nID << std::endl;
+		return false;
+	}
+	mapMasterKeys[nID] = kMasterKey;
+
+	if (nMasterKeyMaxID < nID)
+		nMasterKeyMaxID = nID;
+
+	return true;
+}
+
+
+/*
+ * Look at all the records and parse keys for addresses and private keys
+ */
+bool WalletUtilityDB::parseKeys(bool dumppriv, std::string masterPass)
+{
+	DBErrors result = DB_LOAD_OK;
+	std::string strType;
+	bool first = true;
+
+	try {
+		Dbc* pcursor = GetCursor();
+		if (!pcursor)
+		{
+			LogPrintf("Error getting wallet database cursor\n");
+			result = DB_CORRUPT;
+		}
+
+		if (dumppriv)
+		{
+			while (result == DB_LOAD_OK && true)
+			{
+				CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+				CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+				int result = ReadAtCursor(pcursor, ssKey, ssValue);
+
+				if (result == DB_NOTFOUND) {
+					break;
+				}
+				else if (result != 0)
+				{
+					LogPrintf("Error reading next record from wallet database\n");
+					result = DB_CORRUPT;
+					break;
+				}
+
+				ssKey >> strType;
+				if (strType == "mkey")
+				{
+					updateMasterKeys(ssKey, ssValue);
+				}
+			}
+			pcursor->close();
+			pcursor = GetCursor();
+		}
+
+		while (result == DB_LOAD_OK && true)
+		{
+			CDataStream ssKey(SER_DISK, CLIENT_VERSION);
+			CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+			int ret = ReadAtCursor(pcursor, ssKey, ssValue);
+
+			if (ret == DB_NOTFOUND)
+			{
+				std::cout << " ]" << std::endl;
+				first = true;
+				break;
+			}
+			else if (ret != DB_LOAD_OK)
+			{
+				LogPrintf("Error reading next record from wallet database\n");
+				result = DB_CORRUPT;
+				break;
+			}
+
+			ssKey >> strType;
+
+			if (strType == "key" || strType == "ckey")
+			{
+				std::string strAddr = getAddress(ssKey);
+				std::string strKey = "";
+
+
+				if (dumppriv && strType == "key")
+					strKey = getKey(ssKey, ssValue);
+				if (dumppriv && strType == "ckey")
+				{
+					if (masterPass == "")
+					{
+						std::cout << "Encrypted wallet, please provide a password. See help below" << std::endl;
+						show_help();
+						result = DB_LOAD_FAIL;
+						break;
+					}
+					strKey = getCryptedKey(ssKey, ssValue, masterPass);
+				}
+
+				if (strAddr != "")
+				{
+					if (first)
+						std::cout << "[ ";
+					else
+						std::cout << ", ";
+				}
+
+				if (dumppriv)
+				{
+					std::cout << "{\"addr\" : \"" + strAddr + "\", "
+						<< "\"pkey\" : \"" + strKey + "\"}"
+						<< std::flush;
+				}
+				else
+				{
+					std::cout << "\"" + strAddr + "\"";
+				}
+
+				first = false;
+			}
+		}
+
+		pcursor->close();
+	} catch (DbException &e) {
+		std::cout << "DBException caught " << e.get_errno() << std::endl;
+	} catch (std::exception &e) {
+		std::cout << "Exception caught " << std::endl;
+	}
+
+	if (result == DB_LOAD_OK)
+		return true;
+	else
+		return false;
+}
+
+
+int main(int argc, char* argv[])
+{
+	ParseParameters(argc, argv);
+	std::string walletFile = GetArg("-wallet", "wallet.dat");
+	std::string masterPass = GetArg("-pass", "");
+	bool fDumpPass = GetBoolArg("-dumppass", false);
+	bool help = GetBoolArg("-h", false);
+	bool result = false;
+
+	if (help)
+	{
+		show_help();
+		return 0;
+	}
+
+	try {
+		SelectParams(ChainNameFromCommandLine());
+		result = WalletUtilityDB(walletFile, "r").parseKeys(fDumpPass, masterPass);
+	}
+	catch (const std::exception& e) {
+		std::cout << "Error opening wallet file " << walletFile << std::endl;
+		std::cout << e.what() << std::endl;
+	}
+
+	if (result)
+		return 0;
+	else
+		return -1;
+}