2022-10-19 23:32:11 -07:00
|
|
|
use crate::chain::Nf;
|
2022-11-06 04:50:51 -08:00
|
|
|
use crate::db::ReceivedNote;
|
|
|
|
use crate::sync::tree::Node;
|
|
|
|
use crate::{CompactBlock, CompactOrchardAction, CompactSaplingOutput, CompactTx};
|
|
|
|
use orchard::note_encryption::OrchardDomain;
|
|
|
|
use std::collections::HashMap;
|
2022-10-19 23:32:11 -07:00
|
|
|
use std::convert::TryInto;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use std::time::Instant;
|
|
|
|
use zcash_note_encryption::batch::try_compact_note_decryption;
|
2022-11-06 04:50:51 -08:00
|
|
|
use zcash_note_encryption::{BatchDomain, EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE};
|
2022-10-19 23:32:11 -07:00
|
|
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
|
|
|
|
|
|
|
pub struct DecryptedBlock<D: BatchDomain, VK, DN: DecryptedNote<D, VK>> {
|
|
|
|
pub height: u32,
|
|
|
|
pub spends: Vec<Nf>,
|
|
|
|
pub txs: Vec<DecryptedTx<D, VK, DN>>,
|
|
|
|
pub count_outputs: u32,
|
|
|
|
pub elapsed: usize,
|
|
|
|
_phantom: PhantomData<(D, VK)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DecryptedTx<D: BatchDomain, VK, DN: DecryptedNote<D, VK>> {
|
|
|
|
pub account: u32,
|
|
|
|
pub height: u32,
|
|
|
|
pub timestamp: u32,
|
|
|
|
pub tx_index: usize,
|
|
|
|
pub tx_id: Vec<u8>,
|
|
|
|
pub notes: Vec<DN>,
|
|
|
|
_phantom: PhantomData<(D, VK)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait ViewKey<D: BatchDomain>: Clone {
|
|
|
|
fn account(&self) -> u32;
|
|
|
|
fn ivk(&self) -> D::IncomingViewingKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct OutputPosition {
|
|
|
|
pub height: u32,
|
|
|
|
pub tx_index: usize,
|
|
|
|
pub output_index: usize,
|
|
|
|
pub position_in_block: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait DecryptedNote<D: BatchDomain, VK>: Send + Sync {
|
2022-11-06 04:50:51 -08:00
|
|
|
fn from_parts(
|
|
|
|
vk: VK,
|
|
|
|
note: D::Note,
|
|
|
|
pa: D::Recipient,
|
|
|
|
output_position: OutputPosition,
|
|
|
|
cmx: Node,
|
|
|
|
) -> Self;
|
2022-10-19 23:32:11 -07:00
|
|
|
fn position(&self, block_offset: usize) -> usize;
|
|
|
|
fn cmx(&self) -> Node;
|
|
|
|
fn to_received_note(&self, position: u64) -> ReceivedNote;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deep copy from protobuf message
|
|
|
|
pub struct CompactOutputBytes {
|
2022-10-28 06:02:34 -07:00
|
|
|
pub nullifier: [u8; 32],
|
2022-10-19 23:32:11 -07:00
|
|
|
pub epk: [u8; 32],
|
|
|
|
pub cmx: [u8; 32],
|
|
|
|
pub ciphertext: [u8; 52],
|
|
|
|
}
|
|
|
|
|
2022-10-30 03:03:38 -07:00
|
|
|
impl std::fmt::Display for CompactOutputBytes {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
writeln!(f, "nullifier: {}", hex::encode(self.nullifier))?;
|
|
|
|
writeln!(f, "epk: {}", hex::encode(self.epk))?;
|
|
|
|
writeln!(f, "cmx: {}", hex::encode(self.cmx))?;
|
|
|
|
writeln!(f, "ciphertext: {}", hex::encode(self.ciphertext))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactOutputBytes {
|
|
|
|
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
|
|
|
EphemeralKeyBytes(self.epk)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn cmstar_bytes(&self) -> [u8; 32] {
|
|
|
|
self.cmx
|
|
|
|
}
|
|
|
|
|
|
|
|
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
|
|
|
|
&self.ciphertext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:32:11 -07:00
|
|
|
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
|
|
|
fn from(co: &CompactSaplingOutput) -> Self {
|
|
|
|
CompactOutputBytes {
|
2022-10-28 06:02:34 -07:00
|
|
|
nullifier: [0u8; 32],
|
2022-11-06 04:50:51 -08:00
|
|
|
epk: if co.epk.is_empty() {
|
|
|
|
[0u8; 32]
|
|
|
|
} else {
|
|
|
|
co.epk.clone().try_into().unwrap()
|
|
|
|
},
|
2022-10-27 03:10:51 -07:00
|
|
|
cmx: co.cmu.clone().try_into().unwrap(), // cannot be filtered out
|
2022-11-06 04:50:51 -08:00
|
|
|
ciphertext: if co.ciphertext.is_empty() {
|
|
|
|
[0u8; 52]
|
|
|
|
} else {
|
|
|
|
co.ciphertext.clone().try_into().unwrap()
|
|
|
|
},
|
2022-10-19 23:32:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 06:02:34 -07:00
|
|
|
impl From<&CompactOrchardAction> for CompactOutputBytes {
|
|
|
|
fn from(co: &CompactOrchardAction) -> Self {
|
|
|
|
CompactOutputBytes {
|
|
|
|
nullifier: co.nullifier.clone().try_into().unwrap(),
|
2022-11-06 04:50:51 -08:00
|
|
|
epk: if co.ephemeral_key.is_empty() {
|
|
|
|
[0u8; 32]
|
|
|
|
} else {
|
|
|
|
co.ephemeral_key.clone().try_into().unwrap()
|
|
|
|
},
|
2022-10-28 06:02:34 -07:00
|
|
|
cmx: co.cmx.clone().try_into().unwrap(), // cannot be filtered out
|
2022-11-06 04:50:51 -08:00
|
|
|
ciphertext: if co.ciphertext.is_empty() {
|
|
|
|
[0u8; 52]
|
|
|
|
} else {
|
|
|
|
co.ciphertext.clone().try_into().unwrap()
|
|
|
|
},
|
2022-10-28 06:02:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-19 23:32:11 -07:00
|
|
|
pub struct CompactShieldedOutput(CompactOutputBytes, OutputPosition);
|
|
|
|
|
|
|
|
impl<D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>> ShieldedOutput<D, COMPACT_NOTE_SIZE>
|
2022-11-06 04:50:51 -08:00
|
|
|
for CompactShieldedOutput
|
2022-10-19 23:32:11 -07:00
|
|
|
{
|
|
|
|
fn ephemeral_key(&self) -> EphemeralKeyBytes {
|
|
|
|
EphemeralKeyBytes(self.0.epk)
|
|
|
|
}
|
|
|
|
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes {
|
|
|
|
self.0.cmx
|
|
|
|
}
|
|
|
|
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
|
|
|
|
&self.0.ciphertext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-06 04:50:51 -08:00
|
|
|
pub trait TrialDecrypter<
|
|
|
|
N: Parameters,
|
|
|
|
D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>,
|
|
|
|
VK: ViewKey<D>,
|
|
|
|
DN: DecryptedNote<D, VK>,
|
|
|
|
>: Clone
|
|
|
|
{
|
|
|
|
fn decrypt_notes(&self, block: &CompactBlock, vks: &[VK]) -> DecryptedBlock<D, VK, DN> {
|
2022-10-19 23:32:11 -07:00
|
|
|
let height = BlockHeight::from_u32(block.height as u32);
|
|
|
|
let mut count_outputs = 0u32;
|
|
|
|
let mut spends: Vec<Nf> = vec![];
|
|
|
|
let vvks: Vec<_> = vks.iter().map(|vk| vk.ivk().clone()).collect();
|
|
|
|
let mut outputs = vec![];
|
|
|
|
let mut txs = HashMap::new();
|
|
|
|
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
2022-10-28 06:02:34 -07:00
|
|
|
let tx_inputs = self.spends(vtx);
|
|
|
|
spends.extend(tx_inputs.iter());
|
2022-10-19 23:32:11 -07:00
|
|
|
|
|
|
|
let tx_outputs = self.outputs(vtx);
|
|
|
|
if let Some(fco) = tx_outputs.first() {
|
|
|
|
if !fco.epk.is_empty() {
|
|
|
|
for (output_index, cob) in tx_outputs.into_iter().enumerate() {
|
2022-10-28 06:02:34 -07:00
|
|
|
let domain = self.domain(height, &cob);
|
2022-10-19 23:32:11 -07:00
|
|
|
let pos = OutputPosition {
|
|
|
|
height: block.height as u32,
|
|
|
|
tx_index,
|
|
|
|
output_index,
|
|
|
|
position_in_block: count_outputs as usize,
|
|
|
|
};
|
|
|
|
let output = CompactShieldedOutput(cob, pos);
|
|
|
|
outputs.push((domain, output));
|
|
|
|
|
|
|
|
count_outputs += 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// we filter by transaction, therefore if one epk is empty, every epk is empty
|
|
|
|
// log::info!("Spam Filter tx {}", hex::encode(&vtx.hash));
|
|
|
|
count_outputs += vtx.outputs.len() as u32;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let start = Instant::now();
|
2022-11-06 04:50:51 -08:00
|
|
|
let notes_decrypted = try_compact_note_decryption(&vvks, &outputs);
|
2022-10-19 23:32:11 -07:00
|
|
|
let elapsed = start.elapsed().as_millis() as usize;
|
|
|
|
|
|
|
|
for (pos, opt_note) in notes_decrypted.iter().enumerate() {
|
2022-11-06 04:50:51 -08:00
|
|
|
if let Some(((note, pa), vk_index)) = opt_note {
|
|
|
|
let vk = &vks[*vk_index];
|
2022-10-19 23:32:11 -07:00
|
|
|
let account = vk.account();
|
|
|
|
let output = &outputs[pos % outputs.len()];
|
2022-11-06 04:50:51 -08:00
|
|
|
let tx_index = output.1 .1.tx_index;
|
2022-10-19 23:32:11 -07:00
|
|
|
let tx_key = (account, tx_index);
|
2022-11-06 04:50:51 -08:00
|
|
|
let tx = txs.entry(tx_key).or_insert_with(|| DecryptedTx {
|
|
|
|
account,
|
|
|
|
height: block.height as u32,
|
|
|
|
timestamp: block.time,
|
|
|
|
tx_index,
|
|
|
|
tx_id: block.vtx[tx_index].hash.clone(),
|
|
|
|
notes: vec![],
|
|
|
|
_phantom: PhantomData::default(),
|
2022-10-19 23:32:11 -07:00
|
|
|
});
|
|
|
|
tx.notes.push(DN::from_parts(
|
|
|
|
vk.clone(),
|
|
|
|
note.clone(),
|
|
|
|
pa.clone(),
|
2022-11-06 04:50:51 -08:00
|
|
|
output.1 .1.clone(),
|
|
|
|
output.1 .0.cmx,
|
2022-10-19 23:32:11 -07:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DecryptedBlock {
|
|
|
|
height: block.height as u32,
|
|
|
|
spends,
|
|
|
|
txs: txs.into_values().collect(),
|
|
|
|
count_outputs,
|
|
|
|
elapsed,
|
|
|
|
_phantom: PhantomData::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 06:02:34 -07:00
|
|
|
fn domain(&self, height: BlockHeight, cob: &CompactOutputBytes) -> D;
|
2022-10-19 23:32:11 -07:00
|
|
|
fn spends(&self, vtx: &CompactTx) -> Vec<Nf>;
|
|
|
|
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes>;
|
|
|
|
}
|