From 3e471410f11029b2161006656464a95d818acd19 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 6 Oct 2018 00:45:39 +0100 Subject: [PATCH] Persist Sapling payment address to IVK map This ensures we remember any diversified addresses manually generated outside the wallet. --- src/wallet/gtest/test_wallet_zkeys.cpp | 49 ++++++++++++++++++++++++-- src/wallet/wallet.cpp | 29 +++++++++++++++ src/wallet/wallet.h | 8 +++++ src/wallet/walletdb.cpp | 27 +++++++++++++- src/wallet/walletdb.h | 2 ++ 5 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index ebd6c3bf0..0dcec1077 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -11,7 +11,9 @@ * This test covers Sapling methods on CWallet * GenerateNewSaplingZKey() * AddSaplingZKey() + * AddSaplingIncomingViewingKey() * LoadSaplingZKey() + * LoadSaplingIncomingViewingKey() * LoadSaplingZKeyMetadata() */ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) { @@ -62,6 +64,24 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) { EXPECT_EQ(1, addrs.count(address)); EXPECT_EQ(1, addrs.count(sk.DefaultAddress())); + // Generate a diversified address different to the default + // If we can't get an early diversified address, we are very unlucky + blob88 diversifier; + diversifier.begin()[0] = 10; + auto dpa = sk.ToXFVK().Address(diversifier).get().second; + + // verify wallet only has the default address + EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); + EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa)); + + // manually add a diversified address + auto ivk = fvk.in_viewing_key(); + EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa)); + + // verify wallet did add it + EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress())); + EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa)); + // Load a third key into the wallet auto sk2 = m.Derive(1); ASSERT_TRUE(wallet.LoadSaplingZKey(sk2)); @@ -74,6 +94,13 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) { // check metadata is the same ASSERT_EQ(wallet.mapSaplingZKeyMetadata[ivk2].nCreateTime, now); + + // Load a diversified address for the third key into the wallet + auto dpa2 = sk2.ToXFVK().Address(diversifier).get().second; + EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(sk2.DefaultAddress())); + EXPECT_FALSE(wallet.HaveSaplingIncomingViewingKey(dpa2)); + EXPECT_TRUE(wallet.LoadSaplingPaymentAddress(dpa2, ivk2)); + EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2)); } /** @@ -413,6 +440,18 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) { wallet.GetSaplingPaymentAddresses(addrs); ASSERT_EQ(1, addrs.size()); + // Generate a diversified address different to the default + // If we can't get an early diversified address, we are very unlucky + libzcash::SaplingExtendedSpendingKey extsk; + EXPECT_TRUE(wallet.GetSaplingExtendedSpendingKey(address, extsk)); + blob88 diversifier; + diversifier.begin()[0] = 10; + auto dpa = extsk.ToXFVK().Address(diversifier).get().second; + + // Add diversified address to the wallet + auto ivk = extsk.expsk.full_viewing_key().in_viewing_key(); + EXPECT_TRUE(wallet.AddSaplingIncomingViewingKey(ivk, dpa)); + // encrypt wallet SecureString strWalletPass; strWalletPass.reserve(100); @@ -434,19 +473,25 @@ TEST(wallet_zkeys_tests, WriteCryptedSaplingZkeyDirectToDb) { ASSERT_TRUE(&wallet != &wallet2); ASSERT_TRUE(wallet2.HaveHDSeed()); - // wallet should have two keys + // wallet should have three addresses wallet2.GetSaplingPaymentAddresses(addrs); - ASSERT_EQ(2, addrs.size()); + ASSERT_EQ(3, addrs.size()); //check we have entries for our payment addresses ASSERT_TRUE(addrs.count(address)); ASSERT_TRUE(addrs.count(address2)); + ASSERT_TRUE(addrs.count(dpa)); // spending key is crypted, so we can't extract valid payment address libzcash::SaplingExtendedSpendingKey keyOut; EXPECT_FALSE(wallet2.GetSaplingExtendedSpendingKey(address, keyOut)); ASSERT_FALSE(address == keyOut.DefaultAddress()); + // address -> ivk mapping is not crypted + libzcash::SaplingIncomingViewingKey ivkOut; + EXPECT_TRUE(wallet2.GetSaplingIncomingViewingKey(dpa, ivkOut)); + EXPECT_EQ(ivk, ivkOut); + // unlock wallet to get spending keys and verify payment addresses wallet2.Unlock(strWalletPass); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ee242c233..3690ed166 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -174,6 +174,28 @@ bool CWallet::AddSaplingZKey( return true; } +// Add payment address -> incoming viewing key map entry +bool CWallet::AddSaplingIncomingViewingKey( + const libzcash::SaplingIncomingViewingKey &ivk, + const libzcash::SaplingPaymentAddress &addr) +{ + AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata + + if (!CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr)) { + return false; + } + + if (!fFileBacked) { + return true; + } + + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteSaplingPaymentAddress(addr, ivk); + } + + return true; +} + // Add spending key to keystore and persist to disk bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key) @@ -365,6 +387,13 @@ bool CWallet::LoadSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key) return CCryptoKeyStore::AddSaplingSpendingKey(key, key.DefaultAddress()); } +bool CWallet::LoadSaplingPaymentAddress( + const libzcash::SaplingPaymentAddress &addr, + const libzcash::SaplingIncomingViewingKey &ivk) +{ + return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr); +} + bool CWallet::LoadZKey(const libzcash::SproutSpendingKey &key) { return CCryptoKeyStore::AddSproutSpendingKey(key); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1b11b6349..e7d30f467 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1075,6 +1075,9 @@ public: bool AddSaplingZKey( const libzcash::SaplingExtendedSpendingKey &key, const libzcash::SaplingPaymentAddress &defaultAddr); + bool AddSaplingIncomingViewingKey( + const libzcash::SaplingIncomingViewingKey &ivk, + const libzcash::SaplingPaymentAddress &addr); bool AddCryptedSaplingSpendingKey( const libzcash::SaplingExtendedFullViewingKey &extfvk, const std::vector &vchCryptedSecret, @@ -1083,6 +1086,11 @@ public: bool LoadSaplingZKey(const libzcash::SaplingExtendedSpendingKey &key); //! Load spending key metadata (used by LoadWallet) bool LoadSaplingZKeyMetadata(const libzcash::SaplingIncomingViewingKey &ivk, const CKeyMetadata &meta); + //! Adds a Sapling payment address -> incoming viewing key map entry, + //! without saving it to disk (used by LoadWallet) + bool LoadSaplingPaymentAddress( + const libzcash::SaplingPaymentAddress &addr, + const libzcash::SaplingIncomingViewingKey &ivk); //! Adds an encrypted spending key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, const std::vector &vchCryptedSecret); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 9bab20858..9a878491c 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -175,6 +175,15 @@ bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk, return Write(std::make_pair(std::string("sapzkey"), ivk), key, false); } +bool CWalletDB::WriteSaplingPaymentAddress( + const libzcash::SaplingPaymentAddress &addr, + const libzcash::SaplingIncomingViewingKey &ivk) +{ + nWalletDBUpdated++; + + return Write(std::make_pair(std::string("sapzaddr"), addr), ivk, false); +} + bool CWalletDB::WriteSproutViewingKey(const libzcash::SproutViewingKey &vk) { nWalletDBUpdated++; @@ -416,13 +425,14 @@ public: unsigned int nZKeys; unsigned int nCZKeys; unsigned int nZKeyMeta; + unsigned int nSapZAddrs; bool fIsEncrypted; bool fAnyUnordered; int nFileVersion; vector vWalletUpgrade; CWalletScanState() { - nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = 0; + nKeys = nCKeys = nKeyMeta = nZKeys = nCZKeys = nZKeyMeta = nSapZAddrs = 0; fIsEncrypted = false; fAnyUnordered = false; nFileVersion = 0; @@ -729,6 +739,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, pwallet->LoadSaplingZKeyMetadata(ivk, keyMeta); } + else if (strType == "sapzaddr") + { + libzcash::SaplingPaymentAddress addr; + ssKey >> addr; + libzcash::SaplingIncomingViewingKey ivk; + ssValue >> ivk; + + wss.nSapZAddrs++; + + if (!pwallet->LoadSaplingPaymentAddress(addr, ivk)) + { + strErr = "Error reading wallet database: LoadSaplingPaymentAddress failed"; + return false; + } + } else if (strType == "defaultkey") { ssValue >> pwallet->vchDefaultKey; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index a009b1ab7..b3210bbc0 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -187,6 +187,8 @@ public: bool WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk, const libzcash::SaplingExtendedSpendingKey &key, const CKeyMetadata &keyMeta); + bool WriteSaplingPaymentAddress(const libzcash::SaplingPaymentAddress &addr, + const libzcash::SaplingIncomingViewingKey &ivk); bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr, const libzcash::ReceivingKey & rk, const std::vector& vchCryptedSecret,