Merge pull request #5586 from str4d/feature/wallet_orchard-orchard_receivers

Add Orchard to default UA receiver types
This commit is contained in:
str4d 2022-02-24 15:52:31 +00:00 committed by GitHub
commit 48bf0241e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 186 additions and 13 deletions

View File

@ -55,7 +55,7 @@ class WalletAccountsTest(BitcoinTestFramework):
# Generate the first address for account 0.
addr0 = self.nodes[0].z_getaddressforaccount(0)
assert_equal(addr0['account'], 0)
assert_equal(set(addr0['pools']), set(['transparent', 'sapling']))
assert_equal(set(addr0['pools']), set(['transparent', 'sapling', 'orchard']))
ua0 = addr0['unifiedaddress']
# We pick mnemonic phrases to ensure that we can always generate the default
@ -70,16 +70,38 @@ class WalletAccountsTest(BitcoinTestFramework):
'no address at diversifier index 0',
self.nodes[0].z_getaddressforaccount, 0, [], 0)
# The second address for account 0 is different to the first address.
addr0_2 = self.nodes[0].z_getaddressforaccount(0)
assert_equal(addr0_2['account'], 0)
assert_equal(set(addr0_2['pools']), set(['transparent', 'sapling', 'orchard']))
ua0_2 = addr0_2['unifiedaddress']
assert(ua0 != ua0_2)
# We can generate a fully-shielded address.
addr0_3 = self.nodes[0].z_getaddressforaccount(0, ['sapling', 'orchard'])
assert_equal(addr0_3['account'], 0)
assert_equal(set(addr0_3['pools']), set(['sapling', 'orchard']))
ua0_3 = addr0_3['unifiedaddress']
# We can generate an address without a Sapling receiver.
addr0_4 = self.nodes[0].z_getaddressforaccount(0, ['transparent', 'orchard'])
assert_equal(addr0_4['account'], 0)
assert_equal(set(addr0_4['pools']), set(['transparent', 'orchard']))
ua0_4 = addr0_4['unifiedaddress']
# The first address for account 1 is different to account 0.
addr1 = self.nodes[0].z_getaddressforaccount(1)
assert_equal(addr1['account'], 1)
assert_equal(set(addr1['pools']), set(['transparent', 'sapling']))
assert_equal(set(addr1['pools']), set(['transparent', 'sapling', 'orchard']))
ua1 = addr1['unifiedaddress']
assert(ua0 != ua1)
# The UA contains the expected receiver kinds.
self.check_receiver_types(ua0, ['transparent', 'sapling'])
self.check_receiver_types(ua1, ['transparent', 'sapling'])
self.check_receiver_types(ua0, ['transparent', 'sapling', 'orchard'])
self.check_receiver_types(ua0_2, ['transparent', 'sapling', 'orchard'])
self.check_receiver_types(ua0_3, [ 'sapling', 'orchard'])
self.check_receiver_types(ua0_4, ['transparent', 'orchard'])
self.check_receiver_types(ua1, ['transparent', 'sapling', 'orchard'])
# The balances of the accounts are all zero.
self.check_balance(0, 0, ua0, {})

View File

@ -381,9 +381,10 @@ class ListReceivedTest (BitcoinTestFramework):
r = node.z_getaddressforaccount(account)
unified_addr = r['unifiedaddress']
receivers = node.z_listunifiedreceivers(unified_addr)
assert_equal(len(receivers), 2)
assert_equal(len(receivers), 3)
assert 'transparent' in receivers
assert 'sapling' in receivers
assert 'orchard' in receivers
# Wallet contains no notes
r = node.z_listreceivedbyaddress(unified_addr, 0)
assert_equal(len(r), 0, "unified_addr should have received zero notes")

View File

@ -578,6 +578,35 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
EXPECT_FALSE(ufvkmeta.value().second.has_value());
}
TEST(KeystoreTests, StoreAndRetrieveUFVKByOrchard) {
SelectParams(CBaseChainParams::TESTNET);
CBasicKeyStore keyStore;
auto seed = MnemonicSeed::Random(SLIP44_TESTNET_TYPE);
auto usk = ZcashdUnifiedSpendingKey::ForAccount(seed, SLIP44_TESTNET_TYPE, 0);
EXPECT_TRUE(usk.has_value());
auto ufvk = usk.value().ToFullViewingKey();
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
auto ufvkid = zufvk.GetKeyID();
EXPECT_FALSE(keyStore.GetUnifiedFullViewingKey(ufvkid).has_value());
EXPECT_TRUE(keyStore.AddUnifiedFullViewingKey(zufvk));
EXPECT_EQ(keyStore.GetUnifiedFullViewingKey(ufvkid).value(), zufvk);
auto addrPair = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(zufvk.FindAddress(diversifier_index_t(0), {ReceiverType::Orchard}));
EXPECT_TRUE(addrPair.first.GetOrchardReceiver().has_value());
auto orchardReceiver = addrPair.first.GetOrchardReceiver().value();
// We don't store Orchard addresses in CBasicKeyStore (the addr -> ivk
// mapping is stored in the Rust wallet), but we still detect this because
// we trial-decrypt diversifiers (which also means we learn the index).
auto ufvkmetaUnadded = keyStore.GetUFVKMetadataForReceiver(orchardReceiver);
EXPECT_TRUE(ufvkmetaUnadded.has_value());
EXPECT_EQ(ufvkmetaUnadded.value().first, ufvkid);
EXPECT_EQ(ufvkmetaUnadded.value().second.value(), addrPair.second);
}
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
SelectParams(CBaseChainParams::TESTNET);
CBasicKeyStore keyStore;

View File

@ -73,8 +73,12 @@ impl KeyStore {
}
pub fn add_full_viewing_key(&mut self, fvk: FullViewingKey) {
let ivk = IncomingViewingKey::from(&fvk);
self.viewing_keys.insert(ivk, fvk);
// When we add a full viewing key, we need to add both the internal and external
// incoming viewing keys.
let external_ivk = IncomingViewingKey::from(&fvk);
let internal_ivk = IncomingViewingKey::from(&fvk.derive_internal());
self.viewing_keys.insert(external_ivk, fvk.clone());
self.viewing_keys.insert(internal_ivk, fvk);
}
pub fn add_spending_key(&mut self, sk: SpendingKey) {

View File

@ -44,6 +44,10 @@ class TestWallet : public CWallet {
public:
TestWallet(const CChainParams& params) : CWallet(params) { }
OrchardWallet& GetOrchardWallet() {
return orchardWallet;
}
bool EncryptKeys(CKeyingMaterial& vMasterKeyIn) {
return CCryptoKeyStore::EncryptKeys(vMasterKeyIn);
}
@ -2245,3 +2249,43 @@ TEST(WalletTests, GenerateUnifiedAddress) {
// Revert to default
RegtestDeactivateSapling();
}
TEST(WalletTests, GenerateUnifiedSpendingKeyAddsOrchardAddresses) {
(void) RegtestActivateSapling();
TestWallet wallet(Params());
wallet.GenerateNewSeed();
// Create an account.
auto ufvkpair = wallet.GenerateNewUnifiedSpendingKey();
auto ufvk = ufvkpair.first;
auto account = ufvkpair.second;
auto fvk = ufvk.GetOrchardKey();
EXPECT_TRUE(fvk.has_value());
// At this point the Orchard wallet should contain the change address, but
// no other addresses. We detect this by trying to look up their IVKs.
auto changeAddr = fvk->ToInternalIncomingViewingKey().Address(libzcash::diversifier_index_t{0});
auto internalIvk = wallet.GetOrchardWallet().GetIncomingViewingKeyForAddress(changeAddr);
EXPECT_TRUE(internalIvk.has_value());
EXPECT_EQ(internalIvk.value(), fvk->ToInternalIncomingViewingKey());
auto externalAddr = fvk->ToIncomingViewingKey().Address(libzcash::diversifier_index_t{0});
auto externalIvk = wallet.GetOrchardWallet().GetIncomingViewingKeyForAddress(externalAddr);
EXPECT_FALSE(externalIvk.has_value());
// Generate an address.
auto uaResult = wallet.GenerateUnifiedAddress(account, {ReceiverType::Orchard});
auto ua = std::get_if<std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t>>(&uaResult);
EXPECT_NE(ua, nullptr);
auto uaOrchardReceiver = ua->first.GetOrchardReceiver();
EXPECT_TRUE(uaOrchardReceiver.has_value());
EXPECT_EQ(uaOrchardReceiver.value(), externalAddr);
// Now we can look up the external IVK.
externalIvk = wallet.GetOrchardWallet().GetIncomingViewingKeyForAddress(externalAddr);
EXPECT_TRUE(externalIvk.has_value());
EXPECT_EQ(externalIvk.value(), fvk->ToIncomingViewingKey());
// Revert to default
RegtestDeactivateSapling();
}

View File

@ -3119,14 +3119,16 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
receivers.insert(ReceiverType::P2PKH);
} else if (p == "sapling") {
receivers.insert(ReceiverType::Sapling);
} else if (p == "orchard") {
receivers.insert(ReceiverType::Orchard);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "pool arguments must be \"transparent\", or \"sapling\"");
throw JSONRPCError(RPC_INVALID_PARAMETER, "pool arguments must be \"transparent\", \"sapling\", or \"orchard\"");
}
}
}
if (receivers.empty()) {
// Default is the best and second-best shielded pools, and the transparent pool.
receivers = {ReceiverType::P2PKH, ReceiverType::Sapling};
receivers = {ReceiverType::P2PKH, ReceiverType::Sapling, ReceiverType::Orchard};
}
std::optional<libzcash::diversifier_index_t> j = std::nullopt;
@ -3220,6 +3222,9 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
case ReceiverType::Sapling:
pools.push_back("sapling");
break;
case ReceiverType::Orchard:
pools.push_back("orchard");
break;
default:
// Unreachable
assert(false);
@ -3331,6 +3336,12 @@ UniValue z_listunifiedreceivers(const UniValue& params, bool fHelp)
UniValue result(UniValue::VOBJ);
for (const auto& receiver : ua) {
std::visit(match {
[&](const libzcash::OrchardRawAddress& addr) {
// Create a single-receiver UA that just contains this Orchard receiver.
UnifiedAddress singleReceiver;
singleReceiver.AddReceiver(addr);
result.pushKV("orchard", keyIO.EncodePaymentAddress(singleReceiver));
},
[&](const libzcash::SaplingPaymentAddress& addr) {
result.pushKV("sapling", keyIO.EncodePaymentAddress(addr));
},
@ -3762,6 +3773,7 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
CAmount transparentBalance = 0;
CAmount sproutBalance = 0;
CAmount saplingBalance = 0;
CAmount orchardBalance = 0;
for (const auto& t : spendableInputs.utxos) {
transparentBalance += t.Value();
}
@ -3771,6 +3783,9 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
for (const auto& t : spendableInputs.saplingNoteEntries) {
saplingBalance += t.note.value();
}
for (const auto& t : spendableInputs.orchardNoteMetadata) {
orchardBalance += t.GetNoteValue();
}
UniValue pools(UniValue::VOBJ);
auto renderBalance = [&](std::string poolName, CAmount balance) {
@ -3783,6 +3798,7 @@ UniValue z_getbalanceforviewingkey(const UniValue& params, bool fHelp)
renderBalance("transparent", transparentBalance);
renderBalance("sprout", sproutBalance);
renderBalance("sapling", saplingBalance);
renderBalance("orchard", orchardBalance);
UniValue result(UniValue::VOBJ);
result.pushKV("pools", pools);
@ -3863,12 +3879,16 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
CAmount transparentBalance = 0;
CAmount saplingBalance = 0;
CAmount orchardBalance = 0;
for (const auto& t : spendableInputs.utxos) {
transparentBalance += t.Value();
}
for (const auto& t : spendableInputs.saplingNoteEntries) {
saplingBalance += t.note.value();
}
for (const auto& t : spendableInputs.orchardNoteMetadata) {
orchardBalance += t.GetNoteValue();
}
UniValue pools(UniValue::VOBJ);
auto renderBalance = [&](std::string poolName, CAmount balance) {
@ -3880,6 +3900,7 @@ UniValue z_getbalanceforaccount(const UniValue& params, bool fHelp)
};
renderBalance("transparent", transparentBalance);
renderBalance("sapling", saplingBalance);
renderBalance("orchard", orchardBalance);
UniValue result(UniValue::VOBJ);
result.pushKV("pools", pools);

View File

@ -268,6 +268,25 @@ bool CWallet::AddOrchardFullViewingKey(const libzcash::OrchardFullViewingKey &fv
return true; // TODO ORCHARD: persist fvk
}
// Add Orchard payment address -> incoming viewing key map entry
bool CWallet::AddOrchardRawAddress(
const libzcash::OrchardIncomingViewingKey &ivk,
const libzcash::OrchardRawAddress &addr)
{
AssertLockHeld(cs_wallet); // orchardWallet
if (!orchardWallet.AddRawAddress(addr, ivk)) {
// We should never add an Orchard raw address for which we don't know
// the corresponding FVK.
return false;
};
if (!fFileBacked) {
return true;
}
return true; // TODO ORCHARD: ensure mapping will be recreated on wallet load
}
// Loads a payment address -> incoming viewing key map entry
// to the in-memory wallet's keystore.
bool CWallet::LoadOrchardRawAddress(
@ -577,7 +596,17 @@ std::optional<libzcash::ZcashdUnifiedSpendingKey>
};
// Add Orchard spending key to the wallet
orchardWallet.AddSpendingKey(usk.value().GetOrchardKey());
auto orchardSk = usk.value().GetOrchardKey();
orchardWallet.AddSpendingKey(orchardSk);
// Associate the Orchard default change address with its IVK. We do this
// here because there is only ever a single Orchard change receiver, and
// it is never exposed to the user. External Orchard receivers are added
// when the user calls z_getaddressforaccount.
auto orchardInternalFvk = orchardSk.ToFullViewingKey().ToInternalIncomingViewingKey();
if (!AddOrchardRawAddress(orchardInternalFvk, orchardInternalFvk.Address(0))) {
throw std::runtime_error("CWallet::GenerateUnifiedSpendingKeyForAccount(): Failed to add Orchard change address to the wallet.");
};
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(Params(), ufvk);
if (!CCryptoKeyStore::AddUnifiedFullViewingKey(zufvk)) {
@ -767,6 +796,17 @@ WalletUAGenerationResult CWallet::GenerateUnifiedAddress(
AddSaplingPaymentAddress(dfvk.value().ToIncomingViewingKey(), saplingAddress.value());
}
// If the address has an Orchard component, add an association between
// that address and the Orchard IVK corresponding to the ufvk
auto hasOrchard = receiverTypes.find(ReceiverType::Orchard) != receiverTypes.end();
if (hasOrchard) {
auto fvk = ufvk.value().GetOrchardKey();
auto orchardReceiver = address.first.GetOrchardReceiver();
assert (fvk.has_value() && orchardReceiver.has_value());
AddOrchardRawAddress(fvk.value().ToIncomingViewingKey(), orchardReceiver.value());
}
// Save the metadata for the generated address so that we can re-derive
// it in the future.
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, address.second, receiverTypes);

View File

@ -794,6 +794,7 @@ public:
std::vector<COutput> utxos;
std::vector<SproutNoteEntry> sproutNoteEntries;
std::vector<SaplingNoteEntry> saplingNoteEntries;
std::vector<OrchardNoteMetadata> orchardNoteMetadata;
/**
* Selectively discard notes that are not required to obtain the desired
@ -816,6 +817,9 @@ public:
for (const auto& t : saplingNoteEntries) {
result += t.note.value();
}
for (const auto& t : orchardNoteMetadata) {
result += t.GetNoteValue();
}
return result;
}
@ -1463,7 +1467,15 @@ public:
bool AddOrchardZKey(const libzcash::OrchardSpendingKey &sk);
bool AddOrchardFullViewingKey(const libzcash::OrchardFullViewingKey &fvk);
/**
* Adds an address/ivk mapping to the in-memory wallet. Returns `true`
* Adds an address/ivk mapping to the in-memory wallet. Returns `false` if
* the mapping could not be persisted, or the IVK does not correspond to an
* FVK known by the wallet.
*/
bool AddOrchardRawAddress(
const libzcash::OrchardIncomingViewingKey &ivk,
const libzcash::OrchardRawAddress &addr);
/**
* Loads an address/ivk mapping to the in-memory wallet. Returns `true`
* if the provided IVK corresponds to an FVK known by the wallet.
*/
bool LoadOrchardRawAddress(

View File

@ -24,7 +24,7 @@ OrchardIncomingViewingKey OrchardFullViewingKey::ToIncomingViewingKey() const {
}
OrchardIncomingViewingKey OrchardFullViewingKey::ToInternalIncomingViewingKey() const {
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_incoming_viewing_key(inner.get()));
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_internal_incoming_viewing_key(inner.get()));
}
OrchardSpendingKey OrchardSpendingKey::ForAccount(

View File

@ -150,7 +150,7 @@ UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::FindAddress(
UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::FindAddress(
const diversifier_index_t& j) const {
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling});
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling, ReceiverType::Orchard});
}
std::optional<RecipientAddress> ZcashdUnifiedFullViewingKey::GetChangeAddress(const ChangeRequest& req) const {