wallet-utility: extract addresses and private keys

usage: ./wallet-utility -datadir=<directory>
help: ./wallet-utility -h

Zcash: Backported to older chainparams code, added Zcash libs to LDADD
This commit is contained in:
Chethan Krishna 2016-06-30 18:00:50 -04:00 committed by Simon
parent 64b1d5a9c4
commit 7814d628ce
9 changed files with 460 additions and 3 deletions

1
.gitignore vendored
View File

@ -106,6 +106,7 @@ qa/pull-tester/test.*/*
/doc/doxygen/
libzcashconsensus.pc
wallet-utility
contrib/debian/files
contrib/debian/substvars

View File

@ -14,6 +14,7 @@ endif
BITCOIND_BIN=$(top_builddir)/src/$(BITCOIN_DAEMON_NAME)$(EXEEXT)
BITCOIN_CLI_BIN=$(top_builddir)/src/$(BITCOIN_CLI_NAME)$(EXEEXT)
WALLET_UTILITY_BIN=$(top_builddir)/src/wallet-utility$(EXEEXT)
DIST_DOCS = $(wildcard doc/*.md) $(wildcard doc/release-notes/*.md)
@ -48,6 +49,9 @@ $(BITCOIND_BIN): FORCE
$(BITCOIN_CLI_BIN): FORCE
$(MAKE) -C src $(@F)
$(WALLET_UTILITY_BIN): FORCE
$(MAKE) -C src $(@F)
if USE_LCOV
baseline.info:

View File

@ -218,7 +218,7 @@ CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS"
AC_ARG_WITH([utils],
[AS_HELP_STRING([--with-utils],
[build zcash-cli zcash-tx (default=yes)])],
[build zcash-cli zcash-tx wallet-utility (default=yes)])],
[build_bitcoin_utils=$withval],
[build_bitcoin_utils=yes])
@ -771,7 +771,7 @@ AC_MSG_CHECKING([whether to build bitcoind])
AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes])
AC_MSG_RESULT($build_bitcoind)
AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx)])
AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx wallet-utility)])
AM_CONDITIONAL([BUILD_BITCOIN_UTILS], [test x$build_bitcoin_utils = xyes])
AC_MSG_RESULT($build_bitcoin_utils)

View File

@ -97,6 +97,9 @@ endif
if BUILD_BITCOIN_UTILS
bin_PROGRAMS += zcash-cli zcash-tx
if ENABLE_WALLET
bin_PROGRAMS += wallet-utility
endif
endif
LIBZCASH_H = \
@ -470,6 +473,14 @@ zcash_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
zcash_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
zcash_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)
wallet_utility_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
wallet_utility_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
endif
if TARGET_WINDOWS
zcash_cli_SOURCES += bitcoin-cli-res.rc
endif
@ -486,6 +497,22 @@ zcash_cli_LDADD = \
$(LIBSNARK) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH_LIBS)
if ENABLE_WALLET
wallet_utility_LDADD = \
libbitcoin_wallet.a \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_CRYPTO) \
$(LIBSECP256K1) \
$(LIBBITCOIN_UTIL) \
$(BOOST_LIBS) \
$(BDB_LIBS) \
$(CRYPTO_LIBS) \
$(LIBZCASH) \
$(LIBSNARK) \
$(LIBZCASH_LIBS)
endif
#
# zcash-tx binary #

View File

@ -107,7 +107,8 @@ if ENABLE_WALLET
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)

BIN
src/test/data/wallet.dat Normal file

Binary file not shown.

View File

@ -1,6 +1,7 @@
#ifndef BITCOIN_TEST_TEST_BITCOIN_H
#define BITCOIN_TEST_TEST_BITCOIN_H
#include "chainparamsbase.h"
#include "consensus/upgrades.h"
#include "pubkey.h"
#include "txdb.h"
@ -37,6 +38,17 @@ struct TestingSetup: public JoinSplitTestingSetup {
~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(CBaseChainParams::Network network = CBaseChainParams::MAIN);
~WalletSetup();
};
class CTxMemPoolEntry;
class CTxMemPool;

View File

@ -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()

339
src/wallet-utility.cpp Normal file
View File

@ -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 {
SelectParamsFromCommandLine();
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;
}