Correctly handle imported Sapling addresses

Update the tests to reflect the new listaddresses result
structure.
This commit is contained in:
Kris Nuttycombe 2021-10-06 20:39:52 -06:00
parent 9a11ac73a4
commit 6be880fe34
4 changed files with 252 additions and 84 deletions

View File

@ -398,16 +398,35 @@ UniValue listaddresses(const UniValue& params, bool fHelp)
// This includes transparent addresses generated by the wallet via
// the keypool and Sprout addresses for which we have the
// spending key.
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "legacy_random");
bool hasData = false;
UniValue random_t(UniValue::VOBJ);
if (!t_generated_dests.empty()) {
UniValue random_t_addrs(UniValue::VARR);
for (const CTxDestination& dest : t_generated_dests) {
random_t_addrs.push_back(keyIO.EncodeDestination(dest));
}
random_t.pushKV("addresses", random_t_addrs);
hasData = true;
}
if (!t_change_dests.empty()) {
UniValue random_t_change_addrs(UniValue::VARR);
for (const CTxDestination& dest : t_change_dests) {
random_t_change_addrs.push_back(keyIO.EncodeDestination(dest));
}
random_t.pushKV("change_addresses", random_t_change_addrs);
hasData = true;
}
if (hasData) {
entry.pushKV("transparent", random_t);
}
if (!sproutAddresses.empty()) {
UniValue random_sprout_addrs(UniValue::VARR);
for (const SproutPaymentAddress& addr : sproutAddresses) {
if (HaveSpendingKeyForPaymentAddress(pwalletMain)(addr)) {
@ -415,29 +434,68 @@ UniValue listaddresses(const UniValue& params, bool fHelp)
}
}
/// keypool source only applies to transparent transactions
UniValue random_t(UniValue::VOBJ);
random_t.pushKV("addresses", random_t_addrs);
random_t.pushKV("change_addresses", random_t_change_addrs);
UniValue random_sprout(UniValue::VOBJ);
random_sprout.pushKV("addresses", random_sprout_addrs);
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "legacy_random");
entry.pushKV("transparent", random_t);
entry.pushKV("sprout", random_sprout);
hasData = true;
}
if (hasData) {
ret.push_back(entry);
}
}
/// imported source
{
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "imported");
bool hasData = false;
{
UniValue imported_sapling_addrs(UniValue::VARR);
for (const SaplingPaymentAddress& addr : saplingAddresses) {
if (GetSourceForPaymentAddress(pwalletMain)(addr) == Imported) {
imported_sapling_addrs.push_back(keyIO.EncodePaymentAddress(addr));
}
}
if (!imported_sapling_addrs.empty()) {
UniValue imported_sapling_obj(UniValue::VOBJ);
imported_sapling_obj.pushKV("addresses", imported_sapling_addrs);
UniValue imported_sapling(UniValue::VARR);
imported_sapling.push_back(imported_sapling_obj);
entry.pushKV("sapling", imported_sapling);
hasData = true;
}
}
if (hasData) {
ret.push_back(entry);
}
}
/// imported_watchonly source
{
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "imported_watchonly");
bool hasData = false;
if (!t_watchonly_dests.empty()) {
UniValue watchonly_t_addrs(UniValue::VARR);
for (const CTxDestination& dest: t_watchonly_dests) {
watchonly_t_addrs.push_back(keyIO.EncodeDestination(dest));
}
UniValue watchonly_t(UniValue::VOBJ);
watchonly_t.pushKV("addresses", watchonly_t_addrs);
entry.pushKV("transparent", watchonly_t);
hasData = true;
}
{
UniValue watchonly_sprout_addrs(UniValue::VARR);
for (const SproutPaymentAddress& addr : sproutAddresses) {
if (!HaveSpendingKeyForPaymentAddress(pwalletMain)(addr)) {
@ -445,6 +503,15 @@ UniValue listaddresses(const UniValue& params, bool fHelp)
}
}
if (!watchonly_sprout_addrs.empty()) {
UniValue watchonly_sprout(UniValue::VOBJ);
watchonly_sprout.pushKV("addresses", watchonly_sprout_addrs);
entry.pushKV("sprout", watchonly_sprout);
hasData = true;
}
}
{
UniValue watchonly_sapling_addrs(UniValue::VARR);
for (const SaplingPaymentAddress& addr : saplingAddresses) {
if (!HaveSpendingKeyForPaymentAddress(pwalletMain)(addr)) {
@ -452,48 +519,47 @@ UniValue listaddresses(const UniValue& params, bool fHelp)
}
}
UniValue watchonly_t(UniValue::VOBJ);
watchonly_t.pushKV("addresses", watchonly_t_addrs);
UniValue watchonly_sprout(UniValue::VOBJ);
watchonly_sprout.pushKV("addresses", watchonly_sprout_addrs);
if (!watchonly_sapling_addrs.empty()) {
UniValue watchonly_sapling_obj(UniValue::VOBJ);
watchonly_sapling_obj.pushKV("addresses", watchonly_sapling_addrs);
UniValue watchonly_sapling(UniValue::VARR);
watchonly_sapling.push_back(watchonly_sapling_obj);
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "imported_watchonly");
entry.pushKV("transparent", watchonly_t);
entry.pushKV("sprout", watchonly_sprout);
entry.pushKV("sapling", watchonly_sapling);
hasData = true;
}
}
if (hasData) {
ret.push_back(entry);
}
}
/// legacy_hdseed source
{
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "legacy_hdseed");
// TODO: split up by zip32 account id
UniValue legacy_sapling_addrs(UniValue::VARR);
for (const SaplingPaymentAddress& addr : saplingAddresses) {
if (HaveSpendingKeyForPaymentAddress(pwalletMain)(addr)) {
if (GetSourceForPaymentAddress(pwalletMain)(addr) == LegacyHDSeed) {
legacy_sapling_addrs.push_back(keyIO.EncodePaymentAddress(addr));
}
}
if (!legacy_sapling_addrs.empty()) {
UniValue legacy_sapling_obj(UniValue::VOBJ);
legacy_sapling_obj.pushKV("addresses", legacy_sapling_addrs);
UniValue legacy_sapling(UniValue::VARR);
legacy_sapling.push_back(legacy_sapling_obj);
UniValue entry(UniValue::VOBJ);
entry.pushKV("source", "legacy_hdseed");
entry.pushKV("sapling", legacy_sapling);
ret.push_back(entry);
}
}
return ret;
}

View File

@ -223,14 +223,22 @@ BOOST_AUTO_TEST_CASE(rpc_wallet)
*********************************/
BOOST_CHECK_NO_THROW(retValue = CallRPC("listaddresses"));
UniValue arr = retValue.get_array();
BOOST_CHECK_EQUAL(2, arr.size());
{
BOOST_CHECK_EQUAL(1, arr.size());
bool notFound = true;
for (auto a : arr.getValues()) {
auto source = find_value(a.get_obj(), "source");
if (source.get_str() == "legacy_random") {
auto t_obj = find_value(a.get_obj(), "transparent");
auto addr = find_value(t_obj, "address").get_str();
notFound &= keyIO.DecodeDestination(addr) != demoAddress;
auto addrs = find_value(t_obj, "addresses").get_array();
BOOST_CHECK_EQUAL(2, addrs.size());
for (auto addr : addrs.getValues()) {
notFound &= keyIO.DecodeDestination(addr.get_str()) != demoAddress;
}
}
}
BOOST_CHECK(!notFound);
}
/*********************************
* fundrawtransaction
@ -670,17 +678,35 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
UniValue arr = retValue.get_array();
BOOST_CHECK(arr.size() == (2 * n1));
// Verify that the keys imported are also available from listaddresses
{
BOOST_CHECK_NO_THROW(retValue = CallRPC("listaddresses"));
auto listarr = retValue.get_array();
bool sproutCountMatch = false;
bool saplingCountMatch = false;
for (auto a : listarr.getValues()) {
auto source = find_value(a.get_obj(), "source");
if (source.get_str() == "legacy_random") {
auto sprout_obj = find_value(a.get_obj(), "sprout").get_obj();
auto sprout_addrs = find_value(sprout_obj, "addresses").get_array();
sproutCountMatch = (sprout_addrs.size() == n1);
}
if (source.get_str() == "imported") {
auto sapling_obj = find_value(a.get_obj(), "sapling").get_array()[0];
auto sapling_addrs = find_value(sapling_obj, "addresses").get_array();
saplingCountMatch = (sapling_addrs.size() == n1);
}
}
BOOST_CHECK(sproutCountMatch);
BOOST_CHECK(saplingCountMatch);
}
// Put addresses into a set
std::unordered_set<std::string> myaddrs;
for (UniValue element : arr.getValues()) {
myaddrs.insert(element.get_str());
}
// Verify that the keys imported are also available from listaddresses
BOOST_CHECK_NO_THROW(retValue = CallRPC("listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == (2 * n1));
// Make new addresses for the set
for (int i=0; i<n2; i++) {
myaddrs.insert(keyIO.EncodePaymentAddress(pwalletMain->GenerateNewSproutZKey()));
@ -704,10 +730,21 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
listaddrs.insert(element.get_str());
}
// Verify that the keys imported are also available from listaddresses
// Verify that the newly added sprout keys imported are also available from listaddresses
{
BOOST_CHECK_NO_THROW(retValue = CallRPC("listaddresses"));
arr = retValue.get_array();
BOOST_CHECK(arr.size() == numAddrs);
auto listarr = retValue.get_array();
bool sproutCountMatch = false;
for (auto a : listarr.getValues()) {
auto source = find_value(a.get_obj(), "source");
if (source.get_str() == "legacy_random") {
auto sprout_obj = find_value(a.get_obj(), "sprout").get_obj();
auto sprout_addrs = find_value(sprout_obj, "addresses").get_array();
sproutCountMatch = (sprout_addrs.size() == n1 + n2);
}
}
BOOST_CHECK(sproutCountMatch);
}
// Verify the two sets of addresses are the same
BOOST_CHECK(listaddrs.size() == numAddrs);

View File

@ -5211,6 +5211,50 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding&
return false;
}
///
PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const
{
return Random;
}
PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
// If we have a SaplingExtendedSpendingKey in the wallet, then we will
// also have the corresponding SaplingExtendedFullViewingKey.
if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk)) {
if (m_wallet->HaveSaplingFullViewingKey(ivk)) {
// If we have the HD keypath, it's related to the legacy seed
if (m_wallet->mapSaplingZKeyMetadata.count(ivk) > 0 &&
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath != "") {
return LegacyHDSeed;
} else {
return Imported;
}
} else {
return Imported;
}
} else {
return AddressNotFound;
}
}
PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::UnifiedAddress &uaddr) const
{
// TODO
return AddressNotFound;
}
PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::InvalidEncoding& no) const
{
return AddressNotFound;
}
///
std::optional<libzcash::ViewingKey> GetViewingKeyForPaymentAddress::operator()(
const libzcash::SproutPaymentAddress &zaddr) const
{
@ -5385,7 +5429,7 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
// 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime);
}
if (hdKeypath) {
if (hdKeypath.has_value()) {
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.value();
}
if (seedFpStr) {

View File

@ -1411,6 +1411,27 @@ public:
std::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
};
enum PaymentAddressSource {
Random,
LegacyHDSeed,
MnemonicHDSeed,
Imported,
AddressNotFound,
};
class GetSourceForPaymentAddress
{
private:
CWallet *m_wallet;
public:
GetSourceForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
PaymentAddressSource operator()(const libzcash::SproutPaymentAddress &zaddr) const;
PaymentAddressSource operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
PaymentAddressSource operator()(const libzcash::UnifiedAddress &uaddr) const;
PaymentAddressSource operator()(const libzcash::InvalidEncoding& no) const;
};
enum KeyAddResult {
SpendingKeyExists,
KeyAlreadyExists,