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:
commit
fabff30d88
|
@ -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"]
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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__':
|
||||
|
|
|
@ -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()) << ")";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue