Add correct selection of change addresses to z_sendmany

This also alters `TransactionBuilder::SendChangeTo` to take a
`libzcash::ChangeAddress` value and thus avoids the invalid
t-addr case of CTxDestination.
This commit is contained in:
Kris Nuttycombe 2022-01-19 17:00:13 -07:00
parent 992a47103d
commit c21ffff790
21 changed files with 320 additions and 217 deletions

19
Cargo.lock generated
View File

@ -472,7 +472,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "equihash" name = "equihash"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"blake2b_simd 1.0.0", "blake2b_simd 1.0.0",
"byteorder", "byteorder",
@ -481,7 +481,7 @@ dependencies = [
[[package]] [[package]]
name = "f4jumble" name = "f4jumble"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"blake2b_simd 1.0.0", "blake2b_simd 1.0.0",
] ]
@ -1841,10 +1841,9 @@ dependencies = [
[[package]] [[package]]
name = "zcash_address" name = "zcash_address"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"bech32", "bech32",
"blake2b_simd 1.0.0",
"bs58", "bs58",
"f4jumble", "f4jumble",
"zcash_encoding", "zcash_encoding",
@ -1853,7 +1852,7 @@ dependencies = [
[[package]] [[package]]
name = "zcash_encoding" name = "zcash_encoding"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"nonempty", "nonempty",
@ -1862,7 +1861,7 @@ dependencies = [
[[package]] [[package]]
name = "zcash_history" name = "zcash_history"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"bigint", "bigint",
"blake2b_simd 1.0.0", "blake2b_simd 1.0.0",
@ -1872,7 +1871,7 @@ dependencies = [
[[package]] [[package]]
name = "zcash_note_encryption" name = "zcash_note_encryption"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"chacha20", "chacha20",
"chacha20poly1305", "chacha20poly1305",
@ -1883,7 +1882,7 @@ dependencies = [
[[package]] [[package]]
name = "zcash_primitives" name = "zcash_primitives"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"aes", "aes",
"bip0039", "bip0039",
@ -1901,11 +1900,9 @@ dependencies = [
"incrementalmerkletree", "incrementalmerkletree",
"jubjub", "jubjub",
"lazy_static", "lazy_static",
"log",
"memuse", "memuse",
"nonempty", "nonempty",
"orchard", "orchard",
"pasta_curves",
"rand", "rand",
"rand_core 0.6.3", "rand_core 0.6.3",
"sha2", "sha2",
@ -1917,7 +1914,7 @@ dependencies = [
[[package]] [[package]]
name = "zcash_proofs" name = "zcash_proofs"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=7801dddf35ca247345cf4f5c5e48791297cad531#7801dddf35ca247345cf4f5c5e48791297cad531" source = "git+https://github.com/zcash/librustzcash.git?rev=a01290869a4f0c4e05bdcca550795ea0e76e8550#a01290869a4f0c4e05bdcca550795ea0e76e8550"
dependencies = [ dependencies = [
"bellman", "bellman",
"blake2b_simd 1.0.0", "blake2b_simd 1.0.0",

View File

@ -71,8 +71,8 @@ panic = 'abort'
codegen-units = 1 codegen-units = 1
[patch.crates-io] [patch.crates-io]
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" } zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" } zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" } zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" } zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "7801dddf35ca247345cf4f5c5e48791297cad531" } zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "a01290869a4f0c4e05bdcca550795ea0e76e8550" }

View File

@ -268,17 +268,6 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue); ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
} }
TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress)
{
SelectParams(CBaseChainParams::REGTEST);
const Consensus::Params& consensusParams = Params().GetConsensus();
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_THROW(builder.SendChangeTo(taddr), UniValue);
}
TEST(TransactionBuilder, FailsWithNegativeChange) TEST(TransactionBuilder, FailsWithNegativeChange)
{ {
auto consensusParams = RegtestActivateSapling(); auto consensusParams = RegtestActivateSapling();
@ -344,7 +333,6 @@ TEST(TransactionBuilder, ChangeOutput)
CKey tsk = AddTestCKeyToKeyStore(keystore); CKey tsk = AddTestCKeyToKeyStore(keystore);
auto tkeyid = tsk.GetPubKey().GetID(); auto tkeyid = tsk.GetPubKey().GetID();
auto scriptPubKey = GetScriptForDestination(tkeyid); auto scriptPubKey = GetScriptForDestination(tkeyid);
CTxDestination taddr = tkeyid;
// No change address and no Sapling spends // No change address and no Sapling spends
{ {
@ -387,7 +375,7 @@ TEST(TransactionBuilder, ChangeOutput)
{ {
auto builder = TransactionBuilder(consensusParams, 1, &keystore); auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(taddr); builder.SendChangeTo(tkeyid, {});
auto tx = builder.Build().GetTxOrThrow(); auto tx = builder.Build().GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1); EXPECT_EQ(tx.vin.size(), 1);

View File

@ -306,6 +306,17 @@ extern "C" {
unsigned char *xfvk_i unsigned char *xfvk_i
); );
/**
* Derive the Sapling FVK for an internal BIP44 chain from the
* corresponding external chain FVK.
*/
void librustzcash_zip32_sapling_derive_internal_fvk(
const unsigned char *fvk,
const unsigned char *dk,
unsigned char *fvk_ret,
unsigned char *dk_ret
);
/** /**
* Derive a PaymentAddress from a (SaplingFullViewingKey, DiversifierKey) * Derive a PaymentAddress from a (SaplingFullViewingKey, DiversifierKey)
* pair. Returns 'false' if no valid address can be derived for the * pair. Returns 'false' if no valid address can be derived for the

View File

@ -55,7 +55,7 @@ use zcash_primitives::{
}, },
sapling::{merkle_hash, spend_sig}, sapling::{merkle_hash, spend_sig},
transaction::components::Amount, transaction::components::Amount,
zip32::{self, sapling_address, sapling_find_address}, zip32::{self, sapling_address, sapling_derive_internal_fvk, sapling_find_address},
}; };
use zcash_proofs::{ use zcash_proofs::{
circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH, circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH,
@ -1081,6 +1081,25 @@ pub extern "C" fn librustzcash_zip32_xfvk_derive(
true true
} }
/// Derive the Sapling internal
#[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_derive_internal_fvk(
fvk: *const [c_uchar; 96],
dk: *const [c_uchar; 32],
fvk_ret: *mut [c_uchar; 96],
dk_ret: *mut [c_uchar; 32],
) {
let fvk = FullViewingKey::read(&unsafe { *fvk }[..]).expect("valid Sapling FullViewingKey");
let dk = zip32::DiversifierKey(unsafe { *dk });
let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&fvk, &dk);
let fvk_ret = unsafe { &mut *fvk_ret };
let dk_ret = unsafe { &mut *dk_ret };
fvk_ret.copy_from_slice(&fvk_internal.to_bytes());
dk_ret.copy_from_slice(&dk_internal.0);
}
/// Derive a PaymentAddress from an ExtendedFullViewingKey. /// Derive a PaymentAddress from an ExtendedFullViewingKey.
#[no_mangle] #[no_mangle]
pub extern "C" fn librustzcash_zip32_sapling_address( pub extern "C" fn librustzcash_zip32_sapling_address(

View File

@ -280,29 +280,31 @@ void TransactionBuilder::SetFee(CAmount fee)
this->fee = fee; this->fee = fee;
} }
void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk) // TODO: remove support for transparent change?
{ void TransactionBuilder::SendChangeTo(
saplingChangeAddr = std::make_pair(ovk, changeAddr); const libzcash::RecipientAddress& changeAddr,
sproutChangeAddr = std::nullopt; const uint256& ovk) {
tChangeAddr = std::nullopt; tChangeAddr = std::nullopt;
}
void TransactionBuilder::SendChangeTo(libzcash::SproutPaymentAddress changeAddr)
{
sproutChangeAddr = changeAddr;
saplingChangeAddr = std::nullopt;
tChangeAddr = std::nullopt;
}
void TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
{
if (!IsValidDestination(changeAddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid change address, not a valid taddr.");
}
tChangeAddr = changeAddr;
saplingChangeAddr = std::nullopt; saplingChangeAddr = std::nullopt;
sproutChangeAddr = std::nullopt; sproutChangeAddr = std::nullopt;
std::visit(match {
[&](const CKeyID& keyId) {
tChangeAddr = keyId;
},
[&](const CScriptID& scriptId) {
tChangeAddr = scriptId;
},
[&](const libzcash::SaplingPaymentAddress& changeDest) {
saplingChangeAddr = std::make_pair(ovk, changeDest);
}
}, changeAddr);
}
void TransactionBuilder::SendChangeToSprout(const libzcash::SproutPaymentAddress& zaddr) {
tChangeAddr = std::nullopt;
saplingChangeAddr = std::nullopt;
sproutChangeAddr = zaddr;
} }
TransactionBuilderResult TransactionBuilder::Build() TransactionBuilderResult TransactionBuilder::Build()

View File

@ -13,6 +13,7 @@
#include "script/script.h" #include "script/script.h"
#include "script/standard.h" #include "script/standard.h"
#include "uint256.h" #include "uint256.h"
#include "util/match.h"
#include "zcash/Address.hpp" #include "zcash/Address.hpp"
#include "zcash/IncrementalMerkleTree.hpp" #include "zcash/IncrementalMerkleTree.hpp"
#include "zcash/JoinSplit.hpp" #include "zcash/JoinSplit.hpp"
@ -170,11 +171,8 @@ public:
void AddTransparentOutput(const CTxDestination& to, CAmount value); void AddTransparentOutput(const CTxDestination& to, CAmount value);
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk); void SendChangeTo(const libzcash::RecipientAddress& changeAddr, const uint256& ovk);
void SendChangeToSprout(const libzcash::SproutPaymentAddress& changeAddr);
void SendChangeTo(libzcash::SproutPaymentAddress);
void SendChangeTo(CTxDestination& changeAddr);
TransactionBuilderResult Build(); TransactionBuilderResult Build();

View File

@ -6,6 +6,8 @@
extern UniValue signrawtransaction(const UniValue& params, bool fHelp); 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 SendTransaction(CTransaction& tx, std::optional<std::reference_wrapper<CReserveKey>> reservekey, bool testmode) {
UniValue o(UniValue::VOBJ); UniValue o(UniValue::VOBJ);
// Send the transaction // Send the transaction

View File

@ -60,31 +60,6 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
assert(!recipients_.empty()); assert(!recipients_.empty());
assert(ztxoSelector.RequireSpendingKeys()); assert(ztxoSelector.RequireSpendingKeys());
std::visit(match {
[&](const AccountZTXOPattern& acct) {
isfromtaddr_ =
acct.GetReceiverTypes().empty() ||
acct.GetReceiverTypes().count(ReceiverType::P2PKH) > 0 ||
acct.GetReceiverTypes().count(ReceiverType::P2SH) > 0;
},
[&](const CKeyID& keyId) {
isfromtaddr_ = true;
},
[&](const CScriptID& scriptId) {
isfromtaddr_ = true;
},
[&](const libzcash::SproutPaymentAddress& addr) {
isfromsprout_ = true;
},
[&](const libzcash::SaplingPaymentAddress& addr) {
isfromsapling_ = true;
}
}, ztxoSelector.GetPattern());
if ((isfromsprout_ || isfromsapling_) && minDepth == 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be zero when sending from a shielded address");
}
// calculate the target totals // calculate the target totals
for (const SendManyRecipient& recipient : recipients_) { for (const SendManyRecipient& recipient : recipients_) {
std::visit(match { std::visit(match {
@ -98,7 +73,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
}, },
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
txOutputAmounts_.z_outputs_total += recipient.amount; txOutputAmounts_.z_outputs_total += recipient.amount;
if (isfromsprout_ && !allowRevealedAmounts_) { if (ztxoSelector_.SelectsSprout() && !allowRevealedAmounts_) {
throw JSONRPCError( throw JSONRPCError(
RPC_INVALID_PARAMETER, RPC_INVALID_PARAMETER,
"Sending between shielded pools is not enabled by default because it will " "Sending between shielded pools is not enabled by default because it will "
@ -192,10 +167,6 @@ void AsyncRPCOperation_sendmany::main() {
// //
// At least 4. and 5. differ from the Rust transaction builder. // At least 4. and 5. differ from the Rust transaction builder.
uint256 AsyncRPCOperation_sendmany::main_impl() { uint256 AsyncRPCOperation_sendmany::main_impl() {
// TODO UA: this check will become meaningless.
bool isfromzaddr_ = isfromsprout_ || isfromsapling_;
assert(isfromtaddr_ != isfromzaddr_);
CAmount sendAmount = txOutputAmounts_.z_outputs_total + txOutputAmounts_.t_outputs_total; CAmount sendAmount = txOutputAmounts_.z_outputs_total + txOutputAmounts_.t_outputs_total;
CAmount targetAmount = sendAmount + fee_; CAmount targetAmount = sendAmount + fee_;
@ -245,14 +216,6 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
spendable.LogInputs(getId()); spendable.LogInputs(getId());
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
//
// TODO: This restriction is true by construction as we have no mechanism
// for filtering for notes that will select both Sprout and Sapling notes
// simultaneously, but even if we did it would likely be safe to remove
// this limitation.
assert(spendable.sproutNoteEntries.empty() || spendable.saplingNoteEntries.empty());
CAmount t_inputs_total{0}; CAmount t_inputs_total{0};
CAmount z_inputs_total{0}; CAmount z_inputs_total{0};
for (const auto& t : spendable.utxos) { for (const auto& t : spendable.utxos) {
@ -265,25 +228,16 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
z_inputs_total += t.note.value(); z_inputs_total += t.note.value();
} }
// TODO UA: these restrictions should be removed. if (z_inputs_total > 0 && mindepth_ == 0) {
assert(!isfromtaddr_ || z_inputs_total == 0); throw JSONRPCError(
assert(!isfromzaddr_ || t_inputs_total == 0); RPC_INVALID_PARAMETER,
"Minconf cannot be zero when sending from a shielded address");
if (isfromtaddr_ && (t_inputs_total < targetAmount)) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf("Insufficient transparent funds, have %s, need %s",
FormatMoney(t_inputs_total), FormatMoney(targetAmount)));
}
if (isfromzaddr_ && (z_inputs_total < targetAmount)) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
strprintf("Insufficient shielded funds, have %s, need %s",
FormatMoney(z_inputs_total), FormatMoney(targetAmount)));
} }
// When spending transparent coinbase outputs, all inputs must be fully // When spending transparent coinbase outputs, all inputs must be fully
// consumed, and they may only be sent to shielded recipients. // consumed, and they may only be sent to shielded recipients.
if (spendable.HasTransparentCoinbase()) { if (spendable.HasTransparentCoinbase()) {
if (t_inputs_total != targetAmount) { if (t_inputs_total + z_inputs_total != targetAmount) {
throw JSONRPCError( throw JSONRPCError(
RPC_WALLET_ERROR, RPC_WALLET_ERROR,
strprintf( strprintf(
@ -299,66 +253,55 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
} }
} }
if (isfromtaddr_) { LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
LogPrint("zrpc", "%s: spending %s to send %s with fee %s\n", getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
} else {
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n",
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(fee_));
}
LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total)); LogPrint("zrpc", "%s: transparent input: %s (to choose from)\n", getId(), FormatMoney(t_inputs_total));
LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total)); LogPrint("zrpcunsafe", "%s: private input: %s (to choose from)\n", getId(), FormatMoney(z_inputs_total));
LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(txOutputAmounts_.t_outputs_total)); LogPrint("zrpc", "%s: transparent output: %s\n", getId(), FormatMoney(txOutputAmounts_.t_outputs_total));
LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(txOutputAmounts_.z_outputs_total)); LogPrint("zrpcunsafe", "%s: private output: %s\n", getId(), FormatMoney(txOutputAmounts_.z_outputs_total));
LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(fee_)); LogPrint("zrpc", "%s: fee: %s\n", getId(), FormatMoney(fee_));
CReserveKey keyChange(pwalletMain); auto ovks = this->SelectOVKs();
uint256 ovk; auto selectorAccountId = pwalletMain->FindAccountForSelector(ztxoSelector_);
auto getDefaultOVK = [&]() {
HDSeed seed = pwalletMain->GetHDSeedForRPC();
return ovkForShieldingFromTaddr(seed);
};
auto setTransparentChangeRecipient = [&]() {
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
CPubKey vchPubKey;
bool ret = keyChange.GetReservedKey(vchPubKey);
if (!ret) {
// should never fail, as we just unlocked
throw JSONRPCError(
RPC_WALLET_KEYPOOL_RAN_OUT,
"Could not generate a taddr to use as a change address");
}
CTxDestination changeAddr = vchPubKey.GetID();
builder_.SendChangeTo(changeAddr);
};
// FIXME: use the appropriate shielded pool change address for the
// source unified address account (or the legacy account), and the
// associated OVK
std::visit(match { std::visit(match {
[&](const CKeyID& keyId) {
auto accountId = selectorAccountId.value_or(ZCASH_LEGACY_ACCOUNT);
builder_.SendChangeTo(
pwalletMain->GenerateChangeAddressForAccount(accountId, { libzcash::ChangeType::Sapling }).value(),
ovks.first);
},
[&](const CScriptID& scriptId) {
auto accountId = selectorAccountId.value_or(ZCASH_LEGACY_ACCOUNT);
builder_.SendChangeTo(
pwalletMain->GenerateChangeAddressForAccount(accountId, { libzcash::ChangeType::Sapling }).value(),
ovks.first);
},
[&](const libzcash::SproutPaymentAddress& addr) { [&](const libzcash::SproutPaymentAddress& addr) {
ovk = getDefaultOVK(); // for Sprout, we return change to the originating address.
builder_.SendChangeTo(addr); builder_.SendChangeToSprout(addr);
}, },
[&](const libzcash::SaplingPaymentAddress& addr) { [&](const libzcash::SaplingPaymentAddress& addr) {
libzcash::SaplingExtendedSpendingKey saplingKey; // for Sapling, if using a legacy address, return change to the
assert(pwalletMain->GetSaplingExtendedSpendingKey(addr, saplingKey)); // originating address; otherwise return it to the Sapling internal
// address corresponding to the UFVK.
ovk = saplingKey.expsk.full_viewing_key().ovk; if (selectorAccountId.has_value()) {
builder_.SendChangeTo(addr, ovk); auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
selectorAccountId.value(), { libzcash::ChangeType::Sapling }).value();
builder_.SendChangeTo(changeAddr, ovks.first);
} else {
builder_.SendChangeTo(addr, ovks.first);
}
}, },
[&](const auto& other) { [&](const AccountZTXOPattern& acct) {
ovk = getDefaultOVK(); auto changeAddr = pwalletMain->GenerateChangeAddressForAccount(
setTransparentChangeRecipient(); selectorAccountId.value(), { libzcash::ChangeType::Sapling });
assert(changeAddr.has_value());
builder_.SendChangeTo(changeAddr.value(), ovks.first);
} }
}, ztxoSelector_.GetPattern()); }, ztxoSelector_.GetPattern());
// Track the total of notes that we've added to the builder // Track the total of notes that we've added to the builder. This
// shouldn't strictly be necessary, given `spendable.LimitToAmount`
CAmount sum = 0; CAmount sum = 0;
// Create Sapling outpoints // Create Sapling outpoints
@ -410,7 +353,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
auto value = r.amount; auto value = r.amount;
auto memo = get_memo_from_hex_string(r.memo.has_value() ? r.memo.value() : ""); auto memo = get_memo_from_hex_string(r.memo.has_value() ? r.memo.value() : "");
builder_.AddSaplingOutput(ovk, addr, value, memo); builder_.AddSaplingOutput(ovks.second, addr, value, memo);
} }
}, r.address); }, r.address);
} }
@ -466,12 +409,20 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
auto buildResult = builder_.Build(); auto buildResult = builder_.Build();
auto tx = buildResult.GetTxOrThrow(); auto tx = buildResult.GetTxOrThrow();
UniValue sendResult = SendTransaction(tx, keyChange, testmode); UniValue sendResult = SendTransaction(tx, std::nullopt, testmode);
set_result(sendResult); set_result(sendResult);
return tx.GetHash(); return tx.GetHash();
} }
std::pair<uint256, uint256> AsyncRPCOperation_sendmany::SelectOVKs() const {
//TODO
uint256 internalOVK;
uint256 externalOVK;
return std::make_pair(internalOVK, externalOVK);
}
/** /**
* Compute a dust threshold based upon a standard p2pkh txout. * Compute a dust threshold based upon a standard p2pkh txout.
*/ */
@ -523,4 +474,3 @@ UniValue AsyncRPCOperation_sendmany::getStatus() const {
obj.pushKV("params", contextinfo_ ); obj.pushKV("params", contextinfo_ );
return obj; return obj;
} }

View File

@ -75,13 +75,19 @@ private:
CAmount fee_; CAmount fee_;
UniValue contextinfo_; // optional data to include in return value from getStatus() UniValue contextinfo_; // optional data to include in return value from getStatus()
bool isfromtaddr_{false};
bool isfromsprout_{false}; bool isfromsprout_{false};
bool isfromsapling_{false}; bool isfromsapling_{false};
bool allowRevealedAmounts_{false}; bool allowRevealedAmounts_{false};
uint32_t transparentRecipients_{0}; uint32_t transparentRecipients_{0};
TxOutputAmounts txOutputAmounts_; TxOutputAmounts txOutputAmounts_;
/**
* Compute the internal and external OVKs to use in transaction construction, given
* the payment source and the set of types that correspond to outputs selected for
* being spent in the transaction.
*/
std::pair<uint256, uint256> SelectOVKs() const;
static CAmount DefaultDustThreshold(); static CAmount DefaultDustThreshold();
static std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s); static std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
@ -89,7 +95,6 @@ private:
uint256 main_impl(); uint256 main_impl();
}; };
// To test private methods, a friend class can act as a proxy // To test private methods, a friend class can act as a proxy
class TEST_FRIEND_AsyncRPCOperation_sendmany { class TEST_FRIEND_AsyncRPCOperation_sendmany {
public: public:

View File

@ -258,6 +258,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c
// generate a common one from the HD seed. This ensures the data is // generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32 // recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using. // Sapling key hierarchy, which the user might not be using.
// FIXME: update to use the ZIP-316 OVK
HDSeed seed = pwalletMain->GetHDSeedForRPC(); HDSeed seed = pwalletMain->GetHDSeedForRPC();
uint256 ovk = ovkForShieldingFromTaddr(seed); uint256 ovk = ovkForShieldingFromTaddr(seed);

View File

@ -2199,7 +2199,7 @@ TEST(WalletTests, GenerateUnifiedAddress) {
ua->first.GetSaplingReceiver(), ua->first.GetSaplingReceiver(),
ufvk.GetSaplingKey().value().Address(ua->second)); ufvk.GetSaplingKey().value().Address(ua->second));
auto u4r = wallet.GetUnifiedForReceiver(ua->first.GetSaplingReceiver().value()); auto u4r = wallet.FindUnifiedAddressByReceiver(ua->first.GetSaplingReceiver().value());
EXPECT_EQ(u4r, ua->first); EXPECT_EQ(u4r, ua->first);
// Explicitly trigger the invalid transparent child index failure // Explicitly trigger the invalid transparent child index failure

View File

@ -4044,7 +4044,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
// If the note belongs to a Sapling address that is part of an account in the // If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address. // wallet, show the corresponding Unified Address.
std::string address; std::string address;
const auto ua = pwalletMain->GetUnifiedForReceiver(pa); const auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) { if (ua.has_value()) {
address = keyIO.EncodePaymentAddress(ua.value()); address = keyIO.EncodePaymentAddress(ua.value());
} else { } else {
@ -4097,7 +4097,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
// If the note belongs to a Sapling address that is part of an account in the // If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address. // wallet, show the corresponding Unified Address.
std::string address; std::string address;
const auto ua = pwalletMain->GetUnifiedForReceiver(pa); const auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) { if (ua.has_value()) {
address = keyIO.EncodePaymentAddress(ua.value()); address = keyIO.EncodePaymentAddress(ua.value());
} else { } else {

View File

@ -536,7 +536,7 @@ bool CWallet::AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &uf
return CWalletDB(strWalletFile).WriteUnifiedFullViewingKey(ufvk); return CWalletDB(strWalletFile).WriteUnifiedFullViewingKey(ufvk);
} }
std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) { std::optional<ZcashdUnifiedFullViewingKey> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) const {
if (!mnemonicHDChain.has_value()) { if (!mnemonicHDChain.has_value()) {
throw std::runtime_error( throw std::runtime_error(
"CWallet::GetUnifiedFullViewingKeyByAccount(): Wallet is missing mnemonic seed metadata."); "CWallet::GetUnifiedFullViewingKeyByAccount(): Wallet is missing mnemonic seed metadata.");
@ -1546,6 +1546,22 @@ bool CWallet::SelectorMatchesAddress(
}, selector.GetPattern()); }, selector.GetPattern());
} }
std::optional<RecipientAddress> CWallet::GenerateChangeAddressForAccount(
libzcash::AccountId accountId,
std::set<libzcash::ChangeType> changeOptions) {
auto ufvk = this->GetUnifiedFullViewingKeyByAccount(accountId);
if (ufvk.has_value()) {
for (libzcash::ChangeType t : changeOptions) {
if (t == libzcash::ChangeType::Transparent && accountId == ZCASH_LEGACY_ACCOUNT) {
return GenerateNewKey().GetID();
} else {
return ufvk.value().GetChangeAddress(changeOptions);
}
}
}
return std::nullopt;
}
SpendableInputs CWallet::FindSpendableInputs( SpendableInputs CWallet::FindSpendableInputs(
ZTXOSelector selector, ZTXOSelector selector,
bool allowTransparentCoinbase, bool allowTransparentCoinbase,
@ -5980,8 +5996,12 @@ std::optional<libzcash::AccountId> CWallet::GetUnifiedAccountId(const libzcash::
} }
} }
std::optional<UnifiedAddress> CWallet::GetUnifiedForReceiver(const Receiver& receiver) const { std::optional<ZcashdUnifiedFullViewingKey> CWallet::FindUFVKByReceiver(const libzcash::Receiver& receiver) const {
return std::visit(LookupUnifiedAddress(*this), receiver); return std::visit(UFVKForReceiver(*this), receiver);
}
std::optional<UnifiedAddress> CWallet::FindUnifiedAddressByReceiver(const Receiver& receiver) const {
return std::visit(UnifiedAddressForReceiver(*this), receiver);
} }
// //
@ -6213,7 +6233,6 @@ KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SaplingExtendedFu
return KeyNotAdded; return KeyNotAdded;
} }
} }
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::UnifiedFullViewingKey& no) const { KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::UnifiedFullViewingKey& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unified full viewing key import is not yet supported."); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unified full viewing key import is not yet supported.");
} }
@ -6271,14 +6290,53 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
} }
} }
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const { // UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr); auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
if (ufvkPair.has_value()) { if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first; auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid); auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
if (!(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value())) { // If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no Sapling key part."); // return a non-nullopt value, and since we obtained that metadata by
} // lookup from as Sapling address, it should have a Sapling key component.
assert(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value());
return ufvk.value();
} else {
return std::nullopt;
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const CScriptID& scriptId) const {
// We do not currently generate unified addresses containing P2SH components,
// so there's nothing to look up here.
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;
// transparent address UFVK metadata is always accompanied by the child
// index at which the address was produced
assert(ufvkPair.value().second.has_value());
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value());
return ufvk.value();
} else {
return std::nullopt;
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::UnknownReceiver& receiver) const {
return std::nullopt;
}
// UnifiedAddressForReceiver :: (CWallet&, Receiver) -> std::optional<UnifiedAddress>
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 ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value() && ufvk.value().GetSaplingKey().has_value());
diversifier_index_t j; diversifier_index_t j;
// If the wallet is missing metadata at this UFVK id, it is probably // If the wallet is missing metadata at this UFVK id, it is probably
@ -6300,10 +6358,12 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const l
return std::nullopt; return std::nullopt;
} }
} }
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CScriptID& scriptId) const { std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CScriptID& scriptId) const {
// We do not currently generate unified addresses containing P2SH components,
// so there's nothing to look up here.
return std::nullopt; return std::nullopt;
} }
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const CKeyID& keyId) const { std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const CKeyID& keyId) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId); auto ufvkPair = wallet.GetUFVKMetadataForReceiver(keyId);
if (ufvkPair.has_value()) { if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first; auto ufvkid = ufvkPair.value().first;
@ -6313,7 +6373,7 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const C
diversifier_index_t j = ufvkPair.value().second.value(); diversifier_index_t j = ufvkPair.value().second.value();
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid); auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
if (!(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value())) { if (!(ufvk.has_value() && ufvk.value().GetTransparentKey().has_value())) {
throw std::runtime_error("CWallet::LookupUnifiedAddress(): UFVK has no P2PKH key part."); throw std::runtime_error("CWallet::UnifiedAddressForReceiver(): UFVK has no P2PKH key part.");
} }
// If the wallet is missing metadata at this UFVK id, it is probably // If the wallet is missing metadata at this UFVK id, it is probably
@ -6333,7 +6393,7 @@ std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const C
return std::nullopt; return std::nullopt;
} }
} }
std::optional<libzcash::UnifiedAddress> LookupUnifiedAddress::operator()(const libzcash::UnknownReceiver& receiver) const { std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(const libzcash::UnknownReceiver& receiver) const {
return std::nullopt; return std::nullopt;
} }

View File

@ -1256,6 +1256,10 @@ public:
*/ */
std::optional<libzcash::AccountId> FindAccountForSelector(const ZTXOSelector& paymentSource) const; std::optional<libzcash::AccountId> FindAccountForSelector(const ZTXOSelector& paymentSource) const;
std::optional<libzcash::RecipientAddress> GenerateChangeAddressForAccount(
libzcash::AccountId accountId,
std::set<libzcash::ChangeType> changeOptions);
SpendableInputs FindSpendableInputs( SpendableInputs FindSpendableInputs(
ZTXOSelector paymentSource, ZTXOSelector paymentSource,
bool allowTransparentCoinbase, bool allowTransparentCoinbase,
@ -1413,7 +1417,7 @@ public:
//! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account. //! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account.
std::optional<libzcash::ZcashdUnifiedFullViewingKey> std::optional<libzcash::ZcashdUnifiedFullViewingKey>
GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account); GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account) const;
//! Generate a new unified address for the specified account, diversifier, and //! Generate a new unified address for the specified account, diversifier, and
//! set of receiver types. //! set of receiver types.
@ -1433,7 +1437,9 @@ public:
std::optional<libzcash::UFVKId> FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const; std::optional<libzcash::UFVKId> FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const;
std::optional<libzcash::AccountId> GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const; std::optional<libzcash::AccountId> GetUnifiedAccountId(const libzcash::UFVKId& ufvkId) const;
std::optional<libzcash::UnifiedAddress> GetUnifiedForReceiver(const libzcash::Receiver& receiver) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> FindUFVKByReceiver(const libzcash::Receiver& receiver) const;
std::optional<libzcash::UnifiedAddress> FindUnifiedAddressByReceiver(const libzcash::Receiver& receiver) const;
/** /**
* Increment the next transaction order id * Increment the next transaction order id
@ -1857,12 +1863,25 @@ public:
KeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const; KeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
}; };
class LookupUnifiedAddress { class UFVKForReceiver {
private: private:
const CWallet& wallet; const CWallet& wallet;
public: public:
LookupUnifiedAddress(const CWallet& wallet): wallet(wallet) {} UFVKForReceiver(const CWallet& wallet): wallet(wallet) {}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CScriptID& scriptId) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const CKeyID& keyId) const;
std::optional<libzcash::ZcashdUnifiedFullViewingKey> operator()(const libzcash::UnknownReceiver& receiver) const;
};
class UnifiedAddressForReceiver {
private:
const CWallet& wallet;
public:
UnifiedAddressForReceiver(const CWallet& wallet): wallet(wallet) {}
std::optional<libzcash::UnifiedAddress> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const; std::optional<libzcash::UnifiedAddress> operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const;
std::optional<libzcash::UnifiedAddress> operator()(const CScriptID& scriptId) const; std::optional<libzcash::UnifiedAddress> operator()(const CScriptID& scriptId) const;

View File

@ -23,39 +23,6 @@ const unsigned char ZCASH_UFVK_ID_PERSONAL[BLAKE2bPersonalBytes] =
namespace libzcash { namespace libzcash {
class UnknownReceiver {
public:
uint32_t typecode;
std::vector<uint8_t> data;
UnknownReceiver(uint32_t typecode, std::vector<uint8_t> data) :
typecode(typecode), data(data) {}
friend inline bool operator==(const UnknownReceiver& a, const UnknownReceiver& b) {
return a.typecode == b.typecode && a.data == b.data;
}
friend inline bool operator<(const UnknownReceiver& a, const UnknownReceiver& b) {
// We don't know for certain the preference order of unknown receivers, but it is
// _likely_ that the higher typecode has higher preference. The exact sort order
// doesn't really matter, as unknown receivers have lower preference than known
// receivers.
return (a.typecode > b.typecode ||
(a.typecode == b.typecode && a.data < b.data));
}
};
/**
* Receivers that can appear in a Unified Address.
*
* These types are given in order of preference (as defined in ZIP 316), so that sorting
* variants by `operator<` is equivalent to sorting by preference.
*/
typedef std::variant<
SaplingPaymentAddress,
CScriptID,
CKeyID,
UnknownReceiver> Receiver;
bool HasKnownReceiverType(const Receiver& receiver); bool HasKnownReceiverType(const Receiver& receiver);
struct ReceiverIterator { struct ReceiverIterator {
@ -90,11 +57,6 @@ private:
size_t cur; size_t cur;
}; };
/** A recipient address to which a unified address can be resolved */
typedef std::variant<
CKeyID,
CScriptID,
libzcash::SaplingPaymentAddress> RecipientAddress;
class UnifiedAddress { class UnifiedAddress {
std::vector<Receiver> receivers; std::vector<Receiver> receivers;

View File

@ -17,6 +17,7 @@ const size_t SerializedSaplingPaymentAddressSize = 43;
const size_t SerializedSaplingFullViewingKeySize = 96; const size_t SerializedSaplingFullViewingKeySize = 96;
const size_t SerializedSaplingExpandedSpendingKeySize = 96; const size_t SerializedSaplingExpandedSpendingKeySize = 96;
const size_t SerializedSaplingSpendingKeySize = 32; const size_t SerializedSaplingSpendingKeySize = 32;
const size_t SerializedSaplingDiversifierKeySize = 32;
typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t; typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t;
@ -133,7 +134,7 @@ public:
SaplingExpandedSpendingKey expanded_spending_key() const; SaplingExpandedSpendingKey expanded_spending_key() const;
SaplingFullViewingKey full_viewing_key() const; SaplingFullViewingKey full_viewing_key() const;
// Can derive Sapling addr from default diversifier // Can derive Sapling addr from default diversifier
SaplingPaymentAddress default_address() const; SaplingPaymentAddress default_address() const;
}; };

View File

@ -134,3 +134,7 @@ std::optional<std::pair<UnifiedAddress, diversifier_index_t>> ZcashdUnifiedFullV
const diversifier_index_t& j) const { const diversifier_index_t& j) const {
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling}); return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling});
} }
RecipientAddress ZcashdUnifiedFullViewingKey::GetChangeAddress(const std::set<ChangeType>& changeOptions) const {
throw std::runtime_error("TODO");
}

View File

@ -7,6 +7,7 @@
#include "bip44.h" #include "bip44.h"
#include "key_constants.h" #include "key_constants.h"
#include "script/script.h"
#include "zip32.h" #include "zip32.h"
namespace libzcash { namespace libzcash {
@ -18,6 +19,21 @@ enum class ReceiverType: uint32_t {
//Orchard = 0x03 //Orchard = 0x03
}; };
/** A recipient address to which a unified address can be resolved */
typedef std::variant<
CKeyID,
CScriptID,
libzcash::SaplingPaymentAddress> RecipientAddress;
/**
* An enumeration of the types of change that a transaction may
* produce.
*/
enum class ChangeType {
Sapling,
Transparent,
};
/** /**
* Test whether the specified list of receiver types contains a * Test whether the specified list of receiver types contains a
* shielded receiver type * shielded receiver type
@ -32,7 +48,41 @@ bool HasTransparent(const std::set<ReceiverType>& receiverTypes);
class ZcashdUnifiedSpendingKey; class ZcashdUnifiedSpendingKey;
class UnknownReceiver {
public:
uint32_t typecode;
std::vector<uint8_t> data;
UnknownReceiver(uint32_t typecode, std::vector<uint8_t> data) :
typecode(typecode), data(data) {}
friend inline bool operator==(const UnknownReceiver& a, const UnknownReceiver& b) {
return a.typecode == b.typecode && a.data == b.data;
}
friend inline bool operator<(const UnknownReceiver& a, const UnknownReceiver& b) {
// We don't know for certain the preference order of unknown receivers, but it is
// _likely_ that the higher typecode has higher preference. The exact sort order
// doesn't really matter, as unknown receivers have lower preference than known
// receivers.
return (a.typecode > b.typecode ||
(a.typecode == b.typecode && a.data < b.data));
}
};
/**
* Receivers that can appear in a Unified Address.
*
* These types are given in order of preference (as defined in ZIP 316), so that sorting
* variants by `operator<` is equivalent to sorting by preference.
*/
typedef std::variant<
SaplingPaymentAddress,
CScriptID,
CKeyID,
UnknownReceiver> Receiver;
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp) // prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
// TODO: ZIP-316 encoding should probably be moved here
class UnifiedAddress; class UnifiedAddress;
class UnifiedFullViewingKey; class UnifiedFullViewingKey;
@ -116,6 +166,14 @@ public:
*/ */
std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(const diversifier_index_t& j) const; std::optional<std::pair<UnifiedAddress, diversifier_index_t>> FindAddress(const diversifier_index_t& j) const;
/**
* Return the change address for this UFVK, given the provided
* set of receiver types for pools involved in this transaction.
* If the provided set is empty, return the change address
* corresponding to the most-preferred pool.
*/
RecipientAddress GetChangeAddress(const std::set<ChangeType>& changeOptions) const;
friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b) friend bool operator==(const ZcashdUnifiedFullViewingKey& a, const ZcashdUnifiedFullViewingKey& b)
{ {
return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey; return a.transparentKey == b.transparentKey && a.saplingKey == b.saplingKey;

View File

@ -126,6 +126,25 @@ libzcash::SaplingPaymentAddress SaplingDiversifiableFullViewingKey::DefaultAddre
} }
} }
libzcash::SaplingPaymentAddress SaplingDiversifiableFullViewingKey::GetChangeAddress() const {
CDataStream ss_fvk(SER_NETWORK, PROTOCOL_VERSION);
ss_fvk << fvk;
CSerializeData fvk_bytes(ss_fvk.begin(), ss_fvk.end());
SaplingDiversifiableFullViewingKey internalDFVK;
CSerializeData fvk_bytes_ret(libzcash::SerializedSaplingFullViewingKeySize);
librustzcash_zip32_sapling_derive_internal_fvk(
reinterpret_cast<unsigned char*>(fvk_bytes.data()),
dk.begin(),
reinterpret_cast<unsigned char*>(fvk_bytes_ret.data()),
internalDFVK.dk.begin());
CDataStream ss_fvk_ret(fvk_bytes_ret, SER_NETWORK, PROTOCOL_VERSION);
ss_fvk_ret >> internalDFVK.fvk;
return internalDFVK.DefaultAddress();
}
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Master(const HDSeed& seed) SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Master(const HDSeed& seed)
{ {
auto rawSeed = seed.RawSeed(); auto rawSeed = seed.RawSeed();
@ -208,6 +227,10 @@ SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const
return ret; return ret;
} }
SaplingExtendedSpendingKey SaplingExtendedSpendingKey::DeriveInternalKey() const {
throw std::runtime_error("Not yet implemented");
}
HDKeyPath Zip32AccountKeyPath( HDKeyPath Zip32AccountKeyPath(
uint32_t bip44CoinType, uint32_t bip44CoinType,
libzcash::AccountId accountId, libzcash::AccountId accountId,

View File

@ -155,6 +155,8 @@ public:
libzcash::SaplingPaymentAddress DefaultAddress() const; libzcash::SaplingPaymentAddress DefaultAddress() const;
libzcash::SaplingPaymentAddress GetChangeAddress() const;
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
@ -237,11 +239,12 @@ struct SaplingExtendedSpendingKey {
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId); static std::pair<SaplingExtendedSpendingKey, HDKeyPath> ForAccount(const HDSeed& seed, uint32_t bip44CoinType, libzcash::AccountId accountId);
static std::pair<SaplingExtendedSpendingKey, HDKeyPath> Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex); static std::pair<SaplingExtendedSpendingKey, HDKeyPath> Legacy(const HDSeed& seed, uint32_t bip44CoinType, uint32_t addressIndex);
SaplingExtendedSpendingKey Derive(uint32_t i) const; SaplingExtendedSpendingKey Derive(uint32_t i) const;
SaplingExtendedFullViewingKey ToXFVK() const; SaplingExtendedFullViewingKey ToXFVK() const;
SaplingExtendedSpendingKey DeriveInternalKey() const;
friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b) friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b)
{ {
return a.depth == b.depth && return a.depth == b.depth &&