Add support for Orchard ZSA and updated generic structures for Tx V6 (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.
This commit is contained in:
parent
8e53d246f9
commit
23e318c0b0
|
@ -6000,6 +6000,7 @@ dependencies = [
|
|||
"itertools 0.13.0",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"nonempty",
|
||||
"num-integer",
|
||||
"orchard",
|
||||
"primitive-types",
|
||||
|
|
|
@ -15,7 +15,8 @@ keywords = ["zebra", "zcash"]
|
|||
categories = ["asynchronous", "cryptography::cryptocurrencies", "encoding"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
#default = []
|
||||
default = ["tx-v6"]
|
||||
|
||||
# Production features that activate extra functionality
|
||||
|
||||
|
@ -60,6 +61,11 @@ proptest-impl = [
|
|||
|
||||
bench = ["zebra-test"]
|
||||
|
||||
# Support for transaction version 6
|
||||
tx-v6 = [
|
||||
"nonempty"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Cryptography
|
||||
|
@ -102,6 +108,9 @@ sapling-crypto.workspace = true
|
|||
zcash_protocol.workspace = true
|
||||
zcash_address.workspace = true
|
||||
|
||||
# Used for orchard serialization
|
||||
nonempty = { version = "0.7", optional = true }
|
||||
|
||||
# Time
|
||||
chrono = { version = "0.4.38", default-features = false, features = ["clock", "std", "serde"] }
|
||||
humantime = "2.1.0"
|
||||
|
|
|
@ -40,6 +40,9 @@ pub mod transparent;
|
|||
pub mod value_balance;
|
||||
pub mod work;
|
||||
|
||||
#[cfg(feature = "tx-v6")]
|
||||
pub mod orchard_zsa;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub use block::LedgerState;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ mod action;
|
|||
mod address;
|
||||
mod commitment;
|
||||
mod note;
|
||||
mod orchard_flavor_ext;
|
||||
mod sinsemilla;
|
||||
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
|
@ -23,3 +24,7 @@ pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment};
|
|||
pub use keys::Diversifier;
|
||||
pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey};
|
||||
pub use shielded_data::{AuthorizedAction, Flags, ShieldedData};
|
||||
|
||||
pub(crate) use crate::orchard_zsa::issuance::IssueData;
|
||||
pub(crate) use orchard_flavor_ext::{OrchardFlavorExt, OrchardVanilla, OrchardZSA};
|
||||
pub(crate) use shielded_data::ActionCommon;
|
||||
|
|
|
@ -11,6 +11,7 @@ use super::{
|
|||
commitment::{self, ValueCommitment},
|
||||
keys,
|
||||
note::{self, Nullifier},
|
||||
OrchardFlavorExt,
|
||||
};
|
||||
|
||||
/// An Action description, as described in the [Zcash specification §7.3][actiondesc].
|
||||
|
@ -21,7 +22,7 @@ use super::{
|
|||
///
|
||||
/// [actiondesc]: https://zips.z.cash/protocol/nu5.pdf#actiondesc
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
pub struct Action<V: OrchardFlavorExt> {
|
||||
/// A value commitment to net value of the input note minus the output note
|
||||
pub cv: commitment::ValueCommitment,
|
||||
/// The nullifier of the input note being spent.
|
||||
|
@ -35,14 +36,14 @@ pub struct Action {
|
|||
/// encrypted private key in `out_ciphertext`.
|
||||
pub ephemeral_key: keys::EphemeralPublicKey,
|
||||
/// A ciphertext component for the encrypted output note.
|
||||
pub enc_ciphertext: note::EncryptedNote,
|
||||
pub enc_ciphertext: V::EncryptedNote,
|
||||
/// A ciphertext component that allows the holder of a full viewing key to
|
||||
/// recover the recipient diversified transmission key and the ephemeral
|
||||
/// private key (and therefore the entire note plaintext).
|
||||
pub out_ciphertext: note::WrappedNoteKey,
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Action {
|
||||
impl<V: OrchardFlavorExt> ZcashSerialize for Action<V> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
self.cv.zcash_serialize(&mut writer)?;
|
||||
writer.write_all(&<[u8; 32]>::from(self.nullifier)[..])?;
|
||||
|
@ -55,7 +56,7 @@ impl ZcashSerialize for Action {
|
|||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for Action {
|
||||
impl<V: OrchardFlavorExt> ZcashDeserialize for Action<V> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
// # Consensus
|
||||
//
|
||||
|
@ -93,7 +94,7 @@ 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)?,
|
||||
enc_ciphertext: V::EncryptedNote::zcash_deserialize(&mut reader)?,
|
||||
// Type is `Sym.C`, i.e. `𝔹^Y^{\[N\]}`, i.e. arbitrary-sized byte arrays
|
||||
// https://zips.z.cash/protocol/protocol.pdf#concretesym but fixed to
|
||||
// 80 bytes in https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//! Encrypted parts of Orchard notes.
|
||||
|
||||
// FIXME: make it a generic and add support for OrchardZSA (encrypted tote size ofr it is not 580!)
|
||||
|
||||
use std::{fmt, io};
|
||||
|
||||
use serde_big_array::BigArray;
|
||||
|
@ -12,20 +10,20 @@ use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize}
|
|||
///
|
||||
/// Corresponds to the Orchard 'encCiphertext's
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct EncryptedNote(#[serde(with = "BigArray")] pub(crate) [u8; 580]);
|
||||
pub struct EncryptedNote<const N: usize>(#[serde(with = "BigArray")] pub(crate) [u8; N]);
|
||||
|
||||
// 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> {}
|
||||
|
||||
impl Clone for EncryptedNote {
|
||||
impl<const N: usize> Clone for EncryptedNote<N> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EncryptedNote {
|
||||
impl<const N: usize> fmt::Debug for EncryptedNote<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_tuple("EncryptedNote")
|
||||
.field(&hex::encode(&self.0[..]))
|
||||
|
@ -33,36 +31,36 @@ impl fmt::Debug for EncryptedNote {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for EncryptedNote {}
|
||||
impl<const N: usize> Eq for EncryptedNote<N> {}
|
||||
|
||||
impl From<[u8; 580]> for EncryptedNote {
|
||||
fn from(bytes: [u8; 580]) -> Self {
|
||||
impl<const N: usize> From<[u8; N]> for EncryptedNote<N> {
|
||||
fn from(bytes: [u8; N]) -> Self {
|
||||
EncryptedNote(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncryptedNote> for [u8; 580] {
|
||||
fn from(enc_ciphertext: EncryptedNote) -> Self {
|
||||
impl<const N: usize> From<EncryptedNote<N>> for [u8; N] {
|
||||
fn from(enc_ciphertext: EncryptedNote<N>) -> Self {
|
||||
enc_ciphertext.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for EncryptedNote {
|
||||
impl<const N: usize> PartialEq for EncryptedNote<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0[..] == other.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for EncryptedNote {
|
||||
impl<const N: usize> ZcashSerialize for EncryptedNote<N> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&self.0[..])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashDeserialize for EncryptedNote {
|
||||
impl<const N: usize> ZcashDeserialize for EncryptedNote<N> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
let mut bytes = [0; 580];
|
||||
let mut bytes = [0; N];
|
||||
reader.read_exact(&mut bytes[..])?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
@ -133,7 +131,7 @@ use proptest::prelude::*;
|
|||
proptest! {
|
||||
|
||||
#[test]
|
||||
fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedNote>()) {
|
||||
fn encrypted_ciphertext_roundtrip(ec in any::<EncryptedNote::<{ crate::orchard::ENCRYPTED_NOTE_SIZE_V5 }>>()) {
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
let mut data = Vec::new();
|
||||
|
|
|
@ -20,9 +20,17 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
use super::OrchardFlavorExt;
|
||||
|
||||
#[cfg(not(feature = "tx-v6"))]
|
||||
use super::OrchardVanilla;
|
||||
|
||||
#[cfg(feature = "tx-v6")]
|
||||
use super::OrchardZSA;
|
||||
|
||||
/// A bundle of [`Action`] descriptions and signature data.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct ShieldedData {
|
||||
pub struct ShieldedData<V: OrchardFlavorExt> {
|
||||
/// The orchard flags for this transaction.
|
||||
/// Denoted as `flagsOrchard` in the spec.
|
||||
pub flags: Flags,
|
||||
|
@ -37,13 +45,18 @@ pub struct ShieldedData {
|
|||
pub proof: Halo2Proof,
|
||||
/// The Orchard Actions, in the order they appear in the transaction.
|
||||
/// Denoted as `vActionsOrchard` and `vSpendAuthSigsOrchard` in the spec.
|
||||
pub actions: AtLeastOne<AuthorizedAction>,
|
||||
pub actions: AtLeastOne<AuthorizedAction<V>>,
|
||||
/// A signature on the transaction `sighash`.
|
||||
/// Denoted as `bindingSigOrchard` in the spec.
|
||||
pub binding_sig: Signature<Binding>,
|
||||
|
||||
#[cfg(feature = "tx-v6")]
|
||||
/// Assets intended for burning
|
||||
/// Denoted as `vAssetBurn` in the spec (ZIP 230).
|
||||
pub burn: V::BurnType,
|
||||
}
|
||||
|
||||
impl fmt::Display for ShieldedData {
|
||||
impl<V: OrchardFlavorExt> fmt::Display for ShieldedData<V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut fmter = f.debug_struct("orchard::ShieldedData");
|
||||
|
||||
|
@ -59,13 +72,18 @@ impl fmt::Display for ShieldedData {
|
|||
}
|
||||
}
|
||||
|
||||
impl ShieldedData {
|
||||
impl<V: OrchardFlavorExt> ShieldedData<V> {
|
||||
/// Iterate over the [`Action`]s for the [`AuthorizedAction`]s in this
|
||||
/// transaction, in the order they appear in it.
|
||||
pub fn actions(&self) -> impl Iterator<Item = &Action> {
|
||||
pub fn actions(&self) -> impl Iterator<Item = &Action<V>> {
|
||||
self.actions.actions()
|
||||
}
|
||||
|
||||
// FIXME: add a doc comment
|
||||
pub fn action_commons(&self) -> impl Iterator<Item = ActionCommon> + '_ {
|
||||
self.actions.actions().map(|action| action.into())
|
||||
}
|
||||
|
||||
/// Collect the [`Nullifier`]s for this transaction.
|
||||
pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
|
||||
self.actions().map(|action| &action.nullifier)
|
||||
|
@ -119,9 +137,9 @@ impl ShieldedData {
|
|||
}
|
||||
}
|
||||
|
||||
impl AtLeastOne<AuthorizedAction> {
|
||||
impl<V: OrchardFlavorExt> AtLeastOne<AuthorizedAction<V>> {
|
||||
/// Iterate over the [`Action`]s of each [`AuthorizedAction`].
|
||||
pub fn actions(&self) -> impl Iterator<Item = &Action> {
|
||||
pub fn actions(&self) -> impl Iterator<Item = &Action<V>> {
|
||||
self.iter()
|
||||
.map(|authorized_action| &authorized_action.action)
|
||||
}
|
||||
|
@ -131,23 +149,64 @@ impl AtLeastOne<AuthorizedAction> {
|
|||
///
|
||||
/// Every authorized Orchard `Action` must have a corresponding `SpendAuth` signature.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct AuthorizedAction {
|
||||
pub struct AuthorizedAction<V: OrchardFlavorExt> {
|
||||
/// The action description of this Action.
|
||||
pub action: Action,
|
||||
pub action: Action<V>,
|
||||
/// The spend signature.
|
||||
pub spend_auth_sig: Signature<SpendAuth>,
|
||||
}
|
||||
|
||||
impl AuthorizedAction {
|
||||
impl<V: OrchardFlavorExt> AuthorizedAction<V> {
|
||||
/// The size of a single Action
|
||||
///
|
||||
/// Actions are 5 * 32 + ENCRYPTED_NOTE_SIZE + 80 bytes so the total size of each Action is 820 bytes.
|
||||
/// [7.5 Action Description Encoding and Consensus][ps]
|
||||
///
|
||||
/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
|
||||
pub const ACTION_SIZE: u64 = 5 * 32 + (V::ENCRYPTED_NOTE_SIZE as u64) + 80;
|
||||
|
||||
/// The size of a single `Signature<SpendAuth>`.
|
||||
///
|
||||
/// Each Signature is 64 bytes.
|
||||
/// [7.1 Transaction Encoding and Consensus][ps]
|
||||
///
|
||||
/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
|
||||
pub const SPEND_AUTH_SIG_SIZE: u64 = 64;
|
||||
|
||||
/// The size of a single AuthorizedAction
|
||||
///
|
||||
/// Each serialized `Action` has a corresponding `Signature<SpendAuth>`.
|
||||
pub const AUTHORIZED_ACTION_SIZE: u64 = Self::ACTION_SIZE + Self::SPEND_AUTH_SIG_SIZE;
|
||||
|
||||
/// The maximum number of actions in the transaction.
|
||||
// Since a serialized Vec<AuthorizedAction> uses at least one byte for its length,
|
||||
// and the signature is required,
|
||||
// a valid max allocation can never exceed this size
|
||||
pub const ACTION_MAX_ALLOCATION: u64 = (MAX_BLOCK_BYTES - 1) / Self::AUTHORIZED_ACTION_SIZE;
|
||||
|
||||
// To be but we ensure ACTION_MAX_ALLOCATION is less than 2^16 on compile time
|
||||
// (this is a workaround, as static_assertions::const_assert! doesn't work for generics,
|
||||
// see TrustedPreallocate for Action<V>)
|
||||
const _ACTION_MAX_ALLOCATION_OK: u64 = (1 << 16) - Self::ACTION_MAX_ALLOCATION;
|
||||
/* FIXME: remove this
|
||||
const ACTION_MAX_ALLOCATION_OK: () = assert!(
|
||||
Self::ACTION_MAX_ALLOCATION < 1, //(1 << 16),
|
||||
"must be less than 2^16"
|
||||
);
|
||||
*/
|
||||
|
||||
/// Split out the action and the signature for V5 transaction
|
||||
/// serialization.
|
||||
pub fn into_parts(self) -> (Action, Signature<SpendAuth>) {
|
||||
pub fn into_parts(self) -> (Action<V>, Signature<SpendAuth>) {
|
||||
(self.action, self.spend_auth_sig)
|
||||
}
|
||||
|
||||
// Combine the action and the spend auth sig from V5 transaction
|
||||
/// deserialization.
|
||||
pub fn from_parts(action: Action, spend_auth_sig: Signature<SpendAuth>) -> AuthorizedAction {
|
||||
pub fn from_parts(
|
||||
action: Action<V>,
|
||||
spend_auth_sig: Signature<SpendAuth>,
|
||||
) -> AuthorizedAction<V> {
|
||||
AuthorizedAction {
|
||||
action,
|
||||
spend_auth_sig,
|
||||
|
@ -155,38 +214,48 @@ impl AuthorizedAction {
|
|||
}
|
||||
}
|
||||
|
||||
/// The size of a single Action
|
||||
///
|
||||
/// Actions are 5 * 32 + 580 + 80 bytes so the total size of each Action is 820 bytes.
|
||||
/// [7.5 Action Description Encoding and Consensus][ps]
|
||||
///
|
||||
/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
|
||||
pub const ACTION_SIZE: u64 = 5 * 32 + 580 + 80;
|
||||
// TODO: FIXME: Consider moving it to transaction.rs as it's not used here. Or move its usage here from transaction.rs.
|
||||
/// A struct that contains values of several fields of an `Action` struct.
|
||||
/// Those fields are used in other parts of the code that call the `orchard_actions()` method of the `Transaction`.
|
||||
/// The goal of using `ActionCommon` is that it's not a generic, unlike `Action`, so it can be returned from Transaction methods
|
||||
/// (the fields of `ActionCommon` do not depend on the generic parameter `Version` of `Action`).
|
||||
pub struct ActionCommon {
|
||||
/// A reference to the value commitment to the net value of the input note minus the output note.
|
||||
pub cv: super::commitment::ValueCommitment,
|
||||
/// A reference to the nullifier of the input note being spent.
|
||||
pub nullifier: super::note::Nullifier,
|
||||
/// A reference to the randomized validating key for `spendAuthSig`.
|
||||
pub rk: reddsa::VerificationKeyBytes<SpendAuth>,
|
||||
/// A reference to the x-coordinate of the note commitment for the output note.
|
||||
pub cm_x: pallas::Base,
|
||||
}
|
||||
|
||||
/// The size of a single `Signature<SpendAuth>`.
|
||||
///
|
||||
/// Each Signature is 64 bytes.
|
||||
/// [7.1 Transaction Encoding and Consensus][ps]
|
||||
///
|
||||
/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
|
||||
pub const SPEND_AUTH_SIG_SIZE: u64 = 64;
|
||||
impl<V: OrchardFlavorExt> From<&Action<V>> for ActionCommon {
|
||||
fn from(action: &Action<V>) -> Self {
|
||||
Self {
|
||||
cv: action.cv,
|
||||
nullifier: action.nullifier,
|
||||
rk: action.rk,
|
||||
cm_x: action.cm_x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of a single AuthorizedAction
|
||||
///
|
||||
/// Each serialized `Action` has a corresponding `Signature<SpendAuth>`.
|
||||
pub const AUTHORIZED_ACTION_SIZE: u64 = ACTION_SIZE + SPEND_AUTH_SIG_SIZE;
|
||||
/*
|
||||
struct AssertBlockSizeLimit<const N: u64>;
|
||||
|
||||
impl<const N: u64> AssertBlockSizeLimit<N> {
|
||||
const OK: () = assert!(N < (1 << 16), "must be less than 2^16");
|
||||
}
|
||||
*/
|
||||
|
||||
/// The maximum number of orchard actions in a valid Zcash on-chain transaction V5.
|
||||
///
|
||||
/// If a transaction contains more actions than can fit in maximally large block, it might be
|
||||
/// valid on the network and in the mempool, but it can never be mined into a block. So
|
||||
/// rejecting these large edge-case transactions can never break consensus.
|
||||
impl TrustedPreallocate for Action {
|
||||
impl<V: OrchardFlavorExt> TrustedPreallocate for Action<V> {
|
||||
fn max_allocation() -> u64 {
|
||||
// Since a serialized Vec<AuthorizedAction> uses at least one byte for its length,
|
||||
// and the signature is required,
|
||||
// a valid max allocation can never exceed this size
|
||||
const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
|
||||
// # Consensus
|
||||
//
|
||||
// > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
|
||||
|
@ -196,15 +265,28 @@ impl TrustedPreallocate for Action {
|
|||
// This acts as nActionsOrchard and is therefore subject to the rule.
|
||||
// The maximum value is actually smaller due to the block size limit,
|
||||
// but we ensure the 2^16 limit with a static assertion.
|
||||
static_assertions::const_assert!(MAX < (1 << 16));
|
||||
MAX
|
||||
//
|
||||
// TODO: FIXME: find a better way to use static check (see https://github.com/nvzqz/static-assertions/issues/40,
|
||||
// https://users.rust-lang.org/t/how-do-i-static-assert-a-property-of-a-generic-u32-parameter/76307)?
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl TrustedPreallocate for Signature<SpendAuth> {
|
||||
fn max_allocation() -> u64 {
|
||||
// Each signature must have a corresponding action.
|
||||
Action::max_allocation()
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,56 @@ use crate::{
|
|||
value_balance::{ValueBalance, ValueBalanceError},
|
||||
};
|
||||
|
||||
// FIXME: doc this
|
||||
macro_rules! shielded_data_iter {
|
||||
($self:expr, $mapper:expr) => {
|
||||
match $self {
|
||||
// Maybe Orchard shielded data
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => Box::new(orchard_shielded_data.into_iter().flat_map($mapper)),
|
||||
|
||||
// FIXME: process V6 properly?
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => Box::new(orchard_shielded_data.into_iter().flat_map($mapper)),
|
||||
|
||||
// No Orchard shielded data
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. } => Box::new(std::iter::empty()),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: doc this
|
||||
macro_rules! shielded_data_field {
|
||||
($self:expr, $field:ident) => {
|
||||
match $self {
|
||||
// Maybe Orchard shielded data
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.as_ref().map(|data| data.$field),
|
||||
|
||||
// FIXME: process V6 properly?
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.as_ref().map(|data| data.$field),
|
||||
|
||||
// No Orchard shielded data
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. } => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A Zcash transaction.
|
||||
///
|
||||
/// A transaction is an encoded data structure that facilitates the transfer of
|
||||
|
@ -140,7 +190,7 @@ pub enum Transaction {
|
|||
/// The sapling shielded data for this transaction, if any.
|
||||
sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
||||
/// The orchard data for this transaction, if any.
|
||||
orchard_shielded_data: Option<orchard::ShieldedData>,
|
||||
orchard_shielded_data: Option<orchard::ShieldedData<orchard::OrchardVanilla>>,
|
||||
},
|
||||
// FIXME: implement V6 properly (now it's just a coipy of V5)
|
||||
/// A `version = 6` transaction , which supports Orchard ZSA, Orchard Vanille, Sapling and
|
||||
|
@ -161,8 +211,10 @@ pub enum Transaction {
|
|||
outputs: Vec<transparent::Output>,
|
||||
/// The sapling shielded data for this transaction, if any.
|
||||
sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
||||
/// The orchard data for this transaction, if any.
|
||||
orchard_shielded_data: Option<orchard::ShieldedData>,
|
||||
/// The ZSA orchard shielded data for this transaction, if any.
|
||||
orchard_shielded_data: Option<orchard::ShieldedData<orchard::OrchardZSA>>,
|
||||
/// The ZSA issuance data for this transaction, if any.
|
||||
orchard_zsa_issue_data: Option<orchard::IssueData>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1017,96 +1069,52 @@ impl Transaction {
|
|||
|
||||
// orchard
|
||||
|
||||
/// Access the [`orchard::ShieldedData`] in this transaction,
|
||||
/// regardless of version.
|
||||
pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
|
||||
match self {
|
||||
// Maybe Orchard shielded data
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.as_ref(),
|
||||
|
||||
// FIXME: Support V6/OrchardZSA propetly.
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.as_ref(),
|
||||
|
||||
// No Orchard shielded data
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Modify the [`orchard::ShieldedData`] in this transaction,
|
||||
/// regardless of version.
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
|
||||
match self {
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data: Some(orchard_shielded_data),
|
||||
..
|
||||
} => Some(orchard_shielded_data),
|
||||
|
||||
// FIXME: Support V6/OrchardZSA propetly.
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data: Some(orchard_shielded_data),
|
||||
..
|
||||
} => Some(orchard_shielded_data),
|
||||
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. }
|
||||
| Transaction::V5 {
|
||||
orchard_shielded_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V6 {
|
||||
orchard_shielded_data: None,
|
||||
..
|
||||
} => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the [`orchard::Action`]s in this transaction, if there are any,
|
||||
/// regardless of version.
|
||||
pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
|
||||
self.orchard_shielded_data()
|
||||
.into_iter()
|
||||
.flat_map(orchard::ShieldedData::actions)
|
||||
pub fn orchard_actions(&self) -> Box<dyn Iterator<Item = orchard::ActionCommon> + '_> {
|
||||
shielded_data_iter!(self, orchard::ShieldedData::action_commons)
|
||||
}
|
||||
|
||||
/// Access the [`orchard::Nullifier`]s in this transaction, if there are any,
|
||||
/// regardless of version.
|
||||
pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
|
||||
self.orchard_shielded_data()
|
||||
.into_iter()
|
||||
.flat_map(orchard::ShieldedData::nullifiers)
|
||||
pub fn orchard_nullifiers(&self) -> Box<dyn Iterator<Item = &orchard::Nullifier> + '_> {
|
||||
shielded_data_iter!(self, orchard::ShieldedData::nullifiers)
|
||||
}
|
||||
|
||||
/// Access the note commitments in this transaction, if there are any,
|
||||
/// regardless of version.
|
||||
pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
|
||||
self.orchard_shielded_data()
|
||||
.into_iter()
|
||||
.flat_map(orchard::ShieldedData::note_commitments)
|
||||
pub fn orchard_note_commitments(&self) -> Box<dyn Iterator<Item = &pallas::Base> + '_> {
|
||||
shielded_data_iter!(self, orchard::ShieldedData::note_commitments)
|
||||
}
|
||||
|
||||
/// Access the [`orchard::Flags`] in this transaction, if there is any,
|
||||
/// regardless of version.
|
||||
pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
|
||||
self.orchard_shielded_data()
|
||||
.map(|orchard_shielded_data| orchard_shielded_data.flags)
|
||||
shielded_data_field!(self, flags)
|
||||
}
|
||||
|
||||
/// Return if the transaction has any Orchard shielded data,
|
||||
/// regardless of version.
|
||||
pub fn has_orchard_shielded_data(&self) -> bool {
|
||||
self.orchard_shielded_data().is_some()
|
||||
match self {
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. } => false,
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.is_some(),
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data,
|
||||
..
|
||||
} => orchard_shielded_data.is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: add doc
|
||||
pub fn orchard_shared_anchor(&self) -> Option<orchard::tree::Root> {
|
||||
shielded_data_field!(self, shared_anchor)
|
||||
}
|
||||
|
||||
// value balances
|
||||
|
@ -1501,10 +1509,8 @@ impl Transaction {
|
|||
///
|
||||
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
||||
pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
|
||||
let orchard_value_balance = self
|
||||
.orchard_shielded_data()
|
||||
.map(|shielded_data| shielded_data.value_balance)
|
||||
.unwrap_or_else(Amount::zero);
|
||||
let orchard_value_balance =
|
||||
shielded_data_field!(self, value_balance).unwrap_or_else(Amount::zero);
|
||||
|
||||
ValueBalance::from_orchard_amount(orchard_value_balance)
|
||||
}
|
||||
|
@ -1515,8 +1521,30 @@ impl Transaction {
|
|||
/// See `orchard_value_balance` for details.
|
||||
#[cfg(any(test, feature = "proptest-impl"))]
|
||||
pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
|
||||
self.orchard_shielded_data_mut()
|
||||
.map(|shielded_data| &mut shielded_data.value_balance)
|
||||
match self {
|
||||
Transaction::V5 {
|
||||
orchard_shielded_data: Some(orchard_shielded_data),
|
||||
..
|
||||
} => Some(&mut orchard_shielded_data.value_balance),
|
||||
|
||||
Transaction::V6 {
|
||||
orchard_shielded_data: Some(orchard_shielded_data),
|
||||
..
|
||||
} => Some(&mut orchard_shielded_data.value_balance),
|
||||
|
||||
Transaction::V1 { .. }
|
||||
| Transaction::V2 { .. }
|
||||
| Transaction::V3 { .. }
|
||||
| Transaction::V4 { .. }
|
||||
| Transaction::V5 {
|
||||
orchard_shielded_data: None,
|
||||
..
|
||||
}
|
||||
| Transaction::V6 {
|
||||
orchard_shielded_data: None,
|
||||
..
|
||||
} => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value balances for this transaction using the provided transparent outputs.
|
||||
|
|
|
@ -11,6 +11,7 @@ use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
|
|||
use crate::{
|
||||
amount,
|
||||
block::MAX_BLOCK_BYTES,
|
||||
orchard::OrchardFlavorExt,
|
||||
parameters::{
|
||||
OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID,
|
||||
TX_V6_VERSION_GROUP_ID,
|
||||
|
@ -326,7 +327,7 @@ impl ZcashDeserialize for Option<sapling::ShieldedData<sapling::SharedAnchor>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Option<orchard::ShieldedData> {
|
||||
impl<V: OrchardFlavorExt> ZcashSerialize for Option<orchard::ShieldedData<V>> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
match self {
|
||||
None => {
|
||||
|
@ -342,14 +343,15 @@ impl ZcashSerialize for Option<orchard::ShieldedData> {
|
|||
orchard_shielded_data.zcash_serialize(&mut writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for orchard::ShieldedData {
|
||||
impl<V: OrchardFlavorExt> ZcashSerialize for orchard::ShieldedData<V> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
// Split the AuthorizedAction
|
||||
let (actions, sigs): (Vec<orchard::Action>, Vec<Signature<SpendAuth>>) = self
|
||||
let (actions, sigs): (Vec<orchard::Action<V>>, Vec<Signature<SpendAuth>>) = self
|
||||
.actions
|
||||
.iter()
|
||||
.cloned()
|
||||
|
@ -377,16 +379,20 @@ impl ZcashSerialize for orchard::ShieldedData {
|
|||
// Denoted as `bindingSigOrchard` in the spec.
|
||||
self.binding_sig.zcash_serialize(&mut writer)?;
|
||||
|
||||
#[cfg(feature = "tx-v6")]
|
||||
// Denoted as `vAssetBurn` in the spec (ZIP 230).
|
||||
self.burn.zcash_serialize(&mut writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// we can't split ShieldedData out of Option<ShieldedData> deserialization,
|
||||
// because the counts are read along with the arrays.
|
||||
impl ZcashDeserialize for Option<orchard::ShieldedData> {
|
||||
impl<V: OrchardFlavorExt> ZcashDeserialize for Option<orchard::ShieldedData<V>> {
|
||||
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()?;
|
||||
|
||||
// "The fields flagsOrchard, valueBalanceOrchard, anchorOrchard, sizeProofsOrchard,
|
||||
// proofsOrchard , and bindingSigOrchard are present if and only if nActionsOrchard > 0."
|
||||
|
@ -432,7 +438,7 @@ impl ZcashDeserialize for Option<orchard::ShieldedData> {
|
|||
let binding_sig: Signature<Binding> = (&mut reader).zcash_deserialize_into()?;
|
||||
|
||||
// Create the AuthorizedAction from deserialized parts
|
||||
let authorized_actions: Vec<orchard::AuthorizedAction> = actions
|
||||
let authorized_actions: Vec<orchard::AuthorizedAction<V>> = actions
|
||||
.into_iter()
|
||||
.zip(sigs)
|
||||
.map(|(action, spend_auth_sig)| {
|
||||
|
@ -440,11 +446,17 @@ impl ZcashDeserialize for Option<orchard::ShieldedData> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let actions: AtLeastOne<orchard::AuthorizedAction> = authorized_actions.try_into()?;
|
||||
let actions: AtLeastOne<orchard::AuthorizedAction<V>> = authorized_actions.try_into()?;
|
||||
|
||||
Ok(Some(orchard::ShieldedData {
|
||||
// TODO: FIXME: add a proper comment
|
||||
#[cfg(feature = "tx-v6")]
|
||||
let burn = (&mut reader).zcash_deserialize_into()?;
|
||||
|
||||
Ok(Some(orchard::ShieldedData::<V> {
|
||||
flags,
|
||||
value_balance,
|
||||
#[cfg(feature = "tx-v6")]
|
||||
burn,
|
||||
shared_anchor,
|
||||
proof,
|
||||
actions,
|
||||
|
@ -677,7 +689,6 @@ impl ZcashSerialize for Transaction {
|
|||
orchard_shielded_data.zcash_serialize(&mut writer)?;
|
||||
}
|
||||
|
||||
// FIXME: implement a proper serialization for V6
|
||||
Transaction::V6 {
|
||||
network_upgrade,
|
||||
lock_time,
|
||||
|
@ -686,9 +697,11 @@ impl ZcashSerialize for Transaction {
|
|||
outputs,
|
||||
sapling_shielded_data,
|
||||
orchard_shielded_data,
|
||||
orchard_zsa_issue_data,
|
||||
} => {
|
||||
// FIXME: fix spec or use another link as the current version of the PDF
|
||||
// doesn't contain V6 description.
|
||||
// Transaction V6 spec:
|
||||
// FIXME: specify a proper ref
|
||||
// https://zips.z.cash/protocol/protocol.pdf#txnencoding
|
||||
|
||||
// Denoted as `nVersionGroupId` in the spec.
|
||||
|
@ -723,6 +736,9 @@ impl ZcashSerialize for Transaction {
|
|||
// `flagsOrchard`,`valueBalanceOrchard`, `anchorOrchard`, `sizeProofsOrchard`,
|
||||
// `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
|
||||
orchard_shielded_data.zcash_serialize(&mut writer)?;
|
||||
|
||||
// TODO: FIXME: add ref to spec
|
||||
orchard_zsa_issue_data.zcash_serialize(&mut writer)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -982,8 +998,9 @@ impl ZcashDeserialize for Transaction {
|
|||
}
|
||||
// FIXME: implement a proper deserialization for V6
|
||||
(6, true) => {
|
||||
// FIXME: fix spec or use another link as the current version of the PDF
|
||||
// doesn't contain V6 description.
|
||||
// Transaction V6 spec:
|
||||
// FIXME: specify a proper ref
|
||||
// https://zips.z.cash/protocol/protocol.pdf#txnencoding
|
||||
|
||||
// Denoted as `nVersionGroupId` in the spec.
|
||||
|
@ -1024,6 +1041,9 @@ impl ZcashDeserialize for Transaction {
|
|||
// `proofsOrchard`, `vSpendAuthSigsOrchard`, and `bindingSigOrchard`.
|
||||
let orchard_shielded_data = (&mut limited_reader).zcash_deserialize_into()?;
|
||||
|
||||
// TODO: FIXME: add ref to spec
|
||||
let orchard_zsa_issue_data = (&mut limited_reader).zcash_deserialize_into()?;
|
||||
|
||||
Ok(Transaction::V6 {
|
||||
network_upgrade,
|
||||
lock_time,
|
||||
|
@ -1032,6 +1052,7 @@ impl ZcashDeserialize for Transaction {
|
|||
outputs,
|
||||
sapling_shielded_data,
|
||||
orchard_shielded_data,
|
||||
orchard_zsa_issue_data,
|
||||
})
|
||||
}
|
||||
(_, _) => Err(SerializationError::Parse("bad tx header")),
|
||||
|
@ -1081,6 +1102,11 @@ pub const MIN_TRANSPARENT_TX_V4_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4;
|
|||
/// v5 transactions also have an expiry height and a consensus branch ID.
|
||||
pub const MIN_TRANSPARENT_TX_V5_SIZE: u64 = MIN_TRANSPARENT_TX_SIZE + 4 + 4;
|
||||
|
||||
/// The minimum transaction size for v6 transactions.
|
||||
///
|
||||
/// FIXME: specify a proper value and description.
|
||||
pub const MIN_TRANSPARENT_TX_V6_SIZE: u64 = MIN_TRANSPARENT_TX_V5_SIZE;
|
||||
|
||||
/// No valid Zcash message contains more transactions than can fit in a single block
|
||||
///
|
||||
/// `tx` messages contain a single transaction, and `block` messages are limited to the maximum
|
||||
|
|
Loading…
Reference in New Issue