From 642a1caf93af62fd6b3e8271f547f1cc3fd42a74 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 23 Feb 2017 18:27:41 +0000 Subject: [PATCH 01/12] ViewingKey -> ReceivingKey per zcash/zips#117 --- src/gtest/test_joinsplit.cpp | 2 +- src/gtest/test_keystore.cpp | 12 ++++++------ src/keystore.cpp | 2 +- src/utiltest.cpp | 2 +- src/wallet/asyncrpcoperation_sendmany.cpp | 2 +- src/wallet/crypter.cpp | 8 ++++---- src/wallet/crypter.h | 2 +- src/wallet/gtest/test_wallet.cpp | 2 +- src/wallet/rpcwallet.cpp | 12 ++++++------ src/wallet/wallet.cpp | 12 ++++++------ src/wallet/wallet.h | 4 ++-- src/wallet/walletdb.cpp | 12 ++++++------ src/wallet/walletdb.h | 2 +- src/zcash/Address.cpp | 8 ++++---- src/zcash/Address.hpp | 6 +++--- 15 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/gtest/test_joinsplit.cpp b/src/gtest/test_joinsplit.cpp index 986592e89..979d0d518 100644 --- a/src/gtest/test_joinsplit.cpp +++ b/src/gtest/test_joinsplit.cpp @@ -89,7 +89,7 @@ void test_full_api(ZCJoinSplit* js) // Recipient should decrypt // Now the recipient should spend the money again auto h_sig = js->h_sig(randomSeed, nullifiers, pubKeyHash); - ZCNoteDecryption decryptor(recipient_key.viewing_key()); + ZCNoteDecryption decryptor(recipient_key.receiving_key()); auto note_pt = NotePlaintext::decrypt( decryptor, diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index e94aea53e..23cc45aae 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -43,7 +43,7 @@ TEST(keystore_tests, store_and_retrieve_note_decryptor) { keyStore.AddSpendingKey(sk); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); } #ifdef ENABLE_WALLET @@ -72,13 +72,13 @@ TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) { ASSERT_TRUE(keyStore.GetSpendingKey(addr, keyOut)); ASSERT_EQ(sk, keyOut); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); ASSERT_TRUE(keyStore.EncryptKeys(vMasterKey)); ASSERT_TRUE(keyStore.HaveSpendingKey(addr)); ASSERT_FALSE(keyStore.GetSpendingKey(addr, keyOut)); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); // Unlocking with a random key should fail uint256 r2 {GetRandHash()}; @@ -109,19 +109,19 @@ TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) { ASSERT_TRUE(keyStore.GetSpendingKey(addr2, keyOut)); ASSERT_EQ(sk2, keyOut); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk2.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut); ASSERT_TRUE(keyStore.Lock()); ASSERT_TRUE(keyStore.HaveSpendingKey(addr2)); ASSERT_FALSE(keyStore.GetSpendingKey(addr2, keyOut)); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk2.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut); ASSERT_TRUE(keyStore.Unlock(vMasterKey)); ASSERT_TRUE(keyStore.GetSpendingKey(addr2, keyOut)); ASSERT_EQ(sk2, keyOut); EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut)); - EXPECT_EQ(ZCNoteDecryption(sk2.viewing_key()), decOut); + EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut); keyStore.GetPaymentAddresses(addrs); ASSERT_EQ(2, addrs.size()); diff --git a/src/keystore.cpp b/src/keystore.cpp index f32ba0c32..3c32ab583 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -89,6 +89,6 @@ bool CBasicKeyStore::AddSpendingKey(const libzcash::SpendingKey &sk) LOCK(cs_SpendingKeyStore); auto address = sk.address(); mapSpendingKeys[address] = sk; - mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(sk.viewing_key()))); + mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(sk.receiving_key()))); return true; } diff --git a/src/utiltest.cpp b/src/utiltest.cpp index 5cebc1a5d..e91a796e3 100644 --- a/src/utiltest.cpp +++ b/src/utiltest.cpp @@ -63,7 +63,7 @@ CWalletTx GetValidReceive(ZCJoinSplit& params, libzcash::Note GetNote(ZCJoinSplit& params, const libzcash::SpendingKey& sk, const CTransaction& tx, size_t js, size_t n) { - ZCNoteDecryption decryptor {sk.viewing_key()}; + ZCNoteDecryption decryptor {sk.receiving_key()}; auto hSig = tx.vjoinsplit[js].h_sig(params, tx.joinSplitPubKey); auto note_pt = libzcash::NotePlaintext::decrypt( decryptor, diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 539d5d7d6..59cd3a8fb 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -555,7 +555,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries) // Decrypt the change note's ciphertext to retrieve some data we need - ZCNoteDecryption decryptor(spendingkey_.viewing_key()); + ZCNoteDecryption decryptor(spendingkey_.receiving_key()); auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey); try { NotePlaintext plaintext = NotePlaintext::decrypt( diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 69fe55ebd..69a2649b1 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -316,14 +316,14 @@ bool CCryptoKeyStore::AddSpendingKey(const libzcash::SpendingKey &sk) if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret)) return false; - if (!AddCryptedSpendingKey(address, sk.viewing_key(), vchCryptedSecret)) + if (!AddCryptedSpendingKey(address, sk.receiving_key(), vchCryptedSecret)) return false; } return true; } bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::PaymentAddress &address, - const libzcash::ViewingKey &vk, + const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret) { { @@ -332,7 +332,7 @@ bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::PaymentAddress &addr return false; mapCryptedSpendingKeys[address] = vchCryptedSecret; - mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(vk))); + mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(rk))); } return true; } @@ -384,7 +384,7 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) std::vector vchCryptedSecret; if (!EncryptSecret(vMasterKeyIn, vchSecret, address.GetHash(), vchCryptedSecret)) return false; - if (!AddCryptedSpendingKey(address, sk.viewing_key(), vchCryptedSecret)) + if (!AddCryptedSpendingKey(address, sk.receiving_key(), vchCryptedSecret)) return false; } mapSpendingKeys.clear(); diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index d09cfa846..bcee188cf 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -201,7 +201,7 @@ public: } } virtual bool AddCryptedSpendingKey(const libzcash::PaymentAddress &address, - const libzcash::ViewingKey &vk, + const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret); bool AddSpendingKey(const libzcash::SpendingKey &sk); bool HaveSpendingKey(const libzcash::PaymentAddress &address) const diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 9bcc5f533..b39275f67 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -328,7 +328,7 @@ TEST(wallet_tests, GetNoteNullifier) { auto sk = libzcash::SpendingKey::random(); auto address = sk.address(); - auto dec = ZCNoteDecryption(sk.viewing_key()); + auto dec = ZCNoteDecryption(sk.receiving_key()); auto wtx = GetValidReceive(sk, 10, true); auto note = GetNote(sk, wtx, 0, 1); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 034147f42..d2c558c96 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2667,7 +2667,7 @@ UniValue zc_raw_receive(const UniValue& params, bool fHelp) } } - ZCNoteDecryption decryptor(k.viewing_key()); + ZCNoteDecryption decryptor(k.receiving_key()); NotePlaintext npt = NotePlaintext::decrypt( decryptor, @@ -2908,20 +2908,20 @@ UniValue zc_raw_keygen(const UniValue& params, bool fHelp) auto k = SpendingKey::random(); auto addr = k.address(); - auto viewing_key = k.viewing_key(); + auto receiving_key = k.receiving_key(); - CDataStream viewing(SER_NETWORK, PROTOCOL_VERSION); + CDataStream receiving(SER_NETWORK, PROTOCOL_VERSION); - viewing << viewing_key; + receiving << receiving_key; CZCPaymentAddress pubaddr(addr); CZCSpendingKey spendingkey(k); - std::string viewing_hex = HexStr(viewing.begin(), viewing.end()); + std::string receiving_hex = HexStr(receiving.begin(), receiving.end()); UniValue result(UniValue::VOBJ); result.push_back(Pair("zcaddress", pubaddr.ToString())); result.push_back(Pair("zcsecretkey", spendingkey.ToString())); - result.push_back(Pair("zcviewingkey", viewing_hex)); + result.push_back(Pair("zcviewingkey", receiving_hex)); return result; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a61c1e2f7..677fec319 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -190,10 +190,10 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, bool CWallet::AddCryptedSpendingKey(const libzcash::PaymentAddress &address, - const libzcash::ViewingKey &vk, + const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret) { - if (!CCryptoKeyStore::AddCryptedSpendingKey(address, vk, vchCryptedSecret)) + if (!CCryptoKeyStore::AddCryptedSpendingKey(address, rk, vchCryptedSecret)) return false; if (!fFileBacked) return true; @@ -201,12 +201,12 @@ bool CWallet::AddCryptedSpendingKey(const libzcash::PaymentAddress &address, LOCK(cs_wallet); if (pwalletdbEncryption) { return pwalletdbEncryption->WriteCryptedZKey(address, - vk, + rk, vchCryptedSecret, mapZKeyMetadata[address]); } else { return CWalletDB(strWalletFile).WriteCryptedZKey(address, - vk, + rk, vchCryptedSecret, mapZKeyMetadata[address]); } @@ -236,9 +236,9 @@ bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) +bool CWallet::LoadCryptedZKey(const libzcash::PaymentAddress &addr, const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret) { - return CCryptoKeyStore::AddCryptedSpendingKey(addr, vk, vchCryptedSecret); + return CCryptoKeyStore::AddCryptedSpendingKey(addr, rk, vchCryptedSecret); } bool CWallet::LoadZKey(const libzcash::SpendingKey &key) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2b481d87a..57a71a431 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -950,9 +950,9 @@ public: //! 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 libzcash::ViewingKey &vk, const std::vector &vchCryptedSecret); + bool LoadCryptedZKey(const libzcash::PaymentAddress &addr, const libzcash::ReceivingKey &rk, 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 libzcash::ViewingKey &vk, const std::vector &vchCryptedSecret); + bool AddCryptedSpendingKey(const libzcash::PaymentAddress &address, const libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret); /** * Increment the next transaction order id diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index f25224336..c79a15e30 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -106,7 +106,7 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, } bool CWalletDB::WriteCryptedZKey(const libzcash::PaymentAddress & addr, - const libzcash::ViewingKey &vk, + const libzcash::ReceivingKey &rk, const std::vector& vchCryptedSecret, const CKeyMetadata &keyMeta) { @@ -116,7 +116,7 @@ bool CWalletDB::WriteCryptedZKey(const libzcash::PaymentAddress & addr, if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta)) return false; - if (!Write(std::make_pair(std::string("czkey"), addr), std::make_pair(vk, vchCryptedSecret), false)) + if (!Write(std::make_pair(std::string("czkey"), addr), std::make_pair(rk, vchCryptedSecret), false)) return false; if (fEraseUnencryptedKey) { @@ -585,14 +585,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, libzcash::PaymentAddress addr; ssKey >> addr; // Deserialization of a pair is just one item after another - uint256 vkValue; - ssValue >> vkValue; - libzcash::ViewingKey vk(vkValue); + uint256 rkValue; + ssValue >> rkValue; + libzcash::ReceivingKey rk(rkValue); vector vchCryptedSecret; ssValue >> vchCryptedSecret; wss.nCKeys++; - if (!pwallet->LoadCryptedZKey(addr, vk, vchCryptedSecret)) + if (!pwallet->LoadCryptedZKey(addr, rk, vchCryptedSecret)) { strErr = "Error reading wallet database: LoadCryptedZKey failed"; return false; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index f9f71e00c..b901f539c 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -136,7 +136,7 @@ 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 libzcash::ViewingKey & vk, + const libzcash::ReceivingKey & rk, const std::vector& vchCryptedSecret, const CKeyMetadata &keyMeta); diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 3849b2ffc..75324de4f 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -12,12 +12,12 @@ uint256 PaymentAddress::GetHash() const { return Hash(ss.begin(), ss.end()); } -uint256 ViewingKey::pk_enc() { +uint256 ReceivingKey::pk_enc() { return ZCNoteEncryption::generate_pubkey(*this); } -ViewingKey SpendingKey::viewing_key() const { - return ViewingKey(ZCNoteEncryption::generate_privkey(*this)); +ReceivingKey SpendingKey::receiving_key() const { + return ReceivingKey(ZCNoteEncryption::generate_privkey(*this)); } SpendingKey SpendingKey::random() { @@ -25,7 +25,7 @@ SpendingKey SpendingKey::random() { } PaymentAddress SpendingKey::address() const { - return PaymentAddress(PRF_addr_a_pk(*this), viewing_key().pk_enc()); + return PaymentAddress(PRF_addr_a_pk(*this), receiving_key().pk_enc()); } } diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 9bf22663d..e76973cb6 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -38,9 +38,9 @@ public: } }; -class ViewingKey : public uint256 { +class ReceivingKey : public uint256 { public: - ViewingKey(uint256 sk_enc) : uint256(sk_enc) { } + ReceivingKey(uint256 sk_enc) : uint256(sk_enc) { } uint256 pk_enc(); }; @@ -52,7 +52,7 @@ public: static SpendingKey random(); - ViewingKey viewing_key() const; + ReceivingKey receiving_key() const; PaymentAddress address() const; }; From aa666c967341336c1bfb233135c7c9858863168c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 24 Feb 2017 03:01:00 +0000 Subject: [PATCH 02/12] Implement viewing key storage in the keystore --- src/gtest/test_keystore.cpp | 44 +++++++++++++++++++++++++++++++++++++ src/keystore.cpp | 34 ++++++++++++++++++++++++++++ src/keystore.h | 13 +++++++++++ src/zcash/Address.cpp | 12 ++++++++-- src/zcash/Address.hpp | 31 +++++++++++++++++++++++++- 5 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 23cc45aae..903a48839 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -46,6 +46,50 @@ TEST(keystore_tests, store_and_retrieve_note_decryptor) { EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); } +TEST(keystore_tests, StoreAndRetrieveViewingKey) { + CBasicKeyStore keyStore; + libzcash::ViewingKey vkOut; + libzcash::SpendingKey skOut; + ZCNoteDecryption decOut; + + auto sk = libzcash::SpendingKey::random(); + auto vk = sk.viewing_key(); + auto addr = sk.address(); + + // Sanity-check: we can't get a viewing key we haven't added + EXPECT_FALSE(keyStore.HaveViewingKey(addr)); + EXPECT_FALSE(keyStore.GetViewingKey(addr, vkOut)); + + // and we shouldn't have a spending key or decryptor either + EXPECT_FALSE(keyStore.HaveSpendingKey(addr)); + EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut)); + EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut)); + + keyStore.AddViewingKey(vk); + EXPECT_TRUE(keyStore.HaveViewingKey(addr)); + EXPECT_TRUE(keyStore.GetViewingKey(addr, vkOut)); + EXPECT_EQ(vk, vkOut); + + // We should still not have the spending key... + EXPECT_FALSE(keyStore.HaveSpendingKey(addr)); + EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut)); + + // ... but we should have a decryptor + EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); + EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); + + keyStore.RemoveViewingKey(vk); + EXPECT_FALSE(keyStore.HaveViewingKey(addr)); + EXPECT_FALSE(keyStore.GetViewingKey(addr, vkOut)); + EXPECT_FALSE(keyStore.HaveSpendingKey(addr)); + EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut)); + + // We still have a decryptor because those are cached in memory + // (and also we only remove viewing keys when adding a spending key) + EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); + EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); +} + #ifdef ENABLE_WALLET class TestCCryptoKeyStore : public CCryptoKeyStore { diff --git a/src/keystore.cpp b/src/keystore.cpp index 3c32ab583..323fe710c 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -92,3 +92,37 @@ bool CBasicKeyStore::AddSpendingKey(const libzcash::SpendingKey &sk) mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(sk.receiving_key()))); return true; } + +bool CBasicKeyStore::AddViewingKey(const libzcash::ViewingKey &vk) +{ + LOCK(cs_SpendingKeyStore); + auto address = vk.address(); + mapViewingKeys[address] = vk; + mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(vk.sk_enc))); + return true; +} + +bool CBasicKeyStore::RemoveViewingKey(const libzcash::ViewingKey &vk) +{ + LOCK(cs_SpendingKeyStore); + mapViewingKeys.erase(vk.address()); + return true; +} + +bool CBasicKeyStore::HaveViewingKey(const libzcash::PaymentAddress &address) const +{ + LOCK(cs_SpendingKeyStore); + return mapViewingKeys.count(address) > 0; +} + +bool CBasicKeyStore::GetViewingKey(const libzcash::PaymentAddress &address, + libzcash::ViewingKey &vkOut) const +{ + LOCK(cs_SpendingKeyStore); + ViewingKeyMap::const_iterator mi = mapViewingKeys.find(address); + if (mi != mapViewingKeys.end()) { + vkOut = mi->second; + return true; + } + return false; +} diff --git a/src/keystore.h b/src/keystore.h index 84595cfb0..0b548920b 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -55,12 +55,19 @@ public: virtual bool HaveSpendingKey(const libzcash::PaymentAddress &address) const =0; virtual bool GetSpendingKey(const libzcash::PaymentAddress &address, libzcash::SpendingKey& skOut) const =0; virtual void GetPaymentAddresses(std::set &setAddress) const =0; + + //! Support for viewing keys + virtual bool AddViewingKey(const libzcash::ViewingKey &vk) =0; + virtual bool RemoveViewingKey(const libzcash::ViewingKey &vk) =0; + virtual bool HaveViewingKey(const libzcash::PaymentAddress &address) const =0; + virtual bool GetViewingKey(const libzcash::PaymentAddress &address, libzcash::ViewingKey& vkOut) const =0; }; typedef std::map KeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; typedef std::map SpendingKeyMap; +typedef std::map ViewingKeyMap; typedef std::map NoteDecryptorMap; /** Basic key store, that keeps keys in an address->secret map */ @@ -71,6 +78,7 @@ protected: ScriptMap mapScripts; WatchOnlySet setWatchOnly; SpendingKeyMap mapSpendingKeys; + ViewingKeyMap mapViewingKeys; NoteDecryptorMap mapNoteDecryptors; public: @@ -168,6 +176,11 @@ public: } } } + + virtual bool AddViewingKey(const libzcash::ViewingKey &vk); + virtual bool RemoveViewingKey(const libzcash::ViewingKey &vk); + virtual bool HaveViewingKey(const libzcash::PaymentAddress &address) const; + virtual bool GetViewingKey(const libzcash::PaymentAddress &address, libzcash::ViewingKey& vkOut) const; }; typedef std::vector > CKeyingMaterial; diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 75324de4f..baefeae4e 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -12,20 +12,28 @@ uint256 PaymentAddress::GetHash() const { return Hash(ss.begin(), ss.end()); } -uint256 ReceivingKey::pk_enc() { +uint256 ReceivingKey::pk_enc() const { return ZCNoteEncryption::generate_pubkey(*this); } +PaymentAddress ViewingKey::address() const { + return PaymentAddress(a_pk, sk_enc.pk_enc()); +} + ReceivingKey SpendingKey::receiving_key() const { return ReceivingKey(ZCNoteEncryption::generate_privkey(*this)); } +ViewingKey SpendingKey::viewing_key() const { + return ViewingKey(PRF_addr_a_pk(*this), receiving_key()); +} + SpendingKey SpendingKey::random() { return SpendingKey(random_uint252()); } PaymentAddress SpendingKey::address() const { - return PaymentAddress(PRF_addr_a_pk(*this), receiving_key().pk_enc()); + return viewing_key().address(); } } diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index e76973cb6..4287fee4f 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -40,9 +40,37 @@ public: class ReceivingKey : public uint256 { public: + ReceivingKey() { } ReceivingKey(uint256 sk_enc) : uint256(sk_enc) { } - uint256 pk_enc(); + uint256 pk_enc() const; +}; + +class ViewingKey { +public: + uint256 a_pk; + ReceivingKey sk_enc; + + ViewingKey() : a_pk(), sk_enc() { } + ViewingKey(uint256 a_pk, ReceivingKey sk_enc) : a_pk(a_pk), sk_enc(sk_enc) { } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(a_pk); + READWRITE(sk_enc); + } + + PaymentAddress address() const; + + friend inline bool operator==(const ViewingKey& a, const ViewingKey& b) { + return a.a_pk == b.a_pk && a.sk_enc == b.sk_enc; + } + friend inline bool operator<(const ViewingKey& a, const ViewingKey& b) { + return (a.a_pk < b.a_pk || + (a.a_pk == b.a_pk && a.sk_enc < b.sk_enc)); + } }; class SpendingKey : public uint252 { @@ -53,6 +81,7 @@ public: static SpendingKey random(); ReceivingKey receiving_key() const; + ViewingKey viewing_key() const; PaymentAddress address() const; }; From 13933e4c1344c84e55071e5a6517e484710aa8d4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 Mar 2017 11:09:41 -0800 Subject: [PATCH 03/12] Factor out common logic from CZCPaymentAddress and CZCSpendingKey --- src/base58.cpp | 67 ++++++++++++++++++++------------------------------ src/base58.h | 27 ++++++++++++++------ 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index 12e2496da..2fd2475fe 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -323,67 +323,52 @@ bool CBitcoinSecret::SetString(const std::string& strSecret) return SetString(strSecret.c_str()); } -bool CZCPaymentAddress::Set(const libzcash::PaymentAddress& addr) +template +bool CZCEncoding::Set(const DATA_TYPE& addr) { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss << addr; std::vector addrSerialized(ss.begin(), ss.end()); - assert(addrSerialized.size() == libzcash::SerializedPaymentAddressSize); - SetData(Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS), &addrSerialized[0], libzcash::SerializedPaymentAddressSize); + assert(addrSerialized.size() == SER_SIZE); + SetData(Params().Base58Prefix(PREFIX), &addrSerialized[0], SER_SIZE); return true; } -libzcash::PaymentAddress CZCPaymentAddress::Get() const +template +DATA_TYPE CZCEncoding::Get() const { - if (vchData.size() != libzcash::SerializedPaymentAddressSize) { + if (vchData.size() != SER_SIZE) { throw std::runtime_error( - "payment address is invalid" + PrependName(" is invalid") ); } - if (vchVersion != Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS)) { + if (vchVersion != Params().Base58Prefix(PREFIX)) { throw std::runtime_error( - "payment address is for wrong network type" + PrependName(" is for wrong network type") ); } std::vector serialized(vchData.begin(), vchData.end()); CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); - libzcash::PaymentAddress ret; - ss >> ret; - return ret; -} - -bool CZCSpendingKey::Set(const libzcash::SpendingKey& addr) -{ - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << addr; - std::vector addrSerialized(ss.begin(), ss.end()); - assert(addrSerialized.size() == libzcash::SerializedSpendingKeySize); - SetData(Params().Base58Prefix(CChainParams::ZCSPENDING_KEY), &addrSerialized[0], libzcash::SerializedSpendingKeySize); - return true; -} - -libzcash::SpendingKey CZCSpendingKey::Get() const -{ - if (vchData.size() != libzcash::SerializedSpendingKeySize) { - throw std::runtime_error( - "spending key is invalid" - ); - } - - if (vchVersion != Params().Base58Prefix(CChainParams::ZCSPENDING_KEY)) { - throw std::runtime_error( - "spending key is for wrong network type" - ); - } - - std::vector serialized(vchData.begin(), vchData.end()); - - CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); - libzcash::SpendingKey ret; + DATA_TYPE ret; ss >> ret; return ret; } +// Explicit instantiations for libzcash::PaymentAddress +template bool CZCEncoding::Set(const libzcash::PaymentAddress& addr); +template libzcash::PaymentAddress CZCEncoding::Get() const; + +// Explicit instantiations for libzcash::SpendingKey +template bool CZCEncoding::Set(const libzcash::SpendingKey& sk); +template libzcash::SpendingKey CZCEncoding::Get() const; diff --git a/src/base58.h b/src/base58.h index 88efadbd6..c239e0e99 100644 --- a/src/base58.h +++ b/src/base58.h @@ -96,26 +96,37 @@ public: bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } }; -class CZCPaymentAddress : public CBase58Data { +template +class CZCEncoding : public CBase58Data { +protected: + virtual std::string PrependName(const std::string& s) const = 0; + +public: + bool Set(const DATA_TYPE& addr); + + DATA_TYPE Get() const; +}; + +class CZCPaymentAddress : public CZCEncoding { +protected: + std::string PrependName(const std::string& s) const { return "payment address" + s; } + public: - bool Set(const libzcash::PaymentAddress& addr); CZCPaymentAddress() {} CZCPaymentAddress(const std::string& strAddress) { SetString(strAddress.c_str(), 2); } CZCPaymentAddress(const libzcash::PaymentAddress& addr) { Set(addr); } - - libzcash::PaymentAddress Get() const; }; -class CZCSpendingKey : public CBase58Data { +class CZCSpendingKey : public CZCEncoding { +protected: + std::string PrependName(const std::string& s) const { return "spending key" + s; } + public: - bool Set(const libzcash::SpendingKey& addr); CZCSpendingKey() {} CZCSpendingKey(const std::string& strAddress) { SetString(strAddress.c_str(), 2); } CZCSpendingKey(const libzcash::SpendingKey& addr) { Set(addr); } - - libzcash::SpendingKey Get() const; }; /** base58-encoded Bitcoin addresses. From 49cf707d2ced72698cd44c6f4f3f64072c2fc308 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 Mar 2017 11:20:42 -0800 Subject: [PATCH 04/12] Add Base58 encoding of viewing keys --- src/base58.cpp | 8 ++++++++ src/base58.h | 11 +++++++++++ src/chainparams.cpp | 4 ++++ src/chainparams.h | 1 + src/zcash/Address.hpp | 1 + 5 files changed, 25 insertions(+) diff --git a/src/base58.cpp b/src/base58.cpp index 2fd2475fe..09977ea35 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -365,6 +365,14 @@ template libzcash::PaymentAddress CZCEncoding::Get() const; +// Explicit instantiations for libzcash::ViewingKey +template bool CZCEncoding::Set(const libzcash::ViewingKey& vk); +template libzcash::ViewingKey CZCEncoding::Get() const; + // Explicit instantiations for libzcash::SpendingKey template bool CZCEncoding { +protected: + std::string PrependName(const std::string& s) const { return "viewing key" + s; } + +public: + CZCViewingKey() {} + + CZCViewingKey(const std::string& strViewingKey) { SetString(strViewingKey.c_str(), 3); } + CZCViewingKey(const libzcash::ViewingKey& vk) { Set(vk); } +}; + class CZCSpendingKey : public CZCEncoding { protected: std::string PrependName(const std::string& s) const { return "spending key" + s; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 034eee6b2..23544f488 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -110,6 +110,8 @@ public: base58Prefixes[EXT_SECRET_KEY] = {0x04,0x88,0xAD,0xE4}; // guarantees the first 2 characters, when base58 encoded, are "zc" base58Prefixes[ZCPAYMENT_ADDRRESS] = {0x16,0x9A}; + // guarantees the first 4 characters, when base58 encoded, are "ZiVK" + base58Prefixes[ZCVIEWING_KEY] = {0xA8,0xAB,0xD3}; // guarantees the first 2 characters, when base58 encoded, are "SK" base58Prefixes[ZCSPENDING_KEY] = {0xAB,0x36}; @@ -241,6 +243,8 @@ public: base58Prefixes[EXT_SECRET_KEY] = {0x04,0x35,0x83,0x94}; // guarantees the first 2 characters, when base58 encoded, are "zt" base58Prefixes[ZCPAYMENT_ADDRRESS] = {0x16,0xB6}; + // guarantees the first 4 characters, when base58 encoded, are "ZiVt" + base58Prefixes[ZCVIEWING_KEY] = {0xA8,0xAC,0x0C}; // guarantees the first 2 characters, when base58 encoded, are "ST" base58Prefixes[ZCSPENDING_KEY] = {0xAC,0x08}; diff --git a/src/chainparams.h b/src/chainparams.h index a1de7b493..0de8d01b5 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -44,6 +44,7 @@ public: ZCPAYMENT_ADDRRESS, ZCSPENDING_KEY, + ZCVIEWING_KEY, MAX_BASE58_TYPES }; diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 4287fee4f..2dbe10a60 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -8,6 +8,7 @@ namespace libzcash { const size_t SerializedPaymentAddressSize = 64; +const size_t SerializedViewingKeySize = 64; const size_t SerializedSpendingKeySize = 32; class PaymentAddress { From 167cd333741c234f7fee87f82aa678af653463af Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 Mar 2017 16:10:34 -0800 Subject: [PATCH 05/12] Implement viewing key storage in the wallet --- src/wallet/gtest/test_wallet_zkeys.cpp | 91 ++++++++++++++++++++++++++ src/wallet/wallet.cpp | 31 +++++++++ src/wallet/wallet.h | 6 ++ src/wallet/walletdb.cpp | 26 ++++++++ src/wallet/walletdb.h | 3 + 5 files changed, 157 insertions(+) diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 554a4ee97..b40479e87 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -66,6 +66,53 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) { ASSERT_EQ(m.nCreateTime, now); } +/** + * This test covers methods on CWallet + * AddViewingKey() + * RemoveViewingKey() + * LoadViewingKey() + */ +TEST(wallet_zkeys_tests, StoreAndLoadViewingKeys) { + SelectParams(CBaseChainParams::MAIN); + + CWallet wallet; + + // wallet should be empty + std::set addrs; + wallet.GetPaymentAddresses(addrs); + ASSERT_EQ(0, addrs.size()); + + // manually add new viewing key to wallet + auto sk = libzcash::SpendingKey::random(); + auto vk = sk.viewing_key(); + ASSERT_TRUE(wallet.AddViewingKey(vk)); + + // verify wallet did add it + auto addr = sk.address(); + ASSERT_TRUE(wallet.HaveViewingKey(addr)); + // and that we don't have the corresponding spending key + ASSERT_FALSE(wallet.HaveSpendingKey(addr)); + + // verify viewing key stored correctly + libzcash::ViewingKey vkOut; + wallet.GetViewingKey(addr, vkOut); + ASSERT_EQ(vk, vkOut); + + // Load a second viewing key into the wallet + auto sk2 = libzcash::SpendingKey::random(); + ASSERT_TRUE(wallet.LoadViewingKey(sk2.viewing_key())); + + // verify wallet did add it + auto addr2 = sk2.address(); + ASSERT_TRUE(wallet.HaveViewingKey(addr2)); + ASSERT_FALSE(wallet.HaveSpendingKey(addr2)); + + // Remove the first viewing key + ASSERT_TRUE(wallet.RemoveViewingKey(vk)); + ASSERT_FALSE(wallet.HaveViewingKey(addr)); + ASSERT_TRUE(wallet.HaveViewingKey(addr2)); +} + /** * This test covers methods on CWalletDB * WriteZKey() @@ -138,6 +185,50 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) { ASSERT_EQ(m.nCreateTime, now); } +/** + * This test covers methods on CWalletDB + * WriteViewingKey() + */ +TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) { + SelectParams(CBaseChainParams::TESTNET); + + // Get temporary and unique path for file. + // Note: / operator to append paths + boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + boost::filesystem::create_directories(pathTemp); + mapArgs["-datadir"] = pathTemp.string(); + + bool fFirstRun; + CWallet wallet("wallet-vkey.dat"); + ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun)); + + // No default CPubKey set + ASSERT_TRUE(fFirstRun); + + // create random viewing key and add it to database directly, bypassing wallet + auto sk = libzcash::SpendingKey::random(); + auto vk = sk.viewing_key(); + auto addr = sk.address(); + int64_t now = GetTime(); + CKeyMetadata meta(now); + CWalletDB db("wallet-vkey.dat"); + db.WriteViewingKey(vk); + + // wallet should not be aware of viewing key + ASSERT_FALSE(wallet.HaveViewingKey(addr)); + + // load the wallet again + ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun)); + + // wallet can now see the viewing key + ASSERT_TRUE(wallet.HaveViewingKey(addr)); + + // check key is the same + libzcash::ViewingKey vkOut; + wallet.GetViewingKey(addr, vkOut); + ASSERT_EQ(vk, vkOut); +} + /** diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 677fec319..9e7bba003 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -107,6 +107,10 @@ bool CWallet::AddZKey(const libzcash::SpendingKey &key) if (!CCryptoKeyStore::AddSpendingKey(key)) return false; + // check if we need to remove from viewing keys + if (HaveViewingKey(addr)) + RemoveViewingKey(key.viewing_key()); + if (!fFileBacked) return true; @@ -246,6 +250,33 @@ bool CWallet::LoadZKey(const libzcash::SpendingKey &key) return CCryptoKeyStore::AddSpendingKey(key); } +bool CWallet::AddViewingKey(const libzcash::ViewingKey &vk) +{ + if (!CCryptoKeyStore::AddViewingKey(vk)) + return false; + nTimeFirstKey = 1; // No birthday information for viewing keys. + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteViewingKey(vk); +} + +bool CWallet::RemoveViewingKey(const libzcash::ViewingKey &vk) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveViewingKey(vk)) + return false; + if (fFileBacked) + if (!CWalletDB(strWalletFile).EraseViewingKey(vk)) + return false; + + return true; +} + +bool CWallet::LoadViewingKey(const libzcash::ViewingKey &vk) +{ + return CCryptoKeyStore::AddViewingKey(vk); +} + bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 57a71a431..878a23a5d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -954,6 +954,12 @@ public: //! 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 libzcash::ReceivingKey &rk, const std::vector &vchCryptedSecret); + //! Adds a viewing key to the store, and saves it to disk. + bool AddViewingKey(const libzcash::ViewingKey &vk); + bool RemoveViewingKey(const libzcash::ViewingKey &vk); + //! Adds a viewing key to the store, without saving it to disk (used by LoadWallet) + bool LoadViewingKey(const libzcash::ViewingKey &dest); + /** * Increment the next transaction order id * @return next transaction order id diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index c79a15e30..4bf191380 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -142,6 +142,18 @@ bool CWalletDB::WriteZKey(const libzcash::PaymentAddress& addr, const libzcash:: return Write(std::make_pair(std::string("zkey"), addr), key, false); } +bool CWalletDB::WriteViewingKey(const libzcash::ViewingKey &vk) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("vkey"), vk), '1'); +} + +bool CWalletDB::EraseViewingKey(const libzcash::ViewingKey &vk) +{ + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("vkey"), vk)); +} + bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) { nWalletDBUpdated++; @@ -471,6 +483,19 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, // so set the wallet birthday to the beginning of time. pwallet->nTimeFirstKey = 1; } + else if (strType == "vkey") + { + libzcash::ViewingKey vk; + ssKey >> vk; + char fYes; + ssValue >> fYes; + if (fYes == '1') + pwallet->LoadViewingKey(vk); + + // Viewing keys have no birthday information for now, + // so set the wallet birthday to the beginning of time. + pwallet->nTimeFirstKey = 1; + } else if (strType == "zkey") { libzcash::PaymentAddress addr; @@ -694,6 +719,7 @@ static bool IsKeyType(string strType) { return (strType== "key" || strType == "wkey" || strType == "zkey" || strType == "czkey" || + strType == "vkey" || strType == "mkey" || strType == "ckey"); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index b901f539c..e455ad953 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -140,6 +140,9 @@ public: const std::vector& vchCryptedSecret, const CKeyMetadata &keyMeta); + bool WriteViewingKey(const libzcash::ViewingKey &vk); + bool EraseViewingKey(const libzcash::ViewingKey &vk); + private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); From e85b33a52e8c568cb1026862fd7a4a7c78c1a1c9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Mar 2017 16:13:39 +1300 Subject: [PATCH 06/12] Add RPC methods for exporting/importing viewing keys --- contrib/zcash-cli.bash-completion | 6 +- src/rpcclient.cpp | 1 + src/rpcserver.cpp | 2 + src/rpcserver.h | 2 + src/wallet/rpcdump.cpp | 125 ++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 1 deletion(-) diff --git a/contrib/zcash-cli.bash-completion b/contrib/zcash-cli.bash-completion index 79b57a063..37fa1d116 100644 --- a/contrib/zcash-cli.bash-completion +++ b/contrib/zcash-cli.bash-completion @@ -82,10 +82,14 @@ _zcash_cli() { COMPREPLY=( $( compgen -W "add remove" -- "$cur" ) ) return 0 ;; - fundrawtransaction|getblock|getblockheader|getmempoolancestors|getmempooldescendants|getrawtransaction|gettransaction|listaccounts|listreceivedbyaccount|listreceivedbyaddress|sendrawtransaction|z_importkey) + fundrawtransaction|getblock|getblockheader|getmempoolancestors|getmempooldescendants|getrawtransaction|gettransaction|listaccounts|listreceivedbyaccount|listreceivedbyaddress|sendrawtransaction) COMPREPLY=( $( compgen -W "true false" -- "$cur" ) ) return 0 ;; + z_importkey|z_importviewingkey) + COMPREPLY=( $( compgen -W "yes no whenkeyisnew" -- "$cur" ) ) + return 0 + ;; move|setaccount) _zcash_accounts return 0 diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index c02c51991..7ac5db926 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -114,6 +114,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "z_getoperationstatus", 0}, { "z_getoperationresult", 0}, { "z_importkey", 2 }, + { "z_importviewingkey", 2 }, { "z_getpaymentdisclosure", 1}, { "z_getpaymentdisclosure", 2} }; diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 7a3880902..4859a5ee3 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -395,6 +395,8 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "z_listaddresses", &z_listaddresses, true }, { "wallet", "z_exportkey", &z_exportkey, true }, { "wallet", "z_importkey", &z_importkey, true }, + { "wallet", "z_exportviewingkey", &z_exportviewingkey, true }, + { "wallet", "z_importviewingkey", &z_importviewingkey, true }, { "wallet", "z_exportwallet", &z_exportwallet, true }, { "wallet", "z_importwallet", &z_importwallet, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 321568748..5359f46dd 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -279,6 +279,8 @@ extern UniValue getblocksubsidy(const UniValue& params, bool fHelp); extern UniValue z_exportkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue z_importkey(const UniValue& params, bool fHelp); // in rpcdump.cpp +extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp); // in rpcdump.cpp +extern UniValue z_importviewingkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue z_getnewaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp extern UniValue z_listaddresses(const UniValue& params, bool fHelp); // in rpcwallet.cpp extern UniValue z_exportwallet(const UniValue& params, bool fHelp); // in rpcdump.cpp diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index fe5b83e8d..76a20d66b 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -644,6 +644,91 @@ UniValue z_importkey(const UniValue& params, bool fHelp) return NullUniValue; } +UniValue z_importviewingkey(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "z_importviewingkey \"vkey\" ( rescan startHeight )\n" + "\nAdds a viewing key (as returned by z_exportviewingkey) to your wallet.\n" + "\nArguments:\n" + "1. \"vkey\" (string, required) The viewing key (see z_exportviewingkey)\n" + "2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n" + "3. startHeight (numeric, optional, default=0) Block height to start rescan from\n" + "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nExamples:\n" + "\nImport a viewing key\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\"") + + "\nImport the viewing key without rescan\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\", no") + + "\nImport the viewing key with partial rescan\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\" whenkeyisnew 30000") + + "\nRe-import the viewing key with longer partial rescan\n" + + HelpExampleCli("z_importviewingkey", "\"vkey\" yes 20000") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("z_importviewingkey", "\"vkey\", \"no\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + // Whether to perform rescan after import + bool fRescan = true; + bool fIgnoreExistingKey = true; + if (params.size() > 1) { + auto rescan = params[1].get_str(); + if (rescan.compare("whenkeyisnew") != 0) { + fIgnoreExistingKey = false; + if (rescan.compare("no") == 0) { + fRescan = false; + } else if (rescan.compare("yes") != 0) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + "rescan must be \"yes\", \"no\" or \"whenkeyisnew\""); + } + } + } + + // Height to rescan from + int nRescanHeight = 0; + if (params.size() > 2) + nRescanHeight = params[2].get_int(); + if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); + } + + string strVKey = params[0].get_str(); + CZCViewingKey viewingkey(strVKey); + auto vkey = viewingkey.Get(); + auto addr = vkey.address(); + + { + if (pwalletMain->HaveSpendingKey(addr)) + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key"); + + // Don't throw error in case a viewing key is already there + if (pwalletMain->HaveViewingKey(addr)) { + if (fIgnoreExistingKey) { + return NullUniValue; + } + } else { + pwalletMain->MarkDirty(); + + if (!pwalletMain->AddViewingKey(vkey)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + } + + // We want to scan for transactions and notes + if (fRescan) { + pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true); + } + } + + return NullUniValue; +} UniValue z_exportkey(const UniValue& params, bool fHelp) { @@ -682,3 +767,43 @@ UniValue z_exportkey(const UniValue& params, bool fHelp) return spendingkey.ToString(); } +UniValue z_exportviewingkey(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "z_exportviewingkey \"zaddr\"\n" + "\nReveals the viewing key corresponding to 'zaddr'.\n" + "Then the z_importviewingkey can be used with this output\n" + "\nArguments:\n" + "1. \"zaddr\" (string, required) The zaddr for the viewing key\n" + "\nResult:\n" + "\"vkey\" (string) The viewing key\n" + "\nExamples:\n" + + HelpExampleCli("z_exportviewingkey", "\"myaddress\"") + + HelpExampleRpc("z_exportviewingkey", "\"myaddress\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + + CZCPaymentAddress address(strAddress); + auto addr = address.Get(); + + libzcash::ViewingKey vk; + if (!pwalletMain->GetViewingKey(addr, vk)) { + libzcash::SpendingKey k; + if (!pwalletMain->GetSpendingKey(addr, k)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this zaddr"); + } + vk = k.viewing_key(); + } + + CZCViewingKey viewingkey(vk); + return viewingkey.ToString(); +} From 9a2b8ae57fdae7d05bd52e6424b5b1ccf19c45c4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Mar 2017 16:31:59 +1300 Subject: [PATCH 07/12] Update wallet logic to account for viewing keys The wallet code previously assumed that an unlocked wallet would always have a spending key associated with a note decryptor. Viewing keys break this assumption. --- src/wallet/wallet.cpp | 35 ++++++++++++++++++++++------------- src/wallet/wallet.h | 6 +++++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9e7bba003..b2300c2b4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -977,7 +977,8 @@ void CWallet::MarkDirty() } /** - * Ensure that every note in the wallet has a cached nullifier. + * Ensure that every note in the wallet (for which we possess a spending key) + * has a cached nullifier. */ bool CWallet::UpdateNullifierNoteMap() { @@ -991,16 +992,17 @@ bool CWallet::UpdateNullifierNoteMap() for (std::pair& wtxItem : mapWallet) { for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) { if (!item.second.nullifier) { - auto i = item.first.js; - GetNoteDecryptor(item.second.address, dec); - auto hSig = wtxItem.second.vjoinsplit[i].h_sig( - *pzcashParams, wtxItem.second.joinSplitPubKey); - item.second.nullifier = GetNoteNullifier( - wtxItem.second.vjoinsplit[i], - item.second.address, - dec, - hSig, - item.first.n); + if (GetNoteDecryptor(item.second.address, dec)) { + auto i = item.first.js; + auto hSig = wtxItem.second.vjoinsplit[i].h_sig( + *pzcashParams, wtxItem.second.joinSplitPubKey); + item.second.nullifier = GetNoteNullifier( + wtxItem.second.vjoinsplit[i], + item.second.address, + dec, + hSig, + item.first.n); + } } } UpdateNullifierNoteMapWithTx(wtxItem.second); @@ -1262,7 +1264,9 @@ boost::optional CWallet::GetNoteNullifier(const JSDescription& jsdesc, hSig, (unsigned char) n); auto note = note_pt.note(address); - // SpendingKeys are only available if the wallet is unlocked + // SpendingKeys are only available if: + // - We have them (this isn't a viewing key) + // - The wallet is unlocked libzcash::SpendingKey key; if (GetSpendingKey(address, key)) { ret = note.nullifier(key); @@ -3639,7 +3643,7 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) * Find notes in the wallet filtered by payment address, min depth and ability to spend. * These notes are decrypted and added to the output parameter vector, outEntries. */ -void CWallet::GetFilteredNotes(std::vector & outEntries, std::string address, int minDepth, bool ignoreSpent) +void CWallet::GetFilteredNotes(std::vector & outEntries, std::string address, int minDepth, bool ignoreSpent, bool ignoreUnspendable) { bool fFilterAddress = false; libzcash::PaymentAddress filterPaymentAddress; @@ -3677,6 +3681,11 @@ void CWallet::GetFilteredNotes(std::vector & outEntries, st continue; } + // skip notes which cannot be spent + if (ignoreUnspendable && !HaveSpendingKey(pa)) { + continue; + } + int i = jsop.js; // Index into CTransaction.vjoinsplit int j = jsop.n; // Index into JSDescription.ciphertexts diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 878a23a5d..eaaf6bbdf 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1121,7 +1121,11 @@ public: void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } /* Find notes filtered by payment address, min depth, ability to spend */ - void GetFilteredNotes(std::vector & outEntries, std::string address, int minDepth=1, bool ignoreSpent=true); + void GetFilteredNotes(std::vector & outEntries, + std::string address, + int minDepth=1, + bool ignoreSpent=true, + bool ignoreUnspendable=true); }; From 44e37656bf182fb04de051bd2f748bc6fdd897fc Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 21 Apr 2017 00:59:54 +1200 Subject: [PATCH 08/12] Add watch-only support to Zcash RPC methods Balance totals do not include spends linked to viewing key addresses, as nullifiers cannot be calculated and therefore spends cannot be detected. --- qa/rpc-tests/wallet_nullifiers.py | 45 ++++++++++++++++++++++++++ src/gtest/test_keystore.cpp | 13 ++++++++ src/keystore.h | 6 ++++ src/rpcclient.cpp | 3 ++ src/wallet/rpcwallet.cpp | 52 +++++++++++++++++++++---------- 5 files changed, 102 insertions(+), 17 deletions(-) diff --git a/qa/rpc-tests/wallet_nullifiers.py b/qa/rpc-tests/wallet_nullifiers.py index b7e94b6a6..743af7c92 100755 --- a/qa/rpc-tests/wallet_nullifiers.py +++ b/qa/rpc-tests/wallet_nullifiers.py @@ -170,5 +170,50 @@ class WalletNullifiersTest (BitcoinTestFramework): assert_equal(self.nodes[1].z_getbalance(myzaddr), zaddrremaining2) assert_equal(self.nodes[2].z_getbalance(myzaddr), zaddrremaining2) + # Test viewing keys + + node3mined = Decimal('250.0') + assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, { + 'transparent': node3mined, + 'private': zsendmany2notevalue, + 'total': node3mined + zsendmany2notevalue, + }) + + # add node 1 address and node 2 viewing key to node 3 + myzvkey = self.nodes[2].z_exportviewingkey(myzaddr) + self.nodes[3].importaddress(mytaddr1) + self.nodes[3].z_importviewingkey(myzvkey) + + # Check the address has been imported + assert_equal(myzaddr in self.nodes[3].z_listaddresses(), False) + assert_equal(myzaddr in self.nodes[3].z_listaddresses(True), True) + + # Node 3 should see the same received notes as node 2 + assert_equal( + self.nodes[2].z_listreceivedbyaddress(myzaddr), + self.nodes[3].z_listreceivedbyaddress(myzaddr)) + + # Node 3's balances should be unchanged without explicitly requesting + # to include watch-only balances + assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, { + 'transparent': node3mined, + 'private': zsendmany2notevalue, + 'total': node3mined + zsendmany2notevalue, + }) + + # Wallet can't cache nullifiers for notes received by addresses it only has a + # viewing key for, and therefore can't detect spends. So it sees a balance + # corresponding to the sum of all notes the address received. + # TODO: Fix this during the Sapling upgrade (via #2277) + assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance(1, True).items()}, { + 'transparent': node3mined + Decimal('1.0'), + 'private': zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2, + 'total': node3mined + Decimal('1.0') + zsendmany2notevalue + zsendmanynotevalue + zaddrremaining + zaddrremaining2, + }) + + # Check individual balances reflect the above + assert_equal(self.nodes[3].z_getbalance(mytaddr1), Decimal('1.0')) + assert_equal(self.nodes[3].z_getbalance(myzaddr), zsendmanynotevalue + zaddrremaining + zaddrremaining2) + if __name__ == '__main__': WalletNullifiersTest().main () diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 903a48839..76b57cd9f 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -65,6 +65,11 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) { EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut)); EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut)); + // and we can't find it in our list of addresses + std::set addresses; + keyStore.GetPaymentAddresses(addresses); + EXPECT_FALSE(addresses.count(addr)); + keyStore.AddViewingKey(vk); EXPECT_TRUE(keyStore.HaveViewingKey(addr)); EXPECT_TRUE(keyStore.GetViewingKey(addr, vkOut)); @@ -78,11 +83,19 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) { EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut)); EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut); + // ... and we should find it in our list of addresses + addresses.clear(); + keyStore.GetPaymentAddresses(addresses); + EXPECT_TRUE(addresses.count(addr)); + keyStore.RemoveViewingKey(vk); EXPECT_FALSE(keyStore.HaveViewingKey(addr)); EXPECT_FALSE(keyStore.GetViewingKey(addr, vkOut)); EXPECT_FALSE(keyStore.HaveSpendingKey(addr)); EXPECT_FALSE(keyStore.GetSpendingKey(addr, skOut)); + addresses.clear(); + keyStore.GetPaymentAddresses(addresses); + EXPECT_FALSE(addresses.count(addr)); // We still have a decryptor because those are cached in memory // (and also we only remove viewing keys when adding a spending key) diff --git a/src/keystore.h b/src/keystore.h index 0b548920b..b1ad32a42 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -174,6 +174,12 @@ public: setAddress.insert((*mi).first); mi++; } + ViewingKeyMap::const_iterator mvi = mapViewingKeys.begin(); + while (mvi != mapViewingKeys.end()) + { + setAddress.insert((*mvi).first); + mvi++; + } } } diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 7ac5db926..def32500d 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -103,9 +103,12 @@ static const CRPCConvertParam vRPCConvertParams[] = { "zcbenchmark", 1 }, { "zcbenchmark", 2 }, { "getblocksubsidy", 0}, + { "z_listaddresses", 0}, { "z_listreceivedbyaddress", 1}, { "z_getbalance", 1}, { "z_gettotalbalance", 0}, + { "z_gettotalbalance", 1}, + { "z_gettotalbalance", 2}, { "z_sendmany", 1}, { "z_sendmany", 2}, { "z_sendmany", 3}, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d2c558c96..4e0a798ac 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2960,9 +2960,10 @@ UniValue z_listaddresses(const UniValue& params, bool fHelp) if (fHelp || params.size() > 1) throw runtime_error( - "z_listaddresses\n" + "z_listaddresses ( includeWatchonly )\n" "\nReturns the list of zaddr belonging to the wallet.\n" "\nArguments:\n" + "1. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n" "\nResult:\n" "[ (json array of string)\n" " \"zaddr\" (string) a zaddr belonging to the wallet\n" @@ -2975,16 +2976,23 @@ UniValue z_listaddresses(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); + bool fIncludeWatchonly = false; + if (params.size() > 0) { + fIncludeWatchonly = params[0].get_bool(); + } + UniValue ret(UniValue::VARR); std::set addresses; pwalletMain->GetPaymentAddresses(addresses); for (auto addr : addresses ) { - ret.push_back(CZCPaymentAddress(addr).ToString()); + if (fIncludeWatchonly || pwalletMain->HaveSpendingKey(addr)) { + ret.push_back(CZCPaymentAddress(addr).ToString()); + } } return ret; } -CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) { +CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ignoreUnspendable=true) { set setAddress; vector vecOutputs; CAmount balance = 0; @@ -3006,6 +3014,10 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) { continue; } + if (ignoreUnspendable && !out.fSpendable) { + continue; + } + if (setAddress.size()) { CTxDestination address; if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { @@ -3023,11 +3035,11 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1) { return balance; } -CAmount getBalanceZaddr(std::string address, int minDepth = 1) { +CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) { CAmount balance = 0; std::vector entries; LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->GetFilteredNotes(entries, address, minDepth); + pwalletMain->GetFilteredNotes(entries, address, minDepth, true, ignoreUnspendable); for (auto & entry : entries) { balance += CAmount(entry.plaintext.value); } @@ -3076,14 +3088,14 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr."); } - if (!pwalletMain->HaveSpendingKey(zaddr)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); + if (!(pwalletMain->HaveSpendingKey(zaddr) || pwalletMain->HaveViewingKey(zaddr))) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); } UniValue result(UniValue::VARR); std::vector entries; - pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false); + pwalletMain->GetFilteredNotes(entries, fromaddress, nMinDepth, false, false); for (CNotePlaintextEntry & entry : entries) { UniValue obj(UniValue::VOBJ); obj.push_back(Pair("txid",entry.jsop.hash.ToString())); @@ -3142,16 +3154,16 @@ UniValue z_getbalance(const UniValue& params, bool fHelp) } catch (const std::runtime_error&) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); } - if (!pwalletMain->HaveSpendingKey(zaddr)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); + if (!(pwalletMain->HaveSpendingKey(zaddr) || pwalletMain->HaveViewingKey(zaddr))) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); } } CAmount nBalance = 0; if (fromTaddr) { - nBalance = getBalanceTaddr(fromaddress, nMinDepth); + nBalance = getBalanceTaddr(fromaddress, nMinDepth, false); } else { - nBalance = getBalanceZaddr(fromaddress, nMinDepth); + nBalance = getBalanceZaddr(fromaddress, nMinDepth, false); } return ValueFromAmount(nBalance); @@ -3163,12 +3175,13 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() > 1) + if (fHelp || params.size() > 2) throw runtime_error( - "z_gettotalbalance ( minconf )\n" + "z_gettotalbalance ( minconf includeWatchonly )\n" "\nReturn the total value of funds stored in the node’s wallet.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) Only include private and transparent transactions confirmed at least this many times.\n" + "2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress' and 'z_importviewingkey')\n" "\nResult:\n" "{\n" " \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n" @@ -3187,19 +3200,24 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); int nMinDepth = 1; - if (params.size() == 1) { + if (params.size() > 0) { nMinDepth = params[0].get_int(); } if (nMinDepth < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); } + bool fIncludeWatchonly = false; + if (params.size() > 1) { + fIncludeWatchonly = params[1].get_bool(); + } + // getbalance and "getbalance * 1 true" should return the same number // but they don't because wtx.GetAmounts() does not handle tx where there are no outputs // pwalletMain->GetBalance() does not accept min depth parameter // so we use our own method to get balance of utxos. - CAmount nBalance = getBalanceTaddr("", nMinDepth); - CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth); + CAmount nBalance = getBalanceTaddr("", nMinDepth, !fIncludeWatchonly); + CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth, !fIncludeWatchonly); CAmount nTotalBalance = nBalance + nPrivateBalance; UniValue result(UniValue::VOBJ); result.push_back(Pair("transparent", FormatMoney(nBalance))); From 7b8d4f87ec9c9d1988debf124739fbb95da55b8d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 18 Dec 2017 16:09:43 +0000 Subject: [PATCH 09/12] Modify zcrawkeygen RPC method to set "zcviewingkey" to the viewing key The "zcviewingkey" field has never been documented before, and the method itself is deprecated; this just ensures it is consistent with the rest of the RPC. --- src/wallet/rpcwallet.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4e0a798ac..696c71c71 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2902,26 +2902,23 @@ UniValue zc_raw_keygen(const UniValue& params, bool fHelp) "Output: {\n" " \"zcaddress\": zcaddr,\n" " \"zcsecretkey\": zcsecretkey,\n" + " \"zcviewingkey\": zcviewingkey,\n" "}\n" ); } auto k = SpendingKey::random(); auto addr = k.address(); - auto receiving_key = k.receiving_key(); - - CDataStream receiving(SER_NETWORK, PROTOCOL_VERSION); - - receiving << receiving_key; + auto viewing_key = k.viewing_key(); CZCPaymentAddress pubaddr(addr); CZCSpendingKey spendingkey(k); - std::string receiving_hex = HexStr(receiving.begin(), receiving.end()); + CZCViewingKey viewingkey(viewing_key); UniValue result(UniValue::VOBJ); result.push_back(Pair("zcaddress", pubaddr.ToString())); result.push_back(Pair("zcsecretkey", spendingkey.ToString())); - result.push_back(Pair("zcviewingkey", receiving_hex)); + result.push_back(Pair("zcviewingkey", viewingkey.ToString())); return result; } From bec223514819c931f2d6a8abe87611567846d141 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 20 Dec 2017 11:18:59 +0000 Subject: [PATCH 10/12] Cleanup: Add braces for clarity --- src/wallet/rpcdump.cpp | 9 ++++++--- src/wallet/wallet.cpp | 15 ++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 76a20d66b..52169fe67 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -694,8 +694,9 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) // Height to rescan from int nRescanHeight = 0; - if (params.size() > 2) + if (params.size() > 2) { nRescanHeight = params[2].get_int(); + } if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); } @@ -706,8 +707,9 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) auto addr = vkey.address(); { - if (pwalletMain->HaveSpendingKey(addr)) + if (pwalletMain->HaveSpendingKey(addr)) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key"); + } // Don't throw error in case a viewing key is already there if (pwalletMain->HaveViewingKey(addr)) { @@ -717,8 +719,9 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp) } else { pwalletMain->MarkDirty(); - if (!pwalletMain->AddViewingKey(vkey)) + if (!pwalletMain->AddViewingKey(vkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet"); + } } // We want to scan for transactions and notes diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b2300c2b4..06dae5e83 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -252,22 +252,27 @@ bool CWallet::LoadZKey(const libzcash::SpendingKey &key) bool CWallet::AddViewingKey(const libzcash::ViewingKey &vk) { - if (!CCryptoKeyStore::AddViewingKey(vk)) + if (!CCryptoKeyStore::AddViewingKey(vk)) { return false; + } nTimeFirstKey = 1; // No birthday information for viewing keys. - if (!fFileBacked) + if (!fFileBacked) { return true; + } return CWalletDB(strWalletFile).WriteViewingKey(vk); } bool CWallet::RemoveViewingKey(const libzcash::ViewingKey &vk) { AssertLockHeld(cs_wallet); - if (!CCryptoKeyStore::RemoveViewingKey(vk)) + if (!CCryptoKeyStore::RemoveViewingKey(vk)) { return false; - if (fFileBacked) - if (!CWalletDB(strWalletFile).EraseViewingKey(vk)) + } + if (fFileBacked) { + if (!CWalletDB(strWalletFile).EraseViewingKey(vk)) { return false; + } + } return true; } From 2bbfe6c4e829655ca48dec0349b7e72134b97e17 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 20 Dec 2017 11:24:51 +0000 Subject: [PATCH 11/12] Add cautions to z_getbalance and z_gettotalbalance help text about viewing keys --- src/wallet/rpcwallet.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 696c71c71..829c5c711 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3114,6 +3114,8 @@ UniValue z_getbalance(const UniValue& params, bool fHelp) throw runtime_error( "z_getbalance \"address\" ( minconf )\n" "\nReturns the balance of a taddr or zaddr belonging to the node’s wallet.\n" + "\nCAUTION: If address is a watch-only zaddr, the returned balance may be larger than the actual balance," + "\nbecause spends cannot be detected with incoming viewing keys.\n" "\nArguments:\n" "1. \"address\" (string) The selected address. It may be a transparent or private address.\n" "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" @@ -3176,6 +3178,8 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) throw runtime_error( "z_gettotalbalance ( minconf includeWatchonly )\n" "\nReturn the total value of funds stored in the node’s wallet.\n" + "\nCAUTION: If the wallet contains watch-only zaddrs, the returned private balance may be larger than the actual balance," + "\nbecause spends cannot be detected with incoming viewing keys.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) Only include private and transparent transactions confirmed at least this many times.\n" "2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress' and 'z_importviewingkey')\n" From 5221220c6ceed705c3cb3d2d2d9b2eb13a6ac253 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 20 Dec 2017 18:14:40 +0000 Subject: [PATCH 12/12] Add release notes for incoming viewing keys --- doc/release-notes.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/release-notes.md b/doc/release-notes.md index a29094b51..21c31787b 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,3 +4,31 @@ release-notes at release time) Notable changes =============== +Incoming viewing keys +--------------------- + +Support for incoming viewing keys, as described in +[the Zcash protocol spec](https://github.com/zcash/zips/blob/master/protocol/protocol.pdf), +has been added to the wallet. + +Use the `z_exportviewingkey` RPC method to obtain the incoming viewing key for a +z-address in a node's wallet. For Sprout z-addresses, these always begin with +"ZiVK" (or "ZiVt" for testnet z-addresses). Use `z_importviewingkey` to import +these into another node. + +A node that possesses an incoming viewing key for a z-address can view all past +transactions received by that address, as well as all future transactions sent +to it, by using `z_listreceivedbyaddress`. They cannot spend any funds from the +address. This is similar to the behaviour of "watch-only" t-addresses. + +`z_gettotalbalance` now has an additional boolean parameter for including the +balance of "watch-only" addresses (both transparent and shielded), which is set +to `false` by default. `z_getbalance` has also been updated to work with +watch-only addresses. + +- **Caution:** for z-addresses, these balances will **not** be accurate if any + funds have been sent from the address. This is because incoming viewing keys + cannot detect spends, and so the "balance" is just the sum of all received + notes, including ones that have been spent. Some future use-cases for incoming + viewing keys will include synchronization data to keep their balances accurate + (e.g. [#2542](https://github.com/zcash/zcash/issues/2542)).