Skip to content
Closed
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