Skip to content

Commit

Permalink
Test nullifiers in constant time
Browse files Browse the repository at this point in the history
Checking for spent notes in a block is still not completely constant
time, due to filtering out negative results of the constant-time
comparison.

Part of #84.
  • Loading branch information
str4d committed Oct 9, 2019
1 parent 2bbd25b commit 1e2bc7f
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 22 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zcash_client_backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ff = { version = "0.5.0", path = "../ff" }
hex = "0.3"
pairing = { version = "0.15.0", path = "../pairing" }
protobuf = "2"
subtle = "2"
zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" }

[build-dependencies]
Expand Down
46 changes: 24 additions & 22 deletions zcash_client_backend/src/welding_rig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use ff::{PrimeField, PrimeFieldRepr};
use pairing::bls12_381::{Bls12, Fr, FrRepr};
use std::collections::HashSet;
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_primitives::{
jubjub::{edwards, fs::Fs},
merkle_tree::{CommitmentTree, IncrementalWitness},
Expand Down Expand Up @@ -116,28 +117,29 @@ pub fn scan_block(
let num_outputs = tx.outputs.len();

// Check for spent notes
let shielded_spends: Vec<_> =
tx.spends
.into_iter()
.enumerate()
.filter_map(|(index, spend)| {
if let Some(account) = nullifiers.iter().find_map(|&(nf, acc)| {
if nf == &spend.nf[..] {
Some(acc)
} else {
None
}
}) {
Some(WalletShieldedSpend {
index,
nf: spend.nf,
account,
})
} else {
None
}
})
.collect();
// The only step that is not constant-time is the filter() at the end.
let shielded_spends: Vec<_> = tx
.spends
.into_iter()
.enumerate()
.map(|(index, spend)| {
// Find the first tracked nullifier that matches this spend, and produce
// a WalletShieldedSpend if there is a match, in constant time.
nullifiers
.iter()
.map(|&(nf, account)| CtOption::new(account as u64, nf.ct_eq(&spend.nf[..])))
.fold(CtOption::new(0, 0.into()), |first, next| {
CtOption::conditional_select(&next, &first, first.is_some())
})
.map(|account| WalletShieldedSpend {
index,
nf: spend.nf,
account: account as usize,
})
})
.filter(|spend| spend.is_some().into())
.map(|spend| spend.unwrap())
.collect();

// Collect the set of accounts that were spent from in this transaction
let spent_from_accounts: HashSet<_> =
Expand Down

0 comments on commit 1e2bc7f

Please sign in to comment.