From 74e4bef6f2d107cc05ce3a9afff48c5eb17b2fcd Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 22 Dec 2021 18:20:53 -0700 Subject: [PATCH] Ensure that unified address metadata is always correctly populated. This reworks the way that address metadata is added to the wallet to make adding address metadata to the wallet's in-memory cache the responsibility of a single method rather than distributed across multiple places in the code. --- src/gtest/test_keystore.cpp | 2 +- src/keystore.cpp | 13 ++- src/keystore.h | 15 +-- src/util/match.h | 24 +++++ src/wallet/gtest/test_wallet.cpp | 18 ++-- src/wallet/test/rpc_wallet_tests.cpp | 1 - src/wallet/wallet.cpp | 137 ++++++++++++++------------- src/wallet/wallet.h | 37 +++++--- src/wallet/walletdb.cpp | 22 +++-- src/wallet/walletdb.h | 12 +-- src/zcash/Address.hpp | 21 ++++ 11 files changed, 186 insertions(+), 116 deletions(-) create mode 100644 src/util/match.h diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 5b21d15bb..21c428262 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -560,7 +560,7 @@ TEST(KeystoreTests, AddUnifiedAddress) { auto addrPair = zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::P2PKH, ReceiverType::Sapling}); EXPECT_TRUE(addrPair.first.GetP2PKHReceiver().has_value()); - keyStore.AddUnifiedAddress(ufvkid, addrPair); + keyStore.AddUnifiedAddress(ufvkid, addrPair.second, addrPair.first); auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value()); EXPECT_TRUE(ufvkmeta.has_value()); diff --git a/src/keystore.cpp b/src/keystore.cpp index 169a0e97c..53817a0b7 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -316,9 +316,10 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey( return true; } -void CBasicKeyStore::AddUnifiedAddress( +bool CBasicKeyStore::AddUnifiedAddress( const libzcash::UFVKId& keyId, - const std::pair& ua) + const libzcash::diversifier_index_t& diversifierIndex, + const libzcash::UnifiedAddress& ua) { LOCK(cs_KeyStore); @@ -326,17 +327,19 @@ void CBasicKeyStore::AddUnifiedAddress( // the UA; all other lookups of the associated UFVK will be // made via the protocol-specific viewing key that is used // to trial-decrypt a transaction. - auto addrEntry = std::make_pair(keyId, ua.second); + auto addrEntry = std::make_pair(keyId, diversifierIndex); - auto p2pkhReceiver = ua.first.GetP2PKHReceiver(); + auto p2pkhReceiver = ua.GetP2PKHReceiver(); if (p2pkhReceiver.has_value()) { mapP2PKHUnified.insert(std::make_pair(p2pkhReceiver.value(), addrEntry)); } - auto p2shReceiver = ua.first.GetP2SHReceiver(); + auto p2shReceiver = ua.GetP2SHReceiver(); if (p2shReceiver.has_value()) { mapP2SHUnified.insert(std::make_pair(p2shReceiver.value(), addrEntry)); } + + return true; } std::optional CBasicKeyStore::GetUnifiedFullViewingKey( diff --git a/src/keystore.h b/src/keystore.h index 140baf7a9..d3e1c6c76 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -117,10 +117,10 @@ public: * viewing key upon discovery of the address as having received * funds. */ - virtual void AddUnifiedAddress( - const libzcash::UFVKId& keyId, - const std::pair& ua - ) = 0; + virtual bool AddUnifiedAddress( + const libzcash::UFVKId& keyId, + const libzcash::diversifier_index_t& diversifierIndex, + const libzcash::UnifiedAddress& ua) = 0; virtual std::optional GetUnifiedFullViewingKey( const libzcash::UFVKId& keyId @@ -357,9 +357,10 @@ public: virtual bool AddUnifiedFullViewingKey( const libzcash::ZcashdUnifiedFullViewingKey &ufvk); - virtual void AddUnifiedAddress( - const libzcash::UFVKId& keyId, - const std::pair& ua); + virtual bool AddUnifiedAddress( + const libzcash::UFVKId& keyId, + const libzcash::diversifier_index_t& diversifierIndex, + const libzcash::UnifiedAddress& ua); virtual std::optional GetUnifiedFullViewingKey( const libzcash::UFVKId& keyId) const; diff --git a/src/util/match.h b/src/util/match.h new file mode 100644 index 000000000..d966ae4da --- /dev/null +++ b/src/util/match.h @@ -0,0 +1,24 @@ +// Copyright (c) 2021 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_UTIL_MATCH_H +#define ZCASH_UTIL_MATCH_H + +// Helper for using `std::visit` with Rust-style match syntax. +// +// This can be used in place of defining an explicit visitor. `std::visit` requires an +// exhaustive match; to emulate Rust's catch-all binding, use an `(auto arg)` template +// operator(). +// +// Care must be taken that implicit conversions are handled correctly. For instance, a +// `(double arg)` operator() *will also* bind to `int` and `long`, if there isn't an +// earlier satisfying match. +// +// This corresponds to visitor example #4 in the `std::visit` documentation: +// https://en.cppreference.com/w/cpp/utility/variant/visit +template struct match : Ts... { using Ts::operator()...; }; +// explicit deduction guide (not needed as of C++20) +template match(Ts...) -> match; + +#endif // ZCASH_UTIL_MATCH_H diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index 3e9349c7c..f0e6dce65 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -2199,19 +2199,19 @@ TEST(WalletTests, GenerateUnifiedAddress) { // Create an account, then generate an address for that account. auto skpair = wallet.GenerateNewUnifiedSpendingKey(); uaResult = wallet.GenerateUnifiedAddress( - skpair.second.GetAccountId(), + skpair.second, diversifier_index_t(0), {ReceiverType::P2PKH, ReceiverType::Sapling}); - bool success = std::holds_alternative>(uaResult); - EXPECT_TRUE(success); + auto ua = std::get_if>(&uaResult); + EXPECT_NE(ua, nullptr); + + EXPECT_TRUE(ua->first.GetSaplingReceiver().has_value()); auto ufvk = skpair.first.ToFullViewingKey(); - auto addrpair = std::get>(uaResult); - EXPECT_TRUE(addrpair.first.GetSaplingReceiver().has_value()); EXPECT_EQ( - addrpair.first.GetSaplingReceiver(), - ufvk.GetSaplingKey().value().Address(addrpair.second.GetDiversifierIndex())); + ua->first.GetSaplingReceiver(), + ufvk.GetSaplingKey().value().Address(ua->second)); - auto u4r = wallet.GetUnifiedForReceiver(addrpair.first.GetSaplingReceiver().value()); - EXPECT_EQ(u4r, addrpair.first); + auto u4r = wallet.GetUnifiedForReceiver(ua->first.GetSaplingReceiver().value()); + EXPECT_EQ(u4r, ua->first); } diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 307f5cc31..0528ab0e6 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -79,7 +79,6 @@ static SaplingPaymentAddress DefaultSaplingAddress(CWallet* pwallet) { auto usk = pwallet->GenerateUnifiedSpendingKeyForAccount(0); return usk.value() - .first .ToFullViewingKey() .GetSaplingKey().value() .FindAddress(libzcash::diversifier_index_t(0)).first; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bd31486b7..eab370f21 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -421,7 +421,7 @@ bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullVi return false; } -std::pair CWallet::GenerateNewUnifiedSpendingKey() { +std::pair CWallet::GenerateNewUnifiedSpendingKey() { AssertLockHeld(cs_wallet); if (!mnemonicHDChain.has_value()) { @@ -431,7 +431,8 @@ std::pair CWallet::GenerateNewUn CHDChain& hdChain = mnemonicHDChain.value(); while (true) { - auto usk = GenerateUnifiedSpendingKeyForAccount(hdChain.GetAccountCounter()); + auto accountId = hdChain.GetAccountCounter(); + auto usk = GenerateUnifiedSpendingKeyForAccount(accountId); hdChain.IncrementAccountCounter(); if (usk.has_value()) { @@ -441,14 +442,14 @@ std::pair CWallet::GenerateNewUn "CWallet::GenerateNewUnifiedSpendingKey(): Writing HD chain model failed"); } - return usk.value(); + return std::make_pair(usk.value(), accountId); } } } -std::optional> +std::optional CWallet::GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId) { - AssertLockHeld(cs_wallet); // mapUnifiedSpendingKeyMeta + AssertLockHeld(cs_wallet); // mapUnifiedAccountKeys auto seed = GetMnemonicSeed(); if (!seed.has_value()) { @@ -457,11 +458,10 @@ std::optional CWallet::GetUnifiedFullViewingKeyByAc } auto seedfp = mnemonicHDChain.value().GetSeedFingerprint(); - auto i = mapUnifiedSpendingKeyMeta.find(std::make_pair(seedfp, accountId)); - if (i != mapUnifiedSpendingKeyMeta.end()) { - auto keyId = i->second.GetKeyID(); - return CCryptoKeyStore::GetUnifiedFullViewingKey(keyId); + auto entry = mapUnifiedAccountKeys.find(std::make_pair(seedfp, accountId)); + if (entry != mapUnifiedAccountKeys.end()) { + return CCryptoKeyStore::GetUnifiedFullViewingKey(entry->second); } else { return std::nullopt; } @@ -571,37 +570,42 @@ UAGenerationResult CWallet::GenerateUnifiedAddress( } } - auto identifiedKey = GetUnifiedFullViewingKeyByAccount(accountId); - if (identifiedKey.has_value()) { - auto ufvk = identifiedKey.value(); + auto ufvk = GetUnifiedFullViewingKeyByAccount(accountId); + if (ufvk.has_value()) { + auto ufvkid = ufvk.value().GetKeyID(); // Check whether an address has already been generated for this // diversifier index. If so, ensure that the set of receiver types // being requested is the same as the set of receiver types that was - // previously generated; if so, return the previously generated address, + // previously generated & return the previously generated address, // otherwise return an error. - auto metadata = mapUnifiedFullViewingKeyMeta.find(ufvk.GetKeyID()); - if (metadata != mapUnifiedFullViewingKeyMeta.end()) { + auto metadata = mapUfvkAddressMetadata.find(ufvkid); + if (metadata != mapUfvkAddressMetadata.end()) { auto receivers = metadata->second.GetReceivers(j); if (receivers.has_value()) { if (receivers.value() == receiverTypes) { - ZcashdUnifiedAddressMetadata addrmeta(ufvk.GetKeyID(), j, receiverTypes); - auto addr = ufvk.Address(j, receiverTypes); - return std::make_pair(addr.value(), addrmeta); + return std::make_pair(ufvk.value().Address(j, receiverTypes).value(), j); } else { return AddressGenerationError::ExistingAddressMismatch; } } - } else { - mapUnifiedFullViewingKeyMeta[ufvk.GetKeyID()] = ZcashdUnifiedFullViewingKeyMetadata(accountId); } // Find a working diversifier and construct the associated address. - auto foundAddress = ufvk.FindAddress(j, receiverTypes); + auto foundAddress = ufvk.value().FindAddress(j, receiverTypes); auto diversifierIndex = foundAddress.second; // Persist the newly created address to the keystore - AddUnifiedAddress(ufvk.GetKeyID(), foundAddress); + mapUfvkAddressMetadata[ufvkid].SetReceivers(diversifierIndex, receiverTypes); + CCryptoKeyStore::AddUnifiedAddress(ufvkid, diversifierIndex, foundAddress.first); + + // Save the metadata for the generated address so that we can re-derive + // it in the future. + ZcashdUnifiedAddressMetadata addrmeta(ufvkid, diversifierIndex, receiverTypes); + if (fFileBacked && !CWalletDB(strWalletFile).WriteUnifiedAddressMetadata(addrmeta)) { + throw std::runtime_error( + "CWallet::AdddUnifiedAddress(): Writing unified address metadata failed"); + } if (hasTransparent) { // Regenerate the secret key for the transparent address and add it to @@ -612,65 +616,66 @@ UAGenerationResult CWallet::GenerateUnifiedAddress( AddTransparentSecretKey(seed.Fingerprint(), key); } - // Save the metadata for the generated address so that we can re-derive - // it in the future. - ZcashdUnifiedAddressMetadata addrmeta(ufvk.GetKeyID(), diversifierIndex, receiverTypes); - - // we can safely ignore the return value here; we know that we're adding a new - // set of receivers given the receivers.has_value() check above. - mapUnifiedFullViewingKeyMeta[ufvk.GetKeyID()].SetReceivers(diversifierIndex, receiverTypes); - if (fFileBacked) { - CWalletDB(strWalletFile).WriteUnifiedAddressMetadata(addrmeta); - } - return std::make_pair(foundAddress.first, addrmeta); + return foundAddress; } else { return AddressGenerationError::NoSuchAccount; } } +// NOTE: There is an important relationship between `LoadUnifiedFullViewingKey` +// and `LoadUnifiedAddressMetadata`. In all cases, by the end of the +// wallet-loading process, `mapUfvkAddressMetadata` needs to be fully populated +// for each unified full viewing key. However, we cannot guarantee in what +// order UFVKs and unified address and account metadata are loaded from disk. +// Therefore: +// * When we load a unified full viewing key from disk, we repopulate +// the cache of generated addresses by regenerating the address for each +// (diversifierIndex, {receiverType...}) pair in `mapUfvkAddressMetadata` +// that we have loaded so far. +// * When we load a unified address metadata record from disk: +// - if we have not yet loaded the unified full viewing key, simply add +// an entry `mapUfvkAddressMetadata` so that we can restore the address +// once the UFVK has been loaded. +// - if we have already loaded the unified full viewing key from disk, +// regenerate the address from its metadata and add it to the keystore bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key) { auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), key); - auto metadata = mapUnifiedFullViewingKeyMeta.find(zufvk.GetKeyID()); - if (metadata != mapUnifiedFullViewingKeyMeta.end()) { + auto metadata = mapUfvkAddressMetadata.find(zufvk.GetKeyID()); + if (metadata != mapUfvkAddressMetadata.end()) { // restore unified addresses that have been previously generated to the // keystore for (const auto &[j, receiverTypes] : metadata->second.GetAllReceivers()) { auto addr = zufvk.Address(j, receiverTypes).value(); - AddUnifiedAddress(zufvk.GetKeyID(), std::make_pair(addr, j)); + CCryptoKeyStore::AddUnifiedAddress(zufvk.GetKeyID(), j, addr); } } return CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk); } -bool CWallet::LoadUnifiedAccount(const ZcashdUnifiedAccount &skmeta) +bool CWallet::LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta) { - AssertLockHeld(cs_wallet); // mapUnifiedSpendingKeyMeta + AssertLockHeld(cs_wallet); // mapUnifiedAccountKeys auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId()); - mapUnifiedSpendingKeyMeta.insert({metaKey, skmeta}); - if (mapUnifiedFullViewingKeyMeta.count(skmeta.GetKeyID()) == 0) { - return mapUnifiedFullViewingKeyMeta[skmeta.GetKeyID()].SetAccountId(skmeta.GetAccountId()); - } - - return true; + mapUnifiedAccountKeys.insert({metaKey, skmeta.GetKeyID()}); + return mapUfvkAddressMetadata[skmeta.GetKeyID()].SetAccountId(skmeta.GetAccountId()); } bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta) { - AssertLockHeld(cs_wallet); // mapUnifiedSpendingKeyMeta + AssertLockHeld(cs_wallet); + mapUfvkAddressMetadata[addrmeta.GetKeyID()].SetReceivers( + addrmeta.GetDiversifierIndex(), + addrmeta.GetReceiverTypes()); + auto ufvk = GetUnifiedFullViewingKey(addrmeta.GetKeyID()); if (ufvk.has_value()) { - // restore unified addresses that have been previously generated + // Regenerate the unified address and add it to the keystore. auto j = addrmeta.GetDiversifierIndex(); - auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes()); - if (addr.has_value()) { - AddUnifiedAddress(addrmeta.GetKeyID(), std::make_pair(addr.value(), j)); - } else { - // an error has occurred; the ufvk is loaded but cannot reproduce the - // address identified by the address metadata. - return false; - } + auto addr = ufvk.value().Address(j, addrmeta.GetReceiverTypes()).value(); + return CCryptoKeyStore::AddUnifiedAddress(addrmeta.GetKeyID(), j, addr); } + return true; } @@ -5811,8 +5816,8 @@ std::optional LookupUnifiedAddress::operator()(const l } diversifier_index_t j; - auto metadata = wallet.mapUnifiedFullViewingKeyMeta.find(ufvkid); - if (metadata != wallet.mapUnifiedFullViewingKeyMeta.end()) { + auto metadata = wallet.mapUfvkAddressMetadata.find(ufvkid); + if (metadata != wallet.mapUfvkAddressMetadata.end()) { librustzcash_sapling_diversifier_index( ufvk.value().GetSaplingKey().value().dk.begin(), saplingAddr.d.begin(), @@ -5851,8 +5856,8 @@ std::optional LookupUnifiedAddress::operator()(const C // Find the set of receivers at the diversifier index. If no metadata is available // for the ufvk, or we do not know the receiver types for the address produced // at this diversifier, we cannot reconstruct the address. - auto metadata = wallet.mapUnifiedFullViewingKeyMeta.find(ufvkid); - if (metadata != wallet.mapUnifiedFullViewingKeyMeta.end()) { + auto metadata = wallet.mapUfvkAddressMetadata.find(ufvkid); + if (metadata != wallet.mapUfvkAddressMetadata.end()) { auto receivers = metadata->second.GetReceivers(j); if (receivers.has_value()) { return ufvk.value().Address(j, receivers.value()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 5f5e44089..cb8f82585 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -412,7 +412,7 @@ enum class AddressGenerationError { }; typedef std::variant< - std::pair, + std::pair, AddressGenerationError> UAGenerationResult; /** @@ -681,14 +681,14 @@ public: } }; -class ZcashdUnifiedFullViewingKeyMetadata +class UFVKAddressMetadata { private: std::optional accountId; std::map> addressReceivers; public: - ZcashdUnifiedFullViewingKeyMetadata() {} - ZcashdUnifiedFullViewingKeyMetadata(libzcash::AccountId accountId): accountId(accountId) {} + UFVKAddressMetadata() {} + UFVKAddressMetadata(libzcash::AccountId accountId): accountId(accountId) {} const std::map>& GetAllReceivers() const { return addressReceivers; @@ -704,10 +704,21 @@ public: } } + /** + * Add the specified set of receivers at the provided diversifier index. + * + * Returns `true` if this is a new entry or if the operation would not + * alter the existing set of receiver types at this index, `false` + * otherwise. + */ bool SetReceivers( const libzcash::diversifier_index_t& j, const std::set& receivers) { - return addressReceivers.insert(std::make_pair(j, receivers)).second; + if (addressReceivers.count(j) > 0) { + return addressReceivers[j] == receivers; + } else { + return addressReceivers.insert(std::make_pair(j, receivers)).second; + } } bool SetAccountId(libzcash::AccountId accountIdIn) { @@ -888,8 +899,8 @@ public: std::map mapSproutZKeyMetadata; std::map mapSaplingZKeyMetadata; - std::map, ZcashdUnifiedAccount> mapUnifiedSpendingKeyMeta; - std::map mapUnifiedFullViewingKeyMeta; + std::map, libzcash::UFVKId> mapUnifiedAccountKeys; + std::map mapUfvkAddressMetadata; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -1164,12 +1175,12 @@ public: //! Generate the unified spending key from the wallet's mnemonic seed //! for the next unused account identifier. - std::pair + std::pair GenerateNewUnifiedSpendingKey(); //! Generate the next available unified spending key from the wallet's //! mnemonic seed. - std::optional> + std::optional GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId); //! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account. @@ -1183,14 +1194,14 @@ public: const libzcash::diversifier_index_t& j, const std::set& receivers); - std::optional GetUnifiedForReceiver(const libzcash::Receiver& receiver); - bool AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk); - bool LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk); - bool LoadUnifiedAccount(const ZcashdUnifiedAccount &skmeta); + bool LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk); + bool LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta); bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta); + std::optional GetUnifiedForReceiver(const libzcash::Receiver& receiver); + /** * Increment the next transaction order id * @return next transaction order id diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 712227c47..004467372 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -221,10 +221,9 @@ bool CWalletDB::EraseSaplingExtendedFullViewingKey( // Unified address & key storage // -bool CWalletDB::WriteUnifiedAccount(const ZcashdUnifiedAccount& keymeta) +bool CWalletDB::WriteUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata& keymeta) { nWalletDBUpdateCounter++; - auto ufvkId = keymeta.GetKeyID(); return Write(std::make_pair(std::string("unifiedaccount"), keymeta), 0x00); } @@ -238,8 +237,7 @@ bool CWalletDB::WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey bool CWalletDB::WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta) { nWalletDBUpdateCounter++; - auto ufvkId = addrmeta.GetKeyID(); - return Write(std::make_pair(std::string("unifiedaddrmeta"), ufvkId), addrmeta); + return Write(std::make_pair(std::string("unifiedaddrmeta"), addrmeta), 0x00); } // @@ -688,23 +686,31 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else if (strType == "unifiedaccount") { - auto acct = ZcashdUnifiedAccount::Read(ssKey); + auto acct = ZcashdUnifiedAccountMetadata::Read(ssKey); uint8_t value; ssValue >> value; if (value != 0x00) { - strErr = "Error reading wallet database: invalid unified account value."; + strErr = "Error reading wallet database: invalid unified account metadata."; return false; } - if (!pwallet->LoadUnifiedAccount(acct)) { + if (!pwallet->LoadUnifiedAccountMetadata(acct)) { strErr = "Error reading wallet database: account ID mismatch for unified spending key."; return false; }; } else if (strType == "unifiedaddrmeta") { - auto keymeta = ZcashdUnifiedAddressMetadata::Read(ssValue); + auto keymeta = ZcashdUnifiedAddressMetadata::Read(ssKey); + + uint8_t value; + ssValue >> value; + if (value != 0x00) { + strErr = "Error reading wallet database: invalid unified address metadata."; + return false; + } + if (!pwallet->LoadUnifiedAddressMetadata(keymeta)) { strErr = "Error reading wallet database: cannot reproduce previously generated unified address."; return false; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index e3928d798..8fc72d01f 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -172,16 +172,16 @@ public: } }; -class ZcashdUnifiedAccount { +class ZcashdUnifiedAccountMetadata { private: libzcash::SeedFingerprint seedFp; uint32_t bip44CoinType; libzcash::AccountId accountId; libzcash::UFVKId ufvkId; - ZcashdUnifiedAccount() {} + ZcashdUnifiedAccountMetadata() {} public: - ZcashdUnifiedAccount( + ZcashdUnifiedAccountMetadata( libzcash::SeedFingerprint seedFp, uint32_t bip44CoinType, libzcash::AccountId accountId, @@ -216,8 +216,8 @@ public: } template - static ZcashdUnifiedAccount Read(Stream& stream) { - ZcashdUnifiedAccount meta; + static ZcashdUnifiedAccountMetadata Read(Stream& stream) { + ZcashdUnifiedAccountMetadata meta; stream >> meta; return meta; } @@ -386,7 +386,7 @@ public: /// Unified key support. - bool WriteUnifiedAccount(const ZcashdUnifiedAccount& keymeta); + bool WriteUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata& keymeta); bool WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey& ufvk); bool WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta); diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 23326059b..e690fdf2e 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -6,6 +6,7 @@ #include "key_constants.h" #include "script/script.h" #include "uint256.h" +#include "util/match.h" #include "zcash/address/orchard.hpp" #include "zcash/address/sapling.hpp" #include "zcash/address/sprout.hpp" @@ -116,6 +117,26 @@ public: const std::vector& GetReceiversAsParsed() const { return receivers; } + std::set GetKnownReceiverTypes() const { + std::set result; + for (const auto& receiver : receivers) { + std::visit(match { + [&](const libzcash::SaplingPaymentAddress &zaddr) { + result.insert(ReceiverType::Sapling); + }, + [&](const CScriptID &zaddr) { + result.insert(ReceiverType::P2SH); + }, + [&](const CKeyID &zaddr) { + result.insert(ReceiverType::P2PKH); + }, + [&](const libzcash::UnknownReceiver &uaddr) { + } + }, receiver); + } + return result; + } + ReceiverIterator begin() const { return ReceiverIterator(GetSorted(), 0); }