From af7e263bcc22b639637d16a1dc198fded90dae9e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 12 Oct 2018 18:22:58 +0100 Subject: [PATCH 01/15] Build protobufs for compact formats --- Cargo.lock | 27 +++++++++++ zcash_client_backend/.gitignore | 2 + zcash_client_backend/Cargo.toml | 4 ++ zcash_client_backend/build.rs | 11 +++++ .../proto/compact_formats.proto | 47 +++++++++++++++++++ zcash_client_backend/src/lib.rs | 1 + zcash_client_backend/src/proto/mod.rs | 3 ++ 7 files changed, 95 insertions(+) create mode 100644 zcash_client_backend/.gitignore create mode 100644 zcash_client_backend/build.rs create mode 100644 zcash_client_backend/proto/compact_formats.proto create mode 100644 zcash_client_backend/src/proto/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7a33a24..d87e43d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,6 +467,28 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "protobuf" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "protobuf-codegen" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "1.0.2" @@ -607,6 +629,8 @@ version = "0.1.0" dependencies = [ "bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "pairing 0.15.0", + "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "zcash_primitives 0.1.0", @@ -700,6 +724,9 @@ dependencies = [ "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8" "checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8" +"checksum protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" +"checksum protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a" +"checksum protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" diff --git a/zcash_client_backend/.gitignore b/zcash_client_backend/.gitignore new file mode 100644 index 0000000..7025829 --- /dev/null +++ b/zcash_client_backend/.gitignore @@ -0,0 +1,2 @@ +# Protobufs +src/proto/ diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 7594630..9e6430b 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -14,8 +14,12 @@ edition = "2018" [dependencies] bech32 = "0.7" pairing = { version = "0.15.0", path = "../pairing" } +protobuf = "2" zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" } +[build-dependencies] +protobuf-codegen-pure = "2" + [dev-dependencies] rand_core = "0.5" rand_xorshift = "0.2" diff --git a/zcash_client_backend/build.rs b/zcash_client_backend/build.rs new file mode 100644 index 0000000..41e0214 --- /dev/null +++ b/zcash_client_backend/build.rs @@ -0,0 +1,11 @@ +use protobuf_codegen_pure; + +fn main() { + protobuf_codegen_pure::run(protobuf_codegen_pure::Args { + out_dir: "src/proto", + input: &["proto/compact_formats.proto"], + includes: &["proto"], + customize: Default::default(), + }) + .expect("protoc"); +} diff --git a/zcash_client_backend/proto/compact_formats.proto b/zcash_client_backend/proto/compact_formats.proto new file mode 100644 index 0000000..d65b658 --- /dev/null +++ b/zcash_client_backend/proto/compact_formats.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package cash.z.wallet.sdk.rpc; +option go_package = "walletrpc"; + +// Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. +// bytes fields of hashes are in canonical little-endian format. + +// CompactBlock is a packaging of ONLY the data from a block that's needed to: +// 1. Detect a payment to your shielded Sapling address +// 2. Detect a spend of your shielded Sapling notes +// 3. Update your witnesses to generate new Sapling spend proofs. +message CompactBlock { + uint32 protoVersion = 1; // the version of this wire format, for storage + uint64 height = 2; // the height of this block + bytes hash = 3; + uint32 time = 4; + bytes header = 5; // (hash and time) OR (full header) + repeated CompactTx vtx = 6; // compact transactions from this block +} + +message CompactTx { + // Index and hash will allow the receiver to call out to chain + // explorers or other data structures to retrieve more information + // about this transaction. + uint64 index = 1; + bytes hash = 2; + + // The transaction fee: present if server can provide. In the case of a + // stateless server and a transaction with transparent inputs, this will be + // unset because the calculation requires reference to prior transactions. + // in a pure-Sapling context, the fee will be calculable as: + // valueBalance + (sum(vPubNew) - sum(vPubOld) - sum(tOut)) + uint32 fee = 3; + + repeated CompactSpend spends = 4; + repeated CompactOutput outputs = 5; +} + +message CompactSpend { + bytes nf = 1; +} + +message CompactOutput { + bytes cmu = 1; + bytes epk = 2; + bytes ciphertext = 3; +} diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index 6d1d993..063ab62 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -9,3 +9,4 @@ pub mod constants; pub mod encoding; pub mod keys; +pub mod proto; diff --git a/zcash_client_backend/src/proto/mod.rs b/zcash_client_backend/src/proto/mod.rs new file mode 100644 index 0000000..e24ef5c --- /dev/null +++ b/zcash_client_backend/src/proto/mod.rs @@ -0,0 +1,3 @@ +//! Generated code for handling light client protobuf structs. + +pub mod compact_formats; From 591b1fc28fb82dbfd874127837745789b400fd86 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 12 Oct 2018 18:24:25 +0100 Subject: [PATCH 02/15] Parse compact blocks to find wallet transactions --- Cargo.lock | 13 ++ zcash_client_backend/Cargo.toml | 3 + zcash_client_backend/src/lib.rs | 2 + zcash_client_backend/src/wallet.rs | 32 ++++ zcash_client_backend/src/welding_rig.rs | 194 ++++++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 zcash_client_backend/src/wallet.rs create mode 100644 zcash_client_backend/src/welding_rig.rs diff --git a/Cargo.lock b/Cargo.lock index d87e43d..75ff772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,6 +534,15 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_os" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_xorshift" version = "0.2.0" @@ -628,10 +637,13 @@ name = "zcash_client_backend" version = "0.1.0" dependencies = [ "bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ff 0.5.0", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "pairing 0.15.0", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "zcash_primitives 0.1.0", ] @@ -732,6 +744,7 @@ dependencies = [ "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a" "checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 9e6430b..e639efd 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -13,6 +13,8 @@ edition = "2018" [dependencies] bech32 = "0.7" +ff = { version = "0.5.0", path = "../ff" } +hex = "0.3" pairing = { version = "0.15.0", path = "../pairing" } protobuf = "2" zcash_primitives = { version = "0.1.0", path = "../zcash_primitives" } @@ -22,6 +24,7 @@ protobuf-codegen-pure = "2" [dev-dependencies] rand_core = "0.5" +rand_os = "0.2" rand_xorshift = "0.2" [badges] diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index 063ab62..87a808c 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -10,3 +10,5 @@ pub mod constants; pub mod encoding; pub mod keys; pub mod proto; +pub mod wallet; +pub mod welding_rig; diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs new file mode 100644 index 0000000..4e85eef --- /dev/null +++ b/zcash_client_backend/src/wallet.rs @@ -0,0 +1,32 @@ +//! Structs representing transaction data scanned from the block chain by a wallet or +//! light client. + +use pairing::bls12_381::{Bls12, Fr}; +use zcash_primitives::{ + jubjub::{edwards, PrimeOrder}, + transaction::TxId, +}; + +pub struct EncCiphertextFrag(pub [u8; 52]); + +/// A subset of a [`Transaction`] relevant to wallets and light clients. +/// +/// [`Transaction`]: zcash_primitives::transaction::Transaction +pub struct WalletTx { + pub txid: TxId, + pub num_spends: usize, + pub num_outputs: usize, + pub shielded_outputs: Vec, +} + +/// A subset of an [`OutputDescription`] relevant to wallets and light clients. +/// +/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription +pub struct WalletShieldedOutput { + pub index: usize, + pub cmu: Fr, + pub epk: edwards::Point, + pub enc_ct: EncCiphertextFrag, + pub account: usize, + pub value: u64, +} diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs new file mode 100644 index 0000000..cac83ff --- /dev/null +++ b/zcash_client_backend/src/welding_rig.rs @@ -0,0 +1,194 @@ +//! Tools for scanning a compact representation of the Zcash block chain. + +use ff::{PrimeField, PrimeFieldRepr}; +use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use zcash_primitives::{ + jubjub::{edwards, fs::Fs}, + note_encryption::try_sapling_compact_note_decryption, + transaction::TxId, + zip32::ExtendedFullViewingKey, + JUBJUB, +}; + +use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx}; +use crate::wallet::{EncCiphertextFrag, WalletShieldedOutput, WalletTx}; + +/// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s. +/// +/// Returns a [`WalletShieldedOutput`] if this output belongs to any of the given +/// [`ExtendedFullViewingKey`]s. +fn scan_output( + (index, output): (usize, CompactOutput), + ivks: &[Fs], +) -> Option { + let mut repr = FrRepr::default(); + if repr.read_le(&output.cmu[..]).is_err() { + return None; + } + let cmu = match Fr::from_repr(repr) { + Ok(cmu) => cmu, + Err(_) => return None, + }; + + let epk = match edwards::Point::::read(&output.epk[..], &JUBJUB) { + Ok(p) => match p.as_prime_order(&JUBJUB) { + Some(epk) => epk, + None => return None, + }, + Err(_) => return None, + }; + + let ct = output.ciphertext; + + for (account, ivk) in ivks.iter().enumerate() { + let value = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) { + Some((note, _)) => note.value, + None => continue, + }; + + // It's ours, so let's copy the ciphertext fragment and return + let mut enc_ct = EncCiphertextFrag([0u8; 52]); + enc_ct.0.copy_from_slice(&ct); + + return Some(WalletShieldedOutput { + index, + cmu, + epk, + enc_ct, + account, + value, + }); + } + None +} + +/// Scans a [`CompactTx`] with a set of [`ExtendedFullViewingKey`]s. +/// +/// Returns a [`WalletTx`] if this transaction belongs to any of the given +/// [`ExtendedFullViewingKey`]s. +fn scan_tx(tx: CompactTx, extfvks: &[ExtendedFullViewingKey]) -> Option { + let num_spends = tx.spends.len(); + let num_outputs = tx.outputs.len(); + + // Check for incoming notes + let shielded_outputs: Vec = { + let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); + tx.outputs + .into_iter() + .enumerate() + .filter_map(|(index, output)| scan_output((index, output), &ivks)) + .collect() + }; + + if shielded_outputs.is_empty() { + None + } else { + let mut txid = TxId([0u8; 32]); + txid.0.copy_from_slice(&tx.hash); + Some(WalletTx { + txid, + num_spends, + num_outputs, + shielded_outputs, + }) + } +} + +/// Scans a [`CompactBlock`] for transactions belonging to a set of +/// [`ExtendedFullViewingKey`]s. +/// +/// Returns a vector of [`WalletTx`]s belonging to any of the given +/// [`ExtendedFullViewingKey`]s. +pub fn scan_block(block: CompactBlock, extfvks: &[ExtendedFullViewingKey]) -> Vec { + block + .vtx + .into_iter() + .filter_map(|tx| scan_tx(tx, extfvks)) + .collect() +} + +#[cfg(test)] +mod tests { + use ff::{Field, PrimeField, PrimeFieldRepr}; + use pairing::bls12_381::Bls12; + use rand_core::RngCore; + use rand_os::OsRng; + use zcash_primitives::{ + jubjub::fs::Fs, + note_encryption::{Memo, SaplingNoteEncryption}, + primitives::Note, + transaction::components::Amount, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, + JUBJUB, + }; + + use super::scan_block; + use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx}; + + /// Create a fake CompactBlock at the given height, containing a single output paying + /// the given address. Returns the CompactBlock and the nullifier for the new note. + fn fake_compact_block( + height: i32, + extfvk: ExtendedFullViewingKey, + value: Amount, + ) -> CompactBlock { + let to = extfvk.default_address().unwrap().1; + + // Create a fake Note for the account + let mut rng = OsRng; + let note = Note { + g_d: to.diversifier().g_d::(&JUBJUB).unwrap(), + pk_d: to.pk_d().clone(), + value: value.into(), + r: Fs::random(&mut rng), + }; + let encryptor = SaplingNoteEncryption::new( + extfvk.fvk.ovk, + note.clone(), + to.clone(), + Memo::default(), + &mut rng, + ); + let mut cmu = vec![]; + note.cm(&JUBJUB).into_repr().write_le(&mut cmu).unwrap(); + let mut epk = vec![]; + encryptor.epk().write(&mut epk).unwrap(); + let enc_ciphertext = encryptor.encrypt_note_plaintext(); + + // Create a fake CompactBlock containing the note + let mut cb = CompactBlock::new(); + cb.set_height(height as u64); + + let mut cout = CompactOutput::new(); + cout.set_cmu(cmu); + cout.set_epk(epk); + cout.set_ciphertext(enc_ciphertext[..52].to_vec()); + let mut ctx = CompactTx::new(); + let mut txid = vec![0; 32]; + rng.fill_bytes(&mut txid); + ctx.set_hash(txid); + ctx.outputs.push(cout); + cb.vtx.push(ctx); + + cb + } + + #[test] + fn scan_block_with_my_tx() { + let extsk = ExtendedSpendingKey::master(&[]); + let extfvk = ExtendedFullViewingKey::from(&extsk); + + let cb = fake_compact_block(1, extfvk.clone(), Amount::from_u64(5).unwrap()); + + let txs = scan_block(cb, &[extfvk]); + assert_eq!(txs.len(), 1); + + let tx = &txs[0]; + assert_eq!(tx.num_spends, 0); + assert_eq!(tx.num_outputs, 1); + assert_eq!(tx.shielded_outputs.len(), 1); + assert_eq!(tx.shielded_outputs[0].index, 0); + assert_eq!(tx.shielded_outputs[0].account, 0); + assert_eq!(tx.shielded_outputs[0].value, 5); + } +} From f899ecfce5bc09f6e6a50fd4aadc5dc5b70925d1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 2 Dec 2018 12:14:02 +0000 Subject: [PATCH 03/15] Increment the commitment tree and witnesses while scanning blocks --- zcash_client_backend/src/welding_rig.rs | 180 ++++++++++++++++-------- 1 file changed, 123 insertions(+), 57 deletions(-) diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index cac83ff..73a1666 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -4,23 +4,31 @@ use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr}; use zcash_primitives::{ jubjub::{edwards, fs::Fs}, + merkle_tree::{CommitmentTree, IncrementalWitness}, note_encryption::try_sapling_compact_note_decryption, + sapling::Node, transaction::TxId, zip32::ExtendedFullViewingKey, JUBJUB, }; -use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx}; +use crate::proto::compact_formats::{CompactBlock, CompactOutput}; use crate::wallet::{EncCiphertextFrag, WalletShieldedOutput, WalletTx}; /// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s. /// -/// Returns a [`WalletShieldedOutput`] if this output belongs to any of the given -/// [`ExtendedFullViewingKey`]s. +/// Returns a [`WalletShieldedOutput`] and corresponding [`IncrementalWitness`] if this +/// output belongs to any of the given [`ExtendedFullViewingKey`]s. +/// +/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are incremented +/// with this output's commitment. fn scan_output( (index, output): (usize, CompactOutput), ivks: &[Fs], -) -> Option { + tree: &mut CommitmentTree, + existing_witnesses: &mut [&mut IncrementalWitness], + new_witnesses: &mut [IncrementalWitness], +) -> Option<(WalletShieldedOutput, IncrementalWitness)> { let mut repr = FrRepr::default(); if repr.read_le(&output.cmu[..]).is_err() { return None; @@ -40,6 +48,16 @@ fn scan_output( let ct = output.ciphertext; + // Increment tree and witnesses + let node = Node::new(cmu.into_repr()); + for witness in existing_witnesses { + witness.append(node).unwrap(); + } + for witness in new_witnesses { + witness.append(node).unwrap(); + } + tree.append(node).unwrap(); + for (account, ivk) in ivks.iter().enumerate() { let value = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) { Some((note, _)) => note.value, @@ -50,71 +68,80 @@ fn scan_output( let mut enc_ct = EncCiphertextFrag([0u8; 52]); enc_ct.0.copy_from_slice(&ct); - return Some(WalletShieldedOutput { - index, - cmu, - epk, - enc_ct, - account, - value, - }); + return Some(( + WalletShieldedOutput { + index, + cmu, + epk, + enc_ct, + account, + value, + }, + IncrementalWitness::from_tree(tree), + )); } None } -/// Scans a [`CompactTx`] with a set of [`ExtendedFullViewingKey`]s. -/// -/// Returns a [`WalletTx`] if this transaction belongs to any of the given -/// [`ExtendedFullViewingKey`]s. -fn scan_tx(tx: CompactTx, extfvks: &[ExtendedFullViewingKey]) -> Option { - let num_spends = tx.spends.len(); - let num_outputs = tx.outputs.len(); - - // Check for incoming notes - let shielded_outputs: Vec = { - let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); - tx.outputs - .into_iter() - .enumerate() - .filter_map(|(index, output)| scan_output((index, output), &ivks)) - .collect() - }; - - if shielded_outputs.is_empty() { - None - } else { - let mut txid = TxId([0u8; 32]); - txid.0.copy_from_slice(&tx.hash); - Some(WalletTx { - txid, - num_spends, - num_outputs, - shielded_outputs, - }) - } -} - -/// Scans a [`CompactBlock`] for transactions belonging to a set of -/// [`ExtendedFullViewingKey`]s. +/// Scans a [`CompactBlock`] with a set of [`ExtendedFullViewingKey`]s. /// /// Returns a vector of [`WalletTx`]s belonging to any of the given -/// [`ExtendedFullViewingKey`]s. -pub fn scan_block(block: CompactBlock, extfvks: &[ExtendedFullViewingKey]) -> Vec { - block - .vtx - .into_iter() - .filter_map(|tx| scan_tx(tx, extfvks)) - .collect() +/// [`ExtendedFullViewingKey`]s, and the corresponding new [`IncrementalWitness`]es. +/// +/// The given [`CommitmentTree`] and existing [`IncrementalWitness`]es are +/// incremented appropriately. +pub fn scan_block( + block: CompactBlock, + extfvks: &[ExtendedFullViewingKey], + tree: &mut CommitmentTree, + existing_witnesses: &mut [&mut IncrementalWitness], +) -> Vec<(WalletTx, Vec>)> { + let mut wtxs = vec![]; + let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); + + for tx in block.vtx.into_iter() { + let num_spends = tx.spends.len(); + let num_outputs = tx.outputs.len(); + + // Check for incoming notes while incrementing tree and witnesses + let mut shielded_outputs = vec![]; + let mut new_witnesses = vec![]; + for to_scan in tx.outputs.into_iter().enumerate() { + if let Some((output, new_witness)) = + scan_output(to_scan, &ivks, tree, existing_witnesses, &mut new_witnesses) + { + shielded_outputs.push(output); + new_witnesses.push(new_witness); + } + } + + if !shielded_outputs.is_empty() { + let mut txid = TxId([0u8; 32]); + txid.0.copy_from_slice(&tx.hash); + wtxs.push(( + WalletTx { + txid, + num_spends, + num_outputs, + shielded_outputs, + }, + new_witnesses, + )); + } + } + + wtxs } #[cfg(test)] mod tests { use ff::{Field, PrimeField, PrimeFieldRepr}; - use pairing::bls12_381::Bls12; + use pairing::bls12_381::{Bls12, Fr}; use rand_core::RngCore; use rand_os::OsRng; use zcash_primitives::{ - jubjub::fs::Fs, + jubjub::{fs::Fs, FixedGenerators, JubjubParams, ToUniform}, + merkle_tree::CommitmentTree, note_encryption::{Memo, SaplingNoteEncryption}, primitives::Note, transaction::components::Amount, @@ -125,6 +152,36 @@ mod tests { use super::scan_block; use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx}; + fn random_compact_tx(rng: &mut R) -> CompactTx { + let fake_cmu = { + let fake_cmu = Fr::random(rng); + let mut bytes = vec![]; + fake_cmu.into_repr().write_le(&mut bytes).unwrap(); + bytes + }; + let fake_epk = { + let mut buffer = vec![0; 64]; + rng.fill_bytes(&mut buffer); + let fake_esk = Fs::to_uniform(&buffer[..]); + let fake_epk = JUBJUB + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(fake_esk, &JUBJUB); + let mut bytes = vec![]; + fake_epk.write(&mut bytes).unwrap(); + bytes + }; + let mut cout = CompactOutput::new(); + cout.set_cmu(fake_cmu); + cout.set_epk(fake_epk); + cout.set_ciphertext(vec![0; 52]); + let mut ctx = CompactTx::new(); + let mut txid = vec![0; 32]; + rng.fill_bytes(&mut txid); + ctx.set_hash(txid); + ctx.outputs.push(cout); + ctx + } + /// Create a fake CompactBlock at the given height, containing a single output paying /// the given address. Returns the CompactBlock and the nullifier for the new note. fn fake_compact_block( @@ -159,6 +216,9 @@ mod tests { let mut cb = CompactBlock::new(); cb.set_height(height as u64); + // Add a random Sapling tx before ours + cb.vtx.push(random_compact_tx(&mut rng)); + let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); @@ -179,16 +239,22 @@ mod tests { let extfvk = ExtendedFullViewingKey::from(&extsk); let cb = fake_compact_block(1, extfvk.clone(), Amount::from_u64(5).unwrap()); + assert_eq!(cb.vtx.len(), 2); - let txs = scan_block(cb, &[extfvk]); + let mut tree = CommitmentTree::new(); + let txs = scan_block(cb, &[extfvk], &mut tree, &mut []); assert_eq!(txs.len(), 1); - let tx = &txs[0]; + let (tx, new_witnesses) = &txs[0]; assert_eq!(tx.num_spends, 0); assert_eq!(tx.num_outputs, 1); assert_eq!(tx.shielded_outputs.len(), 1); assert_eq!(tx.shielded_outputs[0].index, 0); assert_eq!(tx.shielded_outputs[0].account, 0); assert_eq!(tx.shielded_outputs[0].value, 5); + + // Check that the witness root matches + assert_eq!(new_witnesses.len(), 1); + assert_eq!(new_witnesses[0].root(), tree.root()); } } From 2b71121681f8d310420b9303ee69f30c9f910d3e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 2 Dec 2018 19:15:04 +0000 Subject: [PATCH 04/15] Return the entire note and recipient address when scanning an output --- zcash_client_backend/src/wallet.rs | 7 +++---- zcash_client_backend/src/welding_rig.rs | 16 ++++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 4e85eef..a20b09f 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -4,11 +4,10 @@ use pairing::bls12_381::{Bls12, Fr}; use zcash_primitives::{ jubjub::{edwards, PrimeOrder}, + primitives::{Note, PaymentAddress}, transaction::TxId, }; -pub struct EncCiphertextFrag(pub [u8; 52]); - /// A subset of a [`Transaction`] relevant to wallets and light clients. /// /// [`Transaction`]: zcash_primitives::transaction::Transaction @@ -26,7 +25,7 @@ pub struct WalletShieldedOutput { pub index: usize, pub cmu: Fr, pub epk: edwards::Point, - pub enc_ct: EncCiphertextFrag, pub account: usize, - pub value: u64, + pub note: Note, + pub to: PaymentAddress, } diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 73a1666..2e6f2b2 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -13,7 +13,7 @@ use zcash_primitives::{ }; use crate::proto::compact_formats::{CompactBlock, CompactOutput}; -use crate::wallet::{EncCiphertextFrag, WalletShieldedOutput, WalletTx}; +use crate::wallet::{WalletShieldedOutput, WalletTx}; /// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s. /// @@ -59,23 +59,19 @@ fn scan_output( tree.append(node).unwrap(); for (account, ivk) in ivks.iter().enumerate() { - let value = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) { - Some((note, _)) => note.value, + let (note, to) = match try_sapling_compact_note_decryption(ivk, &epk, &cmu, &ct) { + Some(ret) => ret, None => continue, }; - // It's ours, so let's copy the ciphertext fragment and return - let mut enc_ct = EncCiphertextFrag([0u8; 52]); - enc_ct.0.copy_from_slice(&ct); - return Some(( WalletShieldedOutput { index, cmu, epk, - enc_ct, account, - value, + note, + to, }, IncrementalWitness::from_tree(tree), )); @@ -251,7 +247,7 @@ mod tests { assert_eq!(tx.shielded_outputs.len(), 1); assert_eq!(tx.shielded_outputs[0].index, 0); assert_eq!(tx.shielded_outputs[0].account, 0); - assert_eq!(tx.shielded_outputs[0].value, 5); + assert_eq!(tx.shielded_outputs[0].note.value, 5); // Check that the witness root matches assert_eq!(new_witnesses.len(), 1); From fb9e9bb12f316a5970a3370aa49d3b5414118c66 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 4 Dec 2018 14:03:12 +0000 Subject: [PATCH 05/15] Check for spent notes while scanning blocks --- zcash_client_backend/src/wallet.rs | 9 ++++ zcash_client_backend/src/welding_rig.rs | 72 ++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index a20b09f..c120ba5 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -15,9 +15,18 @@ pub struct WalletTx { pub txid: TxId, pub num_spends: usize, pub num_outputs: usize, + pub shielded_spends: Vec, pub shielded_outputs: Vec, } +/// A subset of a [`SpendDescription`] relevant to wallets and light clients. +/// +/// [`SpendDescription`]: zcash_primitives::transaction::components::SpendDescription +pub struct WalletShieldedSpend { + pub index: usize, + pub nf: Vec, +} + /// A subset of an [`OutputDescription`] relevant to wallets and light clients. /// /// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 2e6f2b2..0b5a01f 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -13,7 +13,7 @@ use zcash_primitives::{ }; use crate::proto::compact_formats::{CompactBlock, CompactOutput}; -use crate::wallet::{WalletShieldedOutput, WalletTx}; +use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx}; /// Scans a [`CompactOutput`] with a set of [`ExtendedFullViewingKey`]s. /// @@ -89,6 +89,7 @@ fn scan_output( pub fn scan_block( block: CompactBlock, extfvks: &[ExtendedFullViewingKey], + nullifiers: &[&[u8]], tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], ) -> Vec<(WalletTx, Vec>)> { @@ -99,6 +100,23 @@ pub fn scan_block( let num_spends = tx.spends.len(); let num_outputs = tx.outputs.len(); + // Check for spent notes + let shielded_spends: Vec<_> = tx + .spends + .into_iter() + .enumerate() + .filter_map(|(index, spend)| { + if nullifiers.contains(&&spend.nf[..]) { + Some(WalletShieldedSpend { + index, + nf: spend.nf, + }) + } else { + None + } + }) + .collect(); + // Check for incoming notes while incrementing tree and witnesses let mut shielded_outputs = vec![]; let mut new_witnesses = vec![]; @@ -111,7 +129,7 @@ pub fn scan_block( } } - if !shielded_outputs.is_empty() { + if !(shielded_spends.is_empty() && shielded_outputs.is_empty()) { let mut txid = TxId([0u8; 32]); txid.0.copy_from_slice(&tx.hash); wtxs.push(( @@ -119,6 +137,7 @@ pub fn scan_block( txid, num_spends, num_outputs, + shielded_spends, shielded_outputs, }, new_witnesses, @@ -146,9 +165,14 @@ mod tests { }; use super::scan_block; - use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactTx}; + use crate::proto::compact_formats::{CompactBlock, CompactOutput, CompactSpend, CompactTx}; fn random_compact_tx(rng: &mut R) -> CompactTx { + let fake_nf = { + let mut nf = vec![0; 32]; + rng.fill_bytes(&mut nf); + nf + }; let fake_cmu = { let fake_cmu = Fr::random(rng); let mut bytes = vec![]; @@ -166,6 +190,8 @@ mod tests { fake_epk.write(&mut bytes).unwrap(); bytes }; + let mut cspend = CompactSpend::new(); + cspend.set_nf(fake_nf); let mut cout = CompactOutput::new(); cout.set_cmu(fake_cmu); cout.set_epk(fake_epk); @@ -174,14 +200,17 @@ mod tests { let mut txid = vec![0; 32]; rng.fill_bytes(&mut txid); ctx.set_hash(txid); + ctx.spends.push(cspend); ctx.outputs.push(cout); ctx } - /// Create a fake CompactBlock at the given height, containing a single output paying - /// the given address. Returns the CompactBlock and the nullifier for the new note. + /// Create a fake CompactBlock at the given height, with a transaction containing a + /// single spend of the given nullifier and a single output paying the given address. + /// Returns the CompactBlock. fn fake_compact_block( height: i32, + nf: [u8; 32], extfvk: ExtendedFullViewingKey, value: Amount, ) -> CompactBlock { @@ -215,6 +244,8 @@ mod tests { // Add a random Sapling tx before ours cb.vtx.push(random_compact_tx(&mut rng)); + let mut cspend = CompactSpend::new(); + cspend.set_nf(nf.to_vec()); let mut cout = CompactOutput::new(); cout.set_cmu(cmu); cout.set_epk(epk); @@ -223,6 +254,7 @@ mod tests { let mut txid = vec![0; 32]; rng.fill_bytes(&mut txid); ctx.set_hash(txid); + ctx.spends.push(cspend); ctx.outputs.push(cout); cb.vtx.push(ctx); @@ -234,16 +266,17 @@ mod tests { let extsk = ExtendedSpendingKey::master(&[]); let extfvk = ExtendedFullViewingKey::from(&extsk); - let cb = fake_compact_block(1, extfvk.clone(), Amount::from_u64(5).unwrap()); + let cb = fake_compact_block(1, [0; 32], extfvk.clone(), Amount::from_u64(5).unwrap()); assert_eq!(cb.vtx.len(), 2); let mut tree = CommitmentTree::new(); - let txs = scan_block(cb, &[extfvk], &mut tree, &mut []); + let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []); assert_eq!(txs.len(), 1); let (tx, new_witnesses) = &txs[0]; - assert_eq!(tx.num_spends, 0); + assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); + assert_eq!(tx.shielded_spends.len(), 0); assert_eq!(tx.shielded_outputs.len(), 1); assert_eq!(tx.shielded_outputs[0].index, 0); assert_eq!(tx.shielded_outputs[0].account, 0); @@ -253,4 +286,27 @@ mod tests { assert_eq!(new_witnesses.len(), 1); assert_eq!(new_witnesses[0].root(), tree.root()); } + + #[test] + fn scan_block_with_my_spend() { + let extsk = ExtendedSpendingKey::master(&[]); + let extfvk = ExtendedFullViewingKey::from(&extsk); + let nf = [7; 32]; + + let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap()); + assert_eq!(cb.vtx.len(), 2); + + let mut tree = CommitmentTree::new(); + let txs = scan_block(cb, &[], &[&nf], &mut tree, &mut []); + assert_eq!(txs.len(), 1); + + let (tx, new_witnesses) = &txs[0]; + assert_eq!(tx.num_spends, 1); + assert_eq!(tx.num_outputs, 1); + assert_eq!(tx.shielded_spends.len(), 1); + assert_eq!(tx.shielded_outputs.len(), 0); + assert_eq!(tx.shielded_spends[0].index, 0); + assert_eq!(tx.shielded_spends[0].nf, nf); + assert_eq!(new_witnesses.len(), 0); + } } From 3b9dfc1e0b4b2bd8912546d2215e1c471e73fe82 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 11 Jan 2019 14:26:54 -0800 Subject: [PATCH 06/15] Detect change notes while scanning blocks --- zcash_client_backend/src/wallet.rs | 2 + zcash_client_backend/src/welding_rig.rs | 69 ++++++++++++++++++------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index c120ba5..22df1cb 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -25,6 +25,7 @@ pub struct WalletTx { pub struct WalletShieldedSpend { pub index: usize, pub nf: Vec, + pub account: usize, } /// A subset of an [`OutputDescription`] relevant to wallets and light clients. @@ -37,4 +38,5 @@ pub struct WalletShieldedOutput { pub account: usize, pub note: Note, pub to: PaymentAddress, + pub is_change: bool, } diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 0b5a01f..b8310e3 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -2,6 +2,7 @@ use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use std::collections::HashSet; use zcash_primitives::{ jubjub::{edwards, fs::Fs}, merkle_tree::{CommitmentTree, IncrementalWitness}, @@ -25,6 +26,7 @@ use crate::wallet::{WalletShieldedOutput, WalletShieldedSpend, WalletTx}; fn scan_output( (index, output): (usize, CompactOutput), ivks: &[Fs], + spent_from_accounts: &HashSet, tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [IncrementalWitness], @@ -64,6 +66,14 @@ fn scan_output( 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(( WalletShieldedOutput { index, @@ -72,6 +82,7 @@ fn scan_output( account, note, to, + is_change, }, IncrementalWitness::from_tree(tree), )); @@ -89,7 +100,7 @@ fn scan_output( pub fn scan_block( block: CompactBlock, extfvks: &[ExtendedFullViewingKey], - nullifiers: &[&[u8]], + nullifiers: &[(&[u8], usize)], tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], ) -> Vec<(WalletTx, Vec>)> { @@ -101,29 +112,45 @@ 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 nullifiers.contains(&&spend.nf[..]) { - Some(WalletShieldedSpend { - index, - nf: spend.nf, - }) - } else { - None - } - }) - .collect(); + 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(); + + // 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 let mut shielded_outputs = vec![]; let mut new_witnesses = vec![]; for to_scan in tx.outputs.into_iter().enumerate() { - if let Some((output, new_witness)) = - scan_output(to_scan, &ivks, tree, existing_witnesses, &mut new_witnesses) - { + if let Some((output, new_witness)) = scan_output( + to_scan, + &ivks, + &spent_from_accounts, + tree, + existing_witnesses, + &mut new_witnesses, + ) { shielded_outputs.push(output); new_witnesses.push(new_witness); } @@ -292,12 +319,13 @@ mod tests { let extsk = ExtendedSpendingKey::master(&[]); let extfvk = ExtendedFullViewingKey::from(&extsk); let nf = [7; 32]; + let account = 12; let cb = fake_compact_block(1, nf, extfvk, Amount::from_u64(5).unwrap()); assert_eq!(cb.vtx.len(), 2); 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); let (tx, new_witnesses) = &txs[0]; @@ -307,6 +335,7 @@ mod tests { assert_eq!(tx.shielded_outputs.len(), 0); assert_eq!(tx.shielded_spends[0].index, 0); assert_eq!(tx.shielded_spends[0].nf, nf); + assert_eq!(tx.shielded_spends[0].account, account); assert_eq!(new_witnesses.len(), 0); } } From e746f7b6f9b7f9e01104b58f24fc43c1f4a622e3 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 24 Jan 2019 23:26:14 +0000 Subject: [PATCH 07/15] Add tx index within block to WalletTx struct --- zcash_client_backend/src/wallet.rs | 1 + zcash_client_backend/src/welding_rig.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 22df1cb..dd5229b 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -13,6 +13,7 @@ use zcash_primitives::{ /// [`Transaction`]: zcash_primitives::transaction::Transaction pub struct WalletTx { pub txid: TxId, + pub index: usize, pub num_spends: usize, pub num_outputs: usize, pub shielded_spends: Vec, diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index b8310e3..27d29bb 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -162,6 +162,7 @@ pub fn scan_block( wtxs.push(( WalletTx { txid, + index: tx.index as usize, num_spends, num_outputs, shielded_spends, @@ -269,7 +270,11 @@ mod tests { cb.set_height(height as u64); // Add a random Sapling tx before ours - cb.vtx.push(random_compact_tx(&mut rng)); + { + let mut tx = random_compact_tx(&mut rng); + tx.index = cb.vtx.len() as u64; + cb.vtx.push(tx); + } let mut cspend = CompactSpend::new(); cspend.set_nf(nf.to_vec()); @@ -283,6 +288,7 @@ mod tests { ctx.set_hash(txid); ctx.spends.push(cspend); ctx.outputs.push(cout); + ctx.index = cb.vtx.len() as u64; cb.vtx.push(ctx); cb @@ -301,6 +307,7 @@ mod tests { assert_eq!(txs.len(), 1); let (tx, new_witnesses) = &txs[0]; + assert_eq!(tx.index, 1); assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); assert_eq!(tx.shielded_spends.len(), 0); @@ -329,6 +336,7 @@ mod tests { assert_eq!(txs.len(), 1); let (tx, new_witnesses) = &txs[0]; + assert_eq!(tx.index, 1); assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); assert_eq!(tx.shielded_spends.len(), 1); From b66ac117759281c2c3ff3f8ac430e3f98f8807d1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 14 Feb 2019 18:06:50 +0000 Subject: [PATCH 08/15] Update new witnesses with subsequent transactions in the same block --- zcash_client_backend/src/welding_rig.rs | 91 +++++++++++++++++++++---- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 27d29bb..e3f9d8b 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -29,6 +29,7 @@ fn scan_output( spent_from_accounts: &HashSet, tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], + block_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [IncrementalWitness], ) -> Option<(WalletShieldedOutput, IncrementalWitness)> { let mut repr = FrRepr::default(); @@ -55,6 +56,9 @@ fn scan_output( for witness in existing_witnesses { witness.append(node).unwrap(); } + for witness in block_witnesses { + witness.append(node).unwrap(); + } for witness in new_witnesses { witness.append(node).unwrap(); } @@ -104,7 +108,7 @@ pub fn scan_block( tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], ) -> Vec<(WalletTx, Vec>)> { - let mut wtxs = vec![]; + let mut wtxs: Vec<(WalletTx, Vec>)> = vec![]; let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); for tx in block.vtx.into_iter() { @@ -142,17 +146,29 @@ pub fn scan_block( // Check for incoming notes while incrementing tree and witnesses let mut shielded_outputs = vec![]; let mut new_witnesses = vec![]; - for to_scan in tx.outputs.into_iter().enumerate() { - if let Some((output, new_witness)) = scan_output( - to_scan, - &ivks, - &spent_from_accounts, - tree, - existing_witnesses, - &mut new_witnesses, - ) { - shielded_outputs.push(output); - new_witnesses.push(new_witness); + { + // Grab mutable references to new witnesses from previous transactions + // in this block so that we can update them. Scoped so we don't hold + // mutable references to wtxs for too long. + let mut block_witnesses: Vec<_> = wtxs + .iter_mut() + .map(|(_, w)| w.iter_mut().collect::>()) + .flatten() + .collect(); + + for to_scan in tx.outputs.into_iter().enumerate() { + if let Some((output, new_witness)) = scan_output( + to_scan, + &ivks, + &spent_from_accounts, + tree, + existing_witnesses, + &mut block_witnesses, + &mut new_witnesses, + ) { + shielded_outputs.push(output); + new_witnesses.push(new_witness); + } } } @@ -241,6 +257,7 @@ mod tests { nf: [u8; 32], extfvk: ExtendedFullViewingKey, value: Amount, + tx_after: bool, ) -> CompactBlock { let to = extfvk.default_address().unwrap().1; @@ -291,6 +308,13 @@ mod tests { ctx.index = cb.vtx.len() as u64; cb.vtx.push(ctx); + // Optionally add another random Sapling tx after ours + if tx_after { + let mut tx = random_compact_tx(&mut rng); + tx.index = cb.vtx.len() as u64; + cb.vtx.push(tx); + } + cb } @@ -299,7 +323,13 @@ mod tests { let extsk = ExtendedSpendingKey::master(&[]); let extfvk = ExtendedFullViewingKey::from(&extsk); - let cb = fake_compact_block(1, [0; 32], extfvk.clone(), Amount::from_u64(5).unwrap()); + let cb = fake_compact_block( + 1, + [0; 32], + extfvk.clone(), + Amount::from_u64(5).unwrap(), + false, + ); assert_eq!(cb.vtx.len(), 2); let mut tree = CommitmentTree::new(); @@ -321,6 +351,39 @@ mod tests { assert_eq!(new_witnesses[0].root(), tree.root()); } + #[test] + fn scan_block_with_txs_after_my_tx() { + let extsk = ExtendedSpendingKey::master(&[]); + let extfvk = ExtendedFullViewingKey::from(&extsk); + + let cb = fake_compact_block( + 1, + [0; 32], + extfvk.clone(), + Amount::from_u64(5).unwrap(), + true, + ); + assert_eq!(cb.vtx.len(), 3); + + let mut tree = CommitmentTree::new(); + let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []); + assert_eq!(txs.len(), 1); + + let (tx, new_witnesses) = &txs[0]; + assert_eq!(tx.index, 1); + assert_eq!(tx.num_spends, 1); + assert_eq!(tx.num_outputs, 1); + assert_eq!(tx.shielded_spends.len(), 0); + assert_eq!(tx.shielded_outputs.len(), 1); + assert_eq!(tx.shielded_outputs[0].index, 0); + assert_eq!(tx.shielded_outputs[0].account, 0); + assert_eq!(tx.shielded_outputs[0].note.value, 5); + + // Check that the witness root matches + assert_eq!(new_witnesses.len(), 1); + assert_eq!(new_witnesses[0].root(), tree.root()); + } + #[test] fn scan_block_with_my_spend() { let extsk = ExtendedSpendingKey::master(&[]); @@ -328,7 +391,7 @@ mod tests { 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(), false); assert_eq!(cb.vtx.len(), 2); let mut tree = CommitmentTree::new(); From a3b85b8fe6baca50829e296856a0eff38f60e855 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 May 2019 00:08:54 +0100 Subject: [PATCH 09/15] Compute and store BlockHash inside BlockHeader --- zcash_primitives/src/block.rs | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/zcash_primitives/src/block.rs b/zcash_primitives/src/block.rs index ecb0e5b..3140f9b 100644 --- a/zcash_primitives/src/block.rs +++ b/zcash_primitives/src/block.rs @@ -2,6 +2,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use hex; +use sha2::{Digest, Sha256}; use std::fmt; use std::io::{self, Read, Write}; use std::ops::Deref; @@ -22,13 +23,16 @@ impl fmt::Display for BlockHash { } /// A Zcash block header. -pub struct BlockHeader(BlockHeaderData); +pub struct BlockHeader { + hash: BlockHash, + data: BlockHeaderData, +} impl Deref for BlockHeader { type Target = BlockHeaderData; fn deref(&self) -> &BlockHeaderData { - &self.0 + &self.data } } @@ -44,12 +48,31 @@ pub struct BlockHeaderData { } impl BlockHeaderData { - pub fn freeze(self) -> BlockHeader { - BlockHeader(self) + pub fn freeze(self) -> io::Result { + BlockHeader::from_data(self) } } impl BlockHeader { + fn from_data(data: BlockHeaderData) -> io::Result { + let mut header = BlockHeader { + hash: BlockHash([0; 32]), + data, + }; + let mut raw = vec![]; + header.write(&mut raw)?; + header + .hash + .0 + .copy_from_slice(&Sha256::digest(&Sha256::digest(&raw))); + Ok(header) + } + + /// Returns the hash of this header. + pub fn hash(&self) -> BlockHash { + self.hash + } + pub fn read(mut reader: R) -> io::Result { let version = reader.read_i32::()?; @@ -70,7 +93,7 @@ impl BlockHeader { let solution = Vector::read(&mut reader, |r| r.read_u8())?; - Ok(BlockHeader(BlockHeaderData { + BlockHeader::from_data(BlockHeaderData { version, prev_block, merkle_root, @@ -79,7 +102,7 @@ impl BlockHeader { bits, nonce, solution, - })) + }) } pub fn write(&self, mut writer: W) -> io::Result<()> { @@ -206,6 +229,10 @@ mod tests { #[test] fn header_read_write() { let header = BlockHeader::read(&HEADER_MAINNET_415000[..]).unwrap(); + assert_eq!( + format!("{}", header.hash()), + "0000000001ab37793ce771262b2ffa082519aa3fe891250a1adb43baaf856168" + ); let mut encoded = Vec::with_capacity(HEADER_MAINNET_415000.len()); header.write(&mut encoded).unwrap(); assert_eq!(&HEADER_MAINNET_415000[..], &encoded[..]); From 2bbd25b36b3467089af7b81b072993245e42c9a8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 1 May 2019 13:21:48 +0100 Subject: [PATCH 10/15] Add prevHash field to CompactBlock This enables basic verification of chain validity when CompactBlocks are received without the full header. --- .../proto/compact_formats.proto | 7 +-- zcash_client_backend/src/proto/mod.rs | 51 +++++++++++++++++++ zcash_primitives/src/block.rs | 14 +++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/zcash_client_backend/proto/compact_formats.proto b/zcash_client_backend/proto/compact_formats.proto index d65b658..7e1bc54 100644 --- a/zcash_client_backend/proto/compact_formats.proto +++ b/zcash_client_backend/proto/compact_formats.proto @@ -13,9 +13,10 @@ message CompactBlock { uint32 protoVersion = 1; // the version of this wire format, for storage uint64 height = 2; // the height of this block bytes hash = 3; - uint32 time = 4; - bytes header = 5; // (hash and time) OR (full header) - repeated CompactTx vtx = 6; // compact transactions from this block + bytes prevHash = 4; + uint32 time = 5; + bytes header = 6; // (hash, prevHash, and time) OR (full header) + repeated CompactTx vtx = 7; // compact transactions from this block } message CompactTx { diff --git a/zcash_client_backend/src/proto/mod.rs b/zcash_client_backend/src/proto/mod.rs index e24ef5c..50d562b 100644 --- a/zcash_client_backend/src/proto/mod.rs +++ b/zcash_client_backend/src/proto/mod.rs @@ -1,3 +1,54 @@ //! Generated code for handling light client protobuf structs. +use zcash_primitives::block::{BlockHash, BlockHeader}; + pub mod compact_formats; + +impl compact_formats::CompactBlock { + /// Returns the [`BlockHash`] for this block. + /// + /// # Panics + /// + /// This function will panic if [`CompactBlock.header`] is not set and + /// [`CompactBlock.hash`] is not exactly 32 bytes. + /// + /// [`CompactBlock.header`]: #structfield.header + /// [`CompactBlock.hash`]: #structfield.hash + pub fn hash(&self) -> BlockHash { + if let Some(header) = self.header() { + header.hash() + } else { + BlockHash::from_slice(&self.hash) + } + } + + /// Returns the [`BlockHash`] for this block's parent. + /// + /// # Panics + /// + /// This function will panic if [`CompactBlock.header`] is not set and + /// [`CompactBlock.prevHash`] is not exactly 32 bytes. + /// + /// [`CompactBlock.header`]: #structfield.header + /// [`CompactBlock.prevHash`]: #structfield.prevHash + pub fn prev_hash(&self) -> BlockHash { + if let Some(header) = self.header() { + header.prev_block + } else { + BlockHash::from_slice(&self.prevHash) + } + } + + /// Returns the [`BlockHeader`] for this block if present. + /// + /// A convenience method that parses [`CompactBlock.header`] if present. + /// + /// [`CompactBlock.header`]: #structfield.header + pub fn header(&self) -> Option { + if self.header.is_empty() { + None + } else { + BlockHeader::read(&self.header[..]).ok() + } + } +} diff --git a/zcash_primitives/src/block.rs b/zcash_primitives/src/block.rs index 3140f9b..8432cd4 100644 --- a/zcash_primitives/src/block.rs +++ b/zcash_primitives/src/block.rs @@ -22,6 +22,20 @@ impl fmt::Display for BlockHash { } } +impl BlockHash { + /// Constructs a [`BlockHash`] from the given slice. + /// + /// # Panics + /// + /// This function will panic if the slice is not exactly 32 bytes. + pub fn from_slice(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 32); + let mut hash = [0; 32]; + hash.copy_from_slice(&bytes); + BlockHash(hash) + } +} + /// A Zcash block header. pub struct BlockHeader { hash: BlockHash, From 1e2bc7f65ca68674d522d6220d41b7369b3fbedf Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 22 Aug 2019 00:57:04 +0100 Subject: [PATCH 11/15] Test nullifiers in constant time 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. --- Cargo.lock | 7 ++++ zcash_client_backend/Cargo.toml | 1 + zcash_client_backend/src/welding_rig.rs | 46 +++++++++++++------------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75ff772..fe20d7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,6 +588,11 @@ dependencies = [ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "1.0.5" @@ -645,6 +650,7 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "zcash_primitives 0.1.0", ] @@ -751,6 +757,7 @@ dependencies = [ "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index e639efd..3c8a014 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -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] diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index e3f9d8b..307dcea 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -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}, @@ -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<_> = From 7fa51e38c05613c86ac3502466930d15381eb8e5 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 22 Aug 2019 15:18:50 +0100 Subject: [PATCH 12/15] Travis CI: Build before formatting check cargo fmt does not build the code, and running it in a fresh clone of the codebase will fail because the protobuf code has not been generated. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 85dfd70..f0f9e46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,8 @@ before_script: - rustup component add rustfmt script: - - cargo fmt --all -- --check - cargo build --verbose --release --all + - cargo fmt --all -- --check - cargo test --verbose --release --all - cargo test --verbose --release --all -- --ignored From b44653e686e9b7ae16f2228f034535c5dbbdc67a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 26 Aug 2019 11:59:07 +0100 Subject: [PATCH 13/15] Move cmu and epk parsing onto CompactOutput struct --- zcash_client_backend/src/proto/mod.rs | 31 ++++++++++++++++++++++++- zcash_client_backend/src/welding_rig.rs | 25 ++++---------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/zcash_client_backend/src/proto/mod.rs b/zcash_client_backend/src/proto/mod.rs index 50d562b..0ab1b6d 100644 --- a/zcash_client_backend/src/proto/mod.rs +++ b/zcash_client_backend/src/proto/mod.rs @@ -1,6 +1,12 @@ //! Generated code for handling light client protobuf structs. -use zcash_primitives::block::{BlockHash, BlockHeader}; +use ff::{PrimeField, PrimeFieldRepr}; +use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use zcash_primitives::{ + block::{BlockHash, BlockHeader}, + jubjub::{edwards, PrimeOrder}, + JUBJUB, +}; pub mod compact_formats; @@ -52,3 +58,26 @@ impl compact_formats::CompactBlock { } } } + +impl compact_formats::CompactOutput { + /// Returns the note commitment for this output. + /// + /// A convenience method that parses [`CompactOutput.cmu`]. + /// + /// [`CompactOutput.cmu`]: #structfield.cmu + pub fn cmu(&self) -> Result { + let mut repr = FrRepr::default(); + repr.read_le(&self.cmu[..]).map_err(|_| ())?; + Fr::from_repr(repr).map_err(|_| ()) + } + + /// Returns the ephemeral public key for this output. + /// + /// A convenience method that parses [`CompactOutput.epk`]. + /// + /// [`CompactOutput.epk`]: #structfield.epk + pub fn epk(&self) -> Result, ()> { + let p = edwards::Point::::read(&self.epk[..], &JUBJUB).map_err(|_| ())?; + p.as_prime_order(&JUBJUB).ok_or(()) + } +} diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 307dcea..0996607 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -1,17 +1,15 @@ //! Tools for scanning a compact representation of the Zcash block chain. -use ff::{PrimeField, PrimeFieldRepr}; -use pairing::bls12_381::{Bls12, Fr, FrRepr}; +use ff::PrimeField; use std::collections::HashSet; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_primitives::{ - jubjub::{edwards, fs::Fs}, + jubjub::fs::Fs, merkle_tree::{CommitmentTree, IncrementalWitness}, note_encryption::try_sapling_compact_note_decryption, sapling::Node, transaction::TxId, zip32::ExtendedFullViewingKey, - JUBJUB, }; use crate::proto::compact_formats::{CompactBlock, CompactOutput}; @@ -33,23 +31,8 @@ fn scan_output( block_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [IncrementalWitness], ) -> Option<(WalletShieldedOutput, IncrementalWitness)> { - let mut repr = FrRepr::default(); - if repr.read_le(&output.cmu[..]).is_err() { - return None; - } - let cmu = match Fr::from_repr(repr) { - Ok(cmu) => cmu, - Err(_) => return None, - }; - - let epk = match edwards::Point::::read(&output.epk[..], &JUBJUB) { - Ok(p) => match p.as_prime_order(&JUBJUB) { - Some(epk) => epk, - None => return None, - }, - Err(_) => return None, - }; - + let cmu = output.cmu().ok()?; + let epk = output.epk().ok()?; let ct = output.ciphertext; // Increment tree and witnesses From e9f94119bcfc19eb92effb8c47b5cc2f31754c9e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 26 Aug 2019 12:12:32 +0100 Subject: [PATCH 14/15] Store witness inside WalletShieldedOutput --- zcash_client_backend/src/wallet.rs | 3 + zcash_client_backend/src/welding_rig.rs | 82 +++++++++++++------------ 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index dd5229b..dc46a86 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -4,7 +4,9 @@ use pairing::bls12_381::{Bls12, Fr}; use zcash_primitives::{ jubjub::{edwards, PrimeOrder}, + merkle_tree::IncrementalWitness, primitives::{Note, PaymentAddress}, + sapling::Node, transaction::TxId, }; @@ -40,4 +42,5 @@ pub struct WalletShieldedOutput { pub note: Note, pub to: PaymentAddress, pub is_change: bool, + pub witness: IncrementalWitness, } diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 0996607..6b9ec59 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -29,8 +29,8 @@ fn scan_output( tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], block_witnesses: &mut [&mut IncrementalWitness], - new_witnesses: &mut [IncrementalWitness], -) -> Option<(WalletShieldedOutput, IncrementalWitness)> { + new_witnesses: &mut [&mut IncrementalWitness], +) -> Option { let cmu = output.cmu().ok()?; let epk = output.epk().ok()?; let ct = output.ciphertext; @@ -62,18 +62,16 @@ fn scan_output( // - Notes sent from one account to itself. let is_change = spent_from_accounts.contains(&account); - return Some(( - WalletShieldedOutput { - index, - cmu, - epk, - account, - note, - to, - is_change, - }, - IncrementalWitness::from_tree(tree), - )); + return Some(WalletShieldedOutput { + index, + cmu, + epk, + account, + note, + to, + is_change, + witness: IncrementalWitness::from_tree(tree), + }); } None } @@ -91,8 +89,8 @@ pub fn scan_block( nullifiers: &[(&[u8], usize)], tree: &mut CommitmentTree, existing_witnesses: &mut [&mut IncrementalWitness], -) -> Vec<(WalletTx, Vec>)> { - let mut wtxs: Vec<(WalletTx, Vec>)> = vec![]; +) -> Vec { + let mut wtxs: Vec = vec![]; let ivks: Vec<_> = extfvks.iter().map(|extfvk| extfvk.fvk.vk.ivk()).collect(); for tx in block.vtx.into_iter() { @@ -129,20 +127,31 @@ pub fn scan_block( shielded_spends.iter().map(|spend| spend.account).collect(); // Check for incoming notes while incrementing tree and witnesses - let mut shielded_outputs = vec![]; - let mut new_witnesses = vec![]; + let mut shielded_outputs: Vec = 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 // mutable references to wtxs for too long. let mut block_witnesses: Vec<_> = wtxs .iter_mut() - .map(|(_, w)| w.iter_mut().collect::>()) + .map(|tx| { + tx.shielded_outputs + .iter_mut() + .map(|output| &mut output.witness) + }) .flatten() .collect(); for to_scan in tx.outputs.into_iter().enumerate() { - if let Some((output, new_witness)) = scan_output( + // Grab mutable references to new witnesses from previous outputs + // in this transaction so that we can update them. Scoped so we + // don't hold mutable references to shielded_outputs for too long. + let mut new_witnesses: Vec<_> = shielded_outputs + .iter_mut() + .map(|output| &mut output.witness) + .collect(); + + if let Some(output) = scan_output( to_scan, &ivks, &spent_from_accounts, @@ -152,7 +161,6 @@ pub fn scan_block( &mut new_witnesses, ) { shielded_outputs.push(output); - new_witnesses.push(new_witness); } } } @@ -160,17 +168,14 @@ pub fn scan_block( if !(shielded_spends.is_empty() && shielded_outputs.is_empty()) { let mut txid = TxId([0u8; 32]); txid.0.copy_from_slice(&tx.hash); - wtxs.push(( - WalletTx { - txid, - index: tx.index as usize, - num_spends, - num_outputs, - shielded_spends, - shielded_outputs, - }, - new_witnesses, - )); + wtxs.push(WalletTx { + txid, + index: tx.index as usize, + num_spends, + num_outputs, + shielded_spends, + shielded_outputs, + }); } } @@ -321,7 +326,7 @@ mod tests { let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []); assert_eq!(txs.len(), 1); - let (tx, new_witnesses) = &txs[0]; + let tx = &txs[0]; assert_eq!(tx.index, 1); assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); @@ -332,8 +337,7 @@ mod tests { assert_eq!(tx.shielded_outputs[0].note.value, 5); // Check that the witness root matches - assert_eq!(new_witnesses.len(), 1); - assert_eq!(new_witnesses[0].root(), tree.root()); + assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root()); } #[test] @@ -354,7 +358,7 @@ mod tests { let txs = scan_block(cb, &[extfvk], &[], &mut tree, &mut []); assert_eq!(txs.len(), 1); - let (tx, new_witnesses) = &txs[0]; + let tx = &txs[0]; assert_eq!(tx.index, 1); assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); @@ -365,8 +369,7 @@ mod tests { assert_eq!(tx.shielded_outputs[0].note.value, 5); // Check that the witness root matches - assert_eq!(new_witnesses.len(), 1); - assert_eq!(new_witnesses[0].root(), tree.root()); + assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root()); } #[test] @@ -383,7 +386,7 @@ mod tests { let txs = scan_block(cb, &[], &[(&nf, account)], &mut tree, &mut []); assert_eq!(txs.len(), 1); - let (tx, new_witnesses) = &txs[0]; + let tx = &txs[0]; assert_eq!(tx.index, 1); assert_eq!(tx.num_spends, 1); assert_eq!(tx.num_outputs, 1); @@ -392,6 +395,5 @@ mod tests { assert_eq!(tx.shielded_spends[0].index, 0); assert_eq!(tx.shielded_spends[0].nf, nf); assert_eq!(tx.shielded_spends[0].account, account); - assert_eq!(new_witnesses.len(), 0); } } From 274f860202d18350c9489cb7d3f3f3db717f8def Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 18 Sep 2019 17:08:58 +0100 Subject: [PATCH 15/15] Actions: Build before formatting check cargo fmt does not build the code, and running it in a fresh clone of the codebase will fail because the protobuf code has not been generated. --- .github/workflows/rust.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5f76fb0..85231fa 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,6 +14,14 @@ jobs: toolchain: 1.37.0 override: true + # cargo fmt does not build the code, and running it in a fresh clone of + # the codebase will fail because the protobuf code has not been generated. + - name: cargo build + uses: actions-rs/cargo@v1 + with: + command: build + args: --all + # Ensure all code has been formatted with rustfmt - run: rustup component add rustfmt - name: Check formatting