From 73699ceaf69bf17c1ae942b3e204afce82446cf2 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 21 Sep 2016 14:02:35 -0700 Subject: [PATCH] Add support for spending keys to the encrypted wallet. --- src/gtest/test_wallet_zkeys.cpp | 78 +++++++++++++++++++++++++++++++++ src/test/rpc_wallet_tests.cpp | 54 +++++++++++++++++++++++ src/wallet/rpcwallet.cpp | 2 + src/wallet/wallet.cpp | 29 ++++++++++++ src/wallet/wallet.h | 5 ++- src/wallet/walletdb.cpp | 44 ++++++++++++++++--- src/wallet/walletdb.h | 3 ++ 7 files changed, 209 insertions(+), 6 deletions(-) diff --git a/src/gtest/test_wallet_zkeys.cpp b/src/gtest/test_wallet_zkeys.cpp index d812c657d..31a930de0 100644 --- a/src/gtest/test_wallet_zkeys.cpp +++ b/src/gtest/test_wallet_zkeys.cpp @@ -138,3 +138,81 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) { ASSERT_EQ(m.nCreateTime, now); } + + +/** + * This test covers methods on CWalletDB to load/save crypted z keys. + */ +TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) { + ECC_Start(); + + SelectParams(CBaseChainParams::TESTNET); + + // Get temporary and unique path for file. + // Note: / operator to append paths + boost::filesystem::path temp = boost::filesystem::temp_directory_path() / + boost::filesystem::unique_path(); + const std::string path = temp.native(); + + bool fFirstRun; + CWallet wallet(path); + ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun)); + + // No default CPubKey set + ASSERT_TRUE(fFirstRun); + + // wallet should be empty + std::set addrs; + wallet.GetPaymentAddresses(addrs); + ASSERT_EQ(0, addrs.size()); + + // Add random key to the wallet + auto paymentAddress = wallet.GenerateNewZKey(); + + // wallet should have one key + wallet.GetPaymentAddresses(addrs); + ASSERT_EQ(1, addrs.size()); + + // encrypt wallet + SecureString strWalletPass; + strWalletPass.reserve(100); + strWalletPass = "hello"; + ASSERT_TRUE(wallet.EncryptWallet(strWalletPass)); + + // adding a new key will fail as the wallet is locked + EXPECT_ANY_THROW(wallet.GenerateNewZKey()); + + // unlock wallet and then add + wallet.Unlock(strWalletPass); + auto paymentAddress2 = wallet.GenerateNewZKey(); + + // Create a new wallet from the existing wallet path + CWallet wallet2(path); + ASSERT_EQ(DB_LOAD_OK, wallet2.LoadWallet(fFirstRun)); + + // Confirm it's not the same as the other wallet + ASSERT_TRUE(&wallet != &wallet2); + + // wallet should have two keys + wallet2.GetPaymentAddresses(addrs); + ASSERT_EQ(2, addrs.size()); + + // check we have entries for our payment addresses + ASSERT_TRUE(addrs.count(paymentAddress.Get())); + ASSERT_TRUE(addrs.count(paymentAddress2.Get())); + + // spending key is crypted, so we can't extract valid payment address + libzcash::SpendingKey keyOut; + wallet2.GetSpendingKey(paymentAddress.Get(), keyOut); + ASSERT_FALSE(paymentAddress.Get() == keyOut.address()); + + // unlock wallet to get spending keys and verify payment addresses + wallet2.Unlock(strWalletPass); + + wallet2.GetSpendingKey(paymentAddress.Get(), keyOut); + ASSERT_EQ(paymentAddress.Get(), keyOut.address()); + + wallet2.GetSpendingKey(paymentAddress2.Get(), keyOut); + ASSERT_EQ(paymentAddress2.Get(), keyOut.address()); +} + diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 1bf1dbf5b..17b3925f7 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -1041,4 +1041,58 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) } +/* + * This test covers storing encrypted zkeys in the wallet. + */ +BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) +{ + LOCK2(cs_main, pwalletMain->cs_wallet); + Value retValue; + int n = 100; + + // wallet should currently be empty + std::set addrs; + pwalletMain->GetPaymentAddresses(addrs); + BOOST_CHECK(addrs.size()==0); + + // create keys + for (int i = 0; i < n; i++) { + CallRPC("z_getnewaddress"); + } + + // Verify we can list the keys imported + BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); + Array arr = retValue.get_array(); + BOOST_CHECK(arr.size() == n); + + // Encrypt the wallet (we can''t call RPC encryptwallet as that shuts down node) + SecureString strWalletPass; + strWalletPass.reserve(100); + strWalletPass = "hello"; + BOOST_CHECK(pwalletMain->EncryptWallet(strWalletPass)); + + // Verify we can still list the keys imported + BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); + arr = retValue.get_array(); + BOOST_CHECK(arr.size() == n); + + // Try to add a new key, but we can't as the wallet is locked + BOOST_CHECK_THROW(CallRPC("z_getnewaddress"), runtime_error); + + // We can't call RPC walletpassphrase as that invokes RPCRunLater which breaks tests. + // So we manually unlock. + BOOST_CHECK(pwalletMain->Unlock(strWalletPass)); + + // Now add a key + BOOST_CHECK_NO_THROW(CallRPC("z_getnewaddress")); + + // Verify the key has been added + BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); + arr = retValue.get_array(); + BOOST_CHECK(arr.size() == n+1); + + // We can't simulate over RPC the wallet closing and being reloaded + // but there are tests for this in gtest. +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ba04bdfe9..4bf5606ae 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2796,6 +2796,8 @@ Value z_getnewaddress(const Array& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); + CZCPaymentAddress pubaddr = pwalletMain->GenerateNewZKey(); std::string result = pubaddr.ToString(); return result; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b34f918c4..20ef3dfb2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -18,6 +18,7 @@ #include "util.h" #include "utilmoneystr.h" #include "zcash/Note.hpp" +#include "crypter.h" #include @@ -169,6 +170,7 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector &vchCryptedSecret) { + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; if (!fFileBacked) @@ -187,6 +189,28 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, return false; } + +bool CWallet::AddCryptedSpendingKey(const libzcash::PaymentAddress &address, + const std::vector &vchCryptedSecret) +{ + if (!CCryptoKeyStore::AddCryptedSpendingKey(address, vchCryptedSecret)) + return false; + if (!fFileBacked) + return true; + { + LOCK(cs_wallet); + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedZKey(address, + vchCryptedSecret, + mapZKeyMetadata[address]); + else + return CWalletDB(strWalletFile).WriteCryptedZKey(address, + vchCryptedSecret, + mapZKeyMetadata[address]); + } + return false; +} + bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -209,6 +233,11 @@ bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) +{ + return CCryptoKeyStore::AddCryptedSpendingKey(addr, vchCryptedSecret); +} + bool CWallet::LoadZKey(const libzcash::SpendingKey &key) { return CCryptoKeyStore::AddSpendingKey(key); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4bdd45bf8..6dcf727c4 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -752,7 +752,10 @@ public: bool LoadZKey(const libzcash::SpendingKey &key); //! Load spending key metadata (used by LoadWallet) bool LoadZKeyMetadata(const libzcash::PaymentAddress &addr, const CKeyMetadata &meta); - + //! Adds an encrypted spending key to the store, without saving it to disk (used by LoadWallet) + bool LoadCryptedZKey(const libzcash::PaymentAddress &addr, const std::vector &vchCryptedSecret); + //! Adds an encrypted spending key to the store, and saves it to disk (virtual method, declared in crypter.h) + bool AddCryptedSpendingKey(const libzcash::PaymentAddress &address, const std::vector &vchCryptedSecret); /** * Increment the next transaction order id diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 76465029c..18490165a 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -104,6 +104,25 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, return true; } +bool CWalletDB::WriteCryptedZKey(const libzcash::PaymentAddress & addr, + const std::vector& vchCryptedSecret, + const CKeyMetadata &keyMeta) +{ + const bool fEraseUnencryptedKey = true; + nWalletDBUpdated++; + + if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta)) + return false; + + if (!Write(std::make_pair(std::string("czkey"), addr), vchCryptedSecret, false)) + return false; + if (fEraseUnencryptedKey) + { + Erase(std::make_pair(std::string("zkey"), addr)); + } + return true; +} + bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) { nWalletDBUpdated++; @@ -348,6 +367,7 @@ public: unsigned int nCKeys; unsigned int nKeyMeta; unsigned int nZKeys; + unsigned int nCZKeys; unsigned int nZKeyMeta; bool fIsEncrypted; bool fAnyUnordered; @@ -355,7 +375,7 @@ public: vector vWalletUpgrade; CWalletScanState() { - nKeys = nCKeys = nKeyMeta = nZKeys = nZKeyMeta = 0; + nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = 0; fIsEncrypted = false; fAnyUnordered = false; nFileVersion = 0; @@ -557,6 +577,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } wss.fIsEncrypted = true; } + else if (strType == "czkey") + { + libzcash::PaymentAddress addr; + ssKey >> addr; + vector vchCryptedSecret; + ssValue >> vchCryptedSecret; + wss.nCKeys++; + + if (!pwallet->LoadCryptedZKey(addr, vchCryptedSecret)) + { + strErr = "Error reading wallet database: LoadCryptedZKey failed"; + return false; + } + wss.fIsEncrypted = true; + } else if (strType == "keymeta") { CPubKey vchPubKey; @@ -651,7 +686,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, static bool IsKeyType(string strType) { return (strType== "key" || strType == "wkey" || - strType == "zkey" || + strType == "zkey" || strType == "czkey" || strType == "mkey" || strType == "ckey"); } @@ -736,9 +771,8 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); - // TODO: Keep track of encrypted ZKeys - LogPrintf("ZKeys: %u plaintext, -- encrypted, %u w/metadata, %u total\n", - wss.nZKeys, wss.nZKeyMeta, wss.nZKeys + 0); + LogPrintf("ZKeys: %u plaintext, %u encrypted, %u w/metadata, %u total\n", + wss.nZKeys, wss.nCZKeys, wss.nZKeyMeta, wss.nZKeys + wss.nCZKeys); // nTimeFirstKey is only reliable if all keys have metadata if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 317f71304..fc50d5d15 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -135,6 +135,9 @@ public: /// Write spending key to wallet database, where key is payment address and value is spending key. bool WriteZKey(const libzcash::PaymentAddress& addr, const libzcash::SpendingKey& key, const CKeyMetadata &keyMeta); + bool WriteCryptedZKey(const libzcash::PaymentAddress & addr, + const std::vector& vchCryptedSecret, + const CKeyMetadata &keyMeta); private: CWalletDB(const CWalletDB&);