Async Halo2 verifier service (#2645)

* First pass at async Halo2 verification service

Stubs out a batch verifier for the future.
The dependencies for orchard, halo2, librustzcash, zcash_primitives, have
not been resolved.

* Halo2 verifier service and test

* Remove redundant conversion

* Test async halo2 verifier service with pre-computed Orchard shielded data test vectors

* Fix typo

Co-authored-by: Conrado Gouvea <conrado@zfnd.org>

* Assert future result is_ok() in Halo2 verifier test

Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>

* Shorten tower::Service trait constraints for Halo2 verifier tests

* Remove commented out trait constraints

* .expect() vs .unwrap() to parse orchard::redpallas::VerificationKey

* Use .to_vec() for some test vectors

* Fix self-referential Display impl

* Fix deps

* Distinguish orchard vs zebra_chain::orchard imports

* Add test that halo2 verifier fails with malformed proof inputs

* Use thiserror for Halo2Error

* Use ZcashFoundation/orchard instead of dconnolly/orchard

* Add a link to the issue to remove the zfnd fork of orchard crate

* Update zebra-consensus/Cargo.toml

Co-authored-by: teor <teor@riseup.net>

* Add note

* Move artificial Orchard shielded data test vectors to zebra-test

* Align brackets

* Tidy some trait constraints and debug statements

Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>

Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Deirdre Connolly 2021-11-16 22:26:15 -05:00 committed by GitHub
parent b33ffc9df8
commit eda83ebe0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 635 additions and 34 deletions

4
Cargo.lock generated
View File

@ -2142,7 +2142,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.0.0"
source = "git+https://github.com/zcash/orchard.git?rev=2c8241f25b943aa05203eacf9905db117c69bd29#2c8241f25b943aa05203eacf9905db117c69bd29"
source = "git+https://github.com/ZcashFoundation/orchard.git?rev=568e24cd5f129158375d7ac7d98c89ebff4f982f#568e24cd5f129158375d7ac7d98c89ebff4f982f"
dependencies = [
"aes",
"arrayvec 0.7.1",
@ -4375,10 +4375,12 @@ dependencies = [
"futures",
"futures-util",
"halo2",
"hex",
"jubjub 0.8.0",
"lazy_static",
"metrics",
"once_cell",
"orchard",
"proptest",
"proptest-derive",
"rand 0.7.3",

View File

@ -25,6 +25,8 @@ panic = "abort"
# TODO: remove these after a new librustzcash release.
# These are librustzcash requirements specified in its workspace Cargo.toml that we must replicate here
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree", rev = "b7bd6246122a6e9ace8edb51553fbf5228906cbb" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa05203eacf9905db117c69bd29" }
# TODO: replace with upstream orchard when these changes are merged
# https://github.com/ZcashFoundation/zebra/issues/3056
orchard = { git = "https://github.com/ZcashFoundation/orchard.git", rev = "568e24cd5f129158375d7ac7d98c89ebff4f982f" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "53d0a51d33a421cb76d3e3124d1e4c1c9036068e" }

View File

@ -33,7 +33,7 @@ hex = "0.4"
incrementalmerkletree = "0.1.0"
jubjub = "0.8.0"
lazy_static = "1.4.0"
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa05203eacf9905db117c69bd29" }
orchard = "0.0"
rand_core = "0.6"
ripemd160 = "0.9"
secp256k1 = { version = "0.20.3", features = ["serde"] }

View File

@ -1064,6 +1064,12 @@ impl fmt::Debug for EphemeralPublicKey {
impl Eq for EphemeralPublicKey {}
impl From<EphemeralPublicKey> for [u8; 32] {
fn from(epk: EphemeralPublicKey) -> [u8; 32] {
epk.0.to_bytes()
}
}
impl From<&EphemeralPublicKey> for [u8; 32] {
fn from(epk: &EphemeralPublicKey) -> [u8; 32] {
epk.0.to_bytes()

View File

@ -6,15 +6,7 @@ use crate::serialization::{serde_helpers, SerializationError, ZcashDeserialize,
///
/// Corresponds to the Orchard 'encCiphertext's
#[derive(Deserialize, Serialize)]
pub struct EncryptedNote(#[serde(with = "serde_helpers::BigArray")] pub [u8; 580]);
impl fmt::Debug for EncryptedNote {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EncryptedNote")
.field(&hex::encode(&self.0[..]))
.finish()
}
}
pub struct EncryptedNote(#[serde(with = "serde_helpers::BigArray")] pub(crate) [u8; 580]);
// These impls all only exist because of array length restrictions.
// TODO: use const generics https://github.com/ZcashFoundation/zebra/issues/2042
@ -29,14 +21,34 @@ impl Clone for EncryptedNote {
}
}
impl fmt::Debug for EncryptedNote {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("EncryptedNote")
.field(&hex::encode(&self.0[..]))
.finish()
}
}
impl Eq for EncryptedNote {}
impl From<[u8; 580]> for EncryptedNote {
fn from(bytes: [u8; 580]) -> Self {
EncryptedNote(bytes)
}
}
impl From<EncryptedNote> for [u8; 580] {
fn from(enc_ciphertext: EncryptedNote) -> Self {
enc_ciphertext.0
}
}
impl PartialEq for EncryptedNote {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]
}
}
impl Eq for EncryptedNote {}
impl ZcashSerialize for EncryptedNote {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.0[..])?;
@ -56,7 +68,7 @@ impl ZcashDeserialize for EncryptedNote {
///
/// Corresponds to Orchard's 'outCiphertext'
#[derive(Deserialize, Serialize)]
pub struct WrappedNoteKey(#[serde(with = "serde_helpers::BigArray")] pub [u8; 80]);
pub struct WrappedNoteKey(#[serde(with = "serde_helpers::BigArray")] pub(crate) [u8; 80]);
impl fmt::Debug for WrappedNoteKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -78,6 +90,18 @@ impl Clone for WrappedNoteKey {
}
}
impl From<[u8; 80]> for WrappedNoteKey {
fn from(bytes: [u8; 80]) -> Self {
WrappedNoteKey(bytes)
}
}
impl From<WrappedNoteKey> for [u8; 80] {
fn from(out_ciphertext: WrappedNoteKey) -> Self {
out_ciphertext.0
}
}
impl PartialEq for WrappedNoteKey {
fn eq(&self, other: &Self) -> bool {
self.0[..] == other.0[..]

View File

@ -6,7 +6,7 @@ use crate::serialization::{serde_helpers, SerializationError, ZcashDeserialize,
///
/// Corresponds to the Sapling 'encCiphertext's
#[derive(Deserialize, Serialize)]
pub struct EncryptedNote(#[serde(with = "serde_helpers::BigArray")] pub [u8; 580]);
pub struct EncryptedNote(#[serde(with = "serde_helpers::BigArray")] pub(crate) [u8; 580]);
impl fmt::Debug for EncryptedNote {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -55,7 +55,7 @@ impl ZcashDeserialize for EncryptedNote {
///
/// Corresponds to Sapling's 'outCiphertext'
#[derive(Deserialize, Serialize)]
pub struct WrappedNoteKey(#[serde(with = "serde_helpers::BigArray")] pub [u8; 80]);
pub struct WrappedNoteKey(#[serde(with = "serde_helpers::BigArray")] pub(crate) [u8; 80]);
impl fmt::Debug for WrappedNoteKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View File

@ -15,9 +15,13 @@ bellman = "0.11.1"
bls12_381 = "0.6.0"
chrono = "0.4.19"
displaydoc = "0.2.2"
halo2 = "=0.1.0-beta.1"
jubjub = "0.8.0"
lazy_static = "1.4.0"
once_cell = "1.8"
# TODO: replace with upstream orchard when these changes are merged
# https://github.com/ZcashFoundation/zebra/issues/3056
orchard = "0.0.0"
rand = "0.8"
serde = { version = "1", features = ["serde_derive"] }
@ -42,7 +46,7 @@ proptest-derive = { version = "0.3.0", optional = true }
[dev-dependencies]
color-eyre = "0.5.11"
halo2 = "=0.1.0-beta.1"
hex = "0.4.3"
proptest = "0.10"
proptest-derive = "0.3.0"
rand07 = { package = "rand", version = "0.7" }

View File

@ -2,6 +2,7 @@
pub mod ed25519;
pub mod groth16;
pub mod halo2;
pub mod redjubjub;
pub mod redpallas;

View File

@ -0,0 +1,274 @@
//! Async Halo2 batch verifier service
use std::{
convert::TryFrom,
fmt,
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use futures::future::{ready, Ready};
use once_cell::sync::Lazy;
use orchard::circuit::VerifyingKey;
use rand::{thread_rng, CryptoRng, RngCore};
use thiserror::Error;
use tokio::sync::broadcast::{channel, error::RecvError, Sender};
use tower::{util::ServiceFn, Service};
use tower_batch::{Batch, BatchControl};
use tower_fallback::Fallback;
#[cfg(test)]
mod tests;
lazy_static::lazy_static! {
pub static ref VERIFYING_KEY: VerifyingKey = VerifyingKey::build();
}
// === TEMPORARY BATCH HALO2 SUBSTITUTE ===
//
// These types are meant to be API compatible with the batch verification APIs
// in bellman::groth16::batch, reddsa::batch, redjubjub::batch, and
// ed25519-zebra::batch. Once Halo2 batch proof verification math and
// implementation is available, this code can be replaced with that.
/// A Halo2 verification item, used as the request type of the service.
#[derive(Clone, Debug)]
pub struct Item {
instances: Vec<orchard::circuit::Instance>,
proof: orchard::circuit::Proof,
}
impl Item {
/// Perform non-batched verification of this `Item`.
///
/// This is useful (in combination with `Item::clone`) for implementing
/// fallback logic when batch verification fails.
pub fn verify_single(&self, vk: &VerifyingKey) -> Result<(), halo2::plonk::Error> {
self.proof.verify(vk, &self.instances[..])
}
}
#[derive(Default)]
pub struct BatchVerifier {
queue: Vec<Item>,
}
impl BatchVerifier {
pub fn queue(&mut self, item: Item) {
self.queue.push(item);
}
pub fn verify<R: RngCore + CryptoRng>(
self,
_rng: R,
vk: &VerifyingKey,
) -> Result<(), halo2::plonk::Error> {
for item in self.queue {
item.verify_single(vk)?;
}
Ok(())
}
}
// === END TEMPORARY BATCH HALO2 SUBSTITUTE ===
impl From<zebra_chain::orchard::ShieldedData> for Item {
fn from(shielded_data: zebra_chain::orchard::ShieldedData) -> Item {
use orchard::{circuit, note, primitives::redpallas, tree, value};
let anchor = tree::Anchor::from_bytes(shielded_data.shared_anchor.into()).unwrap();
let enable_spend = shielded_data
.flags
.contains(zebra_chain::orchard::Flags::ENABLE_SPENDS);
let enable_output = shielded_data
.flags
.contains(zebra_chain::orchard::Flags::ENABLE_OUTPUTS);
let instances = shielded_data
.actions()
.map(|action| {
circuit::Instance::from_parts(
anchor,
value::ValueCommitment::from_bytes(&action.cv.into()).unwrap(),
note::Nullifier::from_bytes(&action.nullifier.into()).unwrap(),
redpallas::VerificationKey::<redpallas::SpendAuth>::try_from(<[u8; 32]>::from(
action.rk,
))
.expect("should be a valid redpallas spendauth verification key"),
note::ExtractedNoteCommitment::from_bytes(&action.cm_x.into()).unwrap(),
enable_spend,
enable_output,
)
})
.collect();
Item {
instances,
proof: orchard::circuit::Proof::new(shielded_data.proof.0),
}
}
}
/// An error that may occur when verifying [Halo2 proofs of Zcash Orchard Action
/// descriptions][actions].
///
/// [actions]: https://zips.z.cash/protocol/protocol.pdf#actiondesc
// TODO: if halo2::plonk::Error gets the std::error::Error trait derived on it,
// remove this and just wrap `halo2::plonk::Error` as an enum variant of
// `crate::transaction::Error`, which does the trait derivation via `thiserror`
#[derive(Clone, Debug, Error, Eq, PartialEq)]
pub enum Halo2Error {
#[error("the constraint system is not satisfied")]
ConstraintSystemFailure,
#[error("unknown Halo2 error")]
Other,
}
impl From<halo2::plonk::Error> for Halo2Error {
fn from(err: halo2::plonk::Error) -> Halo2Error {
match err {
halo2::plonk::Error::ConstraintSystemFailure => Halo2Error::ConstraintSystemFailure,
_ => Halo2Error::Other,
}
}
}
/// Global batch verification context for Halo2 proofs of Action statements.
///
/// This service transparently batches contemporaneous proof verifications,
/// handling batch failures by falling back to individual verification.
///
/// Note that making a `Service` call requires mutable access to the service, so
/// you should call `.clone()` on the global handle to create a local, mutable
/// handle.
#[allow(dead_code)]
pub static VERIFIER: Lazy<
Fallback<Batch<Verifier, Item>, ServiceFn<fn(Item) -> Ready<Result<(), Halo2Error>>>>,
> = Lazy::new(|| {
Fallback::new(
Batch::new(
Verifier::new(&VERIFYING_KEY),
super::MAX_BATCH_SIZE,
super::MAX_BATCH_LATENCY,
),
// We want to fallback to individual verification if batch verification
// fails, so we need a Service to use. The obvious way to do this would
// be to write a closure that returns an async block. But because we
// have to specify the type of a static, we need to be able to write the
// type of the closure and its return value, and both closures and async
// blocks have eldritch types whose names cannot be written. So instead,
// we use a Ready to avoid an async block and cast the closure to a
// function (which is possible because it doesn't capture any state).
tower::service_fn(
(|item: Item| ready(item.verify_single(&VERIFYING_KEY).map_err(Halo2Error::from)))
as fn(_) -> _,
),
)
});
/// Halo2 proof verifier implementation
///
/// This is the core implementation for the batch verification logic of the
/// Halo2 verifier. It handles batching incoming requests, driving batches to
/// completion, and reporting results.
pub struct Verifier {
/// The sync Halo2 batch verifier.
batch: BatchVerifier,
// Making this 'static makes managing lifetimes much easier.
vk: &'static VerifyingKey,
/// Broadcast sender used to send the result of a batch verification to each
/// request source in the batch.
tx: Sender<Result<(), Halo2Error>>,
}
impl Verifier {
#[allow(dead_code)]
fn new(vk: &'static VerifyingKey) -> Self {
let batch = BatchVerifier::default();
let (tx, _) = channel(super::BROADCAST_BUFFER_SIZE);
Self { batch, vk, tx }
}
}
impl fmt::Debug for Verifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = "Verifier";
f.debug_struct(name)
.field("batch", &"..")
.field("vk", &"..")
.field("tx", &self.tx)
.finish()
}
}
impl Service<BatchControl<Item>> for Verifier {
type Response = ();
type Error = Halo2Error;
type Future = Pin<Box<dyn Future<Output = Result<(), Halo2Error>> + Send + 'static>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: BatchControl<Item>) -> Self::Future {
match req {
BatchControl::Item(item) => {
tracing::trace!("got item");
self.batch.queue(item);
let mut rx = self.tx.subscribe();
Box::pin(async move {
match rx.recv().await {
Ok(result) => {
if result.is_ok() {
tracing::trace!(?result, "verified halo2 proof");
metrics::counter!("proofs.halo2.verified", 1);
} else {
tracing::trace!(?result, "invalid halo2 proof");
metrics::counter!("proofs.halo2.invalid", 1);
}
result
}
Err(RecvError::Lagged(_)) => {
tracing::error!(
"missed channel updates, BROADCAST_BUFFER_SIZE is too low!!"
);
// This is the enum variant that
// orchard::circuit::Proof.verify() returns on
// evaluation failure.
Err(Halo2Error::ConstraintSystemFailure)
}
Err(RecvError::Closed) => panic!("verifier was dropped without flushing"),
}
})
}
BatchControl::Flush => {
tracing::trace!("got flush command");
let batch = mem::take(&mut self.batch);
let _ = self.tx.send(
batch
.verify(thread_rng(), self.vk)
.map_err(Halo2Error::from),
);
Box::pin(async { Ok(()) })
}
}
}
}
impl Drop for Verifier {
fn drop(&mut self) {
// We need to flush the current batch in case there are still any pending futures.
let batch = mem::take(&mut self.batch);
let _ = self.tx.send(
batch
.verify(thread_rng(), self.vk)
.map_err(Halo2Error::from),
);
}
}

View File

@ -0,0 +1,234 @@
//! Tests for verifying simple Halo2 proofs with the async verifier
use std::convert::TryInto;
use futures::stream::{FuturesUnordered, StreamExt};
use tower::ServiceExt;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use orchard::{
builder::Builder,
bundle::Flags,
circuit::ProvingKey,
keys::{FullViewingKey, SpendingKey},
value::NoteValue,
Anchor, Bundle,
};
use rand::rngs::OsRng;
use zebra_chain::{
orchard::ShieldedData,
serialization::{ZcashDeserializeInto, ZcashSerialize},
};
use crate::primitives::halo2::*;
#[allow(dead_code)]
fn generate_test_vectors() {
let proving_key = ProvingKey::build();
let rng = OsRng;
let sk = SpendingKey::from_bytes([7; 32]).unwrap();
let recipient = FullViewingKey::from(&sk).default_address();
let enable_spends = true;
let enable_outputs = true;
let flags =
zebra_chain::orchard::Flags::ENABLE_SPENDS | zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
let anchor_bytes = [0; 32];
let note_value = 10;
let shielded_data: Vec<zebra_chain::orchard::ShieldedData> = (1..=4)
.map(|num_recipients| {
let mut builder = Builder::new(
Flags::from_parts(enable_spends, enable_outputs),
Anchor::from_bytes(anchor_bytes).unwrap(),
);
for _ in 0..num_recipients {
builder
.add_recipient(None, recipient, NoteValue::from_raw(note_value), None)
.unwrap();
}
let bundle: Bundle<_, i64> = builder.build(rng).unwrap();
let bundle = bundle
.create_proof(&proving_key)
.unwrap()
.apply_signatures(rng, [0; 32], &[])
.unwrap();
zebra_chain::orchard::ShieldedData {
flags,
value_balance: note_value.try_into().unwrap(),
shared_anchor: anchor_bytes.try_into().unwrap(),
proof: zebra_chain::primitives::Halo2Proof(
bundle.authorization().proof().as_ref().into(),
),
actions: bundle
.actions()
.iter()
.map(|a| {
let action = zebra_chain::orchard::Action {
cv: a.cv_net().to_bytes().try_into().unwrap(),
nullifier: a.nullifier().to_bytes().try_into().unwrap(),
rk: <[u8; 32]>::from(a.rk()).try_into().unwrap(),
cm_x: pallas::Base::from_bytes(&a.cmx().into()).unwrap(),
ephemeral_key: a.encrypted_note().epk_bytes.try_into().unwrap(),
enc_ciphertext: a.encrypted_note().enc_ciphertext.into(),
out_ciphertext: a.encrypted_note().out_ciphertext.into(),
};
zebra_chain::orchard::shielded_data::AuthorizedAction {
action,
spend_auth_sig: <[u8; 64]>::from(a.authorization()).into(),
}
})
.collect::<Vec<_>>()
.try_into()
.unwrap(),
binding_sig: <[u8; 64]>::from(bundle.authorization().binding_signature())
.try_into()
.unwrap(),
}
})
.collect();
for sd in shielded_data {
println!(
"{}",
hex::encode(sd.clone().zcash_serialize_to_vec().unwrap())
);
}
}
async fn verify_orchard_halo2_proofs<V>(
verifier: &mut V,
shielded_data: Vec<ShieldedData>,
) -> Result<(), V::Error>
where
V: tower::Service<Item, Response = ()>,
<V as tower::Service<Item>>::Error: From<tower::BoxError> + std::fmt::Debug,
{
let mut async_checks = FuturesUnordered::new();
for sd in shielded_data {
tracing::trace!(?sd);
let rsp = verifier.ready().await?.call(Item::from(sd));
async_checks.push(rsp);
}
while let Some(result) = async_checks.next().await {
tracing::trace!(?result);
result?;
}
Ok(())
}
#[tokio::test]
async fn verify_generated_halo2_proofs() {
zebra_test::init();
// These test vectors are generated by `generate_text_vectors()` function.
let shielded_data = zebra_test::vectors::ORCHARD_SHIELDED_DATA
.clone()
.iter()
.map(|bytes| {
let maybe_shielded_data: Option<zebra_chain::orchard::ShieldedData> = bytes
.zcash_deserialize_into()
.expect("a valid orchard::ShieldedData instance");
maybe_shielded_data.unwrap()
})
.collect();
// Use separate verifier so shared batch tasks aren't killed when the test ends (#2390)
let mut verifier = Fallback::new(
Batch::new(
Verifier::new(&VERIFYING_KEY),
crate::primitives::MAX_BATCH_SIZE,
crate::primitives::MAX_BATCH_LATENCY,
),
tower::service_fn(
(|item: Item| ready(item.verify_single(&VERIFYING_KEY).map_err(Halo2Error::from)))
as fn(_) -> _,
),
);
// This should fail if any of the proofs fail to validate.
assert!(verify_orchard_halo2_proofs(&mut verifier, shielded_data)
.await
.is_ok());
}
async fn verify_invalid_orchard_halo2_proofs<V>(
verifier: &mut V,
shielded_data: Vec<ShieldedData>,
) -> Result<(), V::Error>
where
V: tower::Service<Item, Response = ()>,
<V as tower::Service<Item>>::Error: From<tower::BoxError> + std::fmt::Debug,
{
let mut async_checks = FuturesUnordered::new();
for sd in shielded_data {
let mut sd = sd.clone();
sd.flags.remove(zebra_chain::orchard::Flags::ENABLE_SPENDS);
sd.flags.remove(zebra_chain::orchard::Flags::ENABLE_OUTPUTS);
tracing::trace!(?sd);
let rsp = verifier.ready().await?.call(Item::from(sd));
async_checks.push(rsp);
}
while let Some(result) = async_checks.next().await {
tracing::trace!(?result);
result?;
}
Ok(())
}
#[tokio::test]
async fn correctly_err_on_invalid_halo2_proofs() {
zebra_test::init();
// These test vectors are generated by `generate_text_vectors()` function.
let shielded_data = zebra_test::vectors::ORCHARD_SHIELDED_DATA
.clone()
.iter()
.map(|bytes| {
let maybe_shielded_data: Option<zebra_chain::orchard::ShieldedData> = bytes
.zcash_deserialize_into()
.expect("a valid orchard::ShieldedData instance");
maybe_shielded_data.unwrap()
})
.collect();
// Use separate verifier so shared batch tasks aren't killed when the test ends (#2390)
let mut verifier = Fallback::new(
Batch::new(
Verifier::new(&VERIFYING_KEY),
crate::primitives::MAX_BATCH_SIZE,
crate::primitives::MAX_BATCH_LATENCY,
),
tower::service_fn(
(|item: Item| ready(item.verify_single(&VERIFYING_KEY).map_err(Halo2Error::from)))
as fn(_) -> _,
),
);
// This should fail if any of the proofs fail to validate.
assert!(
verify_invalid_orchard_halo2_proofs(&mut verifier, shielded_data)
.await
.is_err()
);
}

View File

@ -0,0 +1,5 @@
//! Test vectors for ingesting and verifying Halo2 proofs from zebra-chain::orchard::ShieldedData
mod orchard_shielded_data;
pub use orchard_shielded_data::ORCHARD_SHIELDED_DATA;

View File

@ -1,4 +1,8 @@
use std::{collections::HashMap, convert::TryFrom, convert::TryInto, sync::Arc};
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
sync::Arc,
};
use halo2::{arithmetic::FieldExt, pasta::pallas};
use tower::{service_fn, ServiceExt};
@ -6,7 +10,7 @@ use tower::{service_fn, ServiceExt};
use zebra_chain::{
amount::{Amount, NonNegative},
block::{self, Block, Height},
orchard::{self, AuthorizedAction, EncryptedNote, WrappedNoteKey},
orchard::AuthorizedAction,
parameters::{Network, NetworkUpgrade},
primitives::{ed25519, x25519, Groth16Proof},
sapling,
@ -93,7 +97,7 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
// If we add ENABLE_SPENDS flag it will pass the inputs check but fails with the outputs
// TODO: Avoid new calls to `insert_fake_orchard_shielded_data` for each check #2409.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS;
assert_eq!(
check::has_inputs_and_outputs(&transaction),
@ -102,7 +106,7 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
// If we add ENABLE_OUTPUTS flag it will pass the outputs check but fails with the inputs
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
assert_eq!(
check::has_inputs_and_outputs(&transaction),
@ -111,7 +115,8 @@ fn fake_v5_transaction_with_orchard_actions_has_inputs_and_outputs() {
// Finally make it valid by adding both required flags
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags =
zebra_chain::orchard::Flags::ENABLE_SPENDS | zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_inputs_and_outputs(&transaction).is_ok());
}
@ -145,17 +150,18 @@ fn fake_v5_transaction_with_orchard_actions_has_flags() {
// If we add ENABLE_SPENDS flag it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
// If we add ENABLE_OUTPUTS flag instead, it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
// If we add BOTH ENABLE_SPENDS and ENABLE_OUTPUTS flags it will pass.
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags =
zebra_chain::orchard::Flags::ENABLE_SPENDS | zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
assert!(check::has_enough_orchard_flags(&transaction).is_ok());
}
@ -231,7 +237,7 @@ fn v5_coinbase_transaction_with_enable_spends_flag_fails_validation() {
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS;
assert_eq!(
check::coinbase_tx_no_prevout_joinsplit_spend(&transaction),
@ -1251,7 +1257,8 @@ fn v5_with_duplicate_orchard_action() {
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
// Enable spends
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS
| zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
// Duplicate the first action
let duplicate_action = shielded_data.actions.first().clone();
@ -1617,9 +1624,9 @@ fn coinbase_outputs_are_decryptable_for_historical_blocks_for_network(
/// Given an Orchard action as a base, fill fields related to note encryption
/// from the given test vector and returned the modified action.
fn fill_action_with_note_encryption_test_vector(
action: &orchard::Action,
action: &zebra_chain::orchard::Action,
v: &zebra_test::vectors::TestVector,
) -> orchard::Action {
) -> zebra_chain::orchard::Action {
let mut action = action.clone();
action.cv = v.cv_net.try_into().expect("test vector must be valid");
action.cm_x = pallas::Base::from_bytes(&v.cmx).unwrap();
@ -1628,8 +1635,8 @@ fn fill_action_with_note_encryption_test_vector(
.ephemeral_key
.try_into()
.expect("test vector must be valid");
action.out_ciphertext = WrappedNoteKey(v.c_out);
action.enc_ciphertext = EncryptedNote(v.c_enc);
action.out_ciphertext = v.c_out.into();
action.enc_ciphertext = v.c_enc.into();
action
}
@ -1654,7 +1661,8 @@ fn coinbase_outputs_are_decryptable_for_fake_v5_blocks() {
.expect("At least one fake V5 transaction with no inputs and no outputs");
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS
| zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
let action =
fill_action_with_note_encryption_test_vector(&shielded_data.actions[0].action, v);
@ -1695,7 +1703,8 @@ fn shielded_outputs_are_not_decryptable_for_fake_v5_blocks() {
.expect("At least one fake V5 transaction with no inputs and no outputs");
let shielded_data = insert_fake_orchard_shielded_data(&mut transaction);
shielded_data.flags = orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS;
shielded_data.flags = zebra_chain::orchard::Flags::ENABLE_SPENDS
| zebra_chain::orchard::Flags::ENABLE_OUTPUTS;
let action =
fill_action_with_note_encryption_test_vector(&shielded_data.actions[0].action, v);

View File

@ -5,9 +5,11 @@ use lazy_static::lazy_static;
mod block;
mod orchard_note_encryption;
mod orchard_shielded_data;
pub use block::*;
pub use orchard_note_encryption::*;
pub use orchard_shielded_data::*;
/// A testnet transaction test vector
///

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,34 @@
//! Orchard shielded data (with Actions) test vectors
//!
//! Generated by `zebra_chain::primitives::halo2::tests::generate_test_vectors()`
//!
//! These are artificial/incomplete `zebra_chain::orchard::ShieldedData`
//! instances, care should be used when using them to test functionality beyond
//! verifying a standalone Orchard Acton Halo2 proof.
#![allow(missing_docs)]
use hex::FromHex;
use lazy_static::lazy_static;
lazy_static! {
pub static ref ORCHARD_SHIELDED_DATA: Vec<&'static [u8]> = [
ORCHARD_SHIELDED_DATA_1_BYTES.as_ref(),
ORCHARD_SHIELDED_DATA_3_BYTES.as_ref(),
ORCHARD_SHIELDED_DATA_3_BYTES.as_ref(),
ORCHARD_SHIELDED_DATA_4_BYTES.as_ref(),
]
.to_vec();
pub static ref ORCHARD_SHIELDED_DATA_1_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("orchard-shielded-data-1.txt").trim())
.expect("Orchard shielded data bytes are in valid hex representation");
pub static ref ORCHARD_SHIELDED_DATA_2_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("orchard-shielded-data-2.txt").trim())
.expect("Orchard shielded data bytes are in valid hex representation");
pub static ref ORCHARD_SHIELDED_DATA_3_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("orchard-shielded-data-3.txt").trim())
.expect("Orchard shielded data bytes are in valid hex representation");
pub static ref ORCHARD_SHIELDED_DATA_4_BYTES: Vec<u8> =
<Vec<u8>>::from_hex(include_str!("orchard-shielded-data-4.txt").trim())
.expect("Orchard shielded data bytes are in valid hex representation");
}