diff --git a/src/gtest/test_keystore.cpp b/src/gtest/test_keystore.cpp index 6d739165f..c99b504d6 100644 --- a/src/gtest/test_keystore.cpp +++ b/src/gtest/test_keystore.cpp @@ -557,17 +557,22 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) { EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk); auto addrPair = std::get>(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()); diff --git a/src/keystore.cpp b/src/keystore.cpp index ba72c42bd..b8f67f97b 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -442,15 +442,36 @@ std::optionalsecond); 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>> FindUFVKId::operator()(const CScriptID& scriptId) const {