From 16d140f4a2cd56197df3a911e27677509e839bd1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 10 Aug 2016 17:47:13 +1200 Subject: [PATCH] Add support for encrypting spending keys --- src/gtest/test_keystore.cpp | 57 ++++++++++++++++++++ src/keystore.h | 1 + src/wallet/crypter.cpp | 104 +++++++++++++++++++++++++++++++++++- src/wallet/crypter.h | 32 ++++++++++- src/zcash/Address.cpp | 8 +++ src/zcash/Address.hpp | 3 ++ 6 files changed, 203 insertions(+), 2 deletions(-) diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 11d967e89..b41cb4f4a 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -1,6 +1,8 @@ #include #include "keystore.h" +#include "random.h" +#include "wallet/crypter.h" #include "zcash/Address.hpp" TEST(keystore_tests, store_and_retrieve_spending_key) { @@ -41,3 +43,58 @@ TEST(keystore_tests, store_and_retrieve_note_decryptor) { EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); EXPECT_EQ(ZCNoteDecryption(sk.viewing_key()), decOut); } + +class TestCCryptoKeyStore : public CCryptoKeyStore +{ +public: + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn) { return CCryptoKeyStore::EncryptKeys(vMasterKeyIn); } + bool Unlock(const CKeyingMaterial& vMasterKeyIn) { return CCryptoKeyStore::Unlock(vMasterKeyIn); } +}; + +TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) { + TestCCryptoKeyStore keyStore; + uint256 r {GetRandHash()}; + CKeyingMaterial vMasterKey (r.begin(), r.end()); + libzcash::SpendingKey keyOut; + std::set addrs; + + // 1) Test adding a key to an unencrypted key store, then encrypting it + auto sk = libzcash::SpendingKey::random(); + auto addr = sk.address(); + keyStore.AddSpendingKey(sk); + ASSERT_TRUE(keyStore.HaveSpendingKey(addr)); + ASSERT_TRUE(keyStore.GetSpendingKey(addr, keyOut)); + ASSERT_EQ(sk, keyOut); + + ASSERT_TRUE(keyStore.EncryptKeys(vMasterKey)); + ASSERT_TRUE(keyStore.HaveSpendingKey(addr)); + ASSERT_FALSE(keyStore.GetSpendingKey(addr, keyOut)); + + ASSERT_TRUE(keyStore.Unlock(vMasterKey)); + ASSERT_TRUE(keyStore.GetSpendingKey(addr, keyOut)); + ASSERT_EQ(sk, keyOut); + + keyStore.GetPaymentAddresses(addrs); + ASSERT_EQ(1, addrs.size()); + ASSERT_EQ(1, addrs.count(addr)); + + // 2) Test adding a spending key to an already-encrypted key store + auto sk2 = libzcash::SpendingKey::random(); + auto addr2 = sk2.address(); + keyStore.AddSpendingKey(sk2); + ASSERT_TRUE(keyStore.HaveSpendingKey(addr2)); + ASSERT_TRUE(keyStore.GetSpendingKey(addr2, keyOut)); + ASSERT_EQ(sk2, keyOut); + + ASSERT_TRUE(keyStore.Lock()); + ASSERT_TRUE(keyStore.HaveSpendingKey(addr2)); + ASSERT_FALSE(keyStore.GetSpendingKey(addr2, keyOut)); + + ASSERT_TRUE(keyStore.Unlock(vMasterKey)); + ASSERT_TRUE(keyStore.GetSpendingKey(addr2, keyOut)); + ASSERT_EQ(sk2, keyOut); + + keyStore.GetPaymentAddresses(addrs); + ASSERT_EQ(2, addrs.size()); + ASSERT_EQ(1, addrs.count(addr2)); +} diff --git a/src/keystore.h b/src/keystore.h index aa3aefdf2..84595cfb0 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -172,5 +172,6 @@ public: typedef std::vector > CKeyingMaterial; typedef std::map > > CryptedKeyMap; +typedef std::map > CryptedSpendingKeyMap; #endif // BITCOIN_KEYSTORE_H diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 0b0fb562e..06753bf15 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -6,6 +6,7 @@ #include "script/script.h" #include "script/standard.h" +#include "streams.h" #include "util.h" #include @@ -135,12 +136,31 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector& vchCryptedSecret, + const libzcash::PaymentAddress& address, + libzcash::SpendingKey& sk) +{ + CKeyingMaterial vchSecret; + if(!DecryptSecret(vMasterKey, vchCryptedSecret, address.GetHash(), vchSecret)) + return false; + + if (vchSecret.size() != 32) + return false; + + // TODO does this undo the benefits of using CKeyingMaterial? + std::vector serialized(vchSecret.begin(), vchSecret.end()); + CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); + ss >> sk; + return sk.address() == address; +} + bool CCryptoKeyStore::SetCrypted() { LOCK(cs_KeyStore); if (fUseCrypto) return true; - if (!mapKeys.empty()) + if (!(mapKeys.empty() && mapSpendingKeys.empty())) return false; fUseCrypto = true; return true; @@ -184,6 +204,21 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) if (fDecryptionThoroughlyChecked) break; } + CryptedSpendingKeyMap::const_iterator skmi = mapCryptedSpendingKeys.begin(); + for (; skmi != mapCryptedSpendingKeys.end(); ++skmi) + { + const libzcash::PaymentAddress &address = (*skmi).first; + const std::vector &vchCryptedSecret = (*skmi).second; + libzcash::SpendingKey sk; + if (!DecryptSpendingKey(vMasterKeyIn, vchCryptedSecret, address, sk)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } if (keyPass && keyFail) { LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); @@ -267,6 +302,59 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co return false; } +bool CCryptoKeyStore::AddSpendingKey(const libzcash::SpendingKey &sk) +{ + { + LOCK(cs_KeyStore); + if (!IsCrypted()) + return CBasicKeyStore::AddSpendingKey(sk); + + if (IsLocked()) + return false; + + std::vector vchCryptedSecret; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << sk; + CKeyingMaterial vchSecret(ss.begin(), ss.end()); + auto address = sk.address(); + if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret)) + return false; + + if (!AddCryptedSpendingKey(address, vchCryptedSecret)) + return false; + } + return true; +} + +bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::PaymentAddress &address, const std::vector &vchCryptedSecret) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + mapCryptedSpendingKeys[address] = vchCryptedSecret; + } + return true; +} + +bool CCryptoKeyStore::GetSpendingKey(const libzcash::PaymentAddress &address, libzcash::SpendingKey &skOut) const +{ + { + LOCK(cs_KeyStore); + if (!IsCrypted()) + return CBasicKeyStore::GetSpendingKey(address, skOut); + + CryptedSpendingKeyMap::const_iterator mi = mapCryptedSpendingKeys.find(address); + if (mi != mapCryptedSpendingKeys.end()) + { + const std::vector &vchCryptedSecret = (*mi).second; + return DecryptSpendingKey(vMasterKey, vchCryptedSecret, address, skOut); + } + } + return false; +} + bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) { { @@ -287,6 +375,20 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) return false; } mapKeys.clear(); + BOOST_FOREACH(SpendingKeyMap::value_type& mSpendingKey, mapSpendingKeys) + { + const libzcash::SpendingKey &sk = mSpendingKey.second; + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << sk; + CKeyingMaterial vchSecret(ss.begin(), ss.end()); + libzcash::PaymentAddress address = sk.address(); + std::vector vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, address.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedSpendingKey(address, vchCryptedSecret)) + return false; + } + mapSpendingKeys.clear(); } return true; } diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 70aeb7672..4dfffa50e 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -8,6 +8,7 @@ #include "keystore.h" #include "serialize.h" #include "support/allocators/secure.h" +#include "zcash/Address.hpp" class uint256; @@ -114,10 +115,11 @@ class CCryptoKeyStore : public CBasicKeyStore { private: CryptedKeyMap mapCryptedKeys; + CryptedSpendingKeyMap mapCryptedSpendingKeys; CKeyingMaterial vMasterKey; - //! if fUseCrypto is true, mapKeys must be empty + //! if fUseCrypto is true, mapKeys and mapSpendingKeys must be empty //! if fUseCrypto is false, vMasterKey must be empty bool fUseCrypto; @@ -185,6 +187,34 @@ public: mi++; } } + virtual bool AddCryptedSpendingKey(const libzcash::PaymentAddress &address, const std::vector &vchCryptedSecret); + bool AddSpendingKey(const libzcash::SpendingKey &sk); + bool HaveSpendingKey(const libzcash::PaymentAddress &address) const + { + { + LOCK(cs_KeyStore); + if (!IsCrypted()) + return CBasicKeyStore::HaveSpendingKey(address); + return mapCryptedSpendingKeys.count(address) > 0; + } + return false; + } + bool GetSpendingKey(const libzcash::PaymentAddress &address, libzcash::SpendingKey &skOut) const; + void GetPaymentAddresses(std::set &setAddress) const + { + if (!IsCrypted()) + { + CBasicKeyStore::GetPaymentAddresses(setAddress); + return; + } + setAddress.clear(); + CryptedSpendingKeyMap::const_iterator mi = mapCryptedSpendingKeys.begin(); + while (mi != mapCryptedSpendingKeys.end()) + { + setAddress.insert((*mi).first); + mi++; + } + } /** * Wallet status (encrypted, locked) changed. diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 9bb32fb6c..3849b2ffc 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -1,9 +1,17 @@ #include "Address.hpp" #include "NoteEncryption.hpp" +#include "hash.h" #include "prf.h" +#include "streams.h" namespace libzcash { +uint256 PaymentAddress::GetHash() const { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << *this; + return Hash(ss.begin(), ss.end()); +} + uint256 ViewingKey::pk_enc() { return ZCNoteEncryption::generate_pubkey(*this); } diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 58caae772..f259818af 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -23,6 +23,9 @@ public: READWRITE(pk_enc); } + //! Get the 256-bit SHA256d hash of this payment address. + uint256 GetHash() const; + friend inline bool operator==(const PaymentAddress& a, const PaymentAddress& b) { return a.a_pk == b.a_pk && a.pk_enc == b.pk_enc; }