zebra-consensus 5.0.0

Implementation of Zcash consensus checks
Documentation
//! Tests for transaction verification

#![allow(clippy::unwrap_in_result)]

use futures::{
    future::ready,
    stream::{FuturesUnordered, StreamExt},
};
use hex::FromHex;
use tower::ServiceExt;

use zebra_chain::{
    block::Block, serialization::ZcashDeserializeInto, sprout::EncryptedNote,
    transaction::Transaction,
};

use crate::primitives::groth16::*;

async fn verify_groth16_joinsplits<V>(
    verifier: &mut V,
    transactions: Vec<std::sync::Arc<Transaction>>,
) -> Result<(), V::Error>
where
    V: tower::Service<Item, Response = ()>,
    <V as tower::Service<Item>>::Error: std::fmt::Debug
        + std::convert::From<
            std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
        >,
{
    let _init_guard = zebra_test::init();

    let mut async_checks = FuturesUnordered::new();

    for tx in transactions {
        let joinsplits = tx.sprout_groth16_joinsplits();

        for joinsplit in joinsplits {
            tracing::trace!(?joinsplit);

            let pub_key = tx
                .sprout_joinsplit_pub_key()
                .expect("pub key must exist since there are joinsplits");
            let joinsplit_rsp = verifier.ready().await?.call(
                DescriptionWrapper(&(joinsplit, &pub_key))
                    .try_into()
                    .map_err(tower_fallback::BoxedError::from)?,
            );

            async_checks.push(joinsplit_rsp);
        }

        while let Some(result) = async_checks.next().await {
            tracing::trace!(?result);
            result?;
        }
    }

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn verify_sprout_groth16() {
    let mut verifier = tower::service_fn(
        (|item: Item| {
            ready(
                item.verify_single(SPROUT.prepared_verifying_key())
                    .map_err(BoxedError::from),
            )
        }) as fn(_) -> _,
    );

    let transactions = zebra_test::vectors::MAINNET_BLOCKS
        .clone()
        .into_values()
        .flat_map(|bytes| {
            let block = bytes
                .zcash_deserialize_into::<Block>()
                .expect("a valid block");
            block.transactions
        })
        .collect();

    // This should fail if any of the proofs fail to validate.
    verify_groth16_joinsplits(&mut verifier, transactions)
        .await
        .expect("verification should pass");
}

async fn verify_groth16_joinsplit_vector<V>(
    verifier: &mut V,
    joinsplit: &JoinSplit<Groth16Proof>,
    pub_key: &ed25519::VerificationKeyBytes,
) -> Result<(), V::Error>
where
    V: tower::Service<Item, Response = ()>,
    <V as tower::Service<Item>>::Error: std::fmt::Debug
        + std::convert::From<
            std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
        >,
{
    let _init_guard = zebra_test::init();

    let mut async_checks = FuturesUnordered::new();

    tracing::trace!(?joinsplit);

    let joinsplit_rsp = verifier.ready().await?.call(
        DescriptionWrapper(&(joinsplit, pub_key))
            .try_into()
            .map_err(tower_fallback::BoxedError::from)?,
    );

    async_checks.push(joinsplit_rsp);

    while let Some(result) = async_checks.next().await {
        tracing::trace!(?result);
        result?;
    }

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn verify_sprout_groth16_vector() {
    let mut verifier = tower::service_fn(
        (|item: Item| {
            ready(
                item.verify_single(SPROUT.prepared_verifying_key())
                    .map_err(BoxedError::from),
            )
        }) as fn(_) -> _,
    );

    // Test vector extracted manually by printing a JoinSplit generated by
    // the test_joinsplit test of the zcashd repository.
    // https://github.com/zcash/zcash/blob/7aaefab2d7d671951f153e47cdd83ae55d78144f/src/gtest/test_joinsplit.cpp#L48
    let joinsplit = JoinSplit::<Groth16Proof> {
        vpub_old: 0x0Ai32.try_into().unwrap(),
        vpub_new: 0.try_into().unwrap(),
        anchor: <[u8; 32]>::from_hex(
            "D7C612C817793191A1E68652121876D6B3BDE40F4FA52BC314145CE6E5CDD259",
        )
        .unwrap()
        .into(),
        nullifiers: [
            <[u8; 32]>::from_hex(
                "F9AD4EED10C97FF8FDE3C63512242D3937C0E2836389A95B972C50FB942F775B",
            )
            .unwrap()
            .into(),
            <[u8; 32]>::from_hex(
                "DF6EB39839A549F0DF24CDEBBB23CA7107E84D2E6BD0294A8B1BFBD0FAE7800C",
            )
            .unwrap()
            .into(),
        ],
        commitments: [
            <[u8; 32]>::from_hex(
                "0D595308A445D07EB62C7C13CB9F2630DFD39E6A060E98A9788C92BDDBAEA538",
            )
            .unwrap()
            .into(),
            <[u8; 32]>::from_hex(
                "EE7D9622C410878A218ED8A8A6A10B11DDBDA83CB2A627508354BFA490E0F33E",
            )
            .unwrap()
            .into(),
        ],
        // The ephemeral key is not validated in the proof, use a dummy value.
        ephemeral_key: [0u8; 32].into(),
        random_seed: <[u8; 32]>::from_hex(
            "6A14E910A94EF500043A42417D8D2B4124AB35DC1E14DDF830EBCF972E850807",
        )
        .unwrap().into(),
        vmacs: [
            <[u8; 32]>::from_hex(
                "630D39F963960E9092E518CEF4C84853C13EF9FC759CBECDD2ED61D1070C82E6",
            )
            .unwrap()
            .into(),
            <[u8; 32]>::from_hex(
                "1C8DCEC25F816D0177AC29958D0B8594EC669AED4A32D9FBEEC3C57B4503F19A",
            )
            .unwrap()
            .into(),
        ],
        zkproof: <[u8; 192]>::from_hex(
            "802BD3D746BA4831E10027C92E0E610618F619E3CE7EE087622BFF86F19B5BC3292DACFD27506C8BFF4C808035EB9C7685010235D47F1D77C5DCC212323E69726F04A46E0BBDCE17C64EEEA36F443E25F21DF2C39FE8A996BAE899AB8F8CCF52054DC6A5553D0F86283E056AED8E6EABE11D85EDF7948005AD9B982759F20E5DE54A59A1B80CD31AD4CC96419492886C91C4D7C521C327B47F4F5688067BE2B19EB8BC0B7BD357BF931CCF8BCC62A7E48A81CD287F00854767B41748F05EDD5B",
        )
        .unwrap()
        .into(),
        // The ciphertexts are not validated in the proof, use a dummy value.
        enc_ciphertexts: [EncryptedNote([0u8; 601]),EncryptedNote([0u8; 601])],
    };

    let pub_key =
        <[u8; 32]>::from_hex("63A144ABC0524C9EADE1DB9DE17AEC4A39626A0FDB597B9EC6DDA327EE9FE845")
            .unwrap()
            .into();

    verify_groth16_joinsplit_vector(&mut verifier, &joinsplit, &pub_key)
        .await
        .expect("verification should pass");
}

async fn verify_invalid_groth16_joinsplit_description<V>(
    verifier: &mut V,
    transactions: Vec<std::sync::Arc<Transaction>>,
) -> Result<(), V::Error>
where
    V: tower::Service<Item, Response = ()>,
    <V as tower::Service<Item>>::Error: std::convert::From<
        std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
    >,
{
    let _init_guard = zebra_test::init();

    let mut async_checks = FuturesUnordered::new();

    for tx in transactions {
        let joinsplits = tx.sprout_groth16_joinsplits();

        for joinsplit in joinsplits {
            // Use an arbitrary public key which is not the correct one,
            // which will make the verification fail.
            let modified_pub_key = [0x42; 32].into();
            let joinsplit_rsp = verifier.ready().await?.call(
                DescriptionWrapper(&(joinsplit, &modified_pub_key))
                    .try_into()
                    .map_err(tower_fallback::BoxedError::from)?,
            );

            async_checks.push(joinsplit_rsp);
        }

        while let Some(result) = async_checks.next().await {
            result?;
        }
    }

    Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn correctly_err_on_invalid_joinsplit_proof() {
    // Use separate verifiers so shared batch tasks aren't killed when the test ends (#2390).
    // Also, since we expect these to fail, we don't want to slow down the communal verifiers.
    let mut verifier = tower::service_fn(
        (|item: Item| {
            ready(
                item.verify_single(SPROUT.prepared_verifying_key())
                    .map_err(BoxedError::from),
            )
        }) as fn(_) -> _,
    );

    let block = zebra_test::vectors::BLOCK_MAINNET_419201_BYTES
        .clone()
        .zcash_deserialize_into::<Block>()
        .expect("a valid block");

    verify_invalid_groth16_joinsplit_description(&mut verifier, block.transactions)
        .await
        .expect_err("unexpected success checking invalid groth16 inputs");
}