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:
commit
8c00ca3b88
|
@ -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),
|
||||||
|
|
|
@ -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()),
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue