Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Transaction verifier #1100

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

18 changes: 18 additions & 0 deletions zebra-chain/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Transactions and transaction-related structures.

use std::fmt;

use serde::{Deserialize, Serialize};

mod hash;
Expand All @@ -20,6 +22,7 @@ pub use joinsplit::JoinSplitData;
pub use lock_time::LockTime;
pub use memo::Memo;
pub use shielded_data::ShieldedData;
pub use sighash::HashType;

use crate::{
amount::Amount,
Expand Down Expand Up @@ -186,3 +189,18 @@ impl Transaction {
sighash::SigHasher::new(self, hash_type, network_upgrade, input).sighash()
}
}

impl fmt::Display for Transaction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let hash = self.hash();

let name = match self {
Transaction::V1 { .. } => "Transaction::V1",
Transaction::V2 { .. } => "Transaction::V2",
Transaction::V3 { .. } => "Transaction::V3",
Transaction::V4 { .. } => "Transaction::V4",
};

write!(f, "{} {{ hash: {}, .. }}", name, hash)
}
}
13 changes: 12 additions & 1 deletion zebra-chain/src/transaction/shielded_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use futures::future::Either;

use crate::{
primitives::redjubjub::{Binding, Signature},
sapling::{Output, Spend},
sapling::{Nullifier, Output, Spend},
serialization::serde_helpers,
};

Expand Down Expand Up @@ -61,6 +61,17 @@ impl ShieldedData {
.into_iter()
.chain(self.rest_outputs.iter())
}

/// Collect the [`Nullifier`]s for this transaction, if it contains
/// [`Spend`]s.
pub fn nullifiers(&self) -> Vec<Nullifier> {
self.spends().map(|spend| spend.nullifier).collect()
}

/// Collect the cm_u's for this transaction, if it contains [`Output`]s.
pub fn note_commitments(&self) -> Vec<jubjub::Fq> {
self.outputs().map(|output| output.cm_u).collect()
}
}

// Technically, it's possible to construct two equivalent representations
Expand Down
5 changes: 5 additions & 0 deletions zebra-chain/src/transaction/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";

bitflags::bitflags! {
/// The different SigHash types, as defined in https://zips.z.cash/zip-0143
pub struct HashType: u32 {
///
const ALL = 0b0000_0001;
///
const NONE = 0b0000_0010;
///
const SINGLE = Self::ALL.bits | Self::NONE.bits;
///
Comment on lines +27 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty doc comments prevent a linter from ever noticing that they're empty, so I think it would be preferable to avoid adding them.

const ANYONECANPAY = 0b1000_0000;
}
}
Expand Down
3 changes: 2 additions & 1 deletion zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ edition = "2018"
[dependencies]
chrono = "0.4.19"
color-eyre = "0.5"
displaydoc = "0.1.7"
jubjub = "0.5.1"
once_cell = "1.4"
rand = "0.7"
redjubjub = "0.2"
Expand All @@ -28,7 +30,6 @@ tower-batch = { path = "../tower-batch/" }
zebra-chain = { path = "../zebra-chain" }
zebra-state = { path = "../zebra-state" }
zebra-script = { path = "../zebra-script" }
displaydoc = "0.1.7"

[dev-dependencies]
rand = "0.7"
Expand Down
44 changes: 37 additions & 7 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@ use std::{
};

use chrono::Utc;
use futures::stream::FuturesUnordered;
use futures_util::FutureExt;
use thiserror::Error;
use tower::{Service, ServiceExt};

use zebra_chain::{
block::{self, Block},
parameters::Network,
parameters::NetworkUpgrade,
transaction::Transaction,
work::equihash,
};
use zebra_state as zs;

use crate::error::*;
use crate::BoxError;
use crate::{
error::*,
transaction::{self, VerifyTransactionError},
};
use crate::{script, BoxError};

mod check;
mod subsidy;
Expand All @@ -37,16 +43,15 @@ mod tests;

/// A service that verifies blocks.
#[derive(Debug)]
pub struct BlockVerifier<S>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
{
pub struct BlockVerifier<S> {
/// The network to be verified.
network: Network,

/// The underlying state service, possibly wrapped in other services.
state_service: S,

/// The transaction verification service
transaction_verifier: transaction::Verifier<S>,
}

#[non_exhaustive]
Expand All @@ -68,6 +73,8 @@ pub enum VerifyBlockError {
Time(zebra_chain::block::BlockTimeError),
#[error("unable to commit block after semantic verification")]
Commit(#[source] BoxError),
#[error("invalid transaction")]
Transaction(#[source] VerifyTransactionError),
}

impl<S> BlockVerifier<S>
Expand All @@ -76,9 +83,14 @@ where
S::Future: Send + 'static,
{
pub fn new(network: Network, state_service: S) -> Self {
let branch = NetworkUpgrade::Sapling.branch_id().unwrap();
let script_verifier = script::Verifier::new(state_service.clone(), branch);
let transaction_verifier = transaction::Verifier::new(script_verifier);

Self {
network,
state_service,
transaction_verifier,
}
}
}
Expand All @@ -102,6 +114,7 @@ where

fn call(&mut self, block: Arc<Block>) -> Self::Future {
let mut state_service = self.state_service.clone();
let mut transaction_verifier = self.transaction_verifier.clone();
let network = self.network;

// TODO(jlusby): Error = Report, handle errors from state_service.
Expand Down Expand Up @@ -159,6 +172,23 @@ where
metrics::gauge!("block.verified.block.height", height.0 as _);
metrics::counter!("block.verified.block.count", 1);

let mut async_checks = FuturesUnordered::new();

for transaction in &block.transactions {
let req = transaction::Request::Block(transaction.clone());
let rsp = transaction_verifier
.ready_and()
.await
.expect("transaction verifier is always ready")
.call(req);
async_checks.push(rsp);
}

use futures::StreamExt;
while let Some(result) = async_checks.next().await {
result.map_err(VerifyBlockError::Transaction)?;
}

// Finally, submit the block for contextual verification.
match state_service
.ready_and()
Expand Down
4 changes: 2 additions & 2 deletions zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ where
#[derive(Debug, Display, Error)]
pub enum VerifyChainError {
/// block could not be checkpointed
Checkpoint(VerifyCheckpointError),
Checkpoint(#[source] VerifyCheckpointError),
/// block could not be verified
Block(VerifyBlockError),
Block(#[source] VerifyBlockError),
}

impl<S> Service<Arc<Block>> for ChainVerifier<S>
Expand Down
2 changes: 1 addition & 1 deletion zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_consensus")]
// Re-enable this after cleaning the API surface.
//#![deny(missing_docs)]
#![allow(clippy::try_err)]
#![allow(clippy::try_err, unused_imports, dead_code, unused_variables)]

pub mod block;
pub mod chain;
Expand Down
1 change: 1 addition & 0 deletions zebra-consensus/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Asynchronous verification of cryptographic primitives.

pub mod groth16;
pub mod redjubjub;

/// The maximum batch size for any of the batch verifiers.
Expand Down
57 changes: 57 additions & 0 deletions zebra-consensus/src/primitives/groth16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::{
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};


use tower::Service;

use zebra_chain::primitives::Groth16Proof;

use crate::BoxError;

/// Provides verification of Groth16 proofs for a specific statement.
///
/// Groth16 proofs require a proof verification key; the [`Verifier`] type is
/// responsible for ownership of the PVK.
pub struct Verifier {
// XXX this needs to hold on to a verification key
}

impl Verifier {
/// Create a new Groth16 verifier, supplying the encoding of the
pub fn new(encoded_verification_key: &[u8]) -> Result<Self, BoxError> {
// parse and turn into a bellman type,
// so that users don't have to have the entire bellman api
unimplemented!();
}
}

// XXX this is copied from the WIP batch bellman impl,
// in the future, replace with a re export

pub struct Item {
pub proof: Groth16Proof,
pub public_inputs: Vec<jubjub::Fr>,
}

// XXX in the future, Verifier will implement
// Service<BatchControl<Item>>> and be wrapped in a Batch
// to get a Service<Item>
// but for now, just implement Service<Item> and do unbatched verif.
//impl Service<BatchControl<Item>> for Verifier {
impl Service<Item> for Verifier {
type Response = ();
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send + 'static>>;

fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, req: Item) -> Self::Future {
unimplemented!()
}
}
3 changes: 2 additions & 1 deletion zebra-consensus/src/primitives/redjubjub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ use std::{
use futures::future::{ready, Ready};
use once_cell::sync::Lazy;
use rand::thread_rng;
use redjubjub::{batch, *};

use tokio::sync::broadcast::{channel, RecvError, Sender};
use tower::{util::ServiceFn, Service};
use tower_batch::{Batch, BatchControl};
use tower_fallback::Fallback;
use zebra_chain::primitives::redjubjub::{batch, *};

/// Global batch verification context for RedJubjub signatures.
///
Expand Down
12 changes: 8 additions & 4 deletions zebra-consensus/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
//! This is an internal module. Use `verify::BlockVerifier` for blocks and their
//! transactions, or `mempool::MempoolTransactionVerifier` for mempool transactions.

use std::future::Future;
use std::{pin::Pin, sync::Arc};

use std::future::Future;
use tower::{Service, ServiceExt};

use zebra_chain::{parameters::ConsensusBranchId, transaction::Transaction, transparent};
use zebra_state as zs;

use crate::BoxError;

/// Internal script verification service.
///
/// After verification, the script future completes. State changes are handled by
/// `BlockVerifier` or `MempoolTransactionVerifier`.
#[derive(Debug, Clone)]
pub struct Verifier<ZS> {
state: ZS,
branch: ConsensusBranchId,
Expand All @@ -35,9 +39,9 @@ impl<ZS> Verifier<ZS> {
}

#[derive(Debug)]
struct Request {
transaction: Arc<Transaction>,
input_index: usize,
pub struct Request {
pub transaction: Arc<Transaction>,
pub input_index: usize,
}

impl<ZS> tower::Service<Request> for Verifier<ZS>
Expand Down
Loading