Trial-decrypt candidate Sapling receivers with all wallet UFVKs

This ensures we have the same semantics for detecting Orchard and
Sapling receivers in UAs, even if the UA they are part of was derived
outside of the zcashd wallet.
This commit is contained in:
Jack Grigg 2022-02-23 02:23:45 +00:00
parent d099e469e1
commit 3179e45467
2 changed files with 33 additions and 7 deletions

View File

@ -557,17 +557,22 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Sapling}));
EXPECT_TRUE(addrPair.first.GetSaplingReceiver().has_value());
auto saplingReceiver = addrPair.first.GetSaplingReceiver().value();
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_FALSE(ufvkmeta.has_value());
// We detect this even though we haven't added the Sapling address, because
// we trial-decrypt diversifiers (which also means we learn the index).
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmetaUnadded.has_value());
EXPECT_EQ(ufvkmetaUnadded.value().first, ufvkid);
EXPECT_EQ(ufvkmetaUnadded.value().second.value(), addrPair.second);
// Adding the Sapling addr -> ivk map entry causes us to find the same UFVK,
// but as we're no longer trial-decrypting we don't learn the index.
auto saplingIvk = zufvk.GetSaplingKey().value().ToIncomingViewingKey();
keyStore.AddSaplingPaymentAddress(saplingIvk, saplingReceiver);
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
EXPECT_FALSE(ufvkmeta.value().second.has_value());

View File

@ -442,15 +442,36 @@ std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_in
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
const auto saplingIvk = keystore.mapSaplingIncomingViewingKeys.find(saplingAddr);
if (saplingIvk != keystore.mapSaplingIncomingViewingKeys.end()) {
// We have either generated this as a receiver via `z_getaddressforaccount` or a
// legacy Sapling address via `z_getnewaddress`, or we have previously detected
// this via trial-decryption of a note.
const auto ufvkId = keystore.mapSaplingKeyUnified.find(saplingIvk->second);
if (ufvkId != keystore.mapSaplingKeyUnified.end()) {
return std::make_pair(ufvkId->second, std::nullopt);
} else {
// If we have the addr -> ivk map entry but not the ivk -> UFVK map entry,
// then this is definitely a legacy Sapling address.
return std::nullopt;
}
} else {
return std::nullopt;
}
// We haven't generated this receiver via `z_getaddressforaccount` (or this is a
// recovery from a backed-up mnemonic which doesn't store receiver types selected by
// users). Trial-decrypt the diversifier of the Sapling address with every UFVK in the
// wallet, to check directly if it belongs to any of them.
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
auto dfvk = v.GetSaplingKey();
if (dfvk.has_value()) {
auto d_idx = dfvk.value().DecryptDiversifier(saplingAddr.d);
auto derived_addr = dfvk.value().Address(d_idx);
if (derived_addr.has_value() && derived_addr.value() == saplingAddr) {
return std::make_pair(k, d_idx);
}
}
}
// We definitely don't know of any UFVK linked to this Sapling address.
return std::nullopt;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const CScriptID& scriptId) const {