From acd6501610817eee0bd1c8ea9c591f043affbaec Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 25 Jun 2011 14:57:32 +0200 Subject: [PATCH 01/12] Prepare codebase for Encrypted Keys. --- src/db.cpp | 18 +++++--- src/init.cpp | 1 - src/key.h | 70 ++++++++++++++++++++++++++++++- src/keystore.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++----- src/keystore.h | 89 +++++++++++++++++++++++++++++++++++++-- src/script.cpp | 2 +- src/ui.cpp | 2 +- src/wallet.cpp | 9 ++-- src/wallet.h | 5 ++- 9 files changed, 274 insertions(+), 29 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index f044355a..c479a452 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -685,7 +685,7 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) //// todo: shouldn't we catch exceptions and try to recover and continue? CRITICAL_BLOCK(pwallet->cs_mapWallet) - CRITICAL_BLOCK(pwallet->cs_mapKeys) + CRITICAL_BLOCK(pwallet->cs_KeyStore) { // Get cursor Dbc* pcursor = GetCursor(); @@ -765,14 +765,20 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) { vector vchPubKey; ssKey >> vchPubKey; - CWalletKey wkey; + CKey key; if (strType == "key") - ssValue >> wkey.vchPrivKey; + { + CPrivKey pkey; + ssValue >> pkey; + key.SetPrivKey(pkey); + } else + { + CWalletKey wkey; ssValue >> wkey; - - pwallet->mapKeys[vchPubKey] = wkey.vchPrivKey; - mapPubKeys[Hash160(vchPubKey)] = vchPubKey; + key.SetPrivKey(wkey.vchPrivKey); + } + pwallet->LoadKey(key); } else if (strType == "defaultkey") { diff --git a/src/init.cpp b/src/init.cpp index 635799cc..21b40d51 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -416,7 +416,6 @@ bool AppInit2(int argc, char* argv[]) //// debug print printf("mapBlockIndex.size() = %d\n", mapBlockIndex.size()); printf("nBestHeight = %d\n", nBestHeight); - printf("mapKeys.size() = %d\n", pwalletMain->mapKeys.size()); printf("setKeyPool.size() = %d\n", pwalletMain->setKeyPool.size()); printf("mapPubKeys.size() = %d\n", mapPubKeys.size()); printf("mapWallet.size() = %d\n", pwalletMain->mapWallet.size()); diff --git a/src/key.h b/src/key.h index c973d6eb..c0fce18b 100644 --- a/src/key.h +++ b/src/key.h @@ -31,6 +31,41 @@ // see www.keylength.com // script supports up to 75 for single byte push +int static inline EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key) +{ + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + +err: + + if (pub_key) + EC_POINT_free(pub_key); + if (ctx != NULL) + BN_CTX_free(ctx); + + return(ok); +} class key_error : public std::runtime_error @@ -42,8 +77,7 @@ public: // secure_allocator is defined in serialize.h typedef std::vector > CPrivKey; - - +typedef std::vector > CSecret; class CKey { @@ -102,6 +136,38 @@ public: return true; } + bool SetSecret(const CSecret& vchSecret) + { + EC_KEY_free(pkey); + pkey = EC_KEY_new_by_curve_name(NID_secp256k1); + if (pkey == NULL) + throw key_error("CKey::SetSecret() : EC_KEY_new_by_curve_name failed"); + if (vchSecret.size() != 32) + throw key_error("CKey::SetSecret() : secret must be 32 bytes"); + BIGNUM *bn = BN_bin2bn(&vchSecret[0],32,BN_new()); + if (bn == NULL) + throw key_error("CKey::SetSecret() : BN_bin2bn failed"); + if (!EC_KEY_regenerate_key(pkey,bn)) + throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed"); + BN_clear_free(bn); + fSet = true; + return true; + } + + CSecret GetSecret() const + { + CSecret vchRet; + vchRet.resize(32); + const BIGNUM *bn = EC_KEY_get0_private_key(pkey); + int nBytes = BN_num_bytes(bn); + if (bn == NULL) + throw key_error("CKey::GetSecret() : EC_KEY_get0_private_key failed"); + int n=BN_bn2bin(bn,&vchRet[32 - nBytes]); + if (n != nBytes) + throw key_error("CKey::GetSecret(): BN_bn2bin failed"); + return vchRet; + } + CPrivKey GetPrivKey() const { unsigned int nSize = i2d_ECPrivateKey(pkey, NULL); diff --git a/src/keystore.cpp b/src/keystore.cpp index 7dd045fe..765144a9 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -5,29 +5,116 @@ #include "headers.h" #include "db.h" - - -////////////////////////////////////////////////////////////////////////////// -// -// mapKeys -// - std::vector CKeyStore::GenerateNewKey() { RandAddSeedPerfmon(); CKey key; key.MakeNewKey(); if (!AddKey(key)) - throw std::runtime_error("GenerateNewKey() : AddKey failed"); + throw std::runtime_error("CKeyStore::GenerateNewKey() : AddKey failed"); return key.GetPubKey(); } -bool CKeyStore::AddKey(const CKey& key) +bool CBasicKeyStore::AddKey(const CKey& key) { - CRITICAL_BLOCK(cs_mapKeys) + CRITICAL_BLOCK(cs_KeyStore) { mapKeys[key.GetPubKey()] = key.GetPrivKey(); mapPubKeys[Hash160(key.GetPubKey())] = key.GetPubKey(); } + return true; +} + +bool CCryptoKeyStore::Unlock(const CMasterKey& vMasterKeyIn) +{ + if (!SetCrypted()) + return false; + + std::map, std::vector >::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const std::vector &vchPubKey = (*mi).first; + const std::vector &vchCryptedSecret = (*mi).second; + CSecret vchSecret; + // decrypt vchCryptedSecret using vMasterKeyIn, into vchSecret + CKey key; + key.SetSecret(vchSecret); + if (key.GetPubKey() == vchPubKey) + break; + return false; + } + vMasterKey = vMasterKeyIn; + return true; +} + +bool CCryptoKeyStore::AddKey(const CKey& key) +{ + CRITICAL_BLOCK(cs_KeyStore) + { + if (!IsCrypted()) + return CBasicKeyStore::AddKey(key); + + if (IsLocked()) + return false; + + CSecret vchSecret = key.GetSecret(); + + std::vector vchCryptedSecret; + // encrypt vchSecret using vMasterKey, into vchCryptedSecret + + AddCryptedKey(key.GetPubKey(), vchCryptedSecret); + } + return true; +} + + +bool CCryptoKeyStore::AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) +{ + CRITICAL_BLOCK(cs_KeyStore) + { + if (!SetCrypted()) + return false; + + mapCryptedKeys[vchPubKey] = vchCryptedSecret; + mapPubKeys[Hash160(vchPubKey)] = vchPubKey; + } + return true; +} + +bool CCryptoKeyStore::GetPrivKey(const std::vector &vchPubKey, CPrivKey& keyOut) const +{ + if (!IsCrypted()) + return CBasicKeyStore::GetPrivKey(vchPubKey, keyOut); + + std::map, std::vector >::const_iterator mi = mapCryptedKeys.find(vchPubKey); + if (mi != mapCryptedKeys.end()) + { + const std::vector &vchCryptedSecret = (*mi).second; + CSecret vchSecret; + // decrypt vchCryptedSecret using vMasterKey into vchSecret; + CKey key; + key.SetSecret(vchSecret); + keyOut = key.GetPrivKey(); + return true; + } + return false; } +bool CCryptoKeyStore::GenerateMasterKey() +{ + if (!mapCryptedKeys.empty()) + return false; + + RandAddSeedPerfmon(); + + vMasterKey.resize(32); + RAND_bytes(&vMasterKey[0], 32); + + if (!IsCrypted()) + { + // upgrade wallet + fUseCrypto = true; + } + + return true; +} diff --git a/src/keystore.h b/src/keystore.h index 6080d7d7..40955354 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -4,12 +4,26 @@ #ifndef BITCOIN_KEYSTORE_H #define BITCOIN_KEYSTORE_H +typedef std::vector > CMasterKey; + class CKeyStore { public: + mutable CCriticalSection cs_KeyStore; + + virtual bool AddKey(const CKey& key) =0; + virtual bool HaveKey(const std::vector &vchPubKey) const =0; + virtual bool GetPrivKey(const std::vector &vchPubKey, CPrivKey& keyOut) const =0; + virtual std::vector GenerateNewKey(); +}; + +class CBasicKeyStore : public CKeyStore +{ +protected: std::map, CPrivKey> mapKeys; - mutable CCriticalSection cs_mapKeys; - virtual bool AddKey(const CKey& key); + +public: + bool AddKey(const CKey& key); bool HaveKey(const std::vector &vchPubKey) const { return (mapKeys.count(vchPubKey) > 0); @@ -24,7 +38,76 @@ public: } return false; } - std::vector GenerateNewKey(); +}; + +class CCryptoKeyStore : public CBasicKeyStore +{ +private: + std::map, std::vector > mapCryptedKeys; + + CMasterKey vMasterKey; + + // if fUseCrypto is true, mapKeys must be empty + // if fUseCrypto is false, vMasterKey must be empty + bool fUseCrypto; + +protected: + bool IsCrypted() const + { + return fUseCrypto; + } + + bool SetCrypted() + { + if (fUseCrypto) + return true; + if (!mapKeys.empty()) + return false; + fUseCrypto = true; + } + + // will encrypt previously unencrypted keys + bool GenerateMasterKey(); + + bool GetMasterKey(CMasterKey &vMasterKeyOut) const + { + if (!IsCrypted()) + return false; + if (IsLocked()) + return false; + vMasterKeyOut = vMasterKey; + return true; + } + bool Unlock(const CMasterKey& vMasterKeyIn); + +public: + CCryptoKeyStore() : fUseCrypto(false) + { + } + + bool IsLocked() const + { + if (!IsCrypted()) + return false; + return vMasterKey.empty(); + } + + bool Lock() + { + if (!SetCrypted()) + return false; + vMasterKey.clear(); + } + + virtual bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); + bool AddKey(const CKey& key); + bool HaveKey(const std::vector &vchPubKey) const + { + if (!IsCrypted()) + return CBasicKeyStore::HaveKey(vchPubKey); + return mapCryptedKeys.count(vchPubKey) > 0; + } + bool GetPrivKey(const std::vector &vchPubKey, CPrivKey& keyOut) const; }; #endif diff --git a/src/script.cpp b/src/script.cpp index bd1b5b3c..c1752503 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1030,7 +1030,7 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash return false; // Compile solution - CRITICAL_BLOCK(keystore.cs_mapKeys) + CRITICAL_BLOCK(keystore.cs_KeyStore) { BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolution) { diff --git a/src/ui.cpp b/src/ui.cpp index 9b84fb9e..db02cb58 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -2382,7 +2382,7 @@ CAddressBookDialog::CAddressBookDialog(wxWindow* parent, const wxString& strInit m_listCtrlReceiving->SetFocus(); // Fill listctrl with address book data - CRITICAL_BLOCK(pwalletMain->cs_mapKeys) + CRITICAL_BLOCK(pwalletMain->cs_KeyStore) CRITICAL_BLOCK(pwalletMain->cs_mapAddressBook) { string strDefaultReceiving = (string)pframeMain->m_textCtrlAddress->GetValue(); diff --git a/src/wallet.cpp b/src/wallet.cpp index 6ef75ef2..a179876d 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -17,7 +17,8 @@ using namespace std; bool CWallet::AddKey(const CKey& key) { - this->CKeyStore::AddKey(key); + if (!CBasicKeyStore::AddKey(key)) + return false; if (!fFileBacked) return true; return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); @@ -783,7 +784,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Reserve a new key pair from key pool vector vchPubKey = reservekey.GetReservedKey(); - assert(mapKeys.count(vchPubKey)); + // assert(mapKeys.count(vchPubKey)); // Fill a vout to ourself, using same address type as the payment CScript scriptChange; @@ -957,7 +958,7 @@ bool CWallet::LoadWallet(bool& fFirstRunRet) if (!mapKeys.count(vchDefaultKey)) { - // Create new default key + // Create new keyUser and set as default key RandAddSeedPerfmon(); SetDefaultKey(GetKeyFromKeyPool()); @@ -1062,7 +1063,7 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) setKeyPool.erase(setKeyPool.begin()); if (!walletdb.ReadPool(nIndex, keypool)) throw runtime_error("ReserveKeyFromKeyPool() : read failed"); - if (!mapKeys.count(keypool.vchPubKey)) + if (!HaveKey(keypool.vchPubKey)) throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); assert(!keypool.vchPubKey.empty()); printf("keypool reserve %"PRI64d"\n", nIndex); diff --git a/src/wallet.h b/src/wallet.h index 7d9db972..8fb29a48 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -12,7 +12,7 @@ class CWalletTx; class CReserveKey; class CWalletDB; -class CWallet : public CKeyStore +class CWallet : public CCryptoKeyStore { private: bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, std::set >& setCoinsRet, int64& nValueRet) const; @@ -48,7 +48,10 @@ public: std::vector vchDefaultKey; + // keystore implementation bool AddKey(const CKey& key); + bool LoadKey(const CKey& key) { return CCryptoKeyStore::AddKey(key); } + bool AddToWallet(const CWalletTx& wtxIn); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate = false); bool EraseFromWallet(uint256 hash); From c1aacf0be347b10a6ab9bbce841e8127412bce41 Mon Sep 17 00:00:00 2001 From: Dylan Noblesmith Date: Fri, 24 Jun 2011 03:03:17 +0000 Subject: [PATCH 02/12] mlock() all private keys in memory Inline comment and idea come from the encprivkeys branch by Matt Corallo . --- src/serialize.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/serialize.h b/src/serialize.h index 31862a71..6952004e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -28,6 +28,18 @@ typedef unsigned long long uint64; #if defined(_MSC_VER) && _MSC_VER < 1300 #define for if (false) ; else for #endif + +#ifdef __WXMSW__ +// This is used to attempt to keep keying material out of swap +// Note that VirtualLock does not provide this as a guarantee on Windows, +// but, in practice, memory that has been VirtualLock'd almost never gets written to +// the pagefile except in rare circumstances where memory is extremely low. +#define mlock(p, n) VirtualLock((p), (n)); +#define munlock(p, n) VirtualUnlock((p), (n)); +#else +#include +#endif + class CScript; class CDataStream; class CAutoFile; @@ -755,7 +767,8 @@ struct ser_streamplaceholder // -// Allocator that clears its contents before deletion +// Allocator that locks its contents from being paged +// out of memory and clears its contents before deletion. // template struct secure_allocator : public std::allocator @@ -777,10 +790,22 @@ struct secure_allocator : public std::allocator template struct rebind { typedef secure_allocator<_Other> other; }; + T* allocate(std::size_t n, const void *hint = 0) + { + T *p; + p = std::allocator::allocate(n, hint); + if (p != NULL) + mlock(p, sizeof(T) * n); + return p; + } + void deallocate(T* p, std::size_t n) { if (p != NULL) + { memset(p, 0, sizeof(T) * n); + munlock(p, sizeof(T) * n); + } std::allocator::deallocate(p, n); } }; From a48c671957e37594d8f9e0fd51b24e7a4f44300e Mon Sep 17 00:00:00 2001 From: Doug Huff Date: Thu, 30 Jun 2011 02:04:44 +0200 Subject: [PATCH 03/12] Make mlock() and munlock() portable to systems that require the address to be on a page boundary. --- src/serialize.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/serialize.h b/src/serialize.h index 6952004e..38c533d9 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -38,6 +38,18 @@ typedef unsigned long long uint64; #define munlock(p, n) VirtualUnlock((p), (n)); #else #include +#include +/* This comes from limits.h if it's not defined there set a sane default */ +#ifndef PAGESIZE +#include +#define PAGESIZE sysconf(_SC_PAGESIZE) +#endif +#define mlock(a,b) \ + mlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ + (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) +#define munlock(a,b) \ + munlock(((void *)(((size_t)(a)) & (~((PAGESIZE)-1)))),\ + (((((size_t)(a)) + (b) - 1) | ((PAGESIZE) - 1)) + 1) - (((size_t)(a)) & (~((PAGESIZE) - 1)))) #endif class CScript; From 4e87d341f75f13bbd7d108c31c03886fbc4df56f Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 8 Jul 2011 15:47:35 +0200 Subject: [PATCH 04/12] Add wallet privkey encryption. This commit adds support for ckeys, or enCrypted private keys, to the wallet. All keys are stored in memory in their encrypted form and thus the passphrase is required from the user to spend coins, or to create new addresses. Keys are encrypted with AES-256-CBC using OpenSSL's EVP library. The key is calculated via EVP_BytesToKey using SHA512 with (by default) 25000 rounds and a random salt. By default, the user's wallet remains unencrypted until they call the RPC command encryptwallet or, from the GUI menu, Options-> Encrypt Wallet. When the user is attempting to call RPC functions which require the password to unlock the wallet, an error will be returned unless they call walletpassphrase