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

View File

@ -12,8 +12,11 @@ use zcash_primitives::{
}; };
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use zcash_primitives::{ use {
keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey, zcash_address::unified::Typecode,
zcash_primitives::{
keys::OutgoingViewingKey, legacy::keys as transparent, legacy::keys::IncomingViewingKey,
},
}; };
use crate::{ use crate::{
@ -418,11 +421,10 @@ where
/// * `prover`: The TxProver to use in constructing the shielded transaction. /// * `prover`: The TxProver to use in constructing the shielded transaction.
/// * `sk`: The secp256k1 secret key that will be used to detect and spend transparent /// * `sk`: The secp256k1 secret key that will be used to detect and spend transparent
/// UTXOs. /// UTXOs.
/// * `extfvk`: The extended full viewing key that will be used to produce the /// * `account`: The ZIP32 account identifier for the account to which funds will
/// Sapling address to which funds will be sent. /// be shielded. Funds will be shielded to the internal (change) address associated with the
/// * `account`: The ZIP32 account identifier associated with the the extended /// most preferred shielded receiver corresponding to this account, or if no shielded
/// full viewing key. This procedure will return an error if this does not correctly /// receiver can be used for this account, this function will return an error.
/// correspond to `extfvk`.
/// * `memo`: A memo to be included in the output to the (internal) recipient. /// * `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 /// 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 /// 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, params: &P,
prover: impl TxProver, prover: impl TxProver,
sk: &transparent::AccountPrivKey, sk: &transparent::AccountPrivKey,
extfvk: &ExtendedFullViewingKey,
account: AccountId, account: AccountId,
memo: &MemoBytes, memo: &MemoBytes,
min_confirmations: u32, min_confirmations: u32,
@ -448,11 +449,22 @@ where
R: Copy + Debug, R: Copy + Debug,
D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>, D: WalletWrite<Error = E, TxRef = R> + WalletWriteTransparent<UtxoRef = U>,
{ {
// Check that the ExtendedSpendingKey we have been given corresponds to the // Obtain the UFVK for the specified account & use its internal change address
// ExtendedFullViewingKey for the account we are spending from. // as the destination for shielded funds.
if !wallet_db.is_valid_account_extfvk(account, extfvk)? { let shielding_address = wallet_db
return Err(E::from(Error::InvalidExtSk(account))); .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 let (latest_scanned_height, latest_anchor) = wallet_db
.get_target_and_anchor_heights(min_confirmations) .get_target_and_anchor_heights(min_confirmations)
@ -461,11 +473,6 @@ where
let account_pubkey = sk.to_account_pubkey(); let account_pubkey = sk.to_account_pubkey();
let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes()); 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 // derive the t-address for the extpubkey at the minimum valid child index
let (taddr, child_index) = account_pubkey let (taddr, child_index) = account_pubkey
.derive_external_ivk() .derive_external_ivk()
@ -497,11 +504,16 @@ where
} }
// there are no sapling notes so we set the change manually // 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 // add the sapling output to shield the funds
builder 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)?; .map_err(Error::Builder)?;
let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?; let (tx, tx_metadata) = builder.build(&prover).map_err(Error::Builder)?;
@ -515,7 +527,7 @@ where
account, account,
outputs: vec![SentTransactionOutput { outputs: vec![SentTransactionOutput {
output_index, output_index,
recipient_address: &RecipientAddress::Shielded(z_address), recipient_address: &RecipientAddress::Shielded(shielding_address),
value: amount_to_shield, value: amount_to_shield,
memo: Some(memo.clone()), 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` refactored to become a member function of a new `DiversifiableFullViewingKey`
type, which represents the ability to derive IVKs, OVKs, and addresses, but type, which represents the ability to derive IVKs, OVKs, and addresses, but
not child viewing keys. 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 - A new module `zcash_primitives::legacy::keys` has been added under the
`transparent-inputs` feature flag to support types related to supporting `transparent-inputs` feature flag to support types related to supporting
transparent components of unified addresses and derivation of OVKs for 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) 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. /// 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 /// This method extracts the diversifier from the given address and decrypts it as a