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

ZSA integration (step 5): Modify Orchard proptest implementations to support ZSA #18

Open
wants to merge 8 commits into
base: zsa-integration-generics
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2896,6 +2896,7 @@ dependencies = [
"memuse",
"nonempty",
"pasta_curves",
"proptest",
"rand 0.8.5",
"reddsa",
"serde",
Expand Down
7 changes: 5 additions & 2 deletions zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ keywords = ["zebra", "zcash"]
categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding"]

[features]
default = []
#default = ["tx-v6"]
#default = []
default = ["tx-v6"]

# Production features that activate extra functionality

Expand Down Expand Up @@ -57,6 +57,7 @@ proptest-impl = [
"rand_chacha",
"tokio/tracing",
"zebra-test",
"orchard/test-dependencies"
]

bench = ["zebra-test"]
Expand Down Expand Up @@ -179,6 +180,8 @@ tokio = { version = "1.41.0", features = ["full", "tracing", "test-util"] }

zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.41" }

orchard = { workspace = true, features = ["test-dependencies"] }

[[bench]]
name = "block"
harness = false
Expand Down
18 changes: 13 additions & 5 deletions zebra-chain/src/orchard/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ use proptest::{array, collection::vec, prelude::*};

use super::{
keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, OrchardFlavorExt,
OrchardVanilla, ValueCommitment,
ValueCommitment,
};

impl Arbitrary for Action<OrchardVanilla> {
impl<V: OrchardFlavorExt> Arbitrary for Action<V>
// FIXME: define the constraint in OrchardFlavorExt?
where
<V::EncryptedNote as Arbitrary>::Strategy: 'static,
{
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<note::Nullifier>(),
any::<SpendAuthVerificationKeyBytes>(),
any::<note::EncryptedNote<{ OrchardVanilla::ENCRYPTED_NOTE_SIZE }>>(),
any::<V::EncryptedNote>(),
any::<note::WrappedNoteKey>(),
)
.prop_map(|(nullifier, rk, enc_ciphertext, out_ciphertext)| Self {
Expand Down Expand Up @@ -55,11 +59,15 @@ impl Arbitrary for note::Nullifier {
type Strategy = BoxedStrategy<Self>;
}

impl Arbitrary for AuthorizedAction<OrchardVanilla> {
impl<V: OrchardFlavorExt + 'static> Arbitrary for AuthorizedAction<V>
// FIXME: define the constraint in OrchardFlavorExt?
where
<V::EncryptedNote as Arbitrary>::Strategy: 'static,
{
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(any::<Action<OrchardVanilla>>(), any::<SpendAuthSignature>())
(any::<Action<V>>(), any::<SpendAuthSignature>())
.prop_map(|(action, spend_auth_sig)| Self {
action,
spend_auth_sig: spend_auth_sig.0,
Expand Down
53 changes: 26 additions & 27 deletions zebra-chain/src/orchard/orchard_flavor_ext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This module defines traits and structures for supporting the Orchard Shielded Protocol
//! for `V5` and `V6` versions of the transaction.
use std::{fmt::Debug, io};
use std::fmt::Debug;

use serde::{de::DeserializeOwned, Serialize};

Expand All @@ -9,17 +9,28 @@ use proptest_derive::Arbitrary;

use orchard::{note_encryption::OrchardDomainCommon, orchard_flavor};

use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::serialization::{ZcashDeserialize, ZcashSerialize};

#[cfg(feature = "tx-v6")]
use crate::orchard_zsa::burn::{Burn, NoBurn};

use super::note;

#[cfg(feature = "tx-v6")]
use crate::orchard_zsa::burn::BurnItem;
#[cfg(not(any(test, feature = "proptest-impl")))]
pub trait TestArbitrary {}

#[cfg(not(any(test, feature = "proptest-impl")))]
impl<T> TestArbitrary for T {}

#[cfg(any(test, feature = "proptest-impl"))]
pub trait TestArbitrary: proptest::prelude::Arbitrary {}

#[cfg(any(test, feature = "proptest-impl"))]
impl<T: proptest::prelude::Arbitrary> TestArbitrary for T {}

/// A trait representing compile-time settings of Orchard Shielded Protocol used in
/// the transactions `V5` and `V6`.
pub trait OrchardFlavorExt: Clone + Debug {
/// A type representing an encrypted note for this protocol version.
/// A type representing an encrypted note for this protocol version.
type EncryptedNote: Clone
+ Debug
Expand All @@ -28,16 +39,18 @@ pub trait OrchardFlavorExt: Clone + Debug {
+ DeserializeOwned
+ Serialize
+ ZcashDeserialize
+ ZcashSerialize;
+ ZcashSerialize
+ TestArbitrary;

/// FIXME: add doc
/// Specifies the Orchard protocol flavor from `orchard` crate used by this implementation.
type Flavor: orchard_flavor::OrchardFlavor;

/// The size of the encrypted note for this protocol version.
const ENCRYPTED_NOTE_SIZE: usize = Self::Flavor::ENC_CIPHERTEXT_SIZE;

/// A type representing a burn field for this protocol version.
type BurnType: Clone + Debug + Default + ZcashDeserialize + ZcashSerialize;
#[cfg(feature = "tx-v6")]
type BurnType: Clone + Debug + Default + ZcashDeserialize + ZcashSerialize + TestArbitrary;
}

/// A structure representing a tag for Orchard protocol variant used for the transaction version `V5`.
Expand All @@ -52,33 +65,19 @@ pub struct OrchardVanilla;
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct OrchardZSA;

/// A special marker type indicating the absence of a burn field in Orchard ShieldedData for `V5` transactions.
/// Useful for unifying ShieldedData serialization and deserialization implementations across various
/// Orchard protocol variants (i.e. various transaction versions).
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct NoBurn;

impl ZcashSerialize for NoBurn {
fn zcash_serialize<W: io::Write>(&self, mut _writer: W) -> Result<(), io::Error> {
Ok(())
}
}

impl ZcashDeserialize for NoBurn {
fn zcash_deserialize<R: io::Read>(mut _reader: R) -> Result<Self, SerializationError> {
Ok(Self)
}
}

impl OrchardFlavorExt for OrchardVanilla {
type Flavor = orchard_flavor::OrchardVanilla;
type EncryptedNote = note::EncryptedNote<{ Self::ENCRYPTED_NOTE_SIZE }>;

#[cfg(feature = "tx-v6")]
type BurnType = NoBurn;
}

#[cfg(feature = "tx-v6")]
impl OrchardFlavorExt for OrchardZSA {
type Flavor = orchard_flavor::OrchardZSA;
type EncryptedNote = note::EncryptedNote<{ Self::ENCRYPTED_NOTE_SIZE }>;
type BurnType = Vec<BurnItem>;

#[cfg(feature = "tx-v6")]
type BurnType = Burn;
}
7 changes: 6 additions & 1 deletion zebra-chain/src/orchard_zsa.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
//! Orchard ZSA related functionality.

// FIXME: remove pub(crate) later if possible
#[cfg(any(test, feature = "proptest-impl"))]
pub(crate) mod arbitrary;

mod common;

pub mod burn;
pub mod issuance;
pub mod serialize;

pub use burn::BurnItem;
68 changes: 68 additions & 0 deletions zebra-chain/src/orchard_zsa/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Randomised data generation for Orchard ZSA types.

use proptest::prelude::*;

use orchard::{bundle::testing::BundleArb, issuance::testing::arb_signed_issue_bundle};

// FIXME: consider using another value, i.e. define MAX_BURN_ITEMS constant for that
use crate::transaction::arbitrary::MAX_ARBITRARY_ITEMS;

use super::{
burn::{Burn, BurnItem, NoBurn},
issuance::IssueData,
};

impl Arbitrary for BurnItem {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// FIXME: move arb_asset_to_burn out of BundleArb in orchard
// as it does not depend on flavor (we pinned it here OrchardVanilla
// just for certainty, as there's no difference, which flavor to use)
// FIXME: consider to use BurnItem(asset_base, value.try_into().expect("Invalid value for Amount"))
// instead of filtering non-convertable values
// FIXME: should we filter/protect from including native assets into burn here?
BundleArb::<orchard::orchard_flavor::OrchardVanilla>::arb_asset_to_burn()
.prop_filter_map("Conversion to Amount failed", |(asset_base, value)| {
BurnItem::try_from((asset_base, value)).ok()
})
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

impl Arbitrary for NoBurn {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// FIXME: consider using this instead, for clarity: any::<()>().prop_map(|_| NoBurn).boxed()
Just(Self).boxed()
}

type Strategy = BoxedStrategy<Self>;
}

impl Arbitrary for Burn {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
prop::collection::vec(any::<BurnItem>(), 0..MAX_ARBITRARY_ITEMS)
.prop_map(|inner| inner.into())
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}

impl Arbitrary for IssueData {
type Parameters = ();

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
arb_signed_issue_bundle(MAX_ARBITRARY_ITEMS)
.prop_map(|bundle| bundle.into())
.boxed()
}

type Strategy = BoxedStrategy<Self>;
}
60 changes: 54 additions & 6 deletions zebra-chain/src/orchard_zsa/burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@ use crate::{
serialization::{SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize},
};

use orchard::note::AssetBase;
use orchard::{note::AssetBase, value::NoteValue};

use super::serialize::ASSET_BASE_SIZE;
use super::common::ASSET_BASE_SIZE;

// Sizes of the serialized values for types in bytes (used for TrustedPreallocate impls)
const AMOUNT_SIZE: u64 = 8;

// FIXME: is this a correct way to calculate (simple sum of sizes of components)?
const BURN_ITEM_SIZE: u64 = ASSET_BASE_SIZE + AMOUNT_SIZE;

/// Represents an Orchard ZSA burn item.
/// Orchard ZSA burn item.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BurnItem(AssetBase, Amount);

// Convert from burn item type used in `orchard` crate
impl TryFrom<(AssetBase, NoteValue)> for BurnItem {
type Error = crate::amount::Error;

fn try_from(item: (AssetBase, NoteValue)) -> Result<Self, Self::Error> {
Ok(Self(item.0, item.1.inner().try_into()?))
}
}

impl ZcashSerialize for BurnItem {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
let BurnItem(asset_base, amount) = self;
Expand Down Expand Up @@ -50,18 +60,16 @@ impl TrustedPreallocate for BurnItem {
}
}

#[cfg(any(test, feature = "proptest-impl"))]
impl serde::Serialize for BurnItem {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// FIXME: return custom error with a meaningful description?
// FIXME: return a custom error with a meaningful description?
(self.0.to_bytes(), &self.1).serialize(serializer)
}
}

#[cfg(any(test, feature = "proptest-impl"))]
impl<'de> serde::Deserialize<'de> for BurnItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand All @@ -78,3 +86,43 @@ impl<'de> serde::Deserialize<'de> for BurnItem {
))
}
}

/// A special marker type indicating the absence of a burn field in Orchard ShieldedData for `V5` transactions.
/// Useful for unifying ShieldedData serialization and deserialization implementations across various
/// Orchard protocol variants (i.e. various transaction versions).
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct NoBurn;

impl ZcashSerialize for NoBurn {
fn zcash_serialize<W: io::Write>(&self, mut _writer: W) -> Result<(), io::Error> {
Ok(())
}
}

impl ZcashDeserialize for NoBurn {
fn zcash_deserialize<R: io::Read>(mut _reader: R) -> Result<Self, SerializationError> {
Ok(Self)
}
}

/// Orchard ZSA burn items (assets intended for burning)
#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Burn(Vec<BurnItem>);

impl From<Vec<BurnItem>> for Burn {
fn from(inner: Vec<BurnItem>) -> Self {
Self(inner)
}
}

impl ZcashSerialize for Burn {
fn zcash_serialize<W: io::Write>(&self, writer: W) -> Result<(), io::Error> {
self.0.zcash_serialize(writer)
}
}

impl ZcashDeserialize for Burn {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
Ok(Burn(Vec::<BurnItem>::zcash_deserialize(reader)?))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, Z
use orchard::note::AssetBase;

// The size of the serialized AssetBase in bytes (used for TrustedPreallocate impls)
pub(crate) const ASSET_BASE_SIZE: u64 = 32;
pub(super) const ASSET_BASE_SIZE: u64 = 32;

impl ZcashSerialize for AssetBase {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
Expand Down
8 changes: 7 additions & 1 deletion zebra-chain/src/orchard_zsa/issuance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ use orchard::{
Address, Note,
};

use super::serialize::ASSET_BASE_SIZE;
use super::common::ASSET_BASE_SIZE;

/// Wrapper for `IssueBundle` used in the context of Transaction V6. This allows the implementation of
/// a Serde serializer for unit tests within this crate.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IssueData(IssueBundle<Signed>);

impl From<IssueBundle<Signed>> for IssueData {
fn from(inner: IssueBundle<Signed>) -> Self {
Self(inner)
}
}

// Sizes of the serialized values for types in bytes (used for TrustedPreallocate impls)
// FIXME: are those values correct (43, 32 etc.)?
//const ISSUANCE_VALIDATING_KEY_SIZE: u64 = 32;
Expand Down
Loading