Merge pull request #5700 from nuttycom/5186-updates-to-z_viewtransaction

Add Orchard and Unified Address support to z_viewtransaction
This commit is contained in:
Kris Nuttycombe 2022-03-18 17:19:31 -06:00 committed by GitHub
commit fabff30d88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 845 additions and 182 deletions

View File

@ -11,7 +11,7 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash/orchard.git"]
git = "https://github.com/zcash/orchard.git"
rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc"
rev = "f4587f790d7317df85a9ee77ce693a06ed6d8d02"
replace-with = "vendored-sources"
[source."https://github.com/zcash/incrementalmerkletree.git"]

2
Cargo.lock generated
View File

@ -1242,7 +1242,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.1.0-beta.1"
source = "git+https://github.com/zcash/orchard.git?rev=a5f701f3186fb619b40f2dee9939e64e4c9eb7cc#a5f701f3186fb619b40f2dee9939e64e4c9eb7cc"
source = "git+https://github.com/zcash/orchard.git?rev=f4587f790d7317df85a9ee77ce693a06ed6d8d02#f4587f790d7317df85a9ee77ce693a06ed6d8d02"
dependencies = [
"aes",
"arrayvec 0.7.2",

View File

@ -88,7 +88,7 @@ codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "62c33e4480a71170b02b9eb7d4b0160194f414ee" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "f4587f790d7317df85a9ee77ce693a06ed6d8d02" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }

View File

@ -173,11 +173,10 @@ class WalletAccountsTest(BitcoinTestFramework):
txid = wait_and_assert_operationid_status(self.nodes[0], opid)
# The wallet should detect the new note as belonging to the UA.
# TODO: Uncomment once z_viewtransaction shows Orchard details (#5186).
#tx_details = self.nodes[0].z_viewtransaction(txid)
#assert_equal(len(tx_details['outputs']), 1)
#assert_equal(tx_details['outputs'][0]['type'], 'orchard')
#assert_equal(tx_details['outputs'][0]['address'], ua0)
tx_details = self.nodes[0].z_viewtransaction(txid)
assert_equal(len(tx_details['outputs']), 1)
assert_equal(tx_details['outputs'][0]['type'], 'orchard')
assert_equal(tx_details['outputs'][0]['address'], ua0)
# The new balance should not be visible with the default minconf, but should be
# visible with minconf=0.

View File

@ -12,8 +12,10 @@ from test_framework.util import (
assert_raises_message,
connect_nodes_bi,
get_coinbase_address,
nuparams,
DEFAULT_FEE,
DEFAULT_FEE_ZATS
DEFAULT_FEE_ZATS,
NU5_BRANCH_ID,
)
from test_framework.util import wait_and_assert_operationid_status, start_nodes
from decimal import Decimal
@ -33,7 +35,12 @@ class ListReceivedTest (BitcoinTestFramework):
def setup_network(self):
self.nodes = start_nodes(
self.num_nodes, self.options.tmpdir,
extra_args=[['-experimentalfeatures', '-orchardwallet']] * self.num_nodes)
extra_args=[[
'-experimentalfeatures',
'-orchardwallet',
nuparams(NU5_BRANCH_ID, 225),
]] * self.num_nodes
)
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2)
connect_nodes_bi(self.nodes, 0, 2)
@ -422,7 +429,7 @@ class ListReceivedTest (BitcoinTestFramework):
assert_equal(r[0]['blockheight'], height+5)
assert_equal(r[0]['blockindex'], 1)
assert 'blocktime' in r[0]
assert_equal(r[1]['pool'], 'transparent')
assert_equal(r[1]['txid'], txid_taddr)
assert_equal(r[1]['amount'], Decimal('0.2'))
@ -435,9 +442,117 @@ class ListReceivedTest (BitcoinTestFramework):
assert_equal(r[1]['blockindex'], -1) # not yet mined
assert 'blocktime' in r[1]
def test_received_orchard(self, height):
self.generate_and_sync(height+1)
taddr = self.nodes[1].getnewaddress()
acct1 = self.nodes[1].z_getnewaccount()['account']
acct2 = self.nodes[1].z_getnewaccount()['account']
addrResO = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
assert_equal(addrResO['pools'], ['orchard'])
uao = addrResO['unifiedaddress']
addrResSO = self.nodes[1].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
assert_equal(addrResSO['pools'], ['sapling', 'orchard'])
uaso = addrResSO['unifiedaddress']
self.nodes[0].sendtoaddress(taddr, 4.0)
self.generate_and_sync(height+2)
acct_node0 = self.nodes[0].z_getnewaccount()['account']
ua_node0 = self.nodes[0].z_getaddressforaccount(acct_node0, ['sapling', 'orchard'])['unifiedaddress']
opid = self.nodes[1].z_sendmany(taddr, [
{'address': uao, 'amount': 1, 'memo': my_memo},
{'address': uaso, 'amount': 2},
])
txid0 = wait_and_assert_operationid_status(self.nodes[1], opid)
self.sync_all()
# Decrypted transaction details should be correct, even though
# the transaction is still just in the mempool
pt = self.nodes[1].z_viewtransaction(txid0)
assert_equal(pt['txid'], txid0)
assert_equal(len(pt['spends']), 0)
assert_equal(len(pt['outputs']), 2)
# Outputs are not returned in a defined order but the amounts are deterministic
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
assert_equal(outputs[0]['type'], 'orchard')
assert_equal(outputs[0]['address'], uao)
assert_equal(outputs[0]['value'], Decimal('1'))
assert_equal(outputs[0]['valueZat'], 100000000)
assert_equal(outputs[0]['action'], 0)
assert_equal(outputs[0]['outgoing'], False)
assert_equal(outputs[0]['memo'], my_memo)
assert_equal(outputs[0]['memoStr'], my_memo_str)
assert_equal(outputs[1]['type'], 'orchard')
assert_equal(outputs[1]['address'], uaso)
assert_equal(outputs[1]['value'], Decimal('2'))
assert_equal(outputs[1]['valueZat'], 200000000)
assert_equal(outputs[1]['action'], 1)
assert_equal(outputs[1]['outgoing'], False)
assert_equal(outputs[1]['memo'], no_memo)
assert 'memoStr' not in outputs[1]
self.generate_and_sync(height+3)
opid = self.nodes[1].z_sendmany(uao, [
{'address': uaso, 'amount': Decimal('0.3')},
{'address': ua_node0, 'amount': Decimal('0.2')}
])
txid1 = wait_and_assert_operationid_status(self.nodes[1], opid)
self.sync_all()
pt = self.nodes[1].z_viewtransaction(txid1)
assert_equal(pt['txid'], txid1)
assert_equal(len(pt['spends']), 1) # one spend we can see
assert_equal(len(pt['outputs']), 3) # one output + one change output we can see
spends = pt['spends']
assert_equal(spends[0]['type'], 'orchard')
assert_equal(spends[0]['action'], 0)
assert_equal(spends[0]['txidPrev'], txid0)
assert_equal(spends[0]['actionPrev'], 0)
assert_equal(spends[0]['address'], uao)
assert_equal(spends[0]['value'], Decimal('1.0'))
assert_equal(spends[0]['valueZat'], 100000000)
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
assert_equal(outputs[0]['type'], 'orchard')
assert_equal(outputs[0]['address'], ua_node0)
assert_equal(outputs[0]['value'], Decimal('0.2'))
assert_equal(outputs[0]['valueZat'], 20000000)
assert_equal(outputs[0]['outgoing'], True)
assert_equal(outputs[0]['walletInternal'], False)
assert_equal(outputs[0]['memo'], no_memo)
assert_equal(outputs[1]['type'], 'orchard')
assert_equal(outputs[1]['address'], uaso)
assert_equal(outputs[1]['value'], Decimal('0.3'))
assert_equal(outputs[1]['valueZat'], 30000000)
assert_equal(outputs[1]['outgoing'], False)
assert_equal(outputs[1]['walletInternal'], False)
assert_equal(outputs[1]['memo'], no_memo)
# Verify that we observe the change output
print("###", uao, "###")
assert_equal(outputs[2]['type'], 'orchard')
assert_equal(outputs[2]['value'], Decimal('0.49999'))
assert_equal(outputs[2]['valueZat'], 49999000)
assert_equal(outputs[2]['outgoing'], False)
assert_equal(outputs[2]['walletInternal'], True)
assert_equal(outputs[2]['memo'], no_memo)
# The change address should have been erased
assert_true('address' not in outputs[2])
def run_test(self):
self.test_received_sprout(200)
self.test_received_sapling(214)
self.test_received_orchard(230)
if __name__ == '__main__':

View File

@ -69,40 +69,8 @@ TEST(Keys, EncodeAndDecodeSapling)
#define MAKE_STRING(x) std::string((x), (x) + sizeof(x))
namespace libzcash {
class ReceiverToString {
public:
ReceiverToString() {}
std::string operator()(const OrchardRawAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
return tfm::format("Orchard(%s)", HexStr(ss.begin(), ss.end()));
}
std::string operator()(const SaplingPaymentAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
return tfm::format("Sapling(%s)", HexStr(ss.begin(), ss.end()));
}
std::string operator()(const CScriptID &p2sh) const {
return tfm::format("P2SH(%s)", p2sh.GetHex());
}
std::string operator()(const CKeyID &p2pkh) const {
return tfm::format("P2PKH(%s)", p2pkh.GetHex());
}
std::string operator()(const UnknownReceiver &unknown) const {
return tfm::format(
"Unknown(%x, %s)",
unknown.typecode,
HexStr(unknown.data.begin(), unknown.data.end()));
}
};
void PrintTo(const Receiver& receiver, std::ostream* os) {
*os << std::visit(ReceiverToString(), receiver);
*os << DebugPrintReceiver(receiver);
}
void PrintTo(const UnifiedAddress& ua, std::ostream* os) {
*os << "UnifiedAddress(" << testing::PrintToString(ua.GetReceiversAsParsed()) << ")";

View File

@ -564,8 +564,8 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
// 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);
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().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.
@ -574,8 +574,8 @@ TEST(KeystoreTests, StoreAndRetrieveUFVK) {
auto ufvkmeta = keyStore.GetUFVKMetadataForReceiver(saplingReceiver);
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
EXPECT_FALSE(ufvkmeta.value().second.has_value());
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
EXPECT_FALSE(ufvkmeta.value().GetDiversifierIndex().has_value());
}
TEST(KeystoreTests, StoreAndRetrieveUFVKByOrchard) {
@ -603,8 +603,8 @@ TEST(KeystoreTests, StoreAndRetrieveUFVKByOrchard) {
// 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);
EXPECT_EQ(ufvkmetaUnadded.value().GetUFVKId(), ufvkid);
EXPECT_EQ(ufvkmetaUnadded.value().GetDiversifierIndex().value(), addrPair.second);
}
TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
@ -627,7 +627,7 @@ TEST(KeystoreTests, AddTransparentReceiverForUnifiedAddress) {
ufvkmeta = keyStore.GetUFVKMetadataForReceiver(addrPair.first.GetP2PKHReceiver().value());
EXPECT_TRUE(ufvkmeta.has_value());
EXPECT_EQ(ufvkmeta.value().first, ufvkid);
EXPECT_EQ(ufvkmeta.value().GetUFVKId(), ufvkid);
}

View File

@ -383,13 +383,13 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> CBasicKeyStore::GetUnifiedF
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
std::optional<AddressUFVKMetadata>
CBasicKeyStore::GetUFVKMetadataForReceiver(const libzcash::Receiver& receiver) const
{
return std::visit(FindUFVKId(*this), receiver);
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
std::optional<AddressUFVKMetadata>
CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr) const
{
std::optional<libzcash::UFVKId> ufvkId;
@ -398,33 +398,36 @@ CBasicKeyStore::GetUFVKMetadataForAddress(const libzcash::UnifiedAddress& addr)
for (const auto& receiver : addr) {
// skip unknown receivers
if (libzcash::HasKnownReceiverType(receiver)) {
auto tmp = GetUFVKMetadataForReceiver(receiver);
if (ufvkId.has_value() && tmp.has_value()) {
auto rmeta = GetUFVKMetadataForReceiver(receiver);
// We should never generate unified addresses with internal receivers
assert(!(rmeta.has_value() && rmeta.value().IsInternalAddress()));
if (ufvkId.has_value() && rmeta.has_value()) {
// If the unified address contains receivers that are associated with
// different UFVKs, we cannot return a singular value.
if (tmp.value().first != ufvkId.value()) {
if (rmeta.value().GetUFVKId() != ufvkId.value()) {
return std::nullopt;
}
if (tmp.value().second.has_value()) {
if (rmeta.value().GetDiversifierIndex().has_value()) {
if (j.has_value()) {
if (tmp.value().second.value() != j.value()) {
if (rmeta.value().GetDiversifierIndex().value() != j.value()) {
jConflict = true;
j = std::nullopt;
}
} else if (!jConflict) {
j = tmp.value().second.value();
j = rmeta.value().GetDiversifierIndex().value();
}
}
} else if (tmp.has_value()) {
ufvkId = tmp.value().first;
j = tmp.value().second;
} else if (rmeta.has_value()) {
ufvkId = rmeta.value().GetUFVKId();
j = rmeta.value().GetDiversifierIndex();
}
}
}
if (ufvkId.has_value()) {
return std::make_pair(ufvkId.value(), j);
return AddressUFVKMetadata(ufvkId.value(), j, false);
} else {
return std::nullopt;
}
@ -465,22 +468,27 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
return result;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
//
// FindUFVKId :: (KeyStore, Receiver) -> std::optional<AddressUFVKMetadata>
//
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
auto fvk = v.GetOrchardKey();
if (fvk.has_value()) {
auto d_idx = fvk.value().ToIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (d_idx.has_value()) {
return std::make_pair(k, d_idx);
return AddressUFVKMetadata(k, d_idx, false);
}
auto internal_d_idx = fvk.value().ToInternalIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (internal_d_idx.has_value()) {
return AddressUFVKMetadata(k, internal_d_idx, true);
}
}
}
return std::nullopt;
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
std::optional<AddressUFVKMetadata> 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
@ -488,7 +496,7 @@ FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const
// 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);
return AddressUFVKMetadata(ufvkId->second, std::nullopt, false);
} else {
// If we have the addr -> ivk map entry but not the ivk -> UFVK map entry,
// then this is definitely a legacy Sapling address.
@ -506,7 +514,13 @@ FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const
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);
return AddressUFVKMetadata(k, d_idx, false);
}
auto internal_d_idx = dfvk.value().DecryptInternalDiversifier(saplingAddr.d);
auto derived_internal_addr = dfvk.value().InternalAddress(internal_d_idx);
if (derived_internal_addr.has_value() && derived_internal_addr.value() == saplingAddr) {
return AddressUFVKMetadata(k, internal_d_idx, true);
}
}
}
@ -514,25 +528,26 @@ FindUFVKId::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const
// 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 {
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CScriptID& scriptId) const {
const auto metadata = keystore.mapP2SHUnified.find(scriptId);
if (metadata != keystore.mapP2SHUnified.end()) {
return metadata->second;
// At present we never generate transparent internal addresses, so this
// must be an external address
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, false);
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const CKeyID& keyId) const {
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const CKeyID& keyId) const {
const auto metadata = keystore.mapP2PKHUnified.find(keyId);
if (metadata != keystore.mapP2PKHUnified.end()) {
return metadata->second;
// At present we never generate transparent internal addresses, so this
// must be an external address
return AddressUFVKMetadata(metadata->second.first, metadata->second.second, false);
} else {
return std::nullopt;
}
}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
std::optional<AddressUFVKMetadata> FindUFVKId::operator()(const libzcash::UnknownReceiver& receiver) const {
return std::nullopt;
}

View File

@ -18,6 +18,20 @@
#include <boost/signals2/signal.hpp>
class AddressUFVKMetadata {
private:
libzcash::UFVKId ufvkId;
std::optional<libzcash::diversifier_index_t> j;
bool internalAddress;
public:
AddressUFVKMetadata(libzcash::UFVKId ufvkId, std::optional<libzcash::diversifier_index_t> j, bool internalAddress)
: ufvkId(ufvkId), j(j), internalAddress(internalAddress) {}
libzcash::UFVKId GetUFVKId() const { return ufvkId; }
std::optional<libzcash::diversifier_index_t> GetDiversifierIndex() const { return j; }
bool IsInternalAddress() const { return internalAddress; }
};
/** A virtual base class for key stores */
class CKeyStore
{
@ -127,8 +141,7 @@ public:
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId) const = 0;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver) const = 0;
/**
@ -136,8 +149,7 @@ public:
* UFVK, return that key's metadata. If all the receivers correspond to
* the same diversifier index, that diversifier index is also returned.
*/
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForAddress(
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
const libzcash::UnifiedAddress& addr) const = 0;
virtual std::optional<libzcash::UFVKId> GetUFVKIdForViewingKey(
@ -379,29 +391,27 @@ public:
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId) const;
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForReceiver(
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForReceiver(
const libzcash::Receiver& receiver) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForReceiver(
const libzcash::Receiver& receiver) const {
auto ufvkMeta = GetUFVKMetadataForReceiver(receiver);
if (ufvkMeta.has_value()) {
return GetUnifiedFullViewingKey(ufvkMeta.value().first);
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
} else {
return std::nullopt;
}
}
virtual std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
GetUFVKMetadataForAddress(
virtual std::optional<AddressUFVKMetadata> GetUFVKMetadataForAddress(
const libzcash::UnifiedAddress& addr) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUFVKForAddress(
const libzcash::UnifiedAddress& addr) const {
auto ufvkMeta = GetUFVKMetadataForAddress(addr);
if (ufvkMeta.has_value()) {
return GetUnifiedFullViewingKey(ufvkMeta.value().first);
return GetUnifiedFullViewingKey(ufvkMeta.value().GetUFVKId());
} else {
return std::nullopt;
}
@ -425,16 +435,11 @@ private:
public:
FindUFVKId(const CBasicKeyStore& keystore): keystore(keystore) {}
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const CScriptID& scriptId) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const CKeyID& keyId) const;
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
operator()(const libzcash::UnknownReceiver& receiver) const;
std::optional<AddressUFVKMetadata> operator()(const libzcash::OrchardRawAddress& orchardAddr) const;
std::optional<AddressUFVKMetadata> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<AddressUFVKMetadata> operator()(const CScriptID& scriptId) const;
std::optional<AddressUFVKMetadata> operator()(const CKeyID& keyId) const;
std::optional<AddressUFVKMetadata> operator()(const libzcash::UnknownReceiver& receiver) const;
};
#endif // BITCOIN_KEYSTORE_H

View File

@ -81,6 +81,11 @@ std::string SaplingOutPoint::ToString() const
return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
}
std::string OrchardOutPoint::ToString() const
{
return strprintf("OrchardOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
}
CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn)
{
prevout = prevoutIn;

View File

@ -107,8 +107,8 @@ typedef void (*push_spend_action_idx_callback_t)(void* rec, uint32_t actionIdx);
* the provided `callbackReceiver` referent using the `push_cb` callback. Note that
* this callback can perform transformations on the provided RawOrchardActionIVK in this
* process. For each action spending one of the wallet's notes, this method will pass
* a `uint32_t` action index corresponding to that action to the `callbackReceiver` referent;
* using the specified callback; usually, this will push the value into a result vector owned
* a `uint32_t` action index corresponding to that action to the `callbackReceiver` referent;
* using the specified callback; usually, this will push the value into a result vector owned
* by the caller.
*
* The provided bundle must be a component of the transaction from which `txid` was
@ -263,6 +263,59 @@ void orchard_wallet_get_filtered_notes(
push_note_callback_t push_cb
);
/**
* A C struct used to transfer Orchard action spend information across the FFI boundary.
* This must have the same in-memory representation as the `FFIActionSpend` type in
* orchard_ffi/wallet.rs.
*/
struct RawOrchardActionSpend {
uint32_t spendActionIdx;
unsigned char outpointTxId[32];
uint32_t outpointActionIdx;
OrchardRawAddressPtr* receivedAt;
CAmount noteValue;
};
/**
* A C struct used to transfer Orchard action output information across the FFI boundary.
* This must have the same in-memory representation as the `FFIActionOutput` type in
* orchard_ffi/wallet.rs.
*/
struct RawOrchardActionOutput {
uint32_t outputActionIdx;
OrchardRawAddressPtr* addr;
CAmount noteValue;
unsigned char memo[512];
bool isOutgoing;
};
typedef void (*push_spend_t)(void* callbackReceiver, const RawOrchardActionSpend data);
typedef void (*push_output_t)(void* callbackReceiver, const RawOrchardActionOutput data);
/**
* Trial-decrypts the specfied Orchard bundle, and uses the provided callbacks to pass
* `RawOrchardActionSpend` and `RawOrchardActionOutput` values (corresponding to the
* actions of that bundle) to the provided result receiver.
*
* Note that the callbacks can perform any necessary conversion from a
* `RawOrchardActionSpend` or `RawOrchardActionOutput` value in addition to modifying the
* provided result receiver.
*
* `raw_ovks` must be a pointer to an array of `unsigned char[32]`.
*
* The `addr` pointer on each `RawOrchardActionOutput` value must be freed using
* `orchard_address_free`.
*/
bool orchard_wallet_get_txdata(
const OrchardWalletPtr* wallet,
const OrchardBundlePtr* bundle,
const unsigned char* raw_ovks,
size_t raw_ovks_len,
void* callbackReceiver,
push_spend_t push_spend_cb,
push_output_t push_output_cb
);
typedef void (*push_txid_callback_t)(void* resultVector, unsigned char txid[32]);

View File

@ -16,7 +16,7 @@ use zcash_primitives::{
use orchard::{
bundle::Authorized,
keys::{FullViewingKey, IncomingViewingKey, SpendingKey},
keys::{FullViewingKey, IncomingViewingKey, OutgoingViewingKey, SpendingKey},
note::Nullifier,
tree::{MerkleHashOrchard, MerklePath},
Address, Bundle, Note,
@ -359,7 +359,7 @@ impl Wallet {
.cloned()
.collect::<Vec<_>>();
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_for_keys(&keys) {
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_with_keys(&keys) {
assert!(self.add_decrypted_note(
None,
txid,
@ -991,6 +991,114 @@ pub extern "C" fn orchard_wallet_get_filtered_notes(
}
}
/// A type used to pass decrypted spend information across the FFI boundary.
/// This must have the same representation as `struct RawOrchardSpendData`
/// in `rust/include/rust/orchard/wallet.h`.
#[repr(C)]
pub struct FFIActionSpend {
spend_action_idx: u32,
outpoint_txid: [u8; 32],
outpoint_action_idx: u32,
received_at: *mut Address,
value: i64,
}
/// A type used to pass decrypted output information across the FFI boundary.
/// This must have the same representation as `struct RawOrchardOutputData`
/// in `rust/include/rust/orchard/wallet.h`.
#[repr(C)]
pub struct FFIActionOutput {
action_idx: u32,
recipient: *mut Address,
value: i64,
memo: [u8; 512],
is_outgoing: bool,
}
/// A C++-allocated function pointer that can send a FFIActionSpend value
/// to a receiver.
pub type SpendPushCB = unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, data: FFIActionSpend);
/// A C++-allocated function pointer that can send a FFIActionOutput value
/// to a receiver.
pub type OutputPushCB =
unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, data: FFIActionOutput);
#[no_mangle]
pub extern "C" fn orchard_wallet_get_txdata(
wallet: *const Wallet,
bundle: *const Bundle<Authorized, Amount>,
raw_ovks: *const [u8; 32],
raw_ovks_len: usize,
callback_receiver: Option<FFICallbackReceiver>,
spend_push_cb: Option<SpendPushCB>,
output_push_cb: Option<OutputPushCB>,
) -> bool {
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
let raw_ovks = unsafe { slice::from_raw_parts(raw_ovks, raw_ovks_len) };
let ovks: Vec<OutgoingViewingKey> = raw_ovks
.iter()
.map(|k| OutgoingViewingKey::from(*k))
.collect();
if let Some(bundle) = unsafe { bundle.as_ref() } {
let ivks = wallet
.key_store
.viewing_keys
.keys()
.cloned()
.collect::<Vec<_>>();
let incoming: BTreeMap<usize, (Note, Address, [u8; 512])> = bundle
.decrypt_outputs_with_keys(&ivks)
.into_iter()
.map(|(idx, _, note, addr, memo)| (idx, (note, addr, memo)))
.collect();
let outgoing: BTreeMap<usize, (Note, Address, [u8; 512])> = bundle
.recover_outputs_with_ovks(&ovks)
.into_iter()
.map(|(idx, _, note, addr, memo)| (idx, (note, addr, memo)))
.collect();
for (idx, action) in bundle.actions().iter().enumerate() {
let nf = action.nullifier();
if let Some(spend) = wallet.nullifiers.get(nf).and_then(|outpoint| {
wallet
.wallet_received_notes
.get(&outpoint.txid)
.and_then(|txnotes| txnotes.decrypted_notes.get(&outpoint.action_idx))
.map(|dnote| FFIActionSpend {
spend_action_idx: idx as u32,
outpoint_txid: *outpoint.txid.as_ref(),
outpoint_action_idx: outpoint.action_idx as u32,
received_at: Box::into_raw(Box::new(dnote.note.recipient())),
value: dnote.note.value().inner() as i64,
})
}) {
unsafe { (spend_push_cb.unwrap())(callback_receiver, spend) };
}
if let Some(((note, addr, memo), is_outgoing)) = incoming
.get(&idx)
.map(|n| (n, false))
.or_else(|| outgoing.get(&idx).map(|n| (n, true)))
{
let output = FFIActionOutput {
action_idx: idx as u32,
recipient: Box::into_raw(Box::new(*addr)),
value: note.value().inner() as i64,
memo: *memo,
is_outgoing,
};
unsafe { (output_push_cb.unwrap())(callback_receiver, output) };
}
}
true
} else {
false
}
}
pub type PushTxId = unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, txid: *const [u8; 32]);
#[no_mangle]

View File

@ -110,6 +110,79 @@ public:
}
};
class OrchardActionSpend {
private:
OrchardOutPoint outPoint;
libzcash::OrchardRawAddress receivedAt;
CAmount noteValue;
public:
OrchardActionSpend(OrchardOutPoint outPoint, libzcash::OrchardRawAddress receivedAt, CAmount noteValue):
outPoint(outPoint), receivedAt(receivedAt), noteValue(noteValue) { }
OrchardOutPoint GetOutPoint() const {
return outPoint;
}
const libzcash::OrchardRawAddress& GetReceivedAt() const {
return receivedAt;
}
CAmount GetNoteValue() const {
return noteValue;
}
};
class OrchardActionOutput {
private:
libzcash::OrchardRawAddress recipient;
CAmount noteValue;
std::array<unsigned char, 512> memo;
bool isOutgoing;
public:
OrchardActionOutput(
libzcash::OrchardRawAddress recipient, CAmount noteValue, std::array<unsigned char, 512> memo, bool isOutgoing):
recipient(recipient), noteValue(noteValue), memo(memo), isOutgoing(isOutgoing) { }
const libzcash::OrchardRawAddress& GetRecipient() const {
return recipient;
}
CAmount GetNoteValue() const {
return noteValue;
}
const std::array<unsigned char, 512>& GetMemo() const {
return memo;
}
bool IsOutgoing() const {
return isOutgoing;
}
};
class OrchardActions {
private:
std::map<uint32_t, OrchardActionSpend> spends;
std::map<uint32_t, OrchardActionOutput> outputs;
public:
OrchardActions() {}
void AddSpend(uint32_t actionIdx, OrchardActionSpend spend) {
spends.insert({actionIdx, spend});
}
void AddOutput(uint32_t actionIdx, OrchardActionOutput output) {
outputs.insert({actionIdx, output});
}
const std::map<uint32_t, OrchardActionSpend>& GetSpends() {
return spends;
}
const std::map<uint32_t, OrchardActionOutput>& GetOutputs() {
return outputs;
}
};
class OrchardWallet
{
@ -355,6 +428,41 @@ public:
void GarbageCollect() {
orchard_wallet_gc_note_commitment_tree(inner.get());
}
static void PushSpendAction(void* receiver, RawOrchardActionSpend rawSpend) {
uint256 txid;
std::move(std::begin(rawSpend.outpointTxId), std::end(rawSpend.outpointTxId), txid.begin());
auto spend = OrchardActionSpend(
OrchardOutPoint(txid, rawSpend.outpointActionIdx),
libzcash::OrchardRawAddress(rawSpend.receivedAt),
rawSpend.noteValue);
reinterpret_cast<OrchardActions*>(receiver)->AddSpend(rawSpend.spendActionIdx, spend);
}
static void PushOutputAction(void* receiver, RawOrchardActionOutput rawOutput) {
std::array<unsigned char, 512> memo;
std::move(std::begin(rawOutput.memo), std::end(rawOutput.memo), memo.begin());
auto output = OrchardActionOutput(
libzcash::OrchardRawAddress(rawOutput.addr),
rawOutput.noteValue,
memo,
rawOutput.isOutgoing);
reinterpret_cast<OrchardActions*>(receiver)->AddOutput(rawOutput.outputActionIdx, output);
}
OrchardActions GetTxActions(const CTransaction& tx, const std::vector<uint256>& ovks) const {
OrchardActions result;
orchard_wallet_get_txdata(
inner.get(),
tx.GetOrchardBundle().inner.get(),
reinterpret_cast<const unsigned char*>(ovks.data()),
ovks.size(),
&result,
PushSpendAction,
PushOutputAction);
return result;
}
};
class OrchardWalletNoteCommitmentTreeWriter

View File

@ -59,6 +59,7 @@ using namespace libzcash;
const std::string ADDR_TYPE_SPROUT = "sprout";
const std::string ADDR_TYPE_SAPLING = "sapling";
const std::string ADDR_TYPE_ORCHARD = "orchard";
extern UniValue TxJoinSplitToJSON(const CTransaction& tx);
@ -3135,7 +3136,7 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
}
if (receivers.empty()) {
// Default is the best and second-best shielded pools, and the transparent pool.
receivers = {ReceiverType::P2PKH, ReceiverType::Sapling, ReceiverType::Orchard};
receivers = CWallet::DefaultReceiverTypes();
}
std::optional<libzcash::diversifier_index_t> j = std::nullopt;
@ -4036,7 +4037,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
" \"jsSpend\" : n, (numeric, sprout) the index of the spend within the JSDescription\n"
" \"spend\" : n, (numeric, sapling) the index of the spend within vShieldedSpend\n"
" \"actionspend\" : n, (numeric, orchard) the index of the action within orchard bundle\n"
" \"action\" : n, (numeric, orchard) the index of the action within orchard bundle\n"
" \"txidPrev\" : \"transactionid\", (string) The id for the transaction this note was created in\n"
" \"jsPrev\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
" \"jsOutputPrev\" : n, (numeric, sprout) the index of the output within the JSDescription\n"
@ -4054,9 +4055,10 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
" \"jsOutput\" : n, (numeric, sprout) the index of the output within the JSDescription\n"
" \"output\" : n, (numeric, sapling) the index of the output within the vShieldedOutput\n"
" \"actionoutput\" : n, (numeric, orchard) the index of the action within the orchard bundle\n"
" \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n"
" \"outgoing\" : true|false (boolean, sapling) True if the output is not for an address in the wallet\n"
" \"action\" : n, (numeric, orchard) the index of the action within the orchard bundle\n"
" \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction. Not included for change outputs.\n"
" \"outgoing\" : true|false (boolean) True if the output is not for an address in the wallet\n"
" \"walletInternal\" : true|false (boolean) True if this is a change output.\n"
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n"
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n"
" \"memo\" : \"hexmemo\", (string) hexadecimal string representation of the memo field\n"
@ -4073,15 +4075,15 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
uint256 hash;
hash.SetHex(params[0].get_str());
uint256 txid;
txid.SetHex(params[0].get_str());
UniValue entry(UniValue::VOBJ);
if (!pwalletMain->mapWallet.count(hash))
if (!pwalletMain->mapWallet.count(txid))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
const CWalletTx& wtx = pwalletMain->mapWallet[txid];
entry.pushKV("txid", hash.GetHex());
entry.pushKV("txid", txid.GetHex());
UniValue spends(UniValue::VARR);
UniValue outputs(UniValue::VARR);
@ -4168,6 +4170,30 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
auto legacyAcctOVKs = legacyKey.GetOVKsForShielding();
ovks.insert(legacyAcctOVKs.first);
ovks.insert(legacyAcctOVKs.second);
// Generate the OVKs for shielding for all unified key components
for (const auto& [_, ufvkid] : pwalletMain->mapUnifiedAccountKeys) {
auto ufvk = pwalletMain->GetUnifiedFullViewingKey(ufvkid);
if (ufvk.has_value()) {
auto tkey = ufvk.value().GetTransparentKey();
if (tkey.has_value()) {
auto tovks = tkey.value().GetOVKsForShielding();
ovks.insert(tovks.first);
ovks.insert(tovks.second);
}
auto skey = ufvk.value().GetSaplingKey();
if (skey.has_value()) {
auto sovks = skey.value().GetOVKs();
ovks.insert(sovks.first);
ovks.insert(sovks.second);
}
auto okey = ufvk.value().GetOrchardKey();
if (okey.has_value()) {
ovks.insert(okey.value().ToExternalOutgoingViewingKey());
ovks.insert(okey.value().ToInternalOutgoingViewingKey());
}
}
}
}
// Sapling spends
@ -4196,23 +4222,22 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
assert(pwalletMain->GetSaplingFullViewingKey(wtxPrev.mapSaplingNoteData.at(op).ivk, extfvk));
ovks.insert(extfvk.fvk.ovk);
// If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address.
std::string address = keyIO.EncodePaymentAddress([&]() {
auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{pa};
}
}());
// Show the address that was cached at transaction construction as the
// recipient.
std::optional<std::string> addrStr;
if (!pwalletMain->IsInternalRecipient(pa)) {
auto addr = pwalletMain->GetPaymentAddressForRecipient(txid, pa);
addrStr = keyIO.EncodePaymentAddress(addr);
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_SAPLING);
entry.pushKV("spend", (int)i);
entry.pushKV("txidPrev", op.hash.GetHex());
entry.pushKV("outputPrev", (int)op.n);
entry.pushKV("address", address);
if (addrStr.has_value()) {
entry.pushKV("address", addrStr.value());
}
entry.pushKV("value", ValueFromAmount(notePt.value()));
entry.pushKV("valueZat", notePt.value());
spends.push_back(entry);
@ -4220,7 +4245,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
// Sapling outputs
for (uint32_t i = 0; i < wtx.vShieldedOutput.size(); ++i) {
auto op = SaplingOutPoint(hash, i);
auto op = SaplingOutPoint(txid, i);
SaplingNotePlaintext notePt;
SaplingPaymentAddress pa;
@ -4250,28 +4275,88 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
}
auto memo = notePt.memo();
// If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address.
std::string address = keyIO.EncodePaymentAddress([&]() {
auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{pa};
}
}());
// Show the address that was cached at transaction construction as the
// recipient.
std::optional<std::string> addrStr;
bool isInternal = pwalletMain->IsInternalRecipient(pa);
if (!isInternal) {
auto addr = pwalletMain->GetPaymentAddressForRecipient(txid, pa);
addrStr = keyIO.EncodePaymentAddress(addr);
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_SAPLING);
entry.pushKV("output", (int)op.n);
entry.pushKV("outgoing", isOutgoing);
entry.pushKV("address", address);
entry.pushKV("walletInternal", isInternal);
if (addrStr.has_value()) {
entry.pushKV("address", addrStr.value());
}
entry.pushKV("value", ValueFromAmount(notePt.value()));
entry.pushKV("valueZat", notePt.value());
addMemo(entry, memo);
outputs.push_back(entry);
}
// TODO unified addresses, orchard, see #5186
std::vector<uint256> ovksVector(ovks.begin(), ovks.end());
OrchardActions orchardActions = wtx.RecoverOrchardActions(ovksVector);
// Orchard spends
for (auto & pair : orchardActions.GetSpends()) {
auto actionIdx = pair.first;
OrchardActionSpend orchardActionSpend = pair.second;
auto outpoint = orchardActionSpend.GetOutPoint();
auto receivedAt = orchardActionSpend.GetReceivedAt();
auto noteValue = orchardActionSpend.GetNoteValue();
std::optional<std::string> addrStr;
if (!pwalletMain->IsInternalRecipient(receivedAt)) {
auto ua = pwalletMain->FindUnifiedAddressByReceiver(receivedAt);
assert(ua.has_value());
addrStr = keyIO.EncodePaymentAddress(ua.value());
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_ORCHARD);
entry.pushKV("action", (int) actionIdx);
entry.pushKV("txidPrev", outpoint.hash.GetHex());
entry.pushKV("actionPrev", (int) outpoint.n);
if (addrStr.has_value()) {
entry.pushKV("address", addrStr.value());
}
entry.pushKV("value", ValueFromAmount(noteValue));
entry.pushKV("valueZat", noteValue);
spends.push_back(entry);
}
// Orchard outputs
for (const auto& [actionIdx, orchardActionOutput] : orchardActions.GetOutputs()) {
auto noteValue = orchardActionOutput.GetNoteValue();
auto recipient = orchardActionOutput.GetRecipient();
auto memo = orchardActionOutput.GetMemo();
// Show the address that was cached at transaction construction as the
// recipient.
std::optional<std::string> addrStr;
bool isInternal = pwalletMain->IsInternalRecipient(recipient);
if (!isInternal) {
auto addr = pwalletMain->GetPaymentAddressForRecipient(txid, recipient);
addrStr = keyIO.EncodePaymentAddress(addr);
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_ORCHARD);
entry.pushKV("action", (int) actionIdx);
entry.pushKV("outgoing", orchardActionOutput.IsOutgoing());
entry.pushKV("walletInternal", isInternal);
if (addrStr.has_value()) {
entry.pushKV("address", addrStr.value());
}
entry.pushKV("value", ValueFromAmount(noteValue));
entry.pushKV("valueZat", noteValue);
addMemo(entry, memo);
outputs.push_back(entry);
}
entry.pushKV("spends", spends);
entry.pushKV("outputs", outputs);
@ -4647,7 +4732,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
}
if (!recipientAddrs.insert(addr.value()).second) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated recipient from address: ") + addrStr);
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated recipient address: ") + addrStr);
}
UniValue memoValue = find_value(o, "memo");

View File

@ -61,6 +61,10 @@ const char * DEFAULT_WALLET_DAT = "wallet.dat";
*/
CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
std::set<ReceiverType> CWallet::DefaultReceiverTypes() {
return {ReceiverType::P2PKH, ReceiverType::Sapling, ReceiverType::Orchard};
}
/** @defgroup mapWallet
*
* @{
@ -853,6 +857,123 @@ bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &add
addrmeta.GetReceiverTypes());
}
PaymentAddress CWallet::GetPaymentAddressForRecipient(
const uint256& txid,
const libzcash::RecipientAddress& recipient) const
{
auto self = this;
auto defaultAddress = [&]() -> PaymentAddress {
auto ufvk = self->GetUFVKForReceiver(RecipientAddressToReceiver(recipient));
return std::visit(match {
[&](const CKeyID& addr) {
auto ua = self->FindUnifiedAddressByReceiver(addr);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{addr};
}
},
[&](const CScriptID& addr) {
auto ua = self->FindUnifiedAddressByReceiver(addr);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{addr};
}
},
[&](const SaplingPaymentAddress& addr) {
auto ua = self->FindUnifiedAddressByReceiver(addr);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else if (ufvk.has_value() && ufvk->GetSaplingKey().has_value()) {
auto saplingKey = ufvk->GetSaplingKey().value();
auto j = saplingKey.DecryptDiversifier(addr.d);
// std::get is safe here because we know we have a valid Sapling diversifier index
auto defaultUA = std::get<std::pair<UnifiedAddress, diversifier_index_t>>(
ufvk->Address(j, CWallet::DefaultReceiverTypes()));
return libzcash::PaymentAddress{defaultUA.first};
} else {
return libzcash::PaymentAddress{addr};
}
},
[&](const OrchardRawAddress& addr) {
auto ua = self->FindUnifiedAddressByReceiver(addr);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else if (ufvk.has_value() && ufvk->GetOrchardKey().has_value()) {
auto orchardKey = ufvk->GetOrchardKey().value();
auto j = orchardKey.ToIncomingViewingKey().DecryptDiversifier(addr);
if (j.has_value()) {
auto genResult = ufvk->Address(j.value(), CWallet::DefaultReceiverTypes());
auto defaultUA = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&genResult);
if (defaultUA != nullptr) {
return libzcash::PaymentAddress{defaultUA->first};
}
}
}
return libzcash::PaymentAddress{UnifiedAddress::ForSingleReceiver(addr)};
}
}, recipient);
};
auto recipientsPtr = sendRecipients.find(txid);
if (recipientsPtr == sendRecipients.end()) {
// we don't know the recipient, so we just return the simplest type
return defaultAddress();
} else {
// search the list of recipient mappings for one corresponding to
// our recipient, and return the known UA if it exists; otherwise
// just use the default.
for (const auto& mapping : recipientsPtr->second) {
if (mapping.address == recipient && mapping.ua.has_value()) {
return PaymentAddress{mapping.ua.value()};
}
}
return defaultAddress();
}
}
bool CWallet::IsInternalRecipient(const libzcash::RecipientAddress& recipient) const
{
auto self = this;
return std::visit(match {
[&](const CKeyID& addr) {
// we never send transparent change when sending to or from a
// unified address
return false;
},
[&](const CScriptID& addr) {
// we never use P2SH for change or shielding
return false;
},
[&](const SaplingPaymentAddress& addr) {
auto ufvk = self->GetUFVKForReceiver(addr);
if (ufvk.has_value()) {
auto changeAddr = ufvk->GetChangeAddress(SaplingChangeRequest());
if (changeAddr.has_value()) {
return changeAddr.value() == recipient;
}
}
return false;
},
[&](const OrchardRawAddress& addr) {
auto ufvk = self->GetUFVKForReceiver(addr);
if (ufvk.has_value()) {
auto changeAddr = ufvk->GetChangeAddress(OrchardChangeRequest());
if (changeAddr.has_value()) {
return changeAddr.value() == recipient;
}
}
return false;
}
}, recipient);
}
void CWallet::LoadRecipientMapping(const uint256& txid, const RecipientMapping& mapping) {
sendRecipients[txid].push_back(mapping);
}
bool CWallet::LoadCaches()
{
AssertLockHeld(cs_wallet);
@ -1486,7 +1607,7 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
for (uint32_t i = 0; i < wtx.GetOrchardBundle().GetNumActions(); i++) {
OrchardOutPoint op(wtx.GetHash(), i);
auto potential_spends = pwalletMain->orchardWallet.GetPotentialSpends(op);
auto potential_spends = orchardWallet.GetPotentialSpends(op);
if (potential_spends.size() <= 1) {
continue; // No conflict if zero or one spends
@ -1673,14 +1794,14 @@ std::optional<ZTXOSelector> CWallet::ZTXOSelectorForAddress(
}
},
[&](const libzcash::UnifiedAddress& ua) {
auto ufvkMeta = this->GetUFVKMetadataForAddress(ua);
auto ufvkMeta = GetUFVKMetadataForAddress(ua);
if (ufvkMeta.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 accountId = this->GetUnifiedAccountId(ufvkMeta.value().first);
auto accountId = this->GetUnifiedAccountId(ufvkMeta.value().GetUFVKId());
if (accountId.has_value()) {
if (allowAddressLinkability) {
pattern = AccountZTXOPattern(accountId.value(), ua.GetKnownReceiverTypes());
@ -1749,13 +1870,13 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
[&](const CKeyID& addr) {
auto meta = self->GetUFVKMetadataForReceiver(addr);
if (meta.has_value()) {
result = self->GetUnifiedAccountId(meta.value().first);
result = self->GetUnifiedAccountId(meta.value().GetUFVKId());
}
},
[&](const CScriptID& addr) {
auto meta = self->GetUFVKMetadataForReceiver(addr);
if (meta.has_value()) {
result = self->GetUnifiedAccountId(meta.value().first);
result = self->GetUnifiedAccountId(meta.value().GetUFVKId());
}
},
[&](const libzcash::SproutPaymentAddress& addr) { },
@ -1763,7 +1884,7 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
[&](const libzcash::SaplingPaymentAddress& addr) {
auto meta = GetUFVKMetadataForReceiver(addr);
if (meta.has_value()) {
result = self->GetUnifiedAccountId(meta.value().first);
result = self->GetUnifiedAccountId(meta.value().GetUFVKId());
}
},
[&](const libzcash::SaplingExtendedFullViewingKey& vk) {
@ -1775,7 +1896,7 @@ std::optional<libzcash::AccountId> CWallet::FindAccountForSelector(const ZTXOSel
[&](const libzcash::UnifiedAddress& addr) {
auto meta = GetUFVKMetadataForAddress(addr);
if (meta.has_value()) {
result = self->GetUnifiedAccountId(meta.value().first);
result = self->GetUnifiedAccountId(meta.value().GetUFVKId());
}
},
[&](const libzcash::UnifiedFullViewingKey& vk) {
@ -1834,16 +1955,16 @@ bool CWallet::SelectorMatchesAddress(
return false;
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
std::optional<AddressUFVKMetadata> meta;
std::visit(match {
[&](const CNoDestination& none) { meta = std::nullopt; },
[&](const auto& addr) { meta = self->GetUFVKMetadataForReceiver(addr); }
}, address);
return (meta.has_value() && meta.value().first == ufvk.GetKeyID(Params()));
return (meta.has_value() && meta.value().GetUFVKId() == ufvk.GetKeyID(Params()));
},
[&](const AccountZTXOPattern& acct) {
if (acct.IncludesP2PKH() || acct.IncludesP2SH()) {
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>> meta;
std::optional<AddressUFVKMetadata> meta;
std::visit(match {
[&](const CNoDestination& none) { meta = std::nullopt; },
[&](const auto& addr) { meta = self->GetUFVKMetadataForReceiver(addr); }
@ -1851,7 +1972,7 @@ bool CWallet::SelectorMatchesAddress(
if (meta.has_value()) {
// use the coin if the account id corresponding to the UFVK is
// the payment source account.
return self->GetUnifiedAccountId(meta.value().first) == std::optional(acct.GetAccountId());
return self->GetUnifiedAccountId(meta.value().GetUFVKId()) == std::optional(acct.GetAccountId());
} else {
// The legacy account is treated as a single pool of
// transparent funds, reproducing wallet behavior prior to
@ -1900,7 +2021,7 @@ bool CWallet::SelectorMatchesAddress(
// the Sapling component of the unified address, we consider that a
// match
return a0Meta.has_value() && uaMeta.has_value() &&
a0Meta.value().first == uaMeta.value().first;
a0Meta.value().GetUFVKId() == uaMeta.value().GetUFVKId();
}
return false;
},
@ -1920,7 +2041,7 @@ bool CWallet::SelectorMatchesAddress(
if (meta.has_value()) {
// use the coin if the account id corresponding to the UFVK is
// the payment source account.
return self->GetUnifiedAccountId(meta.value().first) == std::optional(acct.GetAccountId());
return self->GetUnifiedAccountId(meta.value().GetUFVKId()) == std::optional(acct.GetAccountId());
} else {
return false;
}
@ -2122,7 +2243,7 @@ SpendableInputs CWallet::FindSpendableInputs(
if (orchardReceiver.has_value()) {
auto meta = GetUFVKMetadataForReceiver(orchardReceiver.value());
if (meta.has_value()) {
auto ufvk = GetUnifiedFullViewingKey(meta.value().first);
auto ufvk = GetUnifiedFullViewingKey(meta.value().GetUFVKId());
if (ufvk.has_value()) {
auto fvk = ufvk->GetOrchardKey();
if (fvk.has_value()) {
@ -3594,7 +3715,7 @@ bool CWallet::MnemonicVerified() {
}
HDSeed CWallet::GetHDSeedForRPC() const {
auto seed = pwalletMain->GetMnemonicSeed();
auto seed = GetMnemonicSeed();
if (!seed.has_value()) {
throw JSONRPCError(RPC_WALLET_ERROR, "HD seed not found");
}
@ -3873,6 +3994,12 @@ std::optional<std::pair<
return std::nullopt;
}
OrchardActions CWalletTx::RecoverOrchardActions(const std::vector<uint256>& ovks) const
{
return pwallet->orchardWallet.GetTxActions(*this, ovks);
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
@ -6706,7 +6833,7 @@ PaymentAddressSource GetSourceForPaymentAddress::GetUnifiedSource(const libzcash
auto hdChain = m_wallet->GetMnemonicHDChain();
auto ufvkMeta = m_wallet->GetUFVKMetadataForReceiver(receiver);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().first;
auto ufvkid = ufvkMeta.value().GetUFVKId();
// Look through the UFVKs that we have generated, and confirm that the
// seed fingerprint for the key we find for the ufvkid corresponds to
// the wallet's mnemonic seed.
@ -6789,13 +6916,13 @@ PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::Sapl
PaymentAddressSource GetSourceForPaymentAddress::operator()(const libzcash::UnifiedAddress &uaddr) const
{
auto hdChain = m_wallet->GetMnemonicHDChain();
auto ufvkid = m_wallet->GetUFVKMetadataForAddress(uaddr);
if (ufvkid.has_value()) {
auto ufvkMeta = m_wallet->GetUFVKMetadataForAddress(uaddr);
if (ufvkMeta.has_value()) {
// Look through the UFVKs that we have generated, and confirm that the
// seed fingerprint for the key we find for the ufvkid corresponds to
// seed fingerprint for the key we find for the ufvkMeta corresponds to
// the wallet's mnemonic seed.
for (const auto& [k, v] : m_wallet->mapUnifiedAccountKeys) {
if (v == ufvkid.value().first && hdChain.has_value() && k.first == hdChain.value().GetSeedFingerprint()) {
if (v == ufvkMeta.value().GetUFVKId() && hdChain.has_value() && k.first == hdChain.value().GetSeedFingerprint()) {
return PaymentAddressSource::MnemonicHDSeed;
}
}
@ -6945,9 +7072,9 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
// If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
// return a non-nullopt value, and since we obtained that metadata by
@ -6959,9 +7086,9 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
// If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
// return a non-nullopt value, and since we obtained that metadata by
@ -6978,12 +7105,12 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
return std::nullopt;
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const CKeyID& keyId) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
// transparent address UFVK metadata is always accompanied by the child
// index at which the address was produced
assert(ufvkPair.value().second.has_value());
assert(ufvkMeta.value().GetDiversifierIndex().has_value());
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value());
return ufvk.value();
@ -6999,9 +7126,9 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(
const libzcash::OrchardRawAddress& orchardAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value());
@ -7027,9 +7154,9 @@ std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(
}
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value());
@ -7057,13 +7184,13 @@ std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(co
return std::nullopt;
}
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CKeyID& keyId) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvkMeta = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkMeta.has_value()) {
auto ufvkid = ufvkMeta.value().GetUFVKId();
// transparent address UFVK metadata is always accompanied by the child
// index at which the address was produced
assert(ufvkPair.value().second.has_value());
diversifier_index_t j = ufvkPair.value().second.value();
assert(ufvkMeta.value().GetDiversifierIndex().has_value());
diversifier_index_t j = ufvkMeta.value().GetDiversifierIndex().value();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
if (!(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value())) {
throw std::runtime_error("CWallet::UnifiedAddressForReceiver(): UFVK has no P2PKH key part.");

View File

@ -632,6 +632,7 @@ public:
std::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> RecoverSaplingNoteWithoutLeadByteCheck(SaplingOutPoint op, std::set<uint256>& ovks) const;
OrchardActions RecoverOrchardActions(const std::vector<uint256>& ovks) const;
//! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const;
@ -1005,6 +1006,8 @@ public:
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
private:
friend class CWalletTx;
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
* all coins from coinControl are selected; Never select unconfirmed coins
@ -1291,6 +1294,8 @@ public:
std::map<uint256, CWalletTx> mapWallet;
std::map<uint256, std::vector<RecipientMapping>> sendRecipients;
typedef std::multimap<int64_t, CWalletTx*> TxItems;
TxItems wtxOrdered;
@ -1607,6 +1612,14 @@ public:
bool LoadUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata &skmeta);
bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta);
libzcash::PaymentAddress GetPaymentAddressForRecipient(
const uint256& txid,
const libzcash::RecipientAddress& recipient) const;
bool IsInternalRecipient(
const libzcash::RecipientAddress& recipient) const;
void LoadRecipientMapping(const uint256& txid, const RecipientMapping& mapping);
//! Reconstructs (in memory) caches and mappings for unified accounts,
//! addresses and keying material. This should be called once, after the
//! remainder of the on-disk wallet data has been loaded.
@ -1690,16 +1703,16 @@ public:
bool SaveRecipientMappings(const uint256& txid, const std::vector<RecipientMapping>& recipients)
{
LOCK2(cs_main, cs_wallet);
LogPrintf("SaveRecipientMappings:\n%s", txid.ToString());
for (const auto& recipient : recipients)
{
sendRecipients[txid].push_back(recipient);
if (recipient.ua.has_value()) {
CWalletDB(strWalletFile).WriteRecipientMapping(
assert(CWalletDB(strWalletFile).WriteRecipientMapping(
txid,
recipient.address,
recipient.ua.value()
);
));
}
}
@ -1720,6 +1733,12 @@ public:
*/
static CAmount GetRequiredFee(unsigned int nTxBytes);
/**
* The current set of default receiver types used when the wallet generates
* unified addresses
*/
static std::set<libzcash::ReceiverType> DefaultReceiverTypes();
private:
bool NewKeyPool();
public:

View File

@ -892,6 +892,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error in wallet database: recipientmapping UA does not contain recipient";
return false;
}
pwallet->LoadRecipientMapping(txid, RecipientMapping(ua.value(), recipient));
}
else if (strType == "orchard_note_commitment_tree")
{

View File

@ -67,6 +67,12 @@ class UnifiedAddress {
public:
UnifiedAddress() {}
static UnifiedAddress ForSingleReceiver(Receiver receiver) {
UnifiedAddress ua;
ua.AddReceiver(receiver);
return ua;
}
static std::optional<UnifiedAddress> Parse(const KeyConstants& keyConstants, const std::string& str);
ADD_SERIALIZE_METHODS;

View File

@ -2,9 +2,11 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "tinyformat.h"
#include "zcash/Address.hpp"
#include "unified.h"
#include "util/match.h"
#include "utilstrencodings.h"
#include <rust/unified_keys.h>
@ -39,6 +41,37 @@ Receiver libzcash::RecipientAddressToReceiver(const RecipientAddress& recipient)
}, recipient);
}
std::string libzcash::DebugPrintReceiver(const Receiver& receiver) {
return std::visit(match {
[&](const OrchardRawAddress &zaddr) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
return tfm::format("Orchard(%s)", HexStr(ss.begin(), ss.end()));
},
[&](const SaplingPaymentAddress &zaddr) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
return tfm::format("Sapling(%s)", HexStr(ss.begin(), ss.end()));
},
[&](const CScriptID &p2sh) {
return tfm::format("P2SH(%s)", p2sh.GetHex());
},
[&](const CKeyID &p2pkh) {
return tfm::format("P2PKH(%s)", p2pkh.GetHex());
},
[&](const UnknownReceiver &unknown) {
return tfm::format(
"Unknown(%x, %s)",
unknown.typecode,
HexStr(unknown.data.begin(), unknown.data.end()));
}
}, receiver);
};
std::string libzcash::DebugPrintRecipientAddress(const RecipientAddress& addr) {
return DebugPrintReceiver(RecipientAddressToReceiver(addr));
}
std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,

View File

@ -57,6 +57,8 @@ typedef std::variant<
libzcash::SaplingPaymentAddress,
libzcash::OrchardRawAddress> RecipientAddress;
std::string DebugPrintRecipientAddress(const RecipientAddress& add);
class TransparentChangeRequest {
private:
const diversifier_index_t& index;
@ -126,6 +128,8 @@ typedef std::variant<
Receiver RecipientAddressToReceiver(const RecipientAddress& recipient);
std::string DebugPrintReceiver(const Receiver& receiver);
/**
* An internal identifier for a unified full viewing key, derived as a
* blake2b hash of the serialized form of the UFVK.

View File

@ -178,6 +178,17 @@ public:
return j;
}
// Attempts to construct a valid internal payment address with diversifier
// index `j`; returns std::nullopt if `j` does not result in a valid diversifier
// given this xfvk.
std::optional<libzcash::SaplingPaymentAddress> InternalAddress(diversifier_index_t j) const {
return GetInternalDFVK().Address(j);
}
diversifier_index_t DecryptInternalDiversifier(const diversifier_t& d) const {
return GetInternalDFVK().DecryptDiversifier(d);
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>