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 4): Refactor Orchard structures to generics and add Orchard ZSA support for Transaction V6 #17

Open
wants to merge 23 commits into
base: zsa-integration-txv6
Choose a base branch
from

Conversation

dmidem
Copy link

@dmidem dmidem commented Oct 17, 2024

This PR refactors the Orchard-related structures to be generics and adds support for both Orchard Vanilla and Orchard ZSA in Zebra's Transaction V6. This change allows the codebase to handle both versions of the Orchard protocol and implements necessary serialization/deserialization logic.

Key changes

  • Generic Orchard Structures:
    • Refactored ShieldedData and Action structures to be generic, parameterized by Orchard flavor (OrchardVanilla or OrchardZSA), enabling support for both protocols in Tx V6.
  • burn Field in ShieldedData:
    • Added a burn field to ShieldedData to support ZSA: a unit type for Tx V5 and a vector of burn items for Tx V6.
  • Updates to Transaction Enum Methods:
    • Modified methods in the Transaction enum to handle the new generic structures properly and support both Orchard flavors.
  • Serialization and Deserialization for Tx V6:
    • Implemented serialization and deserialization for Transaction V6, ensuring the new structures are correctly processed while avoiding code redundancy with Tx V5.

…(without unit tests fixing for now).

- Refactored `ShieldedData` and `Action` structures to be generics parameterized by Orchard flavor
  (`OrchardVanilla` or `OrchardZSA`), enabling support for both Orchard protocols in Tx V6.
- Introduced a `burn` field in `ShieldedData` to support ZSA, with unit type for Tx V5 and a vector of burn items for Tx V6.
- Modified `Transaction` enum methods (orchard_...) to handle generics properly, ensuring compatibility with both Orchard flavors.
- Implemented serialization and deserialization for Tx V6 while avoiding code redundancy with Tx V5 wherever possible.
… with the upstream halo2/librustcash/orchard/sapling versions
@dmidem dmidem requested a review from PaulLaux October 17, 2024 09:11
…t_desc to convert slice to vec, as slices require implementation of the serialization from scratch
@dmidem dmidem changed the title Step 4: Refactor Orchard structures to generics and add Orchard ZSA support for Transaction V6 ZSA integration, step 4: Refactor Orchard structures to generics and add Orchard ZSA support for Transaction V6 Oct 18, 2024
@dmidem dmidem changed the title ZSA integration, step 4: Refactor Orchard structures to generics and add Orchard ZSA support for Transaction V6 ZSA integration (step 4): Refactor Orchard structures to generics and add Orchard ZSA support for Transaction V6 Oct 18, 2024
@PaulLaux PaulLaux requested a review from arya2 October 31, 2024 15:08
Copy link

@PaulLaux PaulLaux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments.
In addition,

  • for Serialize / De- Serialize let's use the functions from librustzcash instead of duplicating them. Especially for Issuance (a completely new component in Zebra.)
  • Need to properly handle feature flags. Currently, it is an unclear why 2 different flags are used.

RUSTFLAGS: '--cfg zcash_unstable="nu6"'
RUSTDOCFLAGS: '--cfg zcash_unstable="nu6"'
#RUSTFLAGS: '--cfg zcash_unstable="nu6"'
#RUSTDOCFLAGS: '--cfg zcash_unstable="nu6"'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why comment out?


pub(crate) use shielded_data::ActionCommon;

#[cfg(feature = "tx-v6")]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not #[cfg(zcash_unstable = "nu6")]
we don't need them both, need to decide on one.

Copy link
Collaborator

@arya2 arya2 Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like tx-v6 better because I think we just need the feature to ensure that v6 transactions can't be (de)serialized by versions of Zebra that aren't ready to deploy NU7, but either seems fine.

/// 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated line

@@ -0,0 +1,84 @@
//! This module defines traits and structures for supporting the Orchard Shielded Protocol

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this file? why not use orchard:: orchard_flavor directly and impl the missing parts?

@@ -93,7 +94,8 @@ impl ZcashDeserialize for Action {
// https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
// 580 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
// See [`note::EncryptedNote::zcash_deserialize`].
enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?,
// FIXME: don't mention about 580 here as this should work for OrchardZSA too?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, can remove comment.

@@ -41,7 +40,7 @@ fn generate_test_vectors() {
let anchor_bytes = [0; 32];
let note_value = 10;

let shielded_data: Vec<zebra_chain::orchard::ShieldedData> = (1..=4)
let shielded_data: Vec<zebra_chain::orchard::ShieldedData<OrchardVanilla>> = (1..=4)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let shielded_data: Vec<ShieldedData<OrchardVanilla>> = (1..=4)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subjective: I think orchard::ShieldedData would be slightly more readable, though every option seems fine.


let bundle = bundle
.create_proof(&proving_key, rng)
.unwrap()
.apply_signatures(rng, [0; 32], &[])
.unwrap();

zebra_chain::orchard::ShieldedData {
zebra_chain::orchard::ShieldedData::<OrchardVanilla> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShieldedData::<OrchardVanilla> {

@@ -91,7 +91,7 @@ fn generate_test_vectors() {
// FIXME: support OrchardZSA too, 580 works for OrchardVanilla only!
// FIXME: consider more "type safe" way to do the following conversion
// (now it goes through &[u8])
enc_ciphertext: <[u8; 580]>::try_from(
enc_ciphertext: <[u8; OrchardVanilla::ENCRYPTED_NOTE_SIZE]>::try_from(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type is already known
enc_ciphertext: a.encrypted_note().enc_ciphertext.as_ref().try_into().unwrap().into(),

let maybe_shielded_data: Option<zebra_chain::orchard::ShieldedData<OrchardVanilla>> =
bytes
.zcash_deserialize_into()
.expect("a valid orchard::ShieldedData instance");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let shielded_data = zebra_test::vectors::ORCHARD_SHIELDED_DATA
    .clone()
    .iter()
    .map(|bytes| {
        bytes
            .zcash_deserialize_into::<ShieldedData<OrchardVanilla>>()
            .expect("a valid orchard::ShieldedData instance")
    })
    .collect();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should deserialize to None if there are no actions for OrchardVanilla (see comment on ZcashDeserialize impl about burns).

let maybe_shielded_data: Option<zebra_chain::orchard::ShieldedData<OrchardVanilla>> =
bytes
.zcash_deserialize_into()
.expect("a valid orchard::ShieldedData instance");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Copy link
Collaborator

@arya2 arya2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial initial review, looking great so far!


// These impls all only exist because of array length restrictions.
// TODO: use const generics https://github.com/ZcashFoundation/zebra/issues/2042

impl Copy for EncryptedNote {}
impl<const N: usize> Copy for EncryptedNote<N> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: The Copy, Clone, and Debug traits here can be derived now.

Comment on lines +48 to +49
/// A structure representing a tag for Orchard protocol variant used for the transaction version `V6`
/// (which ZSA features support).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick/Optional:

Suggested change
/// A structure representing a tag for Orchard protocol variant used for the transaction version `V6`
/// (which ZSA features support).
/// A structure representing a tag for Orchard protocol variant used for the transaction version 6
/// (with support for ZSAs).


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

@arya2 arya2 Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, anytime there's a Vec being serialized, there's a CompactSize first, so it does need to insert a 0. Serializing an empty Vec should work too. Or zcash_serialize_empty_list().

Update:

There should be a 0 there for version 6 transactions and nothing for version 5 transactions, the code is correct as-is.

@@ -20,9 +20,17 @@ use crate::{
},
};

use super::OrchardFlavorExt;

#[cfg(not(feature = "tx-v6"))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need OrchardVanilla for v5 transactions

Suggested change
#[cfg(not(feature = "tx-v6"))]

// The following expression doesn't work for generics, so a workaround with _ACTION_MAX_ALLOCATION_OK in
// AuthorizedAction impl is used instead:
// static_assertions::const_assert!(AuthorizedAction::<V>::ACTION_MAX_ALLOCATION < (1 << 16));
AuthorizedAction::<V>::ACTION_MAX_ALLOCATION
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_of() might work here.

let result = Action::<OrchardVanilla>::max_allocation();

// TODO: FIXME: Check this: V6 is used as it provides the max size of the action.
// So it's used even for V5 - is this correct?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use the smaller one. It's okay if the max allocation is a little too large.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add copies of the updated tests for OrchardZSA as well?


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valueBurn is specified as a uint64, but Amount is an i64.

Suggested change
pub struct BurnItem(AssetBase, Amount);
pub struct BurnItem(AssetBase, u64);

Comment on lines +281 to +289
#[cfg(not(feature = "tx-v6"))]
let result = Action::<OrchardVanilla>::max_allocation();

// TODO: FIXME: Check this: V6 is used as it provides the max size of the action.
// So it's used even for V5 - is this correct?
#[cfg(feature = "tx-v6")]
let result = Action::<OrchardZSA>::max_allocation();

result
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion relies on moving OrchardVanilla out from behind the feature flag.

Suggested change
#[cfg(not(feature = "tx-v6"))]
let result = Action::<OrchardVanilla>::max_allocation();
// TODO: FIXME: Check this: V6 is used as it provides the max size of the action.
// So it's used even for V5 - is this correct?
#[cfg(feature = "tx-v6")]
let result = Action::<OrchardZSA>::max_allocation();
result
Action::<OrchardVanilla>::max_allocation()

impl TrustedPreallocate for BurnItem {
fn max_allocation() -> u64 {
// FIXME: is this a correct calculation way?
// The longest Vec<BurnItem> we receive from an honest peer must fit inside a valid block.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is correct as-is, it should be (MAX_BLOCK_BYTES - 1) / BURN_ITEM_SIZE and it should be implemented for BurnItem, not Vec<BurnItem>.

}

#[cfg(any(test, feature = "proptest-impl"))]
impl serde::Serialize for BurnItem {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that it's qualified as serde::Serialize since ZcashSerialize is also used in this file, but we probably don't need this implementation at all. If we do need it, it could probably be conditionally derived with #[cfg_attr(condition, attribute)].

@@ -41,7 +40,7 @@ fn generate_test_vectors() {
let anchor_bytes = [0; 32];
let note_value = 10;

let shielded_data: Vec<zebra_chain::orchard::ShieldedData> = (1..=4)
let shielded_data: Vec<zebra_chain::orchard::ShieldedData<OrchardVanilla>> = (1..=4)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subjective: I think orchard::ShieldedData would be slightly more readable, though every option seems fine.

@@ -91,7 +91,7 @@ fn generate_test_vectors() {
// FIXME: support OrchardZSA too, 580 works for OrchardVanilla only!
// FIXME: consider more "type safe" way to do the following conversion
// (now it goes through &[u8])
enc_ciphertext: <[u8; 580]>::try_from(
enc_ciphertext: <[u8; OrchardVanilla::ENCRYPTED_NOTE_SIZE]>::try_from(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enc_ciphertext: a.encrypted_note().enc_ciphertext.0.into(),

let maybe_shielded_data: Option<zebra_chain::orchard::ShieldedData<OrchardVanilla>> =
bytes
.zcash_deserialize_into()
.expect("a valid orchard::ShieldedData instance");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should deserialize to None if there are no actions for OrchardVanilla (see comment on ZcashDeserialize impl about burns).

fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
// Denoted as `nActionsOrchard` and `vActionsOrchard` in the spec.
let actions: Vec<orchard::Action> = (&mut reader).zcash_deserialize_into()?;
let actions: Vec<orchard::Action<V>> = (&mut reader).zcash_deserialize_into()?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method returns None if actions.is_empty(), but it likely needs to check for asset burns as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add copies of the updated tests for OrchardZSA as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants