Parse compact blocks to find wallet transactions

This commit is contained in:
Jack Grigg 2018-10-12 18:24:25 +01:00
parent af7e263bcc
commit 591b1fc28f
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
5 changed files with 244 additions and 0 deletions

13
Cargo.lock generated
View File

@ -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"

View File

@ -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]

View File

@ -10,3 +10,5 @@ pub mod constants;
pub mod encoding;
pub mod keys;
pub mod proto;
pub mod wallet;
pub mod welding_rig;

View File

@ -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<WalletShieldedOutput>,
}
/// 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<Bls12, PrimeOrder>,
pub enc_ct: EncCiphertextFrag,
pub account: usize,
pub value: u64,
}

View File

@ -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<WalletShieldedOutput> {
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::<Bls12, _>::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<WalletTx> {
let num_spends = tx.spends.len();
let num_outputs = tx.outputs.len();
// Check for incoming notes
let shielded_outputs: Vec<WalletShieldedOutput> = {
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<WalletTx> {
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::<Bls12>(&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);
}
}