Detect change notes while scanning blocks

This commit is contained in:
Jack Grigg 2019-01-11 14:26:54 -08:00
parent 9c51f3426b
commit c1e6b1844c
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
2 changed files with 51 additions and 20 deletions

View File

@ -25,6 +25,7 @@ pub struct WalletTx {
pub struct WalletShieldedSpend { pub struct WalletShieldedSpend {
pub index: usize, pub index: usize,
pub nf: Vec<u8>, pub nf: Vec<u8>,
pub account: usize,
} }
/// A subset of an [`OutputDescription`] relevant to wallets and light clients. /// A subset of an [`OutputDescription`] relevant to wallets and light clients.
@ -37,4 +38,5 @@ pub struct WalletShieldedOutput {
pub account: usize, pub account: usize,
pub note: Note<Bls12>, pub note: Note<Bls12>,
pub to: PaymentAddress<Bls12>, pub to: PaymentAddress<Bls12>,
pub is_change: bool,
} }

View File

@ -2,6 +2,7 @@
use ff::{PrimeField, PrimeFieldRepr}; use ff::{PrimeField, PrimeFieldRepr};
use pairing::bls12_381::{Bls12, Fr, FrRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr};
use std::collections::HashSet;
use zcash_primitives::{ use zcash_primitives::{
jubjub::{edwards, fs::Fs}, jubjub::{edwards, fs::Fs},
merkle_tree::{CommitmentTree, IncrementalWitness}, merkle_tree::{CommitmentTree, IncrementalWitness},
@ -25,6 +26,7 @@ use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx};
fn scan_output( fn scan_output(
(index, output): (usize, CompactOutput), (index, output): (usize, CompactOutput),
ivks: &[Fs], ivks: &[Fs],
spent_from_accounts: &HashSet<usize>,
tree: &mut CommitmentTree<Node>, tree: &mut CommitmentTree<Node>,
existing_witnesses: &mut [&mut IncrementalWitness<Node>], existing_witnesses: &mut [&mut IncrementalWitness<Node>],
new_witnesses: &mut [IncrementalWitness<Node>], new_witnesses: &mut [IncrementalWitness<Node>],
@ -64,6 +66,14 @@ fn scan_output(
None => continue, None => continue,
}; };
// A note is marked as "change" if the account that received it
// also spent notes in the same transaction. This will catch,
// for instance:
// - Change created by spending fractions of notes.
// - Notes created by consolidation transactions.
// - Notes sent from one account to itself.
let is_change = spent_from_accounts.contains(&account);
return Some(( return Some((
WalletShieldedOutput { WalletShieldedOutput {
index, index,
@ -72,6 +82,7 @@ fn scan_output(
account, account,
note, note,
to, to,
is_change,
}, },
IncrementalWitness::from_tree(tree), IncrementalWitness::from_tree(tree),
)); ));
@ -89,7 +100,7 @@ fn scan_output(
pub fn scan_block( pub fn scan_block(
block: CompactBlock, block: CompactBlock,
extfvks: &[ExtendedFullViewingKey], extfvks: &[ExtendedFullViewingKey],
nullifiers: &[&[u8]], nullifiers: &[(&[u8], usize)],
tree: &mut CommitmentTree<Node>, tree: &mut CommitmentTree<Node>,
existing_witnesses: &mut [&mut IncrementalWitness<Node>], existing_witnesses: &mut [&mut IncrementalWitness<Node>],
) -> Vec<(WalletTx, Vec<IncrementalWitness<Node>>)> { ) -> Vec<(WalletTx, Vec<IncrementalWitness<Node>>)> {
@ -101,29 +112,45 @@ pub fn scan_block(
let num_outputs = tx.outputs.len(); let num_outputs = tx.outputs.len();
// Check for spent notes // Check for spent notes
let shielded_spends: Vec<_> = tx let shielded_spends: Vec<_> =
.spends tx.spends
.into_iter() .into_iter()
.enumerate() .enumerate()
.filter_map(|(index, spend)| { .filter_map(|(index, spend)| {
if nullifiers.contains(&&spend.nf[..]) { if let Some(account) = nullifiers.iter().find_map(|&(nf, acc)| {
Some(WalletShieldedSpend { if nf == &spend.nf[..] {
index, Some(acc)
nf: spend.nf, } else {
}) None
} else { }
None }) {
} Some(WalletShieldedSpend {
}) index,
.collect(); nf: spend.nf,
account,
})
} else {
None
}
})
.collect();
// Collect the set of accounts that were spent from in this transaction
let spent_from_accounts: HashSet<_> =
shielded_spends.iter().map(|spend| spend.account).collect();
// Check for incoming notes while incrementing tree and witnesses // Check for incoming notes while incrementing tree and witnesses
let mut shielded_outputs = vec![]; let mut shielded_outputs = vec![];
let mut new_witnesses = vec![]; let mut new_witnesses = vec![];
for to_scan in tx.outputs.into_iter().enumerate() { for to_scan in tx.outputs.into_iter().enumerate() {
if let Some((output, new_witness)) = if let Some((output, new_witness)) = scan_output(
scan_output(to_scan, &ivks, tree, existing_witnesses, &mut new_witnesses) to_scan,
{ &ivks,
&spent_from_accounts,
tree,
existing_witnesses,
&mut new_witnesses,
) {
shielded_outputs.push(output); shielded_outputs.push(output);
new_witnesses.push(new_witness); new_witnesses.push(new_witness);
} }
@ -292,12 +319,13 @@ mod tests {
let extsk = ExtendedSpendingKey::master(&[]); let extsk = ExtendedSpendingKey::master(&[]);
let extfvk = ExtendedFullViewingKey::from(&extsk); let extfvk = ExtendedFullViewingKey::from(&extsk);
let nf = [7; 32]; let nf = [7; 32];
let account = 12;
let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap()); let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap());
assert_eq!(cb.vtx.len(), 2); assert_eq!(cb.vtx.len(), 2);
let mut tree = CommitmentTree::new(); let mut tree = CommitmentTree::new();
let txs = scan_block(cb, &[], &[&nf], &mut tree, &mut []); let txs = scan_block(cb, &[], &[(&nf, account)], &mut tree, &mut []);
assert_eq!(txs.len(), 1); assert_eq!(txs.len(), 1);
let (tx, new_witnesses) = &txs[0]; let (tx, new_witnesses) = &txs[0];
@ -307,6 +335,7 @@ mod tests {
assert_eq!(tx.shielded_outputs.len(), 0); assert_eq!(tx.shielded_outputs.len(), 0);
assert_eq!(tx.shielded_spends[0].index, 0); assert_eq!(tx.shielded_spends[0].index, 0);
assert_eq!(tx.shielded_spends[0].nf, nf); assert_eq!(tx.shielded_spends[0].nf, nf);
assert_eq!(tx.shielded_spends[0].account, account);
assert_eq!(new_witnesses.len(), 0); assert_eq!(new_witnesses.len(), 0);
} }
} }