From e419899a29aefcc4dd0dd5d151dcc9e9e5cf7e09 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 12 Jan 2022 17:26:38 -0700 Subject: [PATCH] Add support for unified addresses to CWallet::ToZTXOSelector --- src/wallet/asyncrpcoperation_sendmany.cpp | 6 ++- src/wallet/wallet.cpp | 47 +++++++++++++++++++++-- src/wallet/wallet.h | 7 +++- src/zcash/Address.cpp | 8 ++++ src/zcash/Address.hpp | 2 + 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index b524ad948..808496a2e 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -61,7 +61,10 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( std::visit(match { [&](const AccountZTXOSelector& acct) { - isfromtaddr_ = (acct.GetAccountId() == ZCASH_LEGACY_ACCOUNT); + isfromtaddr_ = + acct.GetReceiverTypes().empty() || + acct.GetReceiverTypes().count(ReceiverType::P2PKH) > 0 || + acct.GetReceiverTypes().count(ReceiverType::P2SH) > 0; }, [&](const PaymentAddress& addr) { // We don't need to lock on the wallet as spending key related methods are thread-safe @@ -252,6 +255,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { // and send the extra to the recipient or the miner fee to avoid // creating dust change, rather than prohibit them from sending // entirely in this circumstance. + // (Daira disagrees, as this could leak information to the recipient) throw JSONRPCError( RPC_WALLET_INSUFFICIENT_FUNDS, strprintf( diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6f5c83a1d..a3c035cd8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1359,9 +1359,13 @@ void CWallet::SyncMetaData(pair::iterator, typename TxSpe } } +// +// Zcash transaction output selectors +// + std::optional CWallet::ToZTXOSelector(const libzcash::PaymentAddress& addr, bool requireSpendingKey) const { auto self = this; - std::optional result; + std::optional result = std::nullopt; std::visit(match { [&](const CKeyID& addr) { if (!requireSpendingKey || self->HaveKey(addr)) { @@ -1384,7 +1388,21 @@ std::optional CWallet::ToZTXOSelector(const libzcash::PaymentAddre } }, [&](const libzcash::UnifiedAddress& ua) { - // TODO: Find the unified account corresponding to this UA + auto ufvkId = this->FindUnifiedFullViewingKey(ua); + if (ufvkId.has_value()) { + // TODO: at present, the `false` value for the `requireSpendingKey` argument + // is not respected for unified addresses, because we have no notion of + // an account for which we do not control the spending key. An alternate + // approach would be to use the UFVK directly in the case that we cannot + // determine a local account. + auto addrMetaIt = mapUfvkAddressMetadata.find(ufvkId.value()); + if (addrMetaIt != mapUfvkAddressMetadata.end()) { + auto accountId = addrMetaIt->second.GetAccountId(); + if (accountId.has_value()) { + result = AccountZTXOSelector(accountId.value(), ua.GetKnownReceiverTypes()); + } + } + } } }, addr); return result; @@ -5700,8 +5718,31 @@ void CWallet::GetFilteredNotes( } } +std::optional CWallet::FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const { + std::optional ufvkId; + for (const auto& receiver : addr) { + // skip unknown receivers + if (libzcash::HasKnownReceiverType(receiver)) { + auto ufvkPair = this->GetUFVKMetadataForReceiver(receiver); + if (ufvkPair.has_value()) { + if (ufvkId.has_value()) { + // Check that all the receivers belong to the same ufvk; if not, + // we cannot identify a unique UFVK for the address. + if (ufvkPair.value().first != ufvkId.value()) { + return std::nullopt; + } + } else { + ufvkId = ufvkPair.value().first; + } + } else { + return std::nullopt; + } + } + } + return ufvkId; +} -std::optional CWallet::GetUnifiedForReceiver(const Receiver& receiver) { +std::optional CWallet::GetUnifiedForReceiver(const Receiver& receiver) const { return std::visit(LookupUnifiedAddress(*this), receiver); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ac1db7fd2..cb1e2826a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -846,6 +846,10 @@ public: } } + std::optional GetAccountId() const { + return accountId; + } + bool SetAccountId(libzcash::AccountId accountIdIn) { if (accountId.has_value()) { return (accountIdIn == accountId.value()); @@ -1358,7 +1362,8 @@ public: bool LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta); bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta); - std::optional GetUnifiedForReceiver(const libzcash::Receiver& receiver); + std::optional FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const; + std::optional GetUnifiedForReceiver(const libzcash::Receiver& receiver) const; /** * Increment the next transaction order id diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp index 8fa730ba0..706dc1ff5 100644 --- a/src/zcash/Address.cpp +++ b/src/zcash/Address.cpp @@ -73,6 +73,14 @@ std::optional UnifiedAddress::GetSaplingReceiver() const return std::nullopt; } +bool HasKnownReceiverType(const Receiver& receiver) { + return std::visit(match { + [](const SaplingPaymentAddress& addr) { return true; }, + [](const CScriptID& addr) { return true; }, + [](const CKeyID& addr) { return true; }, + [](const UnknownReceiver& addr) { return false; } + }, receiver); +} std::pair AddressInfoFromSpendingKey::operator()(const SproutSpendingKey &sk) const { return std::make_pair("sprout", sk.address()); diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 530aa29e6..8219e422d 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -56,6 +56,8 @@ typedef std::variant< CKeyID, UnknownReceiver> Receiver; +bool HasKnownReceiverType(const Receiver& receiver); + struct ReceiverIterator { using iterator_category = std::random_access_iterator_tag; using difference_type = std::ptrdiff_t;