Merge branch 'master' into feature/wallet_orchard-orchard_sendmany

This commit is contained in:
Jack Grigg 2022-02-25 00:33:28 +00:00
commit bb4e792c84
27 changed files with 565 additions and 223 deletions

View File

@ -18,6 +18,10 @@ $(package)_download_file_aarch64_linux=clang+llvm-$($(package)_version)-aarch64-
$(package)_file_name_aarch64_linux=clang-llvm-$($(package)_version)-aarch64-linux-gnu.tar.xz
$(package)_sha256_hash_aarch64_linux=968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f
ifneq (,$(wildcard /etc/arch-release))
$(package)_dependencies=native_libtinfo
endif
# Ensure we have clang native to the builder, not the target host
ifneq ($(canonical_host),$(build))
$(package)_exact_download_path=$($(package)_download_path_$(build_os))

View File

@ -0,0 +1,22 @@
package=native_tinfo
$(package)_version=5.6.0
$(package)_download_path_linux=http://ftp.debian.org/debian/pool/main/n/ncurses/
$(package)_download_file_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
$(package)_file_name_linux=libtinfo5_6.0+20161126-1+deb9u2_amd64.deb
$(package)_sha256_hash_linux=1d249a3193568b5ef785ad8993b9ba6d6fdca0eb359204c2355532b82d25e9f5
define $(package)_extract_cmds
mkdir -p $($(package)_extract_dir) && \
echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \
$(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \
mkdir -p libtinfo5 && \
ar x --output libtinfo5 $($(package)_source_dir)/$($(package)_file_name) && \
cd libtinfo5 && \
tar xf data.tar.xz
endef
define $(package)_stage_cmds
pwd && \
mkdir -p $($(package)_staging_prefix_dir)/lib && \
cp libtinfo5/lib/x86_64-linux-gnu/libtinfo.so.5.9 $($(package)_staging_prefix_dir)/lib/libtinfo.so.5
endef

View File

@ -1,6 +1,10 @@
zcash_packages := libsodium utfcpp
packages := boost libevent zeromq $(zcash_packages) googletest
native_packages := native_clang native_ccache native_rust
native_packages := native_clang native_ccache native_rust
ifneq (,$(wildcard /etc/arch-release))
native_packages += native_libtinfo
endif
wallet_packages=bdb

View File

@ -6,9 +6,11 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
NU5_BRANCH_ID,
assert_equal,
connect_nodes_bi,
get_coinbase_address,
nuparams,
start_nodes,
wait_and_assert_operationid_status,
)
@ -17,6 +19,7 @@ from decimal import Decimal
SPROUT_TREE_EMPTY_ROOT = "59d2cde5e65c1414c32ba54f0fe4bdb3d67618125286e6a191317917c812c6d7"
SAPLING_TREE_EMPTY_ROOT = "3e49b5f954aa9d3545bc6c37744661eea48d7c34e3000d82b7f0010c30f4c2fb"
ORCHARD_TREE_EMPTY_ROOT = "2fd8e51a03d9bbe2dd809831b1497aeb68a6e37ddf707ced4aa2d8dff13529ae"
NULL_FIELD = "0000000000000000000000000000000000000000000000000000000000000000"
# Verify block header field 'hashFinalSaplingRoot' (returned in rpc as 'finalsaplingroot')
@ -25,17 +28,16 @@ class FinalSaplingRootTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.num_nodes = 4
self.num_nodes = 2
self.setup_clean_chain = True
def setup_network(self, split=False):
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
'-txindex' # Avoid JSONRPC error: No information available about transaction
'-txindex', # Avoid JSONRPC error: No information available about transaction
nuparams(NU5_BRANCH_ID, 210),
'-debug',
]] * self.num_nodes)
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False
self.sync_all()
@ -52,13 +54,19 @@ class FinalSaplingRootTest(BitcoinTestFramework):
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
assert("skipHash" not in treestate["sprout"])
assert "skipHash" not in treestate["sprout"]
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], NULL_FIELD)
# There is no sapling state tree yet, and trying to find it in an earlier
# block won't succeed (we're at genesis block), so skipHash is absent.
assert("finalState" not in treestate["sapling"])
assert("skipHash" not in treestate["sapling"])
assert "finalState" not in treestate["sapling"]
assert "skipHash" not in treestate["sapling"]
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
# There is no orchard state tree yet, and trying to find it in an earlier
# block won't succeed (we're at genesis block), so skipHash is absent.
assert "finalState" not in treestate["orchard"]
assert "skipHash" not in treestate["orchard"]
# Verify all generated blocks contain the empty root of the Sapling tree.
blockcount = self.nodes[0].getblockcount()
@ -70,14 +78,18 @@ class FinalSaplingRootTest(BitcoinTestFramework):
assert_equal(treestate["height"], height)
assert_equal(treestate["hash"], self.nodes[0].getblockhash(height))
assert("skipHash" not in treestate["sprout"])
assert "skipHash" not in treestate["sprout"]
assert_equal(treestate["sprout"]["commitments"]["finalRoot"], SPROUT_TREE_EMPTY_ROOT)
assert_equal(treestate["sprout"]["commitments"]["finalState"], "000000")
assert("skipHash" not in treestate["sapling"])
assert "skipHash" not in treestate["sapling"]
assert_equal(treestate["sapling"]["commitments"]["finalRoot"], SAPLING_TREE_EMPTY_ROOT)
assert_equal(treestate["sapling"]["commitments"]["finalState"], "000000")
assert "skipHash" not in treestate["orchard"]
assert_equal(treestate["orchard"]["commitments"]["finalRoot"], NULL_FIELD)
# Node 0 shields some funds
taddr0 = get_coinbase_address(self.nodes[0])
saplingAddr0 = self.nodes[0].z_getnewaddress('sapling')
@ -93,8 +105,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
# Verify the final Sapling root has changed
blk = self.nodes[0].getblock("201")
root = blk["finalsaplingroot"]
assert(root is not SAPLING_TREE_EMPTY_ROOT)
assert(root is not NULL_FIELD)
assert root is not SAPLING_TREE_EMPTY_ROOT
assert root is not NULL_FIELD
# Verify there is a Sapling output description (its commitment was added to tree)
result = self.nodes[0].getrawtransaction(mytxid, 1)
@ -105,8 +117,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 70)
treestate = new_treestate
@ -145,8 +157,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["sapling"]["commitments"]["finalRoot"], root)
assert_equal(new_treestate["sapling"], treestate["sapling"])
assert(new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"])
assert(new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"])
assert new_treestate["sprout"]["commitments"]["finalRoot"] != treestate["sprout"]["commitments"]["finalRoot"]
assert new_treestate["sprout"]["commitments"]["finalState"] != treestate["sprout"]["commitments"]["finalState"]
assert_equal(len(new_treestate["sprout"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["sprout"]["commitments"]["finalState"]), 134)
treestate = new_treestate
@ -164,7 +176,7 @@ class FinalSaplingRootTest(BitcoinTestFramework):
assert_equal(len(self.nodes[0].getblock("205")["tx"]), 2)
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal("12.34"))
assert(root is not self.nodes[0].getblock("205")["finalsaplingroot"])
assert root is not self.nodes[0].getblock("205")["finalsaplingroot"]
# Verify there is a Sapling output description (its commitment was added to tree)
result = self.nodes[0].getrawtransaction(mytxid, 1)
@ -172,8 +184,8 @@ class FinalSaplingRootTest(BitcoinTestFramework):
new_treestate = self.nodes[0].z_gettreestate(str(-1))
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert(new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"])
assert(new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"])
assert new_treestate["sapling"]["commitments"]["finalRoot"] != treestate["sapling"]["commitments"]["finalRoot"]
assert new_treestate["sapling"]["commitments"]["finalState"] != treestate["sapling"]["commitments"]["finalState"]
assert_equal(len(new_treestate["sapling"]["commitments"]["finalRoot"]), 64)
assert_equal(len(new_treestate["sapling"]["commitments"]["finalState"]), 136)
treestate = new_treestate
@ -200,6 +212,21 @@ class FinalSaplingRootTest(BitcoinTestFramework):
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert_equal(new_treestate["sapling"], treestate["sapling"])
# Activate NU5; more testing should be added once we can mine orchard transactions.
self.sync_all()
self.nodes[0].generate(4)
self.sync_all()
new_treestate = self.nodes[0].z_gettreestate(str(-1))
# sprout and sapling results should not change
assert_equal(new_treestate["sprout"], treestate["sprout"])
assert_equal(new_treestate["sapling"], treestate["sapling"])
assert "skipHash" not in treestate["orchard"]
assert_equal(new_treestate["orchard"]["commitments"]["finalRoot"], ORCHARD_TREE_EMPTY_ROOT)
assert_equal(new_treestate["orchard"]["commitments"]["finalState"], "00")
pass
if __name__ == '__main__':
FinalSaplingRootTest().main()

View File

@ -385,6 +385,15 @@ class ListReceivedTest (BitcoinTestFramework):
assert 'transparent' in receivers
assert 'sapling' in receivers
assert 'orchard' in receivers
assert_raises_message(
JSONRPCException,
"The provided address is a bare receiver from a Unified Address in this wallet.",
node.z_listreceivedbyaddress, receivers['transparent'], 0)
assert_raises_message(
JSONRPCException,
"The provided address is a bare receiver from a Unified Address in this wallet.",
node.z_listreceivedbyaddress, receivers['sapling'], 0)
# Wallet contains no notes
r = node.z_listreceivedbyaddress(unified_addr, 0)
assert_equal(len(r), 0, "unified_addr should have received zero notes")

View File

@ -255,7 +255,7 @@ const size_t ConvertedSaplingExtendedFullViewingKeySize = (ZIP32_XFVK_SIZE * 8 +
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
} // namespace
CTxDestination KeyIO::DecodeDestination(const std::string& str)
CTxDestination KeyIO::DecodeDestination(const std::string& str) const
{
std::vector<unsigned char> data;
uint160 hash;
@ -279,7 +279,7 @@ CTxDestination KeyIO::DecodeDestination(const std::string& str)
return CNoDestination();
};
CKey KeyIO::DecodeSecret(const std::string& str)
CKey KeyIO::DecodeSecret(const std::string& str) const
{
CKey key;
std::vector<unsigned char> data;
@ -295,7 +295,7 @@ CKey KeyIO::DecodeSecret(const std::string& str)
return key;
}
std::string KeyIO::EncodeSecret(const CKey& key)
std::string KeyIO::EncodeSecret(const CKey& key) const
{
assert(key.IsValid());
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::SECRET_KEY);
@ -308,7 +308,7 @@ std::string KeyIO::EncodeSecret(const CKey& key)
return ret;
}
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str) const
{
CExtPubKey key;
std::vector<unsigned char> data;
@ -321,7 +321,7 @@ CExtPubKey KeyIO::DecodeExtPubKey(const std::string& str)
return key;
}
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_PUBLIC_KEY);
size_t size = data.size();
@ -331,7 +331,7 @@ std::string KeyIO::EncodeExtPubKey(const CExtPubKey& key)
return ret;
}
CExtKey KeyIO::DecodeExtKey(const std::string& str)
CExtKey KeyIO::DecodeExtKey(const std::string& str) const
{
CExtKey key;
std::vector<unsigned char> data;
@ -344,7 +344,7 @@ CExtKey KeyIO::DecodeExtKey(const std::string& str)
return key;
}
std::string KeyIO::EncodeExtKey(const CExtKey& key)
std::string KeyIO::EncodeExtKey(const CExtKey& key) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::EXT_SECRET_KEY);
size_t size = data.size();
@ -355,17 +355,17 @@ std::string KeyIO::EncodeExtKey(const CExtKey& key)
return ret;
}
std::string KeyIO::EncodeDestination(const CTxDestination& dest)
std::string KeyIO::EncodeDestination(const CTxDestination& dest) const
{
return std::visit(DestinationEncoder(keyConstants), dest);
}
bool KeyIO::IsValidDestinationString(const std::string& str)
bool KeyIO::IsValidDestinationString(const std::string& str) const
{
return IsValidDestination(DecodeDestination(str));
}
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr)
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const
{
return std::visit(PaymentAddressEncoder(keyConstants), zaddr);
}
@ -441,78 +441,12 @@ std::optional<T1> DecodeAny(
return std::nullopt;
}
static bool AddOrchardReceiver(void* ua, OrchardRawAddressPtr* ptr)
{
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(
libzcash::OrchardRawAddress::KeyIoOnlyFromReceiver(ptr));
}
/**
* `raw` MUST be 43 bytes.
*/
static bool AddSaplingReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 43),
SER_NETWORK,
PROTOCOL_VERSION);
libzcash::SaplingPaymentAddress receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
/**
* `raw` MUST be 20 bytes.
*/
static bool AddP2SHReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
CScriptID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
/**
* `raw` MUST be 20 bytes.
*/
static bool AddP2PKHReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
CKeyID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
static bool AddUnknownReceiver(void* ua, uint32_t typecode, const unsigned char* data, size_t len)
{
libzcash::UnknownReceiver receiver(typecode, std::vector(data, data + len));
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str)
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str) const
{
// Try parsing as a Unified Address.
libzcash::UnifiedAddress ua;
if (zcash_address_parse_unified(
str.c_str(),
keyConstants.NetworkIDString().c_str(),
&ua,
AddOrchardReceiver,
AddSaplingReceiver,
AddP2SHReceiver,
AddP2PKHReceiver,
AddUnknownReceiver)
) {
return ua;
auto ua = libzcash::UnifiedAddress::Parse(keyConstants, str);
if (ua.has_value()) {
return ua.value();
}
// Try parsing as a Sapling address
@ -550,16 +484,17 @@ std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::s
}, DecodeDestination(str));
}
bool KeyIO::IsValidPaymentAddressString(const std::string& str) {
bool KeyIO::IsValidPaymentAddressString(const std::string& str) const
{
return DecodePaymentAddress(str).has_value();
}
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk)
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk) const
{
return std::visit(ViewingKeyEncoder(keyConstants), vk);
}
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str)
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str) const
{
// Try parsing as a Unified full viewing key
auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants);
@ -578,12 +513,12 @@ std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& s
);
}
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey)
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey) const
{
return std::visit(SpendingKeyEncoder(keyConstants), zkey);
}
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str)
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str) const
{
return DecodeAny<libzcash::SpendingKey,

View File

@ -23,28 +23,28 @@ private:
public:
KeyIO(const KeyConstants& keyConstants): keyConstants(keyConstants) { }
CKey DecodeSecret(const std::string& str);
std::string EncodeSecret(const CKey& key);
CKey DecodeSecret(const std::string& str) const;
std::string EncodeSecret(const CKey& key) const;
CExtKey DecodeExtKey(const std::string& str);
std::string EncodeExtKey(const CExtKey& extkey);
CExtPubKey DecodeExtPubKey(const std::string& str);
std::string EncodeExtPubKey(const CExtPubKey& extpubkey);
CExtKey DecodeExtKey(const std::string& str) const;
std::string EncodeExtKey(const CExtKey& extkey) const;
CExtPubKey DecodeExtPubKey(const std::string& str) const;
std::string EncodeExtPubKey(const CExtPubKey& extpubkey) const;
std::string EncodeDestination(const CTxDestination& dest);
CTxDestination DecodeDestination(const std::string& str);
std::string EncodeDestination(const CTxDestination& dest) const;
CTxDestination DecodeDestination(const std::string& str) const;
bool IsValidDestinationString(const std::string& str);
bool IsValidDestinationString(const std::string& str) const;
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr);
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str);
bool IsValidPaymentAddressString(const std::string& str);
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const;
std::optional<libzcash::PaymentAddress> DecodePaymentAddress(const std::string& str) const;
bool IsValidPaymentAddressString(const std::string& str) const;
std::string EncodeViewingKey(const libzcash::ViewingKey& vk);
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str);
std::string EncodeViewingKey(const libzcash::ViewingKey& vk) const;
std::optional<libzcash::ViewingKey> DecodeViewingKey(const std::string& str) const;
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey);
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str);
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey) const;
std::optional<libzcash::SpendingKey> DecodeSpendingKey(const std::string& str) const;
};
#endif // BITCOIN_KEY_IO_H

View File

@ -1247,6 +1247,13 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
" \"finalRoot\": \"hex\", (string)\n"
" \"finalState\": \"hex\" (string)\n"
" }\n"
" },\n"
" \"orchard\": {\n"
" \"skipHash\": \"hash\", (string) hash of most recent block with more information\n"
" \"commitments\": {\n"
" \"finalRoot\": \"hex\", (string)\n"
" \"finalState\": \"hex\" (string)\n"
" }\n"
" }\n"
"}\n"
"\nExamples:\n"
@ -1327,6 +1334,31 @@ UniValue z_gettreestate(const UniValue& params, bool fHelp)
res.pushKV("sapling", sapling_result);
}
// orchard
{
UniValue orchard_result(UniValue::VOBJ);
UniValue orchard_commitments(UniValue::VOBJ);
orchard_commitments.pushKV("finalRoot", pindex->hashFinalOrchardRoot.GetHex());
bool need_skiphash = false;
OrchardMerkleFrontier tree;
if (pcoinsTip->GetOrchardAnchorAt(pindex->hashFinalOrchardRoot, tree)) {
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
s << tree;
orchard_commitments.pushKV("finalState", HexStr(s.begin(), s.end()));
} else {
// Set skipHash to the most recent block that has a finalState.
const CBlockIndex* pindex_skip = pindex->pprev;
while (pindex_skip && !pcoinsTip->GetOrchardAnchorAt(pindex_skip->hashFinalOrchardRoot, tree)) {
pindex_skip = pindex_skip->pprev;
}
if (pindex_skip) {
orchard_result.pushKV("skipHash", pindex_skip->GetBlockHash().GetHex());
}
}
orchard_result.pushKV("commitments", orchard_commitments);
res.pushKV("orchard", orchard_result);
}
return res;
}

View File

@ -6,27 +6,6 @@
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
// TODO: instead of passing a TestMode flag, tests should override `CommitTransaction`
// on the wallet.
UniValue SendTransaction(CTransaction& tx, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode) {
UniValue o(UniValue::VOBJ);
// Send the transaction
if (!testmode) {
CWalletTx wtx(pwalletMain, tx);
if (!pwalletMain->CommitTransaction(wtx, reservekey)) {
// More details in debug.log
throw JSONRPCError(RPC_WALLET_ERROR, "SendTransaction: CommitTransaction failed");
}
o.pushKV("txid", tx.GetHash().ToString());
} else {
// Test mode does not send the transaction to the network.
o.pushKV("test", 1);
o.pushKV("txid", tx.GetHash().ToString());
o.pushKV("hex", EncodeHexTx(tx));
}
return o;
}
std::pair<CTransaction, UniValue> SignSendRawTransaction(UniValue obj, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode) {
// Sign the raw transaction
UniValue rawtxnValue = find_value(obj, "rawtxn");
@ -55,7 +34,9 @@ std::pair<CTransaction, UniValue> SignSendRawTransaction(UniValue obj, std::opti
CTransaction tx;
stream >> tx;
UniValue sendResult = SendTransaction(tx, reservekey, testmode);
// Recipient mappings are not available when sending a raw transaction.
std::vector<RecipientMapping> recipientMappings;
UniValue sendResult = SendTransaction(tx, recipientMappings, reservekey, testmode);
return std::make_pair(tx, sendResult);
}

View File

@ -5,7 +5,9 @@
#ifndef ZCASH_WALLET_ASYNCRPCOPERATION_COMMON_H
#define ZCASH_WALLET_ASYNCRPCOPERATION_COMMON_H
#include "core_io.h"
#include "primitives/transaction.h"
#include "rpc/protocol.h"
#include "univalue.h"
#include "wallet.h"
@ -13,19 +15,48 @@
/**
* Sends a given transaction.
*
* If testmode is false, commit the transaction to the wallet,
*
* If testmode is false, commit the transaction to the wallet,
* return {"txid": tx.GetHash().ToString()}
*
*
* If testmode is true, do not commit the transaction,
* return {"test": 1, "txid": tx.GetHash().ToString(), "hex": EncodeHexTx(tx)}
*/
UniValue SendTransaction(CTransaction& tx, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode);
template <typename RecipientMapping>
UniValue SendTransaction(
const CTransaction& tx,
const std::vector<RecipientMapping>& recipients,
std::optional<std::reference_wrapper<CReserveKey>> reservekey,
bool testmode)
{
UniValue o(UniValue::VOBJ);
// Send the transaction
if (!testmode) {
CWalletTx wtx(pwalletMain, tx);
// save the mapping from (receiver, txid) to UA
if (!pwalletMain->SaveRecipientMappings(tx.GetHash(), recipients)) {
// More details in debug.log
throw JSONRPCError(RPC_WALLET_ERROR, "SendTransaction: SaveRecipientMappings failed");
}
if (!pwalletMain->CommitTransaction(wtx, reservekey)) {
// More details in debug.log
throw JSONRPCError(RPC_WALLET_ERROR, "SendTransaction: CommitTransaction failed");
}
o.pushKV("txid", tx.GetHash().ToString());
} else {
// Test mode does not send the transaction to the network nor save the recipient mappings.
o.pushKV("test", 1);
o.pushKV("txid", tx.GetHash().ToString());
o.pushKV("hex", EncodeHexTx(tx));
}
return o;
}
/**
* Sign and send a raw transaction.
* Raw transaction as hex string should be in object field "rawtxn"
*
*
* Returns a pair of (the parsed transaction, and the result of sending)
*/
std::pair<CTransaction, UniValue> SignSendRawTransaction(UniValue obj, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode);

View File

@ -364,7 +364,9 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
// Build the transaction
tx_ = builder_.Build().GetTxOrThrow();
UniValue sendResult = SendTransaction(tx_, std::nullopt, testmode);
// TODO: update this when unified address support is added
std::vector<RecipientMapping> recipientMappings;
UniValue sendResult = SendTransaction(tx_, recipientMappings, std::nullopt, testmode);
set_result(sendResult);
return true;

View File

@ -464,7 +464,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
auto buildResult = builder_.Build();
auto tx = buildResult.GetTxOrThrow();
UniValue sendResult = SendTransaction(tx, std::nullopt, testmode);
UniValue sendResult = SendTransaction(tx, recipients_, std::nullopt, testmode);
set_result(sendResult);
return tx.GetHash();

View File

@ -25,14 +25,13 @@
using namespace libzcash;
class SendManyRecipient {
class SendManyRecipient : public RecipientMapping {
public:
RecipientAddress address;
CAmount amount;
std::optional<std::string> memo;
SendManyRecipient(RecipientAddress address_, CAmount amount_, std::optional<std::string> memo_) :
address(address_), amount(amount_), memo(memo_) {}
SendManyRecipient(std::optional<libzcash::UnifiedAddress> ua_, libzcash::RecipientAddress address_, CAmount amount_, std::optional<std::string> memo_) :
RecipientMapping(ua_, address_), amount(amount_), memo(memo_) {}
};
class TxOutputAmounts {

View File

@ -213,6 +213,29 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
return std::visit(ShieldToAddress(this, sendAmount), tozaddr_);
}
void ShieldToAddress::shieldToAddress(const libzcash::RecipientAddress& recipient, AsyncRPCOperation_shieldcoinbase *m_op) {
m_op->builder_.SetFee(m_op->fee_);
// Sending from a t-address, which we don't have an ovk for. Instead,
// generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
// FIXME: update to use the ZIP-316 OVK (#5511)
HDSeed seed = pwalletMain->GetHDSeedForRPC();
uint256 ovk = ovkForShieldingFromTaddr(seed);
// Add transparent inputs
for (auto t : m_op->inputs_) {
m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount);
}
// Send all value to the target recipient
m_op->builder_.SendChangeTo(recipient, ovk);
// Build the transaction
m_op->tx_ = m_op->builder_.Build().GetTxOrThrow();
}
bool ShieldToAddress::operator()(const CKeyID &addr) const {
return false;
}
@ -250,30 +273,11 @@ bool ShieldToAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) co
return true;
}
bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
m_op->builder_.SetFee(m_op->fee_);
ShieldToAddress::shieldToAddress(zaddr, m_op);
// Sending from a t-address, which we don't have an ovk for. Instead,
// generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
// FIXME: update to use the ZIP-316 OVK
HDSeed seed = pwalletMain->GetHDSeedForRPC();
uint256 ovk = ovkForShieldingFromTaddr(seed);
// Add transparent inputs
for (auto t : m_op->inputs_) {
m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount);
}
// Send all value to the target z-addr
m_op->builder_.SendChangeTo(zaddr, ovk);
// Build the transaction
m_op->tx_ = m_op->builder_.Build().GetTxOrThrow();
UniValue sendResult = SendTransaction(m_op->tx_, std::nullopt, m_op->testmode);
std::vector<RecipientMapping> recipientMappings;
UniValue sendResult = SendTransaction(m_op->tx_, recipientMappings, std::nullopt, m_op->testmode);
m_op->set_result(sendResult);
return true;
@ -283,7 +287,13 @@ bool ShieldToAddress::operator()(const libzcash::UnifiedAddress &uaddr) const {
// TODO check if an Orchard address is present, send to it if so.
const auto receiver{uaddr.GetSaplingReceiver()};
if (receiver.has_value()) {
return ShieldToAddress(m_op, sendAmount)(receiver.value());
ShieldToAddress::shieldToAddress(receiver.value(), m_op);
std::vector<RecipientMapping> recipientMappings = {RecipientMapping(uaddr, receiver.value())};
UniValue sendResult = SendTransaction(m_op->tx_, recipientMappings, std::nullopt, m_op->testmode);
m_op->set_result(sendResult);
return true;
}
// This UA must contain a transparent address, which can't be the destination of coinbase shielding.
return false;

View File

@ -99,6 +99,8 @@ class ShieldToAddress
private:
AsyncRPCOperation_shieldcoinbase *m_op;
CAmount sendAmount;
static void shieldToAddress(const libzcash::RecipientAddress& recipient, AsyncRPCOperation_shieldcoinbase *m_op);
public:
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
m_op(op), sendAmount(sendAmount) {}

View File

@ -547,6 +547,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp)
if (fHelp || params.size() != 1)
throw runtime_error(
"dumpwallet \"filename\"\n"
"\nDEPRECATED. Please use the z_exportwallet RPC instead.\n"
"\nDumps taddr wallet keys in a human-readable format. Overwriting an existing file is not permitted.\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The filename, saved in folder set by zcashd -exportdir option\n"

View File

@ -27,6 +27,7 @@
#include "primitives/transaction.h"
#include "zcbenchmarks.h"
#include "script/interpreter.h"
#include "zcash/Zcash.h"
#include "zcash/Address.hpp"
#include "zcash/address/zip32.h"
@ -3483,6 +3484,30 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr.");
}
// A non-unified address argument that is a receiver within a
// unified address known to this wallet is not allowed.
if (std::visit(match {
[&](const CKeyID& addr) {
return pwalletMain->FindUnifiedAddressByReceiver(addr).has_value();
},
[&](const CScriptID& addr) {
return pwalletMain->FindUnifiedAddressByReceiver(addr).has_value();
},
[&](const libzcash::SaplingPaymentAddress& addr) {
return pwalletMain->FindUnifiedAddressByReceiver(addr).has_value();
},
[&](const libzcash::SproutPaymentAddress& addr) {
// A unified address can't contain a Sprout receiver.
return false;
},
[&](const libzcash::UnifiedAddress& addr) {
// We allow unified addresses themselves, which cannot recurse.
return false;
}
}, decoded.value())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "The provided address is a bare receiver from a Unified Address in this wallet. Provide the full UA instead.");
}
// Visitor to support Sprout and Sapling addrs
if (!std::visit(PaymentAddressBelongsToWallet(pwalletMain), decoded.value())) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
@ -3917,6 +3942,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
if (fHelp || params.size() > 2)
throw runtime_error(
"z_gettotalbalance ( minconf includeWatchonly )\n"
"\nDEPRECATED. Please use the z_getbalanceforaccount RPC instead.\n"
"\nReturn the total value of funds stored in the node's wallet.\n"
"\nCAUTION: If the wallet contains any addresses for which it only has incoming viewing keys,"
"\nthe returned private balance may be larger than the actual balance, because spends cannot"
@ -4352,43 +4378,16 @@ size_t EstimateTxSize(
int nextBlockHeight) {
CMutableTransaction mtx;
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.nConsensusBranchId = CurrentEpochBranchId(nextBlockHeight, Params().GetConsensus());
bool fromTaddr = std::visit(match {
[&](const AccountZTXOPattern& acct) {
return
acct.GetReceiverTypes().empty() ||
acct.GetReceiverTypes().count(ReceiverType::P2PKH) > 0 ||
acct.GetReceiverTypes().count(ReceiverType::P2SH) > 0;
},
[&](const CKeyID& keyId) {
return true;
},
[&](const CScriptID& scriptId) {
return true;
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
return ufvk.GetTransparentKey().has_value();
},
[&](const libzcash::SproutPaymentAddress& addr) {
return false;
},
[&](const libzcash::SproutViewingKey& addr) {
return false;
},
[&](const libzcash::SaplingPaymentAddress& addr) {
return false;
},
[&](const libzcash::SaplingExtendedFullViewingKey& addr) {
return false;
}
}, ztxoSelector.GetPattern());
bool fromSprout = ztxoSelector.SelectsSprout();
bool fromTaddr = ztxoSelector.SelectsTransparent();
// As a sanity check, estimate and verify that the size of the transaction will be valid.
// Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid.
size_t txsize = 0;
size_t taddrRecipientCount = 0;
size_t orchardRecipientCount = 0;
for (const SendManyRecipient& recipient : recipients) {
std::visit(match {
[&](const CKeyID&) {
@ -4405,13 +4404,27 @@ size_t EstimateTxSize(
jsdesc.proof = GrothProof();
mtx.vJoinSplit.push_back(jsdesc);
},
[&](const libzcash::UnifiedAddress& ua) {
// FIXME
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unified addresses not yet supported.");
[&](const libzcash::OrchardRawAddress& addr) {
if (fromSprout) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Sending funds from a Sprout address to a Unified Address is not supported by z_sendmany");
}
orchardRecipientCount += 1;
}
}, recipient.address);
}
bool nu5Active = Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_NU5);
if (fromSprout || !nu5Active) {
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
} else {
mtx.nVersionGroupId = ZIP225_VERSION_GROUP_ID;
mtx.nVersion = ZIP225_TX_VERSION;
}
CTransaction tx(mtx);
txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion);
if (fromTaddr) {
@ -4420,6 +4433,12 @@ size_t EstimateTxSize(
}
txsize += CTXOUT_REGULAR_SIZE * taddrRecipientCount;
if (orchardRecipientCount > 0) {
// - The Orchard transaction builder pads to a minimum of 2 actions.
// - We subtract 1 because `GetSerializeSize(tx, ...)` already counts
// `ZC_ZIP225_ORCHARD_NUM_ACTIONS_BASE_SIZE`.
txsize += ZC_ZIP225_ORCHARD_BASE_SIZE - 1 + ZC_ZIP225_ORCHARD_MARGINAL_SIZE * std::max(2, (int) orchardRecipientCount);
}
return txsize;
}
@ -4582,7 +4601,12 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
}
recipients.push_back(SendManyRecipient(addr.value(), nAmount, memo));
std::optional<libzcash::UnifiedAddress> ua = std::nullopt;
if (std::holds_alternative<libzcash::UnifiedAddress>(decoded.value())) {
ua = std::get<libzcash::UnifiedAddress>(decoded.value());
}
recipients.push_back(SendManyRecipient(ua, addr.value(), nAmount, memo));
nTotalOut += nAmount;
}
if (recipients.empty()) {

View File

@ -1235,7 +1235,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
{
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
operation->main();
BOOST_CHECK(operation->isFailed());
@ -1247,7 +1247,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
{
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") };
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, taddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
operation->main();
BOOST_CHECK(operation->isFailed());
@ -1259,7 +1259,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
{
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
@ -1359,7 +1359,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true).value();
std::vector<SendManyRecipient> recipients = { SendManyRecipient(pa, 1*COIN, "ABCD") };
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, pa, 1*COIN, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);

View File

@ -6744,7 +6744,7 @@ std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(co
return std::nullopt;
}
bool ZTXOSelector::SelectsTransparent() {
bool ZTXOSelector::SelectsTransparent() const {
return std::visit(match {
[](const CKeyID& keyId) { return true; },
[](const CScriptID& scriptId) { return true; },
@ -6756,14 +6756,14 @@ bool ZTXOSelector::SelectsTransparent() {
[](const AccountZTXOPattern& acct) { return acct.IncludesP2PKH() || acct.IncludesP2SH(); }
}, this->pattern);
}
bool ZTXOSelector::SelectsSprout() {
bool ZTXOSelector::SelectsSprout() const {
return std::visit(match {
[](const libzcash::SproutViewingKey& addr) { return true; },
[](const libzcash::SproutPaymentAddress& extfvk) { return true; },
[](const auto& addr) { return false; }
}, this->pattern);
}
bool ZTXOSelector::SelectsSapling() {
bool ZTXOSelector::SelectsSapling() const {
return std::visit(match {
[](const libzcash::SaplingPaymentAddress& addr) { return true; },
[](const libzcash::SaplingExtendedSpendingKey& extfvk) { return true; },

View File

@ -144,6 +144,15 @@ struct CRecipient
bool fSubtractFeeFromAmount;
};
class RecipientMapping {
public:
std::optional<libzcash::UnifiedAddress> ua;
libzcash::RecipientAddress address;
RecipientMapping(std::optional<libzcash::UnifiedAddress> ua_, libzcash::RecipientAddress address_) :
ua(ua_), address(address_) {}
};
typedef std::map<std::string, std::string> mapValue_t;
@ -784,9 +793,9 @@ public:
return requireSpendingKeys;
}
bool SelectsTransparent();
bool SelectsSprout();
bool SelectsSapling();
bool SelectsTransparent() const;
bool SelectsSprout() const;
bool SelectsSapling() const;
};
class SpendableInputs {
@ -1572,6 +1581,32 @@ public:
*/
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
/**
* Save a set of (txid, RecipientAddress, std::optional<UnifiedAddress>) mappings to the wallet.
* This information is persisted so that it's possible to correctly display the unified
* address to which a payment was sent.
*/
template <typename RecipientMapping>
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)
{
if (recipient.ua.has_value()) {
CWalletDB(strWalletFile).WriteRecipientMapping(
txid,
recipient.address,
recipient.ua.value()
);
}
}
return true;
}
bool CommitTransaction(CWalletTx& wtxNew, std::optional<std::reference_wrapper<CReserveKey>> reservekey);
static CFeeRate minTxFee;
@ -1965,5 +2000,4 @@ public:
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::UnknownReceiver& receiver) const;
};
#endif // BITCOIN_WALLET_WALLET_H

View File

@ -12,6 +12,7 @@
#include "proof_verifier.h"
#include "protocol.h"
#include "serialize.h"
#include "script/standard.h"
#include "sync.h"
#include "util.h"
#include "utiltime.h"
@ -313,6 +314,19 @@ bool CWalletDB::WriteMinVersion(int nVersion)
return Write(std::string("minversion"), nVersion);
}
bool CWalletDB::WriteRecipientMapping(const uint256& txid, const libzcash::RecipientAddress& address, const libzcash::UnifiedAddress& ua)
{
auto recipientReceiver = libzcash::RecipientAddressToReceiver(address);
// Check that recipient address exists in given UA.
if (!ua.ContainsReceiver(recipientReceiver)) {
return false;
}
std::pair<uint256, CSerializeRecipientAddress> key = std::make_pair(txid, CSerializeRecipientAddress(address));
std::string uaString = KeyIO(Params()).EncodePaymentAddress(ua);
return Write(std::make_pair(std::string("recipientmapping"), key), uaString);
}
class CWalletScanState {
public:
unsigned int nKeys;
@ -847,6 +861,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
}
else if (strType == "recipientmapping")
{
uint256 txid;
std::string rawUa;
ssKey >> txid;
auto recipient = CSerializeRecipientAddress::Read(ssKey);
ssValue >> rawUa;
auto ua = libzcash::UnifiedAddress::Parse(Params(), rawUa);
if (!ua.has_value()) {
strErr = "Error in wallet database: non-UnifiedAddress in recipientmapping";
return false;
}
auto recipientReceiver = libzcash::RecipientAddressToReceiver(recipient);
if (!ua.value().ContainsReceiver(recipientReceiver)) {
strErr = "Error in wallet database: recipientmapping UA does not contain recipient";
return false;
}
}
} catch (...)
{
return false;

View File

@ -314,6 +314,69 @@ public:
}
};
// Serialization wrapper for reading and writing RecipientAddress
// as a pair of typecode and address bytes, similar to how unified address
// receivers are written (but excluding the unknown receiver case)
class CSerializeRecipientAddress {
libzcash::RecipientAddress recipient;
libzcash::ReceiverType typecode;
CSerializeRecipientAddress() {} // for serialization only
public:
CSerializeRecipientAddress(libzcash::RecipientAddress recipient): recipient(recipient) {}
template<typename Stream>
void Serialize(Stream& s) const {
std::visit(match {
[&](const CKeyID& keyId) {
ReceiverTypeSer(libzcash::ReceiverType::P2PKH).Serialize(s);
s << keyId;
},
[&](const CScriptID& scriptId) {
ReceiverTypeSer(libzcash::ReceiverType::P2SH).Serialize(s);
s << scriptId;
},
[&](const libzcash::SaplingPaymentAddress& saplingAddr) {
ReceiverTypeSer(libzcash::ReceiverType::Sapling).Serialize(s);
s << saplingAddr;
}
}, recipient);
}
template<typename Stream>
void Unserialize(Stream& s) {
// This cast is fine because ZIP 316 uses CompactSize serialization including the
// size limit, which means it is at most a uint32_t.
typecode = (libzcash::ReceiverType) ReadCompactSize(s);
switch (typecode) {
case libzcash::ReceiverType::P2PKH: {
CKeyID key;
s >> key;
recipient = key;
break;
}
case libzcash::ReceiverType::P2SH: {
CScriptID script;
s >> script;
recipient = script;
break;
}
case libzcash::ReceiverType::Sapling: {
libzcash::SaplingPaymentAddress saplingAddr;
s >> saplingAddr;
recipient = saplingAddr;
break;
}
}
}
template <typename Stream>
static libzcash::RecipientAddress Read(Stream& stream) {
CSerializeRecipientAddress csr;
stream >> csr;
return csr.recipient;
}
};
/** Access to the wallet database */
class CWalletDB : public CDB
@ -356,6 +419,8 @@ public:
bool WriteMinVersion(int nVersion);
bool WriteRecipientMapping(const uint256& txid, const libzcash::RecipientAddress& address, const libzcash::UnifiedAddress& ua);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
/// Erase destination data tuple from wallet database

View File

@ -16,6 +16,81 @@ namespace libzcash {
// Unified Addresses
//
static bool AddOrchardReceiver(void* ua, OrchardRawAddressPtr* ptr)
{
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(
libzcash::OrchardRawAddress::KeyIoOnlyFromReceiver(ptr));
}
/**
* `raw` MUST be 43 bytes.
*/
static bool AddSaplingReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 43),
SER_NETWORK,
PROTOCOL_VERSION);
libzcash::SaplingPaymentAddress receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
/**
* `raw` MUST be 20 bytes.
*/
static bool AddP2SHReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
CScriptID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
/**
* `raw` MUST be 20 bytes.
*/
static bool AddP2PKHReceiver(void* ua, const unsigned char* raw)
{
CDataStream ss(
reinterpret_cast<const char*>(raw),
reinterpret_cast<const char*>(raw + 20),
SER_NETWORK,
PROTOCOL_VERSION);
CKeyID receiver;
ss >> receiver;
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
static bool AddUnknownReceiver(void* ua, uint32_t typecode, const unsigned char* data, size_t len)
{
libzcash::UnknownReceiver receiver(typecode, std::vector(data, data + len));
return reinterpret_cast<libzcash::UnifiedAddress*>(ua)->AddReceiver(receiver);
}
std::optional<UnifiedAddress> UnifiedAddress::Parse(const KeyConstants& keyConstants, const std::string& str) {
libzcash::UnifiedAddress ua;
if (zcash_address_parse_unified(
str.c_str(),
keyConstants.NetworkIDString().c_str(),
&ua,
AddOrchardReceiver,
AddSaplingReceiver,
AddP2SHReceiver,
AddP2PKHReceiver,
AddUnknownReceiver)
) {
return ua;
} else {
return std::nullopt;
}
}
std::vector<const Receiver*> UnifiedAddress::GetSorted() const {
std::vector<const libzcash::Receiver*> sorted;
for (const auto& receiver : receivers) {
@ -43,6 +118,15 @@ bool UnifiedAddress::AddReceiver(Receiver receiver) {
return true;
}
bool UnifiedAddress::ContainsReceiver(const Receiver& receiver) const {
for (const auto& r : GetReceiversAsParsed()) {
if (r == receiver) {
return true;
}
}
return false;
}
std::optional<CKeyID> UnifiedAddress::GetP2PKHReceiver() const {
for (const auto& r : receivers) {
if (std::holds_alternative<CKeyID>(r)) {

View File

@ -66,6 +66,15 @@ class UnifiedAddress {
public:
UnifiedAddress() {}
static std::optional<UnifiedAddress> Parse(const KeyConstants& keyConstants, const std::string& str);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(receivers);
}
/**
* Adds the given receiver to this unified address.
*
@ -78,6 +87,8 @@ public:
*/
bool AddReceiver(Receiver receiver);
bool ContainsReceiver(const Receiver& receiver) const;
const std::vector<Receiver>& GetReceiversAsParsed() const { return receivers; }
std::set<ReceiverType> GetKnownReceiverTypes() const {

View File

@ -28,4 +28,20 @@
#define ZC_SAPLING_ENCCIPHERTEXT_SIZE (ZC_SAPLING_ENCPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES)
#define ZC_SAPLING_OUTCIPHERTEXT_SIZE (ZC_SAPLING_OUTPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES)
// - CompactSize is at least 1 byte
#define ZC_ZIP225_ORCHARD_NUM_ACTIONS_SIZE 1
#define ZC_ZIP225_ORCHARD_FLAGS_SIZE 1
#define ZC_ZIP225_ORCHARD_VALUE_BALANCE_SIZE 8
#define ZC_ZIP225_ORCHARD_ANCHOR_SIZE 32
// - CompactSize is at least 2 bytes because sizeProofsOrchard >= 253
#define ZC_ZIP225_ORCHARD_SIZE_PROOFS_BASE_SIZE 2
#define ZC_ZIP225_ORCHARD_PROOF_BASE_SIZE 2720
#define ZC_ZIP225_ORCHARD_BINDING_SIG_SIZE 64
#define ZC_ZIP225_ORCHARD_BASE_SIZE (ZC_ZIP225_ORCHARD_NUM_ACTIONS_SIZE + ZC_ZIP225_ORCHARD_FLAGS_SIZE + ZC_ZIP225_ORCHARD_VALUE_BALANCE_SIZE + ZC_ZIP225_ORCHARD_ANCHOR_SIZE + ZC_ZIP225_ORCHARD_SIZE_PROOFS_BASE_SIZE + ZC_ZIP225_ORCHARD_PROOF_BASE_SIZE + ZC_ZIP225_ORCHARD_BINDING_SIG_SIZE)
// Marginal transaction size per Orchard Action
#define ZC_ZIP225_ORCHARD_ACTION_SIZE 820
#define ZC_ZIP225_ORCHARD_SPEND_AUTH_SIG_SIZE 64
#define ZC_ZIP225_ORCHARD_PROOF_MARGINAL_SIZE 2272
#define ZC_ZIP225_ORCHARD_MARGINAL_SIZE (ZC_ZIP225_ORCHARD_ACTION_SIZE + ZC_ZIP225_ORCHARD_SPEND_AUTH_SIG_SIZE + ZC_ZIP225_ORCHARD_PROOF_MARGINAL_SIZE)
#endif // ZCASH_ZCASH_ZCASH_H

View File

@ -4,6 +4,7 @@
#include "zcash/Address.hpp"
#include "unified.h"
#include "util/match.h"
#include <rust/unified_keys.h>
@ -29,6 +30,17 @@ bool libzcash::HasTransparent(const std::set<ReceiverType>& receiverTypes) {
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_transparent) != receiverTypes.end();
}
Receiver libzcash::RecipientAddressToReceiver(const RecipientAddress& recipient) {
Receiver recipientReceiver;
std::visit(match {
[&](const CKeyID& key) { recipientReceiver = key; },
[&](const CScriptID& scriptId) { recipientReceiver = scriptId; },
[&](const libzcash::SaplingPaymentAddress& addr) { recipientReceiver = addr; }
}, recipient);
return recipientReceiver;
}
std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,

View File

@ -120,6 +120,8 @@ typedef std::variant<
CKeyID,
UnknownReceiver> Receiver;
Receiver RecipientAddressToReceiver(const RecipientAddress& recipient);
/**
* An internal identifier for a unified full viewing key, derived as a
* blake2b hash of the serialized form of the UFVK.