Zebra/zebra-consensus/src/primitives/halo2/tests.rs

235 lines
7.3 KiB
Rust

//! 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, clippy::print_stdout)]
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()
);
}