diff --git a/doc/README b/doc/README index a4df4c227b..421c83cd53 100644 --- a/doc/README +++ b/doc/README @@ -24,6 +24,44 @@ Unpack the files into a directory and run: bin/64/bitcoin (GUI, 64-bit) bin/64/bitcoind (headless, 64-bit) + +Wallet Encryption +----------------- +Bitcoin supports native wallet encryption so that people who steal your wallet +file don't automatically get access to all of your Bitcoins. In order to enable +this feature, chose "Encrypt Wallet" from the Options menu. You will be prompted +to enter a passphrase, which will be used as the key to encrypt your wallet and +will be needed every time you wish to send Bitcoins. If you lose this passphrase, +you will lose access to spend all of the bitcoins in your wallet, no one, not even +the Bitcoin developers can recover your Bitcoins. This means you are responsible +for your own security, store your password in a secure location and do not forget +it. + +Remember that the encryption built into bitcoin only encrypts the actual keys +which are required to send your bitcoins, not the full wallet. This means that +someone who steals your wallet file will be able to see all the addresses which +belong to you, as well as the relevant transactions, you are only protected from +someone spending your coins. + +It is recommended that you backup your wallet file before you encrypt your wallet. +To do this, close the Bitcoin client and copy the wallet.dat file from ~/.bitcoin/ +on Linux, /Users/(user name)/Application Support/Bitcoin/ on Mac OSX, and +%APPDATA%/Bitcoin/ on Windows (that is /Users/(user name)/AppData/Roaming/Bitcoin on +Windows Vista and 7 and /Documents and Settings/(user name)/Application Data/Bitcoin +on Windows XP). Once you have copied that file to a safe location, reopen the +Bitcoin client and Encrypt your wallet. If everything goes fine, delete the backup +and enjoy your encrypted wallet. Note that once you encrypt your wallet, you will +never be able to go back to a version of the Bitcoin client older than 0.4. + +Keep in mind that you are always responsible for you own security. All it takes is a +slightly more advanced wallet-stealing trojan which installs a keylogger to steal +your wallet passphrase as you enter it in addition to your wallet file and you have +lost all your Bitcoins. Wallet encryption cannot keep you safe if you do not practice +good security, such as running up-to-date antivirus software, only entering your +wallet passphrase in the Bitcoin client and using the same passphrase only as your +wallet passphrase. + + See the documentation at the bitcoin wiki: https://en.bitcoin.it/wiki/Main_Page diff --git a/share/uiproject.fbp b/share/uiproject.fbp index d9d46382d3..d5e6b1e36b 100644 --- a/share/uiproject.fbp +++ b/share/uiproject.fbp @@ -162,6 +162,36 @@ OnMenuOptionsChangeYourAddress + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + &Encrypt Wallet... + m_menuOptionsEncryptWallet + public + + + OnMenuOptionsEncryptWallet + + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + &Change Wallet Encryption Passphrase... + m_menuOptionsChangeWalletPassphrase + public + + + OnMenuOptionsChangeWalletPassphrase + + 0 diff --git a/src/crypter.cpp b/src/crypter.cpp new file mode 100644 index 0000000000..9a8e6ca89a --- /dev/null +++ b/src/crypter.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2011 The Bitcoin Developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include "headers.h" +#ifdef __WXMSW__ +#include +#endif + +#include "crypter.h" +#include "main.h" +#include "util.h" + +bool CCrypter::SetKeyFromPassphrase(const std::string& strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod) +{ + if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE) + return false; + + // Try to keep the keydata out of swap (and be a bit over-careful to keep the IV that we don't even use out of swap) + // Note that this does nothing about suspend-to-disk (which will put all our key data on disk) + // Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process. + mlock(&chKey[0], sizeof chKey); + mlock(&chIV[0], sizeof chIV); + + int i = 0; + if (nDerivationMethod == 0) + i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0], + (unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV); + + if (i != WALLET_CRYPTO_KEY_SIZE) + { + memset(&chKey, 0, sizeof chKey); + memset(&chIV, 0, sizeof chIV); + return false; + } + + fKeySet = true; + return true; +} + +bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector& chNewIV) +{ + if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_KEY_SIZE) + return false; + + // Try to keep the keydata out of swap + // Note that this does nothing about suspend-to-disk (which will put all our key data on disk) + // Note as well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory of the running process. + mlock(&chKey[0], sizeof chKey); + mlock(&chIV[0], sizeof chIV); + + memcpy(&chKey[0], &chNewKey[0], sizeof chKey); + memcpy(&chIV[0], &chNewIV[0], sizeof chIV); + + fKeySet = true; + return true; +} + +bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext) +{ + if (!fKeySet) + return false; + + // max ciphertext len for a n bytes of plaintext is + // n + AES_BLOCK_SIZE - 1 bytes + int nLen = vchPlaintext.size(); + int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0; + vchCiphertext = std::vector (nCLen); + + EVP_CIPHER_CTX ctx; + + EVP_CIPHER_CTX_init(&ctx); + EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV); + + EVP_EncryptUpdate(&ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen); + EVP_EncryptFinal_ex(&ctx, (&vchCiphertext[0])+nCLen, &nFLen); + + EVP_CIPHER_CTX_cleanup(&ctx); + + vchCiphertext.resize(nCLen + nFLen); + return true; +} + +bool CCrypter::Decrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext) +{ + if (!fKeySet) + return false; + + // plaintext will always be equal to or lesser than length of ciphertext + int nLen = vchCiphertext.size(); + int nPLen = nLen, nFLen = 0; + + vchPlaintext = CKeyingMaterial(nPLen); + + EVP_CIPHER_CTX ctx; + + EVP_CIPHER_CTX_init(&ctx); + EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV); + + EVP_DecryptUpdate(&ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen); + EVP_DecryptFinal_ex(&ctx, (&vchPlaintext[0])+nPLen, &nFLen); + + EVP_CIPHER_CTX_cleanup(&ctx); + + vchPlaintext.resize(nPLen + nFLen); + return true; +} + + +bool EncryptSecret(CKeyingMaterial& vMasterKey, const CSecret &vchPlaintext, const uint256& nIV, std::vector &vchCiphertext) +{ + CCrypter cKeyCrypter; + std::vector chIV(WALLET_CRYPTO_KEY_SIZE); + memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE); + if(!cKeyCrypter.SetKey(vMasterKey, chIV)) + return false; + return cKeyCrypter.Encrypt((CKeyingMaterial)vchPlaintext, vchCiphertext); +} + +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector& vchCiphertext, const uint256& nIV, CSecret& vchPlaintext) +{ + CCrypter cKeyCrypter; + std::vector chIV(WALLET_CRYPTO_KEY_SIZE); + memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE); + if(!cKeyCrypter.SetKey(vMasterKey, chIV)) + return false; + return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); +} diff --git a/src/crypter.h b/src/crypter.h new file mode 100644 index 0000000000..5b95ea415e --- /dev/null +++ b/src/crypter.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Bitcoin Developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef __CRYPTER_H__ +#define __CRYPTER_H__ + +#include "key.h" + +const unsigned int WALLET_CRYPTO_KEY_SIZE = 32; +const unsigned int WALLET_CRYPTO_SALT_SIZE = 8; + +/* +Private key encryption is done based on a CMasterKey, +which holds a salt and random encryption key. + +CMasterKeys is encrypted using AES-256-CBC using a key +derived using derivation method nDerivationMethod +(0 == EVP_sha512()) and derivation iterations nDeriveIterations. +vchOtherDerivationParameters is provided for alternative algorithms +which may require more parameters (such as scrypt). + +Wallet Private Keys are then encrypted using AES-256-CBC +with the double-sha256 of the private key as the IV, and the +master key's key as the encryption key. +*/ + +class CMasterKey +{ +public: + std::vector vchCryptedKey; + std::vector vchSalt; + // 0 = EVP_sha512() + // 1 = scrypt() + unsigned int nDerivationMethod; + unsigned int nDeriveIterations; + // Use this for more parameters to key derivation, + // such as the various parameters to scrypt + std::vector vchOtherDerivationParameters; + + IMPLEMENT_SERIALIZE + ( + READWRITE(vchCryptedKey); + READWRITE(vchSalt); + READWRITE(nDerivationMethod); + READWRITE(nDeriveIterations); + READWRITE(vchOtherDerivationParameters); + ) + CMasterKey() + { + // 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M + // ie slightly lower than the lowest hardware we need bother supporting + nDeriveIterations = 25000; + nDerivationMethod = 0; + vchOtherDerivationParameters = std::vector(0); + } +}; + +typedef std::vector > CKeyingMaterial; + +class CCrypter +{ +private: + unsigned char chKey[WALLET_CRYPTO_KEY_SIZE]; + unsigned char chIV[WALLET_CRYPTO_KEY_SIZE]; + bool fKeySet; + +public: + bool SetKeyFromPassphrase(const std::string &strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); + bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext); + bool Decrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext); + bool SetKey(const CKeyingMaterial& chNewKey, const std::vector& chNewIV); + + void CleanKey() + { + memset(&chKey, 0, sizeof chKey); + memset(&chIV, 0, sizeof chIV); + munlock(&chKey, sizeof chKey); + munlock(&chIV, sizeof chIV); + fKeySet = false; + } + + CCrypter() + { + fKeySet = false; + } + + ~CCrypter() + { + CleanKey(); + } +}; + +bool EncryptSecret(CKeyingMaterial& vMasterKey, const CSecret &vchPlaintext, const uint256& nIV, std::vector &vchCiphertext); +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector &vchCiphertext, const uint256& nIV, CSecret &vchPlaintext); + +#endif diff --git a/src/db.cpp b/src/db.cpp index f044355a35..ebe7e3a8f6 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,42 @@ 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); + } + if (!pwallet->LoadKey(key)) + return false; + } + else if (strType == "mkey") + { + unsigned int nID; + ssKey >> nID; + CMasterKey kMasterKey; + ssValue >> kMasterKey; + if(pwallet->mapMasterKeys.count(nID) != 0) + return false; + pwallet->mapMasterKeys[nID] = kMasterKey; + if (pwallet->nMasterKeyMaxID < nID) + pwallet->nMasterKeyMaxID = nID; + } + else if (strType == "ckey") + { + vector vchPubKey; + ssKey >> vchPubKey; + vector vchPrivKey; + ssValue >> vchPrivKey; + if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + return false; } else if (strType == "defaultkey") { @@ -800,7 +828,6 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins; #endif if (strKey == "nTransactionFee") ssValue >> nTransactionFee; - if (strKey == "addrIncoming") ssValue >> addrIncoming; if (strKey == "fLimitProcessors") ssValue >> fLimitProcessors; if (strKey == "nLimitProcessors") ssValue >> nLimitProcessors; if (strKey == "fMinimizeToTray") ssValue >> fMinimizeToTray; @@ -819,7 +846,6 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) printf("nFileVersion = %d\n", nFileVersion); printf("fGenerateBitcoins = %d\n", fGenerateBitcoins); printf("nTransactionFee = %"PRI64d"\n", nTransactionFee); - printf("addrIncoming = %s\n", addrIncoming.ToString().c_str()); printf("fMinimizeToTray = %d\n", fMinimizeToTray); printf("fMinimizeOnClose = %d\n", fMinimizeOnClose); printf("fUseProxy = %d\n", fUseProxy); diff --git a/src/db.h b/src/db.h index b89b34e009..c8d46d8fe6 100644 --- a/src/db.h +++ b/src/db.h @@ -391,6 +391,25 @@ public: return Write(std::make_pair(std::string("key"), vchPubKey), vchPrivKey, false); } + bool WriteCryptedKey(const std::vector& vchPubKey, const std::vector& vchCryptedSecret, bool fEraseUnencryptedKey = true) + { + nWalletDBUpdated++; + if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) + return false; + if (fEraseUnencryptedKey) + { + Erase(std::make_pair(std::string("key"), vchPubKey)); + Erase(std::make_pair(std::string("wkey"), vchPubKey)); + } + return true; + } + + bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) + { + nWalletDBUpdated++; + return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true); + } + bool WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; diff --git a/src/init.cpp b/src/init.cpp index 635799ccf9..21b40d5193 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 c973d6eb82..c43e4ee235 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); @@ -154,22 +220,6 @@ public: return false; return true; } - - static bool Sign(const CPrivKey& vchPrivKey, uint256 hash, std::vector& vchSig) - { - CKey key; - if (!key.SetPrivKey(vchPrivKey)) - return false; - return key.Sign(hash, vchSig); - } - - static bool Verify(const std::vector& vchPubKey, uint256 hash, const std::vector& vchSig) - { - CKey key; - if (!key.SetPubKey(vchPubKey)) - return false; - return key.Verify(hash, vchSig); - } }; #endif diff --git a/src/keystore.cpp b/src/keystore.cpp index 7dd045fe5f..de13958a8b 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -4,13 +4,7 @@ #include "headers.h" #include "db.h" - - - -////////////////////////////////////////////////////////////////////////////// -// -// mapKeys -// +#include "crypter.h" std::vector CKeyStore::GenerateNewKey() { @@ -18,16 +12,136 @@ std::vector CKeyStore::GenerateNewKey() 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_mapPubKeys) + CRITICAL_BLOCK(cs_KeyStore) { mapKeys[key.GetPubKey()] = key.GetPrivKey(); mapPubKeys[Hash160(key.GetPubKey())] = key.GetPubKey(); } + return true; } +std::vector CCryptoKeyStore::GenerateNewKey() +{ + RandAddSeedPerfmon(); + CKey key; + key.MakeNewKey(); + if (!AddKey(key)) + throw std::runtime_error("CCryptoKeyStore::GenerateNewKey() : AddKey failed"); + return key.GetPubKey(); +} + +bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) +{ + CRITICAL_BLOCK(cs_vMasterKey) + { + 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; + if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecret)) + return false; + 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) + CRITICAL_BLOCK(cs_vMasterKey) + { + if (!IsCrypted()) + return CBasicKeyStore::AddKey(key); + + if (IsLocked()) + return false; + + std::vector vchCryptedSecret; + std::vector vchPubKey = key.GetPubKey(); + if (!EncryptSecret(vMasterKey, key.GetSecret(), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + return false; + + if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) + return false; + } + return true; +} + + +bool CCryptoKeyStore::AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) +{ + CRITICAL_BLOCK(cs_mapPubKeys) + CRITICAL_BLOCK(cs_KeyStore) + { + if (!SetCrypted()) + return false; + + mapCryptedKeys[vchPubKey] = vchCryptedSecret; + mapPubKeys[Hash160(vchPubKey)] = vchPubKey; + } + return true; +} + +bool CCryptoKeyStore::GetPrivKey(const std::vector &vchPubKey, CKey& keyOut) const +{ + CRITICAL_BLOCK(cs_vMasterKey) + { + 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; + if (!DecryptSecret(vMasterKey, (*mi).second, Hash((*mi).first.begin(), (*mi).first.end()), vchSecret)) + return false; + keyOut.SetSecret(vchSecret); + return true; + } + } + return false; +} + +bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + CRITICAL_BLOCK(cs_KeyStore) + CRITICAL_BLOCK(cs_vMasterKey) + { + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + CKey key; + BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys) + { + if (!key.SetPrivKey(mKey.second)) + return false; + std::vector vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, key.GetSecret(), Hash(mKey.first.begin(), mKey.first.end()), vchCryptedSecret)) + return false; + if (!AddCryptedKey(mKey.first, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + } + return true; +} diff --git a/src/keystore.h b/src/keystore.h index 6080d7d7f5..0dc09f05b8 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -4,27 +4,112 @@ #ifndef BITCOIN_KEYSTORE_H #define BITCOIN_KEYSTORE_H +#include "crypter.h" + class CKeyStore { public: - std::map, CPrivKey> mapKeys; - mutable CCriticalSection cs_mapKeys; - virtual bool AddKey(const CKey& key); + 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, CKey& keyOut) const =0; + virtual std::vector GenerateNewKey(); +}; + +typedef std::map, CPrivKey> KeyMap; + +class CBasicKeyStore : public CKeyStore +{ +protected: + KeyMap mapKeys; + +public: + bool AddKey(const CKey& key); bool HaveKey(const std::vector &vchPubKey) const { return (mapKeys.count(vchPubKey) > 0); } - bool GetPrivKey(const std::vector &vchPubKey, CPrivKey& keyOut) const + bool GetPrivKey(const std::vector &vchPubKey, CKey& keyOut) const { std::map, CPrivKey>::const_iterator mi = mapKeys.find(vchPubKey); if (mi != mapKeys.end()) { - keyOut = (*mi).second; + keyOut.SetPrivKey((*mi).second); return true; } return false; } +}; + +class CCryptoKeyStore : public CBasicKeyStore +{ +private: + std::map, std::vector > mapCryptedKeys; + + CKeyingMaterial vMasterKey; + + // if fUseCrypto is true, mapKeys must be empty + // if fUseCrypto is false, vMasterKey must be empty + bool fUseCrypto; + +protected: + bool SetCrypted() + { + if (fUseCrypto) + return true; + if (!mapKeys.empty()) + return false; + fUseCrypto = true; + return true; + } + + // will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + bool Unlock(const CKeyingMaterial& vMasterKeyIn); + +public: + mutable CCriticalSection cs_vMasterKey; //No guarantees master key wont get locked before you can use it, so lock this first + + CCryptoKeyStore() : fUseCrypto(false) + { + } + + bool IsCrypted() const + { + return fUseCrypto; + } + + bool IsLocked() const + { + if (!IsCrypted()) + return false; + return vMasterKey.empty(); + } + + bool Lock() + { + CRITICAL_BLOCK(cs_vMasterKey) + { + if (!SetCrypted()) + return false; + + vMasterKey.clear(); + } + return true; + } + + virtual bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); std::vector GenerateNewKey(); + 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, CKey& keyOut) const; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 594f1d3bc4..6e973493a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,7 +55,6 @@ int64 nHPSTimerStart; // Settings int fGenerateBitcoins = false; int64 nTransactionFee = 0; -CAddress addrIncoming; int fLimitProcessors = false; int nLimitProcessors = 1; int fMinimizeToTray = true; @@ -2213,7 +2212,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Keep giving the same key to the same ip until they use it if (!mapReuseKey.count(pfrom->addr.ip)) - mapReuseKey[pfrom->addr.ip] = pwalletMain->GetKeyFromKeyPool(); + mapReuseKey[pfrom->addr.ip] = pwalletMain->GetOrReuseKeyFromPool(); // Send back approval of order and pubkey to use CScript scriptPubKey; diff --git a/src/main.h b/src/main.h index 124c7c2671..d34f68f9d0 100644 --- a/src/main.h +++ b/src/main.h @@ -70,7 +70,6 @@ extern std::set setpwalletRegistered; // Settings extern int fGenerateBitcoins; extern int64 nTransactionFee; -extern CAddress addrIncoming; extern int fLimitProcessors; extern int nLimitProcessors; extern int fMinimizeToTray; diff --git a/src/makefile.mingw b/src/makefile.mingw index 507833be48..16a0540094 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -33,7 +33,8 @@ DEFS=-DWIN32 -D__WXMSW__ -D_WINDOWS -DNOPCH -DUSE_SSL DEBUGFLAGS=-g -D__WXDEBUG__ CFLAGS=-mthreads -O2 -w -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) HEADERS=headers.h strlcpy.h serialize.h uint256.h util.h key.h bignum.h base58.h \ - script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h init.h + script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h \ + init.h crypter.h ifdef USE_UPNP INCLUDEPATHS += -I"C:\upnpc-exe-win32-20110215" @@ -55,6 +56,7 @@ OBJS= \ obj/wallet.o \ obj/rpc.o \ obj/init.o \ + obj/crypter.o \ cryptopp/obj/sha.o \ cryptopp/obj/cpu.o diff --git a/src/makefile.osx b/src/makefile.osx index 784596b72d..89788562c3 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -33,7 +33,8 @@ DEBUGFLAGS=-g -DwxDEBUG_LEVEL=0 # ppc doesn't work because we don't support big-endian CFLAGS=-mmacosx-version-min=10.5 -arch i386 -arch x86_64 -O3 -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) HEADERS=headers.h strlcpy.h serialize.h uint256.h util.h key.h bignum.h base58.h \ - script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h init.h + script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h \ + init.h crypter.h OBJS= \ obj/util.o \ @@ -46,6 +47,7 @@ OBJS= \ obj/wallet.o \ obj/rpc.o \ obj/init.o \ + obj/crypter.o \ cryptopp/obj/sha.o \ cryptopp/obj/cpu.o diff --git a/src/makefile.unix b/src/makefile.unix index bb26bf5edd..0567f8b75e 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -39,7 +39,8 @@ LIBS+= \ DEBUGFLAGS=-g -D__WXDEBUG__ CXXFLAGS=-O2 -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) HEADERS=headers.h strlcpy.h serialize.h uint256.h util.h key.h bignum.h base58.h \ - script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h init.h + script.h db.h net.h irc.h keystore.h main.h wallet.h rpc.h uibase.h ui.h noui.h \ + init.h crypter.h OBJS= \ obj/util.o \ @@ -52,6 +53,7 @@ OBJS= \ obj/wallet.o \ obj/rpc.o \ obj/init.o \ + obj/crypter.o \ cryptopp/obj/sha.o \ cryptopp/obj/cpu.o diff --git a/src/rpc.cpp b/src/rpc.cpp index 6f951b7431..fbed626a84 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -36,6 +36,9 @@ void ThreadRPCServer2(void* parg); typedef Value(*rpcfn_type)(const Array& params, bool fHelp); extern map mapCallTable; +static int64 nWalletUnlockTime; +static CCriticalSection cs_nWalletUnlockTime; + Object JSONRPCError(int code, const string& message) { @@ -309,7 +312,10 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("hashespersec", gethashespersec(params, false))); obj.push_back(Pair("testnet", fTestNet)); obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime())); + obj.push_back(Pair("keypoolsize", pwalletMain->GetKeyPoolSize())); obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee))); + if (pwalletMain->IsCrypted()) + obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime)); obj.push_back(Pair("errors", GetWarnings("statusbar"))); return obj; } @@ -324,13 +330,19 @@ Value getnewaddress(const Array& params, bool fHelp) "If [account] is specified (recommended), it is added to the address book " "so payments received with the address will be credited to [account]."); + if (!pwalletMain->IsLocked()) + pwalletMain->TopUpKeyPool(); + + if (pwalletMain->GetKeyPoolSize() < 1) + throw JSONRPCError(-12, "Error: Keypool ran out, please call keypoolrefill first"); + // Parse the account first so we don't generate a key if there's an error string strAccount; if (params.size() > 0) strAccount = AccountFromValue(params[0]); // Generate a new key that is added to wallet - string strAddress = PubKeyToAddress(pwalletMain->GetKeyFromKeyPool()); + string strAddress = PubKeyToAddress(pwalletMain->GetOrReuseKeyFromPool()); // This could be done in the same main CS as GetKeyFromKeyPool. CRITICAL_BLOCK(pwalletMain->cs_mapAddressBook) @@ -346,37 +358,48 @@ string GetAccountAddress(string strAccount, bool bForceNew=false) string strAddress; CWalletDB walletdb(pwalletMain->strWalletFile); - walletdb.TxnBegin(); CAccount account; - walletdb.ReadAccount(strAccount, account); - - // Check if the current key has been used - if (!account.vchPubKey.empty()) + CRITICAL_BLOCK(pwalletMain->cs_mapAddressBook) { - CScript scriptPubKey; - scriptPubKey.SetBitcoinAddress(account.vchPubKey); - for (map::iterator it = pwalletMain->mapWallet.begin(); - it != pwalletMain->mapWallet.end() && !account.vchPubKey.empty(); - ++it) + walletdb.ReadAccount(strAccount, account); + + bool bKeyUsed = false; + + // Check if the current key has been used + if (!account.vchPubKey.empty()) { - const CWalletTx& wtx = (*it).second; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (txout.scriptPubKey == scriptPubKey) - account.vchPubKey.clear(); + CScript scriptPubKey; + scriptPubKey.SetBitcoinAddress(account.vchPubKey); + for (map::iterator it = pwalletMain->mapWallet.begin(); + it != pwalletMain->mapWallet.end() && !account.vchPubKey.empty(); + ++it) + { + const CWalletTx& wtx = (*it).second; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if (txout.scriptPubKey == scriptPubKey) + bKeyUsed = true; + } + } + + // Generate a new key + if (account.vchPubKey.empty() || bForceNew || bKeyUsed) + { + if (pwalletMain->GetKeyPoolSize() < 1) + { + if (bKeyUsed || bForceNew) + throw JSONRPCError(-12, "Error: Keypool ran out, please call topupkeypool first"); + } + else + { + account.vchPubKey = pwalletMain->GetOrReuseKeyFromPool(); + string strAddress = PubKeyToAddress(account.vchPubKey); + pwalletMain->SetAddressBookName(strAddress, strAccount); + walletdb.WriteAccount(strAccount, account); + } } } - // Generate a new key - if (account.vchPubKey.empty() || bForceNew) - { - account.vchPubKey = pwalletMain->GetKeyFromKeyPool(); - string strAddress = PubKeyToAddress(account.vchPubKey); - pwalletMain->SetAddressBookName(strAddress, strAccount); - walletdb.WriteAccount(strAccount, account); - } - - walletdb.TxnCommit(); strAddress = PubKeyToAddress(account.vchPubKey); return strAddress; @@ -510,7 +533,12 @@ Value settxfee(const Array& params, bool fHelp) Value sendtoaddress(const Array& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + throw runtime_error( + "sendtoaddress [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.00000001\n" + "requires wallet passphrase to be set with walletpassphrase first"); + if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) throw runtime_error( "sendtoaddress [comment] [comment-to]\n" " is a real and is rounded to the nearest 0.00000001"); @@ -528,7 +556,11 @@ Value sendtoaddress(const Array& params, bool fHelp) wtx.mapValue["to"] = params[3].get_str(); CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) { + if(pwalletMain->IsLocked()) + throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); + string strError = pwalletMain->SendMoneyToBitcoinAddress(strAddress, nAmount, wtx); if (strError != "") throw JSONRPCError(-4, strError); @@ -773,7 +805,12 @@ Value movecmd(const Array& params, bool fHelp) Value sendfrom(const Array& params, bool fHelp) { - if (fHelp || params.size() < 3 || params.size() > 6) + if (pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) + throw runtime_error( + "sendfrom [minconf=1] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.00000001\n" + "requires wallet passphrase to be set with walletpassphrase first"); + if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 3 || params.size() > 6)) throw runtime_error( "sendfrom [minconf=1] [comment] [comment-to]\n" " is a real and is rounded to the nearest 0.00000001"); @@ -794,7 +831,11 @@ Value sendfrom(const Array& params, bool fHelp) CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(pwalletMain->cs_mapWallet) + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) { + if(pwalletMain->IsLocked()) + throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); + // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); if (nAmount > nBalance) @@ -809,9 +850,15 @@ Value sendfrom(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } + Value sendmany(const Array& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) + throw runtime_error( + "sendmany {address:amount,...} [minconf=1] [comment]\n" + "amounts are double-precision floating point numbers\n" + "requires wallet passphrase to be set with walletpassphrase first"); + if (!pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 4)) throw runtime_error( "sendmany {address:amount,...} [minconf=1] [comment]\n" "amounts are double-precision floating point numbers"); @@ -851,7 +898,11 @@ Value sendmany(const Array& params, bool fHelp) CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(pwalletMain->cs_mapWallet) + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) { + if(pwalletMain->IsLocked()) + throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); + // Check funds int64 nBalance = GetAccountBalance(strAccount, nMinDepth); if (totalAmount > nBalance) @@ -1281,6 +1332,219 @@ Value backupwallet(const Array& params, bool fHelp) } +Value keypoolrefill(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) + throw runtime_error( + "keypoolrefill\n" + "Fills the keypool, requires wallet passphrase to be set."); + if (!pwalletMain->IsCrypted() && (fHelp || params.size() > 0)) + throw runtime_error( + "keypoolrefill\n" + "Fills the keypool."); + + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) + { + if (pwalletMain->IsLocked()) + throw JSONRPCError(-13, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + pwalletMain->TopUpKeyPool(); + } + + if (pwalletMain->GetKeyPoolSize() < GetArg("-keypool", 100)) + throw JSONRPCError(-4, "Error refreshing keypool."); + + return Value::null; +} + + +void ThreadTopUpKeyPool(void* parg) +{ + pwalletMain->TopUpKeyPool(); +} + +void ThreadCleanWalletPassphrase(void* parg) +{ + int64 nMyWakeTime = GetTime() + *((int*)parg); + + if (nWalletUnlockTime == 0) + { + CRITICAL_BLOCK(cs_nWalletUnlockTime) + { + nWalletUnlockTime = nMyWakeTime; + } + + while (GetTime() < nWalletUnlockTime) + Sleep(GetTime() - nWalletUnlockTime); + + CRITICAL_BLOCK(cs_nWalletUnlockTime) + { + nWalletUnlockTime = 0; + } + } + else + { + CRITICAL_BLOCK(cs_nWalletUnlockTime) + { + if (nWalletUnlockTime < nMyWakeTime) + nWalletUnlockTime = nMyWakeTime; + } + free(parg); + return; + } + + pwalletMain->Lock(); + + delete (int*)parg; +} + +Value walletpassphrase(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) + throw runtime_error( + "walletpassphrase \n" + "Stores the wallet decryption key in memory for seconds."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called."); + + if (!pwalletMain->IsLocked()) + throw JSONRPCError(-17, "Error: Wallet is already unlocked."); + + // Note that the walletpassphrase is stored in params[0] which is not mlock()ed + string strWalletPass; + strWalletPass.reserve(100); + mlock(&strWalletPass[0], strWalletPass.capacity()); + strWalletPass = params[0].get_str(); + + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) + { + if (strWalletPass.length() > 0) + { + if (!pwalletMain->Unlock(strWalletPass)) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); + } + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + } + else + throw runtime_error( + "walletpassphrase \n" + "Stores the wallet decryption key in memory for seconds."); + } + + CreateThread(ThreadTopUpKeyPool, NULL); + int* pnSleepTime = new int(params[1].get_int()); + CreateThread(ThreadCleanWalletPassphrase, pnSleepTime); + + return Value::null; +} + + +Value walletpassphrasechange(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) + throw runtime_error( + "walletpassphrasechange \n" + "Changes the wallet passphrase from to ."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); + + string strOldWalletPass; + strOldWalletPass.reserve(100); + mlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + strOldWalletPass = params[0].get_str(); + + string strNewWalletPass; + strNewWalletPass.reserve(100); + mlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + strNewWalletPass = params[1].get_str(); + + if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) + throw runtime_error( + "walletpassphrasechange \n" + "Changes the wallet passphrase from to ."); + + if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) + { + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + throw JSONRPCError(-14, "Error: The wallet passphrase entered was incorrect."); + } + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + + return Value::null; +} + + +Value walletlock(const Array& params, bool fHelp) +{ + if (pwalletMain->IsCrypted() && (fHelp || params.size() != 0)) + throw runtime_error( + "walletlock\n" + "Removes the wallet encryption key from memory, locking the wallet.\n" + "After calling this method, you will need to call walletpassphrase again\n" + "before being able to call any methods which require the wallet to be unlocked."); + if (fHelp) + return true; + if (!pwalletMain->IsCrypted()) + throw JSONRPCError(-15, "Error: running with an unencrypted wallet, but walletlock was called."); + + pwalletMain->Lock(); + CRITICAL_BLOCK(cs_nWalletUnlockTime) + { + nWalletUnlockTime = 0; + } + + return Value::null; +} + + +Value encryptwallet(const Array& params, bool fHelp) +{ + if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) + throw runtime_error( + "encryptwallet \n" + "Encrypts the wallet with ."); + if (fHelp) + return true; + if (pwalletMain->IsCrypted()) + throw JSONRPCError(-15, "Error: running with an encrypted wallet, but encryptwallet was called."); + + string strWalletPass; + strWalletPass.reserve(100); + mlock(&strWalletPass[0], strWalletPass.capacity()); + strWalletPass = params[0].get_str(); + + if (strWalletPass.length() < 1) + throw runtime_error( + "encryptwallet \n" + "Encrypts the wallet with ."); + + if (!pwalletMain->EncryptWallet(strWalletPass)) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + throw JSONRPCError(-16, "Error: Failed to encrypt the wallet."); + } + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + + return Value::null; +} + + Value validateaddress(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -1432,44 +1696,49 @@ Value getwork(const Array& params, bool fHelp) pair pCallTable[] = { - make_pair("help", &help), - make_pair("stop", &stop), - make_pair("getblockcount", &getblockcount), - make_pair("getblocknumber", &getblocknumber), - make_pair("getconnectioncount", &getconnectioncount), - make_pair("getdifficulty", &getdifficulty), - make_pair("getgenerate", &getgenerate), - make_pair("setgenerate", &setgenerate), - make_pair("gethashespersec", &gethashespersec), - make_pair("getinfo", &getinfo), - make_pair("getnewaddress", &getnewaddress), - make_pair("getaccountaddress", &getaccountaddress), - make_pair("setaccount", &setaccount), - make_pair("setlabel", &setaccount), // deprecated - make_pair("getaccount", &getaccount), - make_pair("getlabel", &getaccount), // deprecated - make_pair("getaddressesbyaccount", &getaddressesbyaccount), - make_pair("getaddressesbylabel", &getaddressesbyaccount), // deprecated - make_pair("sendtoaddress", &sendtoaddress), - make_pair("getamountreceived", &getreceivedbyaddress), // deprecated, renamed to getreceivedbyaddress - make_pair("getallreceived", &listreceivedbyaddress), // deprecated, renamed to listreceivedbyaddress - make_pair("getreceivedbyaddress", &getreceivedbyaddress), - make_pair("getreceivedbyaccount", &getreceivedbyaccount), - make_pair("getreceivedbylabel", &getreceivedbyaccount), // deprecated - make_pair("listreceivedbyaddress", &listreceivedbyaddress), - make_pair("listreceivedbyaccount", &listreceivedbyaccount), - make_pair("listreceivedbylabel", &listreceivedbyaccount), // deprecated - make_pair("backupwallet", &backupwallet), - make_pair("validateaddress", &validateaddress), - make_pair("getbalance", &getbalance), - make_pair("move", &movecmd), - make_pair("sendfrom", &sendfrom), - make_pair("sendmany", &sendmany), - make_pair("gettransaction", &gettransaction), - make_pair("listtransactions", &listtransactions), - make_pair("getwork", &getwork), - make_pair("listaccounts", &listaccounts), - make_pair("settxfee", &settxfee), + make_pair("help", &help), + make_pair("stop", &stop), + make_pair("getblockcount", &getblockcount), + make_pair("getblocknumber", &getblocknumber), + make_pair("getconnectioncount", &getconnectioncount), + make_pair("getdifficulty", &getdifficulty), + make_pair("getgenerate", &getgenerate), + make_pair("setgenerate", &setgenerate), + make_pair("gethashespersec", &gethashespersec), + make_pair("getinfo", &getinfo), + make_pair("getnewaddress", &getnewaddress), + make_pair("getaccountaddress", &getaccountaddress), + make_pair("setaccount", &setaccount), + make_pair("setlabel", &setaccount), // deprecated + make_pair("getaccount", &getaccount), + make_pair("getlabel", &getaccount), // deprecated + make_pair("getaddressesbyaccount", &getaddressesbyaccount), + make_pair("getaddressesbylabel", &getaddressesbyaccount), // deprecated + make_pair("sendtoaddress", &sendtoaddress), + make_pair("getamountreceived", &getreceivedbyaddress), // deprecated, renamed to getreceivedbyaddress + make_pair("getallreceived", &listreceivedbyaddress), // deprecated, renamed to listreceivedbyaddress + make_pair("getreceivedbyaddress", &getreceivedbyaddress), + make_pair("getreceivedbyaccount", &getreceivedbyaccount), + make_pair("getreceivedbylabel", &getreceivedbyaccount), // deprecated + make_pair("listreceivedbyaddress", &listreceivedbyaddress), + make_pair("listreceivedbyaccount", &listreceivedbyaccount), + make_pair("listreceivedbylabel", &listreceivedbyaccount), // deprecated + make_pair("backupwallet", &backupwallet), + make_pair("keypoolrefill", &keypoolrefill), + make_pair("walletpassphrase", &walletpassphrase), + make_pair("walletpassphrasechange", &walletpassphrasechange), + make_pair("walletlock", &walletlock), + make_pair("encryptwallet", &encryptwallet), + make_pair("validateaddress", &validateaddress), + make_pair("getbalance", &getbalance), + make_pair("move", &movecmd), + make_pair("sendfrom", &sendfrom), + make_pair("sendmany", &sendmany), + make_pair("gettransaction", &gettransaction), + make_pair("listtransactions", &listtransactions), + make_pair("getwork", &getwork), + make_pair("listaccounts", &listaccounts), + make_pair("settxfee", &settxfee), }; map mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); @@ -1493,6 +1762,9 @@ string pAllowInSafeMode[] = "getaddressesbyaccount", "getaddressesbylabel", // deprecated "backupwallet", + "keypoolrefill", + "walletpassphrase", + "walletlock", "validateaddress", "getwork", }; @@ -2130,6 +2402,7 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); if (strMethod == "listtransactions" && n > 2) ConvertTo(params[2]); if (strMethod == "listaccounts" && n > 0) ConvertTo(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo(params[1]); if (strMethod == "sendmany" && n > 1) { string s = params[1].get_str(); diff --git a/src/script.cpp b/src/script.cpp index bd1b5b3c5f..aa7f1f5116 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) { @@ -1038,13 +1038,13 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash { // Sign const valtype& vchPubKey = item.second; - CPrivKey privkey; - if (!keystore.GetPrivKey(vchPubKey, privkey)) + CKey key; + if (!keystore.GetPrivKey(vchPubKey, key)) return false; if (hash != 0) { vector vchSig; - if (!CKey::Sign(privkey, hash, vchSig)) + if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig; @@ -1057,13 +1057,13 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash if (mi == mapPubKeys.end()) return false; const vector& vchPubKey = (*mi).second; - CPrivKey privkey; - if (!keystore.GetPrivKey(vchPubKey, privkey)) + CKey key; + if (!keystore.GetPrivKey(vchPubKey, key)) return false; if (hash != 0) { vector vchSig; - if (!CKey::Sign(privkey, hash, vchSig)) + if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig << vchPubKey; @@ -1089,8 +1089,40 @@ bool IsStandard(const CScript& scriptPubKey) bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { - CScript scriptSig; - return Solver(keystore, scriptPubKey, 0, 0, scriptSig); + vector > vSolution; + if (!Solver(scriptPubKey, vSolution)) + return false; + + // Compile solution + CRITICAL_BLOCK(keystore.cs_KeyStore) + { + BOOST_FOREACH(PAIRTYPE(opcodetype, valtype)& item, vSolution) + { + if (item.first == OP_PUBKEY) + { + // Sign + const valtype& vchPubKey = item.second; + if (!keystore.HaveKey(vchPubKey)) + return false; + } + else if (item.first == OP_PUBKEYHASH) + { + // Sign and give pubkey + map::iterator mi = mapPubKeys.find(uint160(item.second)); + if (mi == mapPubKeys.end()) + return false; + const vector& vchPubKey = (*mi).second; + if (!keystore.HaveKey(vchPubKey)) + return false; + } + else + { + return false; + } + } + } + + return true; } diff --git a/src/serialize.h b/src/serialize.h index 31862a71a9..38c533d9ae 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -28,6 +28,30 @@ 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 +#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; class CDataStream; class CAutoFile; @@ -755,7 +779,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 +802,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); } }; diff --git a/src/ui.cpp b/src/ui.cpp index ff0b4afb55..eae0a4f4c8 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -245,6 +245,41 @@ void SetDefaultReceivingAddress(const string& strAddress) } } +bool GetWalletPassphrase() +{ + if (pwalletMain->IsLocked()) + { + string strWalletPass; + strWalletPass.reserve(100); + mlock(&strWalletPass[0], strWalletPass.capacity()); + + // obtain current wallet encrypt/decrypt key, from passphrase + // Note that the passphrase is not mlock()d during this entry and could potentially + // be obtained from disk long after bitcoin has run. + strWalletPass = wxGetPasswordFromUser(_("Enter the current passphrase to the wallet."), + _("Passphrase")).ToStdString(); + + if (!strWalletPass.size()) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + wxMessageBox(_("Please supply the current wallet decryption passphrase."), "Bitcoin"); + return false; + } + + if (!pwalletMain->Unlock(strWalletPass)) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + wxMessageBox(_("The passphrase entered for the wallet decryption was incorrect."), "Bitcoin"); + return false; + } + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + } + return true; +} + @@ -333,6 +368,11 @@ CMainFrame::CMainFrame(wxWindow* parent) : CMainFrameBase(parent) if (CWalletDB(pwalletMain->strWalletFile,"r").ReadDefaultKey(vchPubKey)) m_textCtrlAddress->SetValue(PubKeyToAddress(vchPubKey)); + if (pwalletMain->IsCrypted()) + m_menuOptions->Remove(m_menuOptionsEncryptWallet); + else + m_menuOptions->Remove(m_menuOptionsChangeWalletPassphrase); + // Fill listctrl with wallet transactions RefreshListCtrl(); } @@ -1122,6 +1162,169 @@ void CMainFrame::OnMenuOptionsChangeYourAddress(wxCommandEvent& event) return; } +void CMainFrame::OnMenuOptionsEncryptWallet(wxCommandEvent& event) +{ + // Options->Encrypt Wallet + if (pwalletMain->IsCrypted()) + { + wxMessageBox(_("Wallet already encrypted."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + string strWalletPass; + strWalletPass.reserve(100); + mlock(&strWalletPass[0], strWalletPass.capacity()); + + // obtain current wallet encrypt/decrypt key, from passphrase + // Note that the passphrase is not mlock()d during this entry and could potentially + // be obtained from disk long after bitcoin has run. + strWalletPass = wxGetPasswordFromUser(_("Enter the new passphrase to the wallet.\nPlease use a passphrase of 10 or more random characters, or eight or more words."), + _("Passphrase")).ToStdString(); + + if (!strWalletPass.size()) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + wxMessageBox(_("Error: The supplied passphrase was too short."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + if(wxMessageBox(_("WARNING: If you encrypt your wallet and lose your passphrase, you will LOSE ALL OF YOUR BITCOINS!\nAre you sure you wish to encrypt your wallet?"), "Bitcoin", wxYES_NO) != wxYES) + return; + + string strWalletPassTest; + strWalletPassTest.reserve(100); + mlock(&strWalletPassTest[0], strWalletPassTest.capacity()); + strWalletPassTest = wxGetPasswordFromUser(_("Please re-enter your new wallet passphrase."), + _("Passphrase")).ToStdString(); + + if (strWalletPassTest != strWalletPass) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + fill(strWalletPassTest.begin(), strWalletPassTest.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + munlock(&strWalletPassTest[0], strWalletPassTest.capacity()); + wxMessageBox(_("Error: the supplied passphrases didn't match."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + if (!pwalletMain->EncryptWallet(strWalletPass)) + { + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + fill(strWalletPassTest.begin(), strWalletPassTest.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + munlock(&strWalletPassTest[0], strWalletPassTest.capacity()); + wxMessageBox(_("Wallet encryption failed."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + fill(strWalletPass.begin(), strWalletPass.end(), '\0'); + fill(strWalletPassTest.begin(), strWalletPassTest.end(), '\0'); + munlock(&strWalletPass[0], strWalletPass.capacity()); + munlock(&strWalletPassTest[0], strWalletPassTest.capacity()); + wxMessageBox(_("Wallet Encrypted.\nRemember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer."), "Bitcoin"); + + m_menuOptions->Remove(m_menuOptionsEncryptWallet); + m_menuOptions->Insert(m_menuOptions->GetMenuItemCount() - 1, m_menuOptionsChangeWalletPassphrase); +} + +void CMainFrame::OnMenuOptionsChangeWalletPassphrase(wxCommandEvent& event) +{ + // Options->Change Wallet Encryption Passphrase + if (!pwalletMain->IsCrypted()) + { + wxMessageBox(_("Wallet is unencrypted, please encrypt it first."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + string strOldWalletPass; + strOldWalletPass.reserve(100); + mlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + + // obtain current wallet encrypt/decrypt key, from passphrase + // Note that the passphrase is not mlock()d during this entry and could potentially + // be obtained from disk long after bitcoin has run. + strOldWalletPass = wxGetPasswordFromUser(_("Enter the current passphrase to the wallet."), + _("Passphrase")).ToStdString(); + + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) + { + bool fWasLocked = pwalletMain->IsLocked(); + pwalletMain->Lock(); + + if (!strOldWalletPass.size() || !pwalletMain->Unlock(strOldWalletPass)) + { + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + wxMessageBox(_("The passphrase entered for the wallet decryption was incorrect."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + if (fWasLocked) + pwalletMain->Lock(); + + string strNewWalletPass; + strNewWalletPass.reserve(100); + mlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + + // obtain new wallet encrypt/decrypt key, from passphrase + // Note that the passphrase is not mlock()d during this entry and could potentially + // be obtained from disk long after bitcoin has run. + strNewWalletPass = wxGetPasswordFromUser(_("Enter the new passphrase for the wallet."), + _("Passphrase")).ToStdString(); + + if (!strNewWalletPass.size()) + { + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + wxMessageBox(_("Error: The supplied passphrase was too short."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + string strNewWalletPassTest; + strNewWalletPassTest.reserve(100); + mlock(&strNewWalletPassTest[0], strNewWalletPassTest.capacity()); + + // obtain new wallet encrypt/decrypt key, from passphrase + // Note that the passphrase is not mlock()d during this entry and could potentially + // be obtained from disk long after bitcoin has run. + strNewWalletPassTest = wxGetPasswordFromUser(_("Re-enter the new passphrase for the wallet."), + _("Passphrase")).ToStdString(); + + if (strNewWalletPassTest != strNewWalletPass) + { + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + fill(strNewWalletPassTest.begin(), strNewWalletPassTest.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + munlock(&strNewWalletPassTest[0], strNewWalletPassTest.capacity()); + wxMessageBox(_("Error: the supplied passphrases didn't match."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + + if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) + { + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + fill(strNewWalletPassTest.begin(), strNewWalletPassTest.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + munlock(&strNewWalletPassTest[0], strNewWalletPassTest.capacity()); + wxMessageBox(_("The passphrase entered for the wallet decryption was incorrect."), "Bitcoin", wxOK | wxICON_ERROR); + return; + } + fill(strOldWalletPass.begin(), strOldWalletPass.end(), '\0'); + fill(strNewWalletPass.begin(), strNewWalletPass.end(), '\0'); + fill(strNewWalletPassTest.begin(), strNewWalletPassTest.end(), '\0'); + munlock(&strOldWalletPass[0], strOldWalletPass.capacity()); + munlock(&strNewWalletPass[0], strNewWalletPass.capacity()); + munlock(&strNewWalletPassTest[0], strNewWalletPassTest.capacity()); + wxMessageBox(_("Wallet Passphrase Changed."), "Bitcoin"); + } +} + void CMainFrame::OnMenuOptionsOptions(wxCommandEvent& event) { // Options->Options @@ -1182,8 +1385,19 @@ void CMainFrame::OnButtonNew(wxCommandEvent& event) return; string strName = dialog.GetValue(); - // Generate new key - string strAddress = PubKeyToAddress(pwalletMain->GetKeyFromKeyPool()); + string strAddress; + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) + { + bool fWasLocked = pwalletMain->IsLocked(); + if (!GetWalletPassphrase()) + return; + + // Generate new key + strAddress = PubKeyToAddress(pwalletMain->GetOrReuseKeyFromPool()); + + if (fWasLocked) + pwalletMain->Lock(); + } // Save CRITICAL_BLOCK(pwalletMain->cs_mapAddressBook) @@ -1947,7 +2161,12 @@ void CSendDialog::OnButtonSend(wxCommandEvent& event) if (fBitcoinAddress) { CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) { + bool fWasLocked = pwalletMain->IsLocked(); + if (!GetWalletPassphrase()) + return; + // Send to bitcoin address CScript scriptPubKey; scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; @@ -1956,13 +2175,22 @@ void CSendDialog::OnButtonSend(wxCommandEvent& event) if (strError == "") wxMessageBox(_("Payment sent "), _("Sending...")); else if (strError == "ABORTED") + { + if (fWasLocked) + pwalletMain->Lock(); return; // leave send dialog open + } else { wxMessageBox(strError + " ", _("Sending...")); EndModal(false); + if (fWasLocked) + pwalletMain->Lock(); return; } + + if (fWasLocked) + pwalletMain->Lock(); } } else @@ -2246,16 +2474,27 @@ void CSendingDialog::OnReply2(CDataStream& vRecv) Error(_("Insufficient funds")); return; } + CReserveKey reservekey(pwalletMain); int64 nFeeRequired; - if (!pwalletMain->CreateTransaction(scriptPubKey, nPrice, wtx, reservekey, nFeeRequired)) + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) { - if (nPrice + nFeeRequired > pwalletMain->GetBalance()) - Error(strprintf(_("This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds"), FormatMoney(nFeeRequired).c_str())); - else - Error(_("Transaction creation failed")); - return; - } + bool fWasLocked = pwalletMain->IsLocked(); + if (!GetWalletPassphrase()) + return; + + if (!pwalletMain->CreateTransaction(scriptPubKey, nPrice, wtx, reservekey, nFeeRequired)) + { + if (nPrice + nFeeRequired > pwalletMain->GetBalance()) + Error(strprintf(_("This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds"), FormatMoney(nFeeRequired).c_str())); + else + Error(_("Transaction creation failed")); + return; + } + + if (fWasLocked) + pwalletMain->Lock(); + } // Transaction fee if (!ThreadSafeAskFee(nFeeRequired, _("Sending..."), this)) @@ -2382,7 +2621,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(); @@ -2581,8 +2820,18 @@ void CAddressBookDialog::OnButtonNew(wxCommandEvent& event) return; strName = dialog.GetValue(); - // Generate new key - strAddress = PubKeyToAddress(pwalletMain->GetKeyFromKeyPool()); + CRITICAL_BLOCK(pwalletMain->cs_vMasterKey) + { + bool fWasLocked = pwalletMain->IsLocked(); + if (!GetWalletPassphrase()) + return; + + // Generate new key + strAddress = PubKeyToAddress(pwalletMain->GetOrReuseKeyFromPool()); + + if (fWasLocked) + pwalletMain->Lock(); + } } // Add to list and select it diff --git a/src/ui.h b/src/ui.h index 3f06ad90cb..3bf741534c 100644 --- a/src/ui.h +++ b/src/ui.h @@ -59,6 +59,8 @@ protected: void OnMenuFileExit(wxCommandEvent& event); void OnUpdateUIOptionsGenerate(wxUpdateUIEvent& event); void OnMenuOptionsChangeYourAddress(wxCommandEvent& event); + void OnMenuOptionsEncryptWallet(wxCommandEvent& event); + void OnMenuOptionsChangeWalletPassphrase(wxCommandEvent& event); void OnMenuOptionsOptions(wxCommandEvent& event); void OnMenuHelpAbout(wxCommandEvent& event); void OnButtonSend(wxCommandEvent& event); diff --git a/src/uibase.cpp b/src/uibase.cpp index 1b901a1edb..18eec44138 100644 --- a/src/uibase.cpp +++ b/src/uibase.cpp @@ -32,6 +32,12 @@ CMainFrameBase::CMainFrameBase( wxWindow* parent, wxWindowID id, const wxString& m_menuOptionsChangeYourAddress = new wxMenuItem( m_menuOptions, wxID_ANY, wxString( _("&Your Receiving Addresses...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuOptions->Append( m_menuOptionsChangeYourAddress ); + m_menuOptionsEncryptWallet = new wxMenuItem( m_menuOptions, wxID_ANY, wxString( _("&Encrypt Wallet...") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuOptions->Append( m_menuOptionsEncryptWallet ); + + m_menuOptionsChangeWalletPassphrase = new wxMenuItem( m_menuOptions, wxID_ANY, wxString( _("&Change Wallet Encryption Passphrase...") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuOptions->Append( m_menuOptionsChangeWalletPassphrase ); + wxMenuItem* m_menuOptionsOptions; m_menuOptionsOptions = new wxMenuItem( m_menuOptions, wxID_PREFERENCES, wxString( _("&Options...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuOptions->Append( m_menuOptionsOptions ); @@ -187,6 +193,8 @@ CMainFrameBase::CMainFrameBase( wxWindow* parent, wxWindowID id, const wxString& this->Connect( wxEVT_PAINT, wxPaintEventHandler( CMainFrameBase::OnPaint ) ); this->Connect( m_menuFileExit->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuFileExit ) ); this->Connect( m_menuOptionsChangeYourAddress->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsChangeYourAddress ) ); + this->Connect( m_menuOptionsEncryptWallet->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsEncryptWallet ) ); + this->Connect( m_menuOptionsChangeWalletPassphrase->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsChangeWalletPassphrase ) ); this->Connect( m_menuOptionsOptions->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsOptions ) ); this->Connect( m_menuHelpAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuHelpAbout ) ); this->Connect( wxID_BUTTONSEND, wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( CMainFrameBase::OnButtonSend ) ); @@ -245,6 +253,8 @@ CMainFrameBase::~CMainFrameBase() this->Disconnect( wxEVT_PAINT, wxPaintEventHandler( CMainFrameBase::OnPaint ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuFileExit ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsChangeYourAddress ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsEncryptWallet ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsChangeWalletPassphrase ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuOptionsOptions ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( CMainFrameBase::OnMenuHelpAbout ) ); this->Disconnect( wxID_BUTTONSEND, wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( CMainFrameBase::OnButtonSend ) ); diff --git a/src/uibase.h b/src/uibase.h index 78f3d1b385..ca0730b20e 100644 --- a/src/uibase.h +++ b/src/uibase.h @@ -98,6 +98,8 @@ class CMainFrameBase : public wxFrame virtual void OnPaint( wxPaintEvent& event ) { event.Skip(); } virtual void OnMenuFileExit( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuOptionsChangeYourAddress( wxCommandEvent& event ) { event.Skip(); } + virtual void OnMenuOptionsEncryptWallet( wxCommandEvent& event ) { event.Skip(); } + virtual void OnMenuOptionsChangeWalletPassphrase( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuOptionsOptions( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuHelpAbout( wxCommandEvent& event ) { event.Skip(); } virtual void OnButtonSend( wxCommandEvent& event ) { event.Skip(); } @@ -115,6 +117,8 @@ class CMainFrameBase : public wxFrame public: wxMenu* m_menuOptions; + wxMenuItem* m_menuOptionsEncryptWallet; + wxMenuItem* m_menuOptionsChangeWalletPassphrase; wxStatusBar* m_statusBar; wxTextCtrl* m_textCtrlAddress; wxListCtrl* m_listCtrlAll; diff --git a/src/wallet.cpp b/src/wallet.cpp index 6ef75ef27f..93313e7b27 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -5,11 +5,11 @@ #include "headers.h" #include "db.h" #include "cryptopp/sha.h" +#include "crypter.h" using namespace std; - ////////////////////////////////////////////////////////////////////////////// // // mapWallet @@ -17,10 +17,180 @@ using namespace std; bool CWallet::AddKey(const CKey& key) { - this->CKeyStore::AddKey(key); + if (!CCryptoKeyStore::AddKey(key)) + return false; if (!fFileBacked) return true; - return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); + if (!IsCrypted()) + return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); +} + +bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector &vchCryptedSecret) +{ + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + if (!fFileBacked) + return true; + CRITICAL_BLOCK(cs_pwalletdbEncryption) + { + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret); + else + return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret); + } +} + +bool CWallet::Unlock(const string& strWalletPassphrase) +{ + CRITICAL_BLOCK(cs_vMasterKey) + { + if (!IsLocked()) + return false; + + CCrypter crypter; + CKeyingMaterial vMasterKey; + + BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(vMasterKey)) + return true; + } + } + return false; +} + +bool CWallet::ChangeWalletPassphrase(const string& strOldWalletPassphrase, const string& strNewWalletPassphrase) +{ + CRITICAL_BLOCK(cs_vMasterKey) + { + bool fWasLocked = IsLocked(); + + Lock(); + + CCrypter crypter; + CKeyingMaterial vMasterKey; + BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if(!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(vMasterKey)) + { + int64 nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (pMasterKey.second.nDeriveIterations < 25000) + pMasterKey.second.nDeriveIterations = 25000; + + printf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) + return false; + CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); + if (fWasLocked) + Lock(); + return true; + } + } + } + return false; +} + + +// This class implements an addrIncoming entry that causes pre-0.4 +// clients to crash on startup if reading a private-key-encrypted wallet. +class CCorruptAddress +{ +public: + IMPLEMENT_SERIALIZE + ( + if (nType & SER_DISK) + READWRITE(nVersion); + ) +}; + +bool CWallet::EncryptWallet(const string& strWalletPassphrase) +{ + CRITICAL_BLOCK(cs_mapPubKeys) + CRITICAL_BLOCK(cs_KeyStore) + CRITICAL_BLOCK(cs_vMasterKey) + CRITICAL_BLOCK(cs_pwalletdbEncryption) + { + if (IsCrypted()) + return false; + + CKeyingMaterial vMasterKey; + RandAddSeedPerfmon(); + + vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); + RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); + + CMasterKey kMasterKey; + + RandAddSeedPerfmon(); + kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); + RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + + CCrypter crypter; + int64 nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (kMasterKey.nDeriveIterations < 25000) + kMasterKey.nDeriveIterations = 25000; + + printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) + return false; + if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) + return false; + + mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; + if (fFileBacked) + { + pwalletdbEncryption = new CWalletDB(strWalletFile); + pwalletdbEncryption->TxnBegin(); + pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); + } + + if (!EncryptKeys(vMasterKey)) + { + if (fFileBacked) + pwalletdbEncryption->TxnAbort(); + exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet. + } + + if (fFileBacked) + { + CCorruptAddress corruptAddress; + pwalletdbEncryption->WriteSetting("addrIncoming", corruptAddress); + if (!pwalletdbEncryption->TxnCommit()) + exit(1); //We now have keys encrypted in memory, but no on disk...die to avoid confusion and let the user reload their unencrypted wallet. + + pwalletdbEncryption->Close(); + pwalletdbEncryption = NULL; + } + + Lock(); + } + return true; } void CWallet::WalletUpdateSpent(const CTransaction &tx) @@ -98,7 +268,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) BOOST_FOREACH(const CTxOut& txout, wtx.vout) { if (txout.scriptPubKey == scriptDefaultKey) - SetDefaultKey(GetKeyFromKeyPool()); + SetDefaultKey(GetOrReuseKeyFromPool()); } // Notify UI @@ -783,7 +953,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; @@ -903,15 +1073,24 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, { CReserveKey reservekey(this); int64 nFeeRequired; - if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) + CRITICAL_BLOCK(cs_vMasterKey) { - string strError; - if (nValue + nFeeRequired > GetBalance()) - strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str()); - else - strError = _("Error: Transaction creation failed "); - printf("SendMoney() : %s", strError.c_str()); - return strError; + if (IsLocked()) + { + string strError = _("Error: Wallet locked, unable to create transaction "); + printf("SendMoney() : %s", strError.c_str()); + return strError; + } + if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) + { + string strError; + if (nValue + nFeeRequired > GetBalance()) + strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str()); + else + strError = _("Error: Transaction creation failed "); + printf("SendMoney() : %s", strError.c_str()); + return strError; + } } if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL)) @@ -955,12 +1134,12 @@ bool CWallet::LoadWallet(bool& fFirstRunRet) return false; fFirstRunRet = vchDefaultKey.empty(); - if (!mapKeys.count(vchDefaultKey)) + if (!HaveKey(vchDefaultKey)) { - // Create new default key + // Create new keyUser and set as default key RandAddSeedPerfmon(); - SetDefaultKey(GetKeyFromKeyPool()); + SetDefaultKey(GetOrReuseKeyFromPool()); if (!SetAddressBookName(PubKeyToAddress(vchDefaultKey), "")) return false; } @@ -1033,14 +1212,16 @@ bool GetWalletFile(CWallet* pwallet, string &strWalletFileOut) return true; } -void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +bool CWallet::TopUpKeyPool() { - nIndex = -1; - keypool.vchPubKey.clear(); CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_mapWallet) CRITICAL_BLOCK(cs_setKeyPool) + CRITICAL_BLOCK(cs_vMasterKey) { + if (IsLocked()) + return false; + CWalletDB walletdb(strWalletFile); // Top up key pool @@ -1051,18 +1232,36 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) if (!setKeyPool.empty()) nEnd = *(--setKeyPool.end()) + 1; if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) - throw runtime_error("ReserveKeyFromKeyPool() : writing generated key failed"); + throw runtime_error("TopUpKeyPool() : writing generated key failed"); setKeyPool.insert(nEnd); printf("keypool added key %"PRI64d", size=%d\n", nEnd, setKeyPool.size()); } + } + return true; +} + +void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +{ + nIndex = -1; + keypool.vchPubKey.clear(); + CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_setKeyPool) + { + if (!IsLocked()) + TopUpKeyPool(); // Get the oldest key - assert(!setKeyPool.empty()); + if(setKeyPool.empty()) + return; + + CWalletDB walletdb(strWalletFile); + nIndex = *(setKeyPool.begin()); 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); @@ -1091,11 +1290,13 @@ void CWallet::ReturnKey(int64 nIndex) printf("keypool return %"PRI64d"\n", nIndex); } -vector CWallet::GetKeyFromKeyPool() +vector CWallet::GetOrReuseKeyFromPool() { int64 nIndex = 0; CKeyPool keypool; ReserveKeyFromKeyPool(nIndex, keypool); + if(nIndex == -1) + return vchDefaultKey; KeepKey(nIndex); return keypool.vchPubKey; } @@ -1105,6 +1306,8 @@ int64 CWallet::GetOldestKeyPoolTime() int64 nIndex = 0; CKeyPool keypool; ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex == -1) + return GetTime(); ReturnKey(nIndex); return keypool.nTime; } diff --git a/src/wallet.h b/src/wallet.h index 7d9db97267..d336d38765 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -12,12 +12,14 @@ 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; bool SelectCoins(int64 nTargetValue, std::set >& setCoinsRet, int64& nValueRet) const; + CWalletDB *pwalletdbEncryption; + CCriticalSection cs_pwalletdbEncryption; public: bool fFileBacked; @@ -26,14 +28,22 @@ public: std::set setKeyPool; CCriticalSection cs_setKeyPool; + typedef std::map MasterKeyMap; + MasterKeyMap mapMasterKeys; + unsigned int nMasterKeyMaxID; + CWallet() { fFileBacked = false; + nMasterKeyMaxID = 0; + pwalletdbEncryption = NULL; } CWallet(std::string strWalletFileIn) { strWalletFile = strWalletFileIn; fFileBacked = true; + nMasterKeyMaxID = 0; + pwalletdbEncryption = NULL; } mutable CCriticalSection cs_mapWallet; @@ -48,7 +58,16 @@ public: std::vector vchDefaultKey; + // keystore implementation bool AddKey(const CKey& key); + bool LoadKey(const CKey& key) { return CCryptoKeyStore::AddKey(key); } + bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); + bool LoadCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret) { return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } + + bool Unlock(const std::string& strWalletPassphrase); + bool ChangeWalletPassphrase(const std::string& strOldWalletPassphrase, const std::string& strNewWalletPassphrase); + bool EncryptWallet(const std::string& strWalletPassphrase); + bool AddToWallet(const CWalletTx& wtxIn); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate = false); bool EraseFromWallet(uint256 hash); @@ -64,10 +83,11 @@ public: std::string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); std::string SendMoneyToBitcoinAddress(std::string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); + bool TopUpKeyPool(); void ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool); void KeepKey(int64 nIndex); void ReturnKey(int64 nIndex); - std::vector GetKeyFromKeyPool(); + std::vector GetOrReuseKeyFromPool(); int64 GetOldestKeyPoolTime(); bool IsMine(const CTxIn& txin) const; @@ -174,6 +194,11 @@ public: } } + int GetKeyPoolSize() + { + return setKeyPool.size(); + } + bool GetTransaction(const uint256 &hashTx, CWalletTx& wtx); bool SetDefaultKey(const std::vector &vchPubKey);