Merge pull request #615 from nuttycom/wallet/shield_to_ufvk

Shield funds to the internal Sapling key for a specified account.
This commit is contained in:
Kris Nuttycombe 2022-08-26 16:45:15 -06:00 committed by GitHub
commit 8c00ca3b88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 21 deletions

View File

@ -2,6 +2,7 @@
use std::error;
use std::fmt;
use zcash_address::unified::Typecode;
use zcash_primitives::{
consensus::BlockHeight,
sapling::Node,
@ -23,6 +24,9 @@ pub enum ChainInvalid {
#[derive(Debug)]
pub enum Error<NoteId> {
/// No account with the given identifier was found in the wallet.
AccountNotFound(AccountId),
/// The amount specified exceeds the allowed range.
InvalidAmount,
@ -48,6 +52,9 @@ pub enum Error<NoteId> {
/// does not correspond to root of the current commitment tree.
InvalidWitnessAnchor(NoteId, BlockHeight),
/// No key of the given type was associated with the specified account.
KeyNotFound(AccountId, Typecode),
/// The wallet must first perform a scan of the blockchain before other
/// operations can be performed.
ScanRequired,
@ -79,6 +86,9 @@ impl ChainInvalid {
impl<N: fmt::Display> fmt::Display for Error<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Error::AccountNotFound(account) => {
write!(f, "Wallet does not contain account {}", u32::from(*account))
}
Error::InvalidAmount => write!(
f,
"The value lies outside the valid range of Zcash amounts."
@ -104,6 +114,9 @@ impl<N: fmt::Display> fmt::Display for Error<N> {
"Witness for note {} has incorrect anchor after scanning block {}",
id_note, last_height
),
Error::KeyNotFound(account, typecode) => {
write!(f, "No {:?} key was available for account {}", typecode, u32::from(*account))
}
Error::ScanRequired => write!(f, "Must scan blocks first"),
Error::Builder(e) => write!(f, "{:?}", e),
Error::Protobuf(e) => write!(f, "{}", e),

View File

@ -12,8 +12,11 @@ use zcash_primitives::{
};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{
keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey,
use {
zcash_address::unified::Typecode,
zcash_primitives::{
keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey,
},
};
use crate::{
@ -418,11 +421,10 @@ where
/// * `prover`: The TxProver to use in constructing the shielded transaction.
/// * `sk`: The secp256k1 secret key that will be used to detect and spend transparent
/// UTXOs.
/// * `extfvk`: The extended full viewing key that will be used to produce the
/// Sapling address to which funds will be sent.
/// * `account`: The ZIP32 account identifier associated with the the extended
/// full viewing key. This procedure will return an error if this does not correctly
/// correspond to `extfvk`.
/// * `account`: The ZIP32 account identifier for the account to which funds will
/// be shielded. Funds will be shielded to the internal (change) address associated with the
/// most preferred shielded receiver corresponding to this account, or if no shielded
/// receiver can be used for this account, this function will return an error.
/// * `memo`: A memo to be included in the output to the (internal) recipient.
/// This can be used to take notes about auto-shielding operations internal
/// to the wallet that the wallet can use to improve how it represents those
@ -437,7 +439,6 @@ pub fn shield_transparent_funds<E, N, P, D, R, U>(
params: &P,
prover: impl TxProver,
sk: &transparent::AccountPrivKey,
extfvk: &ExtendedFullViewingKey,
account: AccountId,
memo: &MemoBytes,
min_confirmations: u32,
@ -448,11 +449,22 @@ where
R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
{
// Check that the ExtendedSpendingKey we have been given corresponds to the
// ExtendedFullViewingKey for the account we are spending from.
if !wallet_db.is_valid_account_extfvk(account, extfvk)? {
return Err(E::from(Error::InvalidExtSk(account)));
}
// Obtain the UFVK for the specified account & use its internal change address
// as the destination for shielded funds.
let shielding_address = wallet_db
.get_unified_full_viewing_keys()
.and_then(|ufvks| {
ufvks
.get(&account)
.ok_or_else(|| E::from(Error::AccountNotFound(account)))
.and_then(|ufvk| {
// TODO: select the most preferred shielded receiver once we have the ability to
// spend Orchard funds.
ufvk.sapling()
.map(|dfvk| dfvk.change_address().1)
.ok_or_else(|| E::from(Error::KeyNotFound(account, Typecode::Sapling)))
})
})?;
let (latest_scanned_height, latest_anchor) = wallet_db
.get_target_and_anchor_heights(min_confirmations)
@ -461,11 +473,6 @@ where
let account_pubkey = sk.to_account_pubkey();
let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes());
// derive own shielded address from the provided extended full viewing key
// TODO: this should become the internal change address derived from
// the wallet's UFVK
let z_address = extfvk.default_address().1;
// derive the t-address for the extpubkey at the minimum valid child index
let (taddr, child_index) = account_pubkey
.derive_external_ivk()
@ -497,11 +504,16 @@ where
}
// there are no sapling notes so we set the change manually
builder.send_change_to(ovk, z_address.clone());
builder.send_change_to(ovk, shielding_address.clone());
// add the sapling output to shield the funds
builder
.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, memo.clone())
.add_sapling_output(
Some(ovk),
shielding_address.clone(),
amount_to_shield,
memo.clone(),
)
.map_err(Error::Builder)?;
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
@ -515,7 +527,7 @@ where
account,
outputs: vec![SentTransactionOutput {
output_index,
recipient_address: &RecipientAddress::Shielded(z_address),
recipient_address: &RecipientAddress::Shielded(shielding_address),
value: amount_to_shield,
memo: Some(memo.clone()),
}],

View File

@ -108,6 +108,11 @@ and this library adheres to Rust's notion of
refactored to become a member function of a new `DiversifiableFullViewingKey`
type, which represents the ability to derive IVKs, OVKs, and addresses, but
not child viewing keys.
- `zcash_primitives::sapling::keys::DiversifiableFullViewingKey::change_address`
has been added as a convenience method for obtaining the change address
at the default diversifier. This address **MUST NOT** be encoded and exposed
to users. User interfaces should instead mark these notes as "change notes" or
"internal wallet operations".
- A new module `zcash_primitives::legacy::keys` has been added under the
`transparent-inputs` feature flag to support types related to supporting
transparent components of unified addresses and derivation of OVKs for

View File

@ -292,6 +292,16 @@ impl DiversifiableFullViewingKey {
zip32::sapling_default_address(&self.fvk, &self.dk)
}
/// Returns the internal address corresponding to the smallest valid diversifier index,
/// along with that index.
///
/// This address **MUST NOT** be encoded and exposed to end users. User interfaces
/// should instead mark these notes as "change notes" or "internal wallet operations".
pub fn change_address(&self) -> (zip32::DiversifierIndex, PaymentAddress) {
let internal_dfvk = self.derive_internal();
zip32::sapling_default_address(&internal_dfvk.fvk, &internal_dfvk.dk)
}
/// Attempts to decrypt the given address's diversifier with this full viewing key.
///
/// This method extracts the diversifier from the given address and decrypts it as a