Allow block scanning using either IVKs or FVKs.
This commit is contained in:
parent
16289750e8
commit
0e022f2283
|
@ -9,7 +9,7 @@ use zcash_primitives::{
|
|||
consensus::BlockHeight,
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
note_encryption::Memo,
|
||||
primitives::{Note, Nullifier, PaymentAddress},
|
||||
primitives::{Nullifier, PaymentAddress},
|
||||
sapling::Node,
|
||||
transaction::{components::Amount, Transaction, TxId},
|
||||
zip32::ExtendedFullViewingKey,
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
|||
data_api::wallet::ANCHOR_OFFSET,
|
||||
decrypt::DecryptedOutput,
|
||||
proto::compact_formats::CompactBlock,
|
||||
wallet::{AccountId, SpendableNote, WalletShieldedOutput, WalletTx},
|
||||
wallet::{AccountId, SpendableNote, WalletTx},
|
||||
};
|
||||
|
||||
pub mod chain;
|
||||
|
@ -184,7 +184,7 @@ pub struct PrunedBlock<'a> {
|
|||
pub block_hash: BlockHash,
|
||||
pub block_time: u32,
|
||||
pub commitment_tree: &'a CommitmentTree<Node>,
|
||||
pub transactions: &'a Vec<WalletTx>,
|
||||
pub transactions: &'a Vec<WalletTx<Nullifier>>,
|
||||
}
|
||||
|
||||
pub struct ReceivedTransaction<'a> {
|
||||
|
@ -205,6 +205,7 @@ pub struct SentTransaction<'a> {
|
|||
/// This trait encapsulates the write capabilities required to update stored
|
||||
/// wallet data.
|
||||
pub trait WalletWrite: WalletRead {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn insert_pruned_block(
|
||||
&mut self,
|
||||
block: &PrunedBlock,
|
||||
|
@ -251,70 +252,6 @@ pub trait BlockSource {
|
|||
F: FnMut(CompactBlock) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// This trait provides a generalization over shielded output representations
|
||||
/// that allows a wallet to avoid coupling to a specific one.
|
||||
// TODO: it'd probably be better not to unify the definitions of
|
||||
// `WalletShieldedOutput` and `DecryptedOutput` via a compositional
|
||||
// approach, if possible.
|
||||
pub trait ShieldedOutput {
|
||||
fn index(&self) -> usize;
|
||||
fn account(&self) -> AccountId;
|
||||
fn to(&self) -> &PaymentAddress;
|
||||
fn note(&self) -> &Note;
|
||||
fn memo(&self) -> Option<&Memo>;
|
||||
fn is_change(&self) -> Option<bool>;
|
||||
fn nullifier(&self) -> Option<Nullifier>;
|
||||
}
|
||||
|
||||
impl ShieldedOutput for WalletShieldedOutput {
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
fn account(&self) -> AccountId {
|
||||
self.account
|
||||
}
|
||||
fn to(&self) -> &PaymentAddress {
|
||||
&self.to
|
||||
}
|
||||
fn note(&self) -> &Note {
|
||||
&self.note
|
||||
}
|
||||
fn memo(&self) -> Option<&Memo> {
|
||||
None
|
||||
}
|
||||
fn is_change(&self) -> Option<bool> {
|
||||
Some(self.is_change)
|
||||
}
|
||||
|
||||
fn nullifier(&self) -> Option<Nullifier> {
|
||||
self.nf.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShieldedOutput for DecryptedOutput {
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
fn account(&self) -> AccountId {
|
||||
self.account
|
||||
}
|
||||
fn to(&self) -> &PaymentAddress {
|
||||
&self.to
|
||||
}
|
||||
fn note(&self) -> &Note {
|
||||
&self.note
|
||||
}
|
||||
fn memo(&self) -> Option<&Memo> {
|
||||
Some(&self.memo)
|
||||
}
|
||||
fn is_change(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
fn nullifier(&self) -> Option<Nullifier> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
pub mod testing {
|
||||
use std::collections::HashMap;
|
||||
|
@ -447,6 +384,7 @@ pub mod testing {
|
|||
}
|
||||
|
||||
impl WalletWrite for MockWalletDB {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn insert_pruned_block(
|
||||
&mut self,
|
||||
_block: &PrunedBlock,
|
||||
|
|
|
@ -291,7 +291,7 @@ where
|
|||
let block_hash = BlockHash::from_slice(&block.hash);
|
||||
let block_time = block.time;
|
||||
|
||||
let txs: Vec<WalletTx> = {
|
||||
let txs: Vec<WalletTx<Nullifier>> = {
|
||||
let mut witness_refs: Vec<_> = witnesses.iter_mut().map(|w| &mut w.1).collect();
|
||||
|
||||
scan_block(
|
||||
|
@ -344,11 +344,10 @@ where
|
|||
.flat_map(|tx| tx.shielded_spends.iter().map(|spend| spend.nf))
|
||||
.collect();
|
||||
nullifiers.retain(|(_, nf)| !spent_nf.contains(nf));
|
||||
nullifiers.extend(txs.iter().flat_map(|tx| {
|
||||
tx.shielded_outputs
|
||||
.iter()
|
||||
.flat_map(|out| out.nf.map(|nf| (out.account, nf)))
|
||||
}));
|
||||
nullifiers.extend(
|
||||
txs.iter()
|
||||
.flat_map(|tx| tx.shielded_outputs.iter().map(|out| (out.account, out.nf))),
|
||||
);
|
||||
|
||||
witnesses.extend(new_witnesses);
|
||||
|
||||
|
|
|
@ -30,13 +30,13 @@ impl ConditionallySelectable for AccountId {
|
|||
/// A subset of a [`Transaction`] relevant to wallets and light clients.
|
||||
///
|
||||
/// [`Transaction`]: zcash_primitives::transaction::Transaction
|
||||
pub struct WalletTx {
|
||||
pub struct WalletTx<N> {
|
||||
pub txid: TxId,
|
||||
pub index: usize,
|
||||
pub num_spends: usize,
|
||||
pub num_outputs: usize,
|
||||
pub shielded_spends: Vec<WalletShieldedSpend>,
|
||||
pub shielded_outputs: Vec<WalletShieldedOutput>,
|
||||
pub shielded_outputs: Vec<WalletShieldedOutput<N>>,
|
||||
}
|
||||
|
||||
/// A subset of a [`SpendDescription`] relevant to wallets and light clients.
|
||||
|
@ -51,7 +51,7 @@ pub struct WalletShieldedSpend {
|
|||
/// A subset of an [`OutputDescription`] relevant to wallets and light clients.
|
||||
///
|
||||
/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription
|
||||
pub struct WalletShieldedOutput {
|
||||
pub struct WalletShieldedOutput<N> {
|
||||
pub index: usize,
|
||||
pub cmu: bls12_381::Scalar,
|
||||
pub epk: jubjub::ExtendedPoint,
|
||||
|
@ -60,7 +60,7 @@ pub struct WalletShieldedOutput {
|
|||
pub to: PaymentAddress,
|
||||
pub is_change: bool,
|
||||
pub witness: IncrementalWitness<Node>,
|
||||
pub nf: Option<Nullifier>,
|
||||
pub nf: N,
|
||||
}
|
||||
|
||||
pub struct SpendableNote {
|
||||
|
|
|
@ -7,7 +7,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight},
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
note_encryption::try_sapling_compact_note_decryption,
|
||||
primitives::{Nullifier, ViewingKey},
|
||||
primitives::{Note, Nullifier, PaymentAddress, SaplingIvk, ViewingKey},
|
||||
sapling::Node,
|
||||
transaction::TxId,
|
||||
};
|
||||
|
@ -25,17 +25,17 @@ use crate::wallet::{AccountId, WalletShieldedOutput, WalletShieldedSpend, Wallet
|
|||
///
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn scan_output<P: consensus::Parameters>(
|
||||
fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
(index, output): (usize, CompactOutput),
|
||||
vks: &[(AccountId, &ViewingKey)],
|
||||
vks: &[(AccountId, &K)],
|
||||
spent_from_accounts: &HashSet<AccountId>,
|
||||
tree: &mut CommitmentTree<Node>,
|
||||
existing_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
block_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
new_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
) -> Option<WalletShieldedOutput> {
|
||||
) -> Option<WalletShieldedOutput<K::Nf>> {
|
||||
let cmu = output.cmu().ok()?;
|
||||
let epk = output.epk().ok()?;
|
||||
let ct = output.ciphertext;
|
||||
|
@ -54,12 +54,10 @@ fn scan_output<P: consensus::Parameters>(
|
|||
tree.append(node).unwrap();
|
||||
|
||||
for (account, vk) in vks.iter() {
|
||||
let ivk = vk.ivk();
|
||||
let (note, to) =
|
||||
match try_sapling_compact_note_decryption(params, height, &ivk, &epk, &cmu, &ct) {
|
||||
Some(ret) => ret,
|
||||
None => continue,
|
||||
};
|
||||
let (note, to) = match vk.try_decryption(params, height, &epk, &cmu, &ct) {
|
||||
Some(ret) => ret,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// A note is marked as "change" if the account that received it
|
||||
// also spent notes in the same transaction. This will catch,
|
||||
|
@ -70,7 +68,7 @@ fn scan_output<P: consensus::Parameters>(
|
|||
let is_change = spent_from_accounts.contains(&account);
|
||||
|
||||
let witness = IncrementalWitness::from_tree(tree);
|
||||
let nf = note.nf(&vk, witness.position() as u64);
|
||||
let nf = vk.nf(¬e, &witness);
|
||||
|
||||
return Some(WalletShieldedOutput {
|
||||
index,
|
||||
|
@ -81,30 +79,81 @@ fn scan_output<P: consensus::Parameters>(
|
|||
to,
|
||||
is_change,
|
||||
witness,
|
||||
nf: Some(nf),
|
||||
nf,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Scans a [`CompactBlock`] with a set of [`ViewingKey`]s.
|
||||
pub trait ScanningKey {
|
||||
type Nf;
|
||||
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
) -> Option<(Note, PaymentAddress)>;
|
||||
|
||||
fn nf(&self, note: &Note, witness: &IncrementalWitness<Node>) -> Self::Nf;
|
||||
}
|
||||
|
||||
impl ScanningKey for ViewingKey {
|
||||
type Nf = Nullifier;
|
||||
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
) -> Option<(Note, PaymentAddress)> {
|
||||
try_sapling_compact_note_decryption(params, height, &self.ivk(), &epk, &cmu, &ct)
|
||||
}
|
||||
|
||||
fn nf(&self, note: &Note, witness: &IncrementalWitness<Node>) -> Self::Nf {
|
||||
note.nf(self, witness.position() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScanningKey for SaplingIvk {
|
||||
type Nf = ();
|
||||
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
) -> Option<(Note, PaymentAddress)> {
|
||||
try_sapling_compact_note_decryption(params, height, self, &epk, &cmu, &ct)
|
||||
}
|
||||
|
||||
fn nf(&self, _note: &Note, _witness: &IncrementalWitness<Node>) {}
|
||||
}
|
||||
|
||||
/// Scans a [`CompactBlock`] with a set of [`ScanningKeys`]s.
|
||||
///
|
||||
/// Returns a vector of [`WalletTx`]s belonging to any of the given
|
||||
/// [`ViewingKey`]s.
|
||||
/// [`ScanningKey`]s. If scanning with a full viewing key, the nullifiers
|
||||
/// of the resulting [`WalletShieldedOutput`]s will also be computed.
|
||||
///
|
||||
/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are
|
||||
/// incremented appropriately.
|
||||
///
|
||||
/// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey
|
||||
pub fn scan_block<P: consensus::Parameters>(
|
||||
pub fn scan_block<P: consensus::Parameters, K: ScanningKey>(
|
||||
params: &P,
|
||||
block: CompactBlock,
|
||||
vks: &[(AccountId, &ViewingKey)],
|
||||
vks: &[(AccountId, &K)],
|
||||
nullifiers: &[(AccountId, Nullifier)],
|
||||
tree: &mut CommitmentTree<Node>,
|
||||
existing_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
) -> Vec<WalletTx> {
|
||||
let mut wtxs: Vec<WalletTx> = vec![];
|
||||
) -> Vec<WalletTx<K::Nf>> {
|
||||
let mut wtxs: Vec<WalletTx<K::Nf>> = vec![];
|
||||
let block_height = block.height();
|
||||
|
||||
for tx in block.vtx.into_iter() {
|
||||
|
@ -145,7 +194,7 @@ pub fn scan_block<P: consensus::Parameters>(
|
|||
shielded_spends.iter().map(|spend| spend.account).collect();
|
||||
|
||||
// Check for incoming notes while incrementing tree and witnesses
|
||||
let mut shielded_outputs: Vec<WalletShieldedOutput> = vec![];
|
||||
let mut shielded_outputs: Vec<WalletShieldedOutput<K::Nf>> = vec![];
|
||||
{
|
||||
// Grab mutable references to new witnesses from previous transactions
|
||||
// in this block so that we can update them. Scoped so we don't hold
|
||||
|
@ -211,7 +260,7 @@ mod tests {
|
|||
constants::SPENDING_KEY_GENERATOR,
|
||||
merkle_tree::CommitmentTree,
|
||||
note_encryption::{Memo, SaplingNoteEncryption},
|
||||
primitives::{Note, Nullifier},
|
||||
primitives::{Note, Nullifier, SaplingIvk},
|
||||
transaction::components::Amount,
|
||||
util::generate_random_rseed,
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
|
@ -408,12 +457,13 @@ mod tests {
|
|||
|
||||
let cb = fake_compact_block(1u32.into(), nf, extfvk, Amount::from_u64(5).unwrap(), false);
|
||||
assert_eq!(cb.vtx.len(), 2);
|
||||
let vks: Vec<(AccountId, &SaplingIvk)> = vec![];
|
||||
|
||||
let mut tree = CommitmentTree::empty();
|
||||
let txs = scan_block(
|
||||
&Network::TestNetwork,
|
||||
cb,
|
||||
&[],
|
||||
&vks[..],
|
||||
&[(account, nf)],
|
||||
&mut tree,
|
||||
&mut [],
|
||||
|
|
|
@ -388,6 +388,7 @@ impl<'a, P: consensus::Parameters> DataConnStmtCache<'a, P> {
|
|||
}
|
||||
|
||||
impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn insert_pruned_block(
|
||||
&mut self,
|
||||
block: &PrunedBlock,
|
||||
|
|
|
@ -9,7 +9,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight, NetworkUpgrade},
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
note_encryption::Memo,
|
||||
primitives::{Nullifier, PaymentAddress},
|
||||
primitives::{Note, Nullifier, PaymentAddress},
|
||||
sapling::Node,
|
||||
transaction::{components::Amount, Transaction, TxId},
|
||||
zip32::ExtendedFullViewingKey,
|
||||
|
@ -17,12 +17,12 @@ use zcash_primitives::{
|
|||
|
||||
use zcash_client_backend::{
|
||||
address::RecipientAddress,
|
||||
data_api::{error::Error, ShieldedOutput},
|
||||
data_api::error::Error,
|
||||
encoding::{
|
||||
decode_extended_full_viewing_key, decode_payment_address, encode_extended_full_viewing_key,
|
||||
encode_payment_address,
|
||||
},
|
||||
wallet::{AccountId, WalletTx},
|
||||
wallet::{AccountId, WalletShieldedOutput, WalletTx},
|
||||
DecryptedOutput,
|
||||
};
|
||||
|
||||
|
@ -31,6 +31,70 @@ use crate::{error::SqliteClientError, DataConnStmtCache, NoteId, WalletDB};
|
|||
pub mod init;
|
||||
pub mod transact;
|
||||
|
||||
/// This trait provides a generalization over shielded output representations
|
||||
/// that allows a wallet to avoid coupling to a specific one.
|
||||
// TODO: it'd probably be better not to unify the definitions of
|
||||
// `WalletShieldedOutput` and `DecryptedOutput` via a compositional
|
||||
// approach, if possible.
|
||||
pub trait ShieldedOutput {
|
||||
fn index(&self) -> usize;
|
||||
fn account(&self) -> AccountId;
|
||||
fn to(&self) -> &PaymentAddress;
|
||||
fn note(&self) -> &Note;
|
||||
fn memo(&self) -> Option<&Memo>;
|
||||
fn is_change(&self) -> Option<bool>;
|
||||
fn nullifier(&self) -> Option<Nullifier>;
|
||||
}
|
||||
|
||||
impl ShieldedOutput for WalletShieldedOutput<Nullifier> {
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
fn account(&self) -> AccountId {
|
||||
self.account
|
||||
}
|
||||
fn to(&self) -> &PaymentAddress {
|
||||
&self.to
|
||||
}
|
||||
fn note(&self) -> &Note {
|
||||
&self.note
|
||||
}
|
||||
fn memo(&self) -> Option<&Memo> {
|
||||
None
|
||||
}
|
||||
fn is_change(&self) -> Option<bool> {
|
||||
Some(self.is_change)
|
||||
}
|
||||
|
||||
fn nullifier(&self) -> Option<Nullifier> {
|
||||
Some(self.nf)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShieldedOutput for DecryptedOutput {
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
fn account(&self) -> AccountId {
|
||||
self.account
|
||||
}
|
||||
fn to(&self) -> &PaymentAddress {
|
||||
&self.to
|
||||
}
|
||||
fn note(&self) -> &Note {
|
||||
&self.note
|
||||
}
|
||||
fn memo(&self) -> Option<&Memo> {
|
||||
Some(&self.memo)
|
||||
}
|
||||
fn is_change(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
fn nullifier(&self) -> Option<Nullifier> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the address for the account.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -458,9 +522,9 @@ pub fn insert_block<'a, P>(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put_tx_meta<'a, P>(
|
||||
pub fn put_tx_meta<'a, P, N>(
|
||||
stmts: &mut DataConnStmtCache<'a, P>,
|
||||
tx: &WalletTx,
|
||||
tx: &WalletTx<N>,
|
||||
height: BlockHeight,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
let txid = tx.txid.0.to_vec();
|
||||
|
|
Loading…
Reference in New Issue