diff options
author | Matt Corallo <matt@bluematt.me> | 2011-07-08 15:47:35 +0200 |
---|---|---|
committer | Matt Corallo <matt@bluematt.me> | 2011-07-13 02:11:25 +0200 |
commit | 4e87d341f75f13bbd7d108c31c03886fbc4df56f (patch) | |
tree | 30d95cc29ddb116328f87245fab0d5b787646bb4 | |
parent | a48c671957e37594d8f9e0fd51b24e7a4f44300e (diff) |
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 <passphrase> 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 <passphrase> <time to keep key in memory> first.
A keypoolrefill command has been added which tops up the users keypool
(requiring the passphrase via walletpassphrase first).
keypoolsize has been added to the output of getinfo to show the user the
number of keys left before they need to specify their passphrase (and call
keypoolrefill).
Note that walletpassphrase will automatically fill keypool in a separate
thread which it spawns when the passphrase is set. This could cause some
delays in other threads waiting for locks on the wallet passphrase, including
one which could cause the passphrase to be stored longer than expected,
however it will not allow the passphrase to be used longer than expected as
ThreadCleanWalletPassphrase will attempt to get a lock on the key as soon
as the specified lock time has arrived.
When the keypool runs out (and wallet is locked) GetOrReuseKeyFromPool
returns vchDefaultKey, meaning miners may start to generate many blocks to
vchDefaultKey instead of a new key each time.
A walletpassphrasechange <oldpassphrase> <newpassphrase> has been added to
allow the user to change their password via RPC.
Whenever keying material (unencrypted private keys, the user's passphrase,
the wallet's AES key) is stored unencrypted in memory, any reasonable attempt
is made to mlock/VirtualLock that memory before storing the keying material.
This is not true in several (commented) cases where mlock/VirtualLocking the
memory is not possible.
Although encryption of private keys in memory can be very useful on desktop
systems (as some small amount of protection against stupid viruses), on an
RPC server, the password is entered fairly insecurely. Thus, the only main
advantage encryption has for RPC servers is for RPC servers that do not spend
coins, except in rare cases, eg. a webserver of a merchant which only receives
payment except for cases of manual intervention.
Thanks to jgarzik for the original patch and sipa, gmaxwell and many others
for all their input.
Conflicts:
src/wallet.cpp
-rw-r--r-- | share/uiproject.fbp | 30 | ||||
-rw-r--r-- | src/crypter.cpp | 132 | ||||
-rw-r--r-- | src/crypter.h | 96 | ||||
-rw-r--r-- | src/db.cpp | 24 | ||||
-rw-r--r-- | src/db.h | 19 | ||||
-rw-r--r-- | src/keystore.cpp | 115 | ||||
-rw-r--r-- | src/keystore.h | 46 | ||||
-rw-r--r-- | src/main.cpp | 2 | ||||
-rw-r--r-- | src/makefile.mingw | 4 | ||||
-rw-r--r-- | src/makefile.osx | 4 | ||||
-rw-r--r-- | src/makefile.unix | 4 | ||||
-rw-r--r-- | src/rpc.cpp | 375 | ||||
-rw-r--r-- | src/script.cpp | 36 | ||||
-rw-r--r-- | src/ui.cpp | 263 | ||||
-rw-r--r-- | src/ui.h | 2 | ||||
-rw-r--r-- | src/uibase.cpp | 12 | ||||
-rw-r--r-- | src/uibase.h | 2 | ||||
-rw-r--r-- | src/wallet.cpp | 182 | ||||
-rw-r--r-- | src/wallet.h | 20 |
19 files changed, 1199 insertions, 169 deletions
diff --git a/share/uiproject.fbp b/share/uiproject.fbp index d9d46382d3..cc735c6d78 100644 --- a/share/uiproject.fbp +++ b/share/uiproject.fbp @@ -167,6 +167,36 @@ <property name="checked">0</property>
<property name="enabled">1</property>
<property name="help"></property>
+ <property name="id">wxID_ANY</property>
+ <property name="kind">wxITEM_NORMAL</property>
+ <property name="label">&Encrypt Wallet...</property>
+ <property name="name">m_menuOptionsEncryptWallet</property>
+ <property name="permission">none</property>
+ <property name="shortcut"></property>
+ <property name="unchecked_bitmap"></property>
+ <event name="OnMenuSelection">OnMenuOptionsEncryptWallet</event>
+ <event name="OnUpdateUI"></event>
+ </object>
+ <object class="wxMenuItem" expanded="1">
+ <property name="bitmap"></property>
+ <property name="checked">0</property>
+ <property name="enabled">1</property>
+ <property name="help"></property>
+ <property name="id">wxID_ANY</property>
+ <property name="kind">wxITEM_NORMAL</property>
+ <property name="label">&Change Wallet Encryption Passphrase...</property>
+ <property name="name">m_menuOptionsChangeWalletPassphrase</property>
+ <property name="permission">none</property>
+ <property name="shortcut"></property>
+ <property name="unchecked_bitmap"></property>
+ <event name="OnMenuSelection">OnMenuOptionsChangeWalletPassphrase</event>
+ <event name="OnUpdateUI"></event>
+ </object>
+ <object class="wxMenuItem" expanded="1">
+ <property name="bitmap"></property>
+ <property name="checked">0</property>
+ <property name="enabled">1</property>
+ <property name="help"></property>
<property name="id">wxID_PREFERENCES</property>
<property name="kind">wxITEM_NORMAL</property>
<property name="label">&Options...</property>
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 <openssl/aes.h> +#include <openssl/evp.h> +#include <vector> +#include <string> +#include "headers.h" +#ifdef __WXMSW__ +#include <windows.h> +#endif + +#include "crypter.h" +#include "main.h" +#include "util.h" + +bool CCrypter::SetKeyFromPassphrase(const std::string& strKeyData, const std::vector<unsigned char>& 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<unsigned char>& 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<unsigned char> &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<unsigned char> (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<unsigned char>& 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<unsigned char> &vchCiphertext) +{ + CCrypter cKeyCrypter; + std::vector<unsigned char> 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<unsigned char>& vchCiphertext, const uint256& nIV, CSecret& vchPlaintext) +{ + CCrypter cKeyCrypter; + std::vector<unsigned char> 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<unsigned char> vchCryptedKey; + std::vector<unsigned char> 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<unsigned char> 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<unsigned char>(0); + } +}; + +typedef std::vector<unsigned char, secure_allocator<unsigned char> > 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<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); + bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext); + bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext); + bool SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& 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<unsigned char> &vchCiphertext); +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char> &vchCiphertext, const uint256& nIV, CSecret &vchPlaintext); + +#endif diff --git a/src/db.cpp b/src/db.cpp index c479a452cd..e639a34799 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -778,7 +778,29 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) ssValue >> wkey; key.SetPrivKey(wkey.vchPrivKey); } - pwallet->LoadKey(key); + 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<unsigned char> vchPubKey; + ssKey >> vchPubKey; + vector<unsigned char> vchPrivKey; + ssValue >> vchPrivKey; + if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey)) + return false; } else if (strType == "defaultkey") { @@ -391,6 +391,25 @@ public: return Write(std::make_pair(std::string("key"), vchPubKey), vchPrivKey, false); } + bool WriteCryptedKey(const std::vector<unsigned char>& vchPubKey, const std::vector<unsigned char>& 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/keystore.cpp b/src/keystore.cpp index 765144a9b7..f659495a1a 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -4,6 +4,7 @@ #include "headers.h" #include "db.h" +#include "crypter.h" std::vector<unsigned char> CKeyStore::GenerateNewKey() { @@ -17,6 +18,7 @@ std::vector<unsigned char> CKeyStore::GenerateNewKey() bool CBasicKeyStore::AddKey(const CKey& key) { + CRITICAL_BLOCK(cs_mapPubKeys) CRITICAL_BLOCK(cs_KeyStore) { mapKeys[key.GetPubKey()] = key.GetPrivKey(); @@ -25,31 +27,46 @@ bool CBasicKeyStore::AddKey(const CKey& key) return true; } -bool CCryptoKeyStore::Unlock(const CMasterKey& vMasterKeyIn) +std::vector<unsigned char> CCryptoKeyStore::GenerateNewKey() { - if (!SetCrypted()) - return false; + RandAddSeedPerfmon(); + CKey key; + key.MakeNewKey(); + if (!AddKey(key)) + throw std::runtime_error("CCryptoKeyStore::GenerateNewKey() : AddKey failed"); + return key.GetPubKey(); +} - std::map<std::vector<unsigned char>, std::vector<unsigned char> >::const_iterator mi = mapCryptedKeys.begin(); - for (; mi != mapCryptedKeys.end(); ++mi) +bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) +{ + CRITICAL_BLOCK(cs_vMasterKey) { - const std::vector<unsigned char> &vchPubKey = (*mi).first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second; - CSecret vchSecret; - // decrypt vchCryptedSecret using vMasterKeyIn, into vchSecret - CKey key; - key.SetSecret(vchSecret); - if (key.GetPubKey() == vchPubKey) - break; - return false; + if (!SetCrypted()) + return false; + + std::map<std::vector<unsigned char>, std::vector<unsigned char> >::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const std::vector<unsigned char> &vchPubKey = (*mi).first; + const std::vector<unsigned char> &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; } - vMasterKey = vMasterKeyIn; return true; } bool CCryptoKeyStore::AddKey(const CKey& key) { CRITICAL_BLOCK(cs_KeyStore) + CRITICAL_BLOCK(cs_vMasterKey) { if (!IsCrypted()) return CBasicKeyStore::AddKey(key); @@ -57,12 +74,13 @@ bool CCryptoKeyStore::AddKey(const CKey& key) if (IsLocked()) return false; - CSecret vchSecret = key.GetSecret(); - std::vector<unsigned char> vchCryptedSecret; - // encrypt vchSecret using vMasterKey, into vchCryptedSecret + std::vector<unsigned char> vchPubKey = key.GetPubKey(); + if (!EncryptSecret(vMasterKey, key.GetSecret(), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + return false; - AddCryptedKey(key.GetPubKey(), vchCryptedSecret); + if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) + return false; } return true; } @@ -70,6 +88,7 @@ bool CCryptoKeyStore::AddKey(const CKey& key) bool CCryptoKeyStore::AddCryptedKey(const std::vector<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { + CRITICAL_BLOCK(cs_mapPubKeys) CRITICAL_BLOCK(cs_KeyStore) { if (!SetCrypted()) @@ -83,38 +102,48 @@ bool CCryptoKeyStore::AddCryptedKey(const std::vector<unsigned char> &vchPubKey, bool CCryptoKeyStore::GetPrivKey(const std::vector<unsigned char> &vchPubKey, CPrivKey& keyOut) const { - if (!IsCrypted()) - return CBasicKeyStore::GetPrivKey(vchPubKey, keyOut); - - std::map<std::vector<unsigned char>, std::vector<unsigned char> >::const_iterator mi = mapCryptedKeys.find(vchPubKey); - if (mi != mapCryptedKeys.end()) + CRITICAL_BLOCK(cs_vMasterKey) { - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second; - CSecret vchSecret; - // decrypt vchCryptedSecret using vMasterKey into vchSecret; - CKey key; - key.SetSecret(vchSecret); - keyOut = key.GetPrivKey(); - return true; + if (!IsCrypted()) + return CBasicKeyStore::GetPrivKey(vchPubKey, keyOut); + + std::map<std::vector<unsigned char>, std::vector<unsigned char> >::const_iterator mi = mapCryptedKeys.find(vchPubKey); + if (mi != mapCryptedKeys.end()) + { + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second; + CSecret vchSecret; + if (!DecryptSecret(vMasterKey, (*mi).second, Hash((*mi).first.begin(), (*mi).first.end()), vchSecret)) + return false; + CKey key; + key.SetSecret(vchSecret); + keyOut = key.GetPrivKey(); + return true; + } } return false; } -bool CCryptoKeyStore::GenerateMasterKey() +bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) { - if (!mapCryptedKeys.empty()) - return false; - - RandAddSeedPerfmon(); - - vMasterKey.resize(32); - RAND_bytes(&vMasterKey[0], 32); - - if (!IsCrypted()) + CRITICAL_BLOCK(cs_KeyStore) + CRITICAL_BLOCK(cs_vMasterKey) { - // upgrade wallet + 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<unsigned char> 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 4095535493..8d445befea 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -4,7 +4,7 @@ #ifndef BITCOIN_KEYSTORE_H #define BITCOIN_KEYSTORE_H -typedef std::vector<unsigned char, secure_allocator<unsigned char> > CMasterKey; +#include "crypter.h" class CKeyStore { @@ -17,10 +17,12 @@ public: virtual std::vector<unsigned char> GenerateNewKey(); }; +typedef std::map<std::vector<unsigned char>, CPrivKey> KeyMap; + class CBasicKeyStore : public CKeyStore { protected: - std::map<std::vector<unsigned char>, CPrivKey> mapKeys; + KeyMap mapKeys; public: bool AddKey(const CKey& key); @@ -45,18 +47,13 @@ class CCryptoKeyStore : public CBasicKeyStore private: std::map<std::vector<unsigned char>, std::vector<unsigned char> > mapCryptedKeys; - CMasterKey vMasterKey; + CKeyingMaterial 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) @@ -64,27 +61,26 @@ protected: if (!mapKeys.empty()) return false; fUseCrypto = true; + return true; } // will encrypt previously unencrypted keys - bool GenerateMasterKey(); + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - bool GetMasterKey(CMasterKey &vMasterKeyOut) const - { - if (!IsCrypted()) - return false; - if (IsLocked()) - return false; - vMasterKeyOut = vMasterKey; - return true; - } - bool Unlock(const CMasterKey& 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()) @@ -94,12 +90,18 @@ public: bool Lock() { - if (!SetCrypted()) - return false; - vMasterKey.clear(); + CRITICAL_BLOCK(cs_vMasterKey) + { + if (!SetCrypted()) + return false; + + vMasterKey.clear(); + } + return true; } virtual bool AddCryptedKey(const std::vector<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + std::vector<unsigned char> GenerateNewKey(); bool AddKey(const CKey& key); bool HaveKey(const std::vector<unsigned char> &vchPubKey) const { diff --git a/src/main.cpp b/src/main.cpp index 594f1d3bc4..53cdab175a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2213,7 +2213,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/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..3a573043f7 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -309,6 +309,7 @@ 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))); obj.push_back(Pair("errors", GetWarnings("statusbar"))); return obj; @@ -324,13 +325,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 +353,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<uint256, CWalletTx>::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<uint256, CWalletTx>::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) - { - account.vchPubKey = pwalletMain->GetKeyFromKeyPool(); - string strAddress = PubKeyToAddress(account.vchPubKey); - pwalletMain->SetAddressBookName(strAddress, strAccount); - walletdb.WriteAccount(strAccount, account); + // 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); + } + } } - walletdb.TxnCommit(); strAddress = PubKeyToAddress(account.vchPubKey); return strAddress; @@ -510,7 +528,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 <bitcoinaddress> <amount> [comment] [comment-to]\n" + "<amount> 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 <bitcoinaddress> <amount> [comment] [comment-to]\n" "<amount> is a real and is rounded to the nearest 0.00000001"); @@ -528,7 +551,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 +800,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 <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" + "<amount> 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 <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n" "<amount> is a real and is rounded to the nearest 0.00000001"); @@ -794,7 +826,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 +845,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 <fromaccount> {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 <fromaccount> {address:amount,...} [minconf=1] [comment]\n" "amounts are double-precision floating point numbers"); @@ -851,7 +893,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 +1327,198 @@ 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) +{ + static int64 nWakeTime; + int64 nMyWakeTime = GetTime() + *((int*)parg); + static CCriticalSection cs_nWakeTime; + + if (nWakeTime == 0) + { + CRITICAL_BLOCK(cs_nWakeTime) + { + nWakeTime = nMyWakeTime; + } + + while (GetTime() < nWakeTime) + Sleep(GetTime() - nWakeTime); + + CRITICAL_BLOCK(cs_nWakeTime) + { + nWakeTime = 0; + } + } + else + { + CRITICAL_BLOCK(cs_nWakeTime) + { + if (nWakeTime < nMyWakeTime) + nWakeTime = 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 <passphrase> <timeout>\n" + "Stores the wallet decryption key in memory for <timeout> 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 <passphrase> <timeout>\n" + "Stores the wallet decryption key in memory for <timeout> 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 <oldpassphrase> <newpassphrase>\n" + "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); + 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 <oldpassphrase> <newpassphrase>\n" + "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); + + 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 encryptwallet(const Array& params, bool fHelp) +{ + if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) + throw runtime_error( + "encryptwallet <passphrase>\n" + "Encrypts the wallet with <passphrase>."); + 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 <passphrase>\n" + "Encrypts the wallet with <passphrase>."); + + 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 +1670,48 @@ Value getwork(const Array& params, bool fHelp) pair<string, rpcfn_type> 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("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<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); @@ -1493,6 +1735,8 @@ string pAllowInSafeMode[] = "getaddressesbyaccount", "getaddressesbylabel", // deprecated "backupwallet", + "keypoolrefill", + "walletpassphrase", "validateaddress", "getwork", }; @@ -2130,6 +2374,7 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); if (strMethod == "sendmany" && n > 1) { string s = params[1].get_str(); diff --git a/src/script.cpp b/src/script.cpp index c17525034a..13a53d6b9c 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -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<pair<opcodetype, valtype> > 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<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second)); + if (mi == mapPubKeys.end()) + return false; + const vector<unsigned char>& vchPubKey = (*mi).second; + if (!keystore.HaveKey(vchPubKey)) + return false; + } + else + { + return false; + } + } + } + + return true; } diff --git a/src/ui.cpp b/src/ui.cpp index db02cb5831..52315e1fe4 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; +} + @@ -1122,6 +1157,166 @@ 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"); +} + +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 +1377,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 +2153,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 +2167,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 +2466,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)) @@ -2581,8 +2812,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 @@ -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..9ac4ec8e65 100644 --- a/src/uibase.cpp +++ b/src/uibase.cpp @@ -32,6 +32,14 @@ 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 ); + wxMenuItem* m_menuOptionsEncryptWallet; + m_menuOptionsEncryptWallet = new wxMenuItem( m_menuOptions, wxID_ANY, wxString( _("&Encrypt Wallet...") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuOptions->Append( m_menuOptionsEncryptWallet ); + + wxMenuItem* m_menuOptionsChangeWalletPassphrase; + 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 +195,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 +255,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..e2dbb0bd4d 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(); } diff --git a/src/wallet.cpp b/src/wallet.cpp index a179876dcf..c3d793d379 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,11 +17,120 @@ using namespace std; bool CWallet::AddKey(const CKey& key) { - if (!CBasicKeyStore::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<unsigned char> &vchPubKey, const vector<unsigned char> &vchCryptedSecret) +{ + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + if (!fFileBacked) + return true; + 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)) + { + 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; +} + +bool CWallet::EncryptWallet(const string& strWalletPassphrase) +{ + //TODO: use db commits + CRITICAL_BLOCK(cs_mapPubKeys) + CRITICAL_BLOCK(cs_KeyStore) + CRITICAL_BLOCK(cs_vMasterKey) + { + 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; + 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) + { + DBFlush(false); + CWalletDB(strWalletFile).WriteMasterKey(nMasterKeyMaxID, kMasterKey); + DBFlush(false); + } + + if (!EncryptKeys(vMasterKey)) + exit(1); //We now probably have half of our keys encrypted, and half not...die and let the user ask someone with experience to recover their wallet. + + Lock(); + } + return true; } void CWallet::WalletUpdateSpent(const CTransaction &tx) @@ -99,7 +208,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) BOOST_FOREACH(const CTxOut& txout, wtx.vout) { if (txout.scriptPubKey == scriptDefaultKey) - SetDefaultKey(GetKeyFromKeyPool()); + SetDefaultKey(GetOrReuseKeyFromPool()); } // Notify UI @@ -904,15 +1013,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)) @@ -956,12 +1074,12 @@ bool CWallet::LoadWallet(bool& fFirstRunRet) return false; fFirstRunRet = vchDefaultKey.empty(); - if (!mapKeys.count(vchDefaultKey)) + if (!HaveKey(vchDefaultKey)) { // Create new keyUser and set as default key RandAddSeedPerfmon(); - SetDefaultKey(GetKeyFromKeyPool()); + SetDefaultKey(GetOrReuseKeyFromPool()); if (!SetAddressBookName(PubKeyToAddress(vchDefaultKey), "")) return false; } @@ -1034,14 +1152,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 @@ -1052,13 +1172,31 @@ 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)) @@ -1092,11 +1230,13 @@ void CWallet::ReturnKey(int64 nIndex) printf("keypool return %"PRI64d"\n", nIndex); } -vector<unsigned char> CWallet::GetKeyFromKeyPool() +vector<unsigned char> CWallet::GetOrReuseKeyFromPool() { int64 nIndex = 0; CKeyPool keypool; ReserveKeyFromKeyPool(nIndex, keypool); + if(nIndex == -1) + return vchDefaultKey; KeepKey(nIndex); return keypool.vchPubKey; } @@ -1106,6 +1246,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 8fb29a4892..c0ee24f0be 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -26,14 +26,20 @@ public: std::set<int64> setKeyPool; CCriticalSection cs_setKeyPool; + typedef std::map<unsigned int, CMasterKey> MasterKeyMap; + MasterKeyMap mapMasterKeys; + unsigned int nMasterKeyMaxID; + CWallet() { fFileBacked = false; + nMasterKeyMaxID = 0; } CWallet(std::string strWalletFileIn) { strWalletFile = strWalletFileIn; fFileBacked = true; + nMasterKeyMaxID = 0; } mutable CCriticalSection cs_mapWallet; @@ -51,6 +57,12 @@ public: // keystore implementation bool AddKey(const CKey& key); bool LoadKey(const CKey& key) { return CCryptoKeyStore::AddKey(key); } + bool AddCryptedKey(const std::vector<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool LoadCryptedKey(const std::vector<unsigned char> &vchPubKey, const std::vector<unsigned char> &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); @@ -67,10 +79,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<unsigned char> GetKeyFromKeyPool(); + std::vector<unsigned char> GetOrReuseKeyFromPool(); int64 GetOldestKeyPoolTime(); bool IsMine(const CTxIn& txin) const; @@ -177,6 +190,11 @@ public: } } + int GetKeyPoolSize() + { + return setKeyPool.size(); + } + bool GetTransaction(const uint256 &hashTx, CWalletTx& wtx); bool SetDefaultKey(const std::vector<unsigned char> &vchPubKey); |