Skip to main content

zebra_chain/orchard/
shielded_data.rs

1//! Orchard shielded data for `V5` `Transaction`s.
2
3use std::{
4    cmp::{Eq, PartialEq},
5    fmt::{self, Debug},
6    io,
7};
8
9use byteorder::{ReadBytesExt, WriteBytesExt};
10use halo2::pasta::pallas;
11use reddsa::{orchard::Binding, orchard::SpendAuth, Signature};
12
13use crate::{
14    amount::{Amount, NegativeAllowed},
15    block::MAX_BLOCK_BYTES,
16    orchard::{tree, Action, Nullifier, ValueCommitment},
17    primitives::Halo2Proof,
18    serialization::{
19        AtLeastOne, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize,
20    },
21};
22
23/// Returns the canonical size in bytes of an Orchard proof for `num_actions` actions.
24///
25/// An Orchard proof is a Halo2 proof whose length is exactly linear in the number of
26/// actions (circuit instances): 4992 bytes for 1 action and 7264 bytes for 2 actions,
27/// i.e. a fixed base plus 2272 bytes per action. The exact constants are owned by the
28/// `orchard` crate, which derives them from the action circuit's `halo2_proofs`
29/// `CircuitCost` and cross-checks them in its circuit tests, so we delegate to
30/// [`orchard::Proof::expected_proof_size`] rather than re-deriving them here. The
31/// `expected_proof_size_known_values` guard test cross-checks the returned values.
32pub(crate) fn expected_proof_size(num_actions: usize) -> usize {
33    orchard::Proof::expected_proof_size(num_actions)
34}
35
36/// A bundle of [`Action`] descriptions and signature data.
37#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
38pub struct ShieldedData {
39    /// The orchard flags for this transaction.
40    /// Denoted as `flagsOrchard` in the spec.
41    pub flags: Flags,
42    /// The net value of Orchard spends minus outputs.
43    /// Denoted as `valueBalanceOrchard` in the spec.
44    pub value_balance: Amount,
45    /// The shared anchor for all `Spend`s in this transaction.
46    /// Denoted as `anchorOrchard` in the spec.
47    pub shared_anchor: tree::Root,
48    /// The aggregated zk-SNARK proof for all the actions in this transaction.
49    /// Denoted as `proofsOrchard` in the spec.
50    pub proof: Halo2Proof,
51    /// The Orchard Actions, in the order they appear in the transaction.
52    /// Denoted as `vActionsOrchard` and `vSpendAuthSigsOrchard` in the spec.
53    pub actions: AtLeastOne<AuthorizedAction>,
54    /// A signature on the transaction `sighash`.
55    /// Denoted as `bindingSigOrchard` in the spec.
56    pub binding_sig: Signature<Binding>,
57}
58
59/// A v6 (NU6.3) Orchard-protocol shielded bundle — used for both the Orchard and the Ironwood pool.
60///
61/// This newtype wraps [`ShieldedData`] to give it the NU6.3 flag-byte serialization
62/// ([`FlagsV6`], which permits the `enableCrossAddress` flag), distinct from the
63/// pre-NU6.3 serialization that the bare [`ShieldedData`] uses for v5 Orchard bundles. The two
64/// formats differ only in which flag bits are reserved; encoding the format in the type keeps the
65/// v5 and v6 (de)serialization paths from being confused.
66#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
67pub struct ShieldedDataV6(ShieldedData);
68
69impl ShieldedDataV6 {
70    /// Wraps a v5-shaped Orchard [`ShieldedData`] as a v6 (NU6.3) Orchard bundle.
71    pub fn new(shielded_data: ShieldedData) -> Self {
72        Self(shielded_data)
73    }
74
75    /// Returns the inner Orchard [`ShieldedData`].
76    pub fn data(&self) -> &ShieldedData {
77        &self.0
78    }
79
80    /// Returns the inner Orchard [`ShieldedData`], mutably.
81    pub fn data_mut(&mut self) -> &mut ShieldedData {
82        &mut self.0
83    }
84
85    /// Consumes the wrapper, returning the inner Orchard [`ShieldedData`].
86    pub fn into_inner(self) -> ShieldedData {
87        self.0
88    }
89}
90
91impl fmt::Display for ShieldedData {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        let mut fmter = f.debug_struct("orchard::ShieldedData");
94
95        fmter.field("actions", &self.actions.len());
96        fmter.field("value_balance", &self.value_balance);
97        fmter.field("flags", &self.flags);
98
99        fmter.field("proof_len", &self.proof.zcash_serialized_size());
100
101        fmter.field("shared_anchor", &self.shared_anchor);
102
103        fmter.finish()
104    }
105}
106
107impl ShieldedData {
108    /// Iterate over the [`Action`]s for the [`AuthorizedAction`]s in this
109    /// transaction, in the order they appear in it.
110    pub fn actions(&self) -> impl Iterator<Item = &Action> {
111        self.actions.actions()
112    }
113
114    /// Returns whether the proof has the canonical length for its number of actions.
115    ///
116    /// An Orchard proof is stored as an unbounded byte sequence, so a proof that is
117    /// present but not canonically sized can be padded with arbitrary trailing data
118    /// without affecting its validity. Bundles are parsed leniently (so that historical
119    /// transactions remain deserializable), so this is enforced separately as a
120    /// height-gated consensus rule. See `GHSA-jfw5-j458-pfv6`.
121    pub fn proof_size_is_canonical(&self) -> bool {
122        self.proof.0.len() == expected_proof_size(self.actions.len())
123    }
124
125    /// Collect the [`Nullifier`]s for this transaction.
126    pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
127        self.actions().map(|action| &action.nullifier)
128    }
129
130    /// Calculate the Action binding verification key.
131    ///
132    /// Getting the binding signature validating key from the Action description
133    /// value commitments and the balancing value implicitly checks that the
134    /// balancing value is consistent with the value transferred in the
135    /// Action descriptions, but also proves that the signer knew the
136    /// randomness used for the Action value commitments, which
137    /// prevents replays of Action descriptions that perform an output.
138    /// In Orchard, all Action descriptions have a spend authorization signature,
139    /// therefore the proof of knowledge of the value commitment randomness
140    /// is less important, but stills provides defense in depth, and reduces the
141    /// differences between Orchard and Sapling.
142    ///
143    /// The net value of Orchard spends minus outputs in a transaction
144    /// is called the balancing value, measured in zatoshi as a signed integer
145    /// cv_balance.
146    ///
147    /// Consistency of cv_balance with the value commitments in Action
148    /// descriptions is enforced by the binding signature.
149    ///
150    /// Instead of generating a key pair at random, we generate it as a function
151    /// of the value commitments in the Action descriptions of the transaction, and
152    /// the balancing value.
153    ///
154    /// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
155    pub fn binding_verification_key(&self) -> reddsa::VerificationKeyBytes<Binding> {
156        let cv: ValueCommitment = self.actions().map(|action| action.cv).sum();
157        let cv_balance: ValueCommitment =
158            ValueCommitment::new(pallas::Scalar::zero(), self.value_balance);
159
160        let key_bytes: [u8; 32] = (cv - cv_balance).into();
161        key_bytes.into()
162    }
163
164    /// Provide access to the `value_balance` field of the shielded data.
165    ///
166    /// Needed to calculate the sapling value balance.
167    pub fn value_balance(&self) -> Amount<NegativeAllowed> {
168        self.value_balance
169    }
170
171    /// Collect the cm_x's for this transaction, if it contains [`Action`]s with
172    /// outputs, in the order they appear in the transaction.
173    pub fn note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
174        self.actions().map(|action| &action.cm_x)
175    }
176}
177
178/// A trait for types that can provide Orchard actions.
179pub trait OrchardActions {
180    /// Returns an iterator over the actions in this type.
181    fn actions(&self) -> impl Iterator<Item = &Action> + '_;
182}
183
184impl OrchardActions for AtLeastOne<AuthorizedAction> {
185    /// Iterate over the [`Action`]s of each [`AuthorizedAction`].
186    fn actions(&self) -> impl Iterator<Item = &Action> + '_ {
187        self.iter()
188            .map(|authorized_action| &authorized_action.action)
189    }
190}
191
192/// An authorized action description.
193///
194/// Every authorized Orchard `Action` must have a corresponding `SpendAuth` signature.
195#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
196pub struct AuthorizedAction {
197    /// The action description of this Action.
198    pub action: Action,
199    /// The spend signature.
200    pub spend_auth_sig: Signature<SpendAuth>,
201}
202
203impl AuthorizedAction {
204    /// Split out the action and the signature for V5 transaction
205    /// serialization.
206    pub fn into_parts(self) -> (Action, Signature<SpendAuth>) {
207        (self.action, self.spend_auth_sig)
208    }
209
210    // Combine the action and the spend auth sig from V5 transaction
211    /// deserialization.
212    pub fn from_parts(action: Action, spend_auth_sig: Signature<SpendAuth>) -> AuthorizedAction {
213        AuthorizedAction {
214            action,
215            spend_auth_sig,
216        }
217    }
218}
219
220/// The size of a single Action
221///
222/// Actions are 5 * 32 + 580 + 80 bytes so the total size of each Action is 820 bytes.
223/// [7.5 Action Description Encoding and Consensus][ps]
224///
225/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
226pub const ACTION_SIZE: u64 = 5 * 32 + 580 + 80;
227
228/// The size of a single `Signature<SpendAuth>`.
229///
230/// Each Signature is 64 bytes.
231/// [7.1 Transaction Encoding and Consensus][ps]
232///
233/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
234pub const SPEND_AUTH_SIG_SIZE: u64 = 64;
235
236/// The size of a single AuthorizedAction
237///
238/// Each serialized `Action` has a corresponding `Signature<SpendAuth>`.
239pub const AUTHORIZED_ACTION_SIZE: u64 = ACTION_SIZE + SPEND_AUTH_SIG_SIZE;
240
241/// The maximum number of orchard actions in a valid Zcash on-chain transaction V5.
242///
243/// If a transaction contains more actions than can fit in maximally large block, it might be
244/// valid on the network and in the mempool, but it can never be mined into a block. So
245/// rejecting these large edge-case transactions can never break consensus.
246impl TrustedPreallocate for Action {
247    fn max_allocation() -> u64 {
248        // Since a serialized Vec<AuthorizedAction> uses at least one byte for its length,
249        // and the signature is required,
250        // a valid max allocation can never exceed this size
251        const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
252        // # Consensus
253        //
254        // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
255        //
256        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
257        //
258        // This acts as nActionsOrchard and is therefore subject to the rule.
259        // The maximum value is actually smaller due to the block size limit,
260        // but we ensure the 2^16 limit with a static assertion.
261        static_assertions::const_assert!(MAX < (1 << 16));
262        MAX
263    }
264}
265
266impl TrustedPreallocate for Signature<SpendAuth> {
267    fn max_allocation() -> u64 {
268        // Each signature must have a corresponding action.
269        Action::max_allocation()
270    }
271}
272
273bitflags! {
274    /// Per-Transaction flags for Orchard.
275    ///
276    /// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
277    /// the relevant note spending and creation consensus rules.
278    ///
279    /// # Consensus
280    ///
281    /// > [NU5 onward] In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard
282    /// > field MUST be zero.
283    ///
284    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
285    ///
286    /// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
287    /// set of valid flags)
288    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
289    pub struct Flags: u8 {
290        /// Enable spending non-zero valued Orchard notes.
291        ///
292        /// "the `enableSpendsOrchard` flag, if present, MUST be 0 for coinbase transactions"
293        const ENABLE_SPENDS = 0b00000001;
294        /// Enable creating new non-zero valued Orchard notes.
295        const ENABLE_OUTPUTS = 0b00000010;
296        /// `enableCrossAddress` (NU6.3, bit 2): allow output notes to use a different
297        /// protocol-level address than the spending key.
298        ///
299        /// Reserved (MUST be 0) for the Orchard pool in every tx version. Valid only for the
300        /// Ironwood pool (v6), parsed via the `FlagsV6` newtype.
301        const ENABLE_CROSS_ADDRESS = 0b00000100;
302    }
303}
304
305/// The Orchard flags of an Ironwood (v6) bundle.
306///
307/// Newtype over [`Flags`] whose [`ZcashDeserialize`] impl uses the NU6.3 Ironwood flag-byte format:
308/// bit 2 (`enableCrossAddress`) is valid and only bits 3..7 are reserved. The bare [`Flags`] codec
309/// is the format for every Orchard-pool bundle (v5 *and* v6), where bits 2..7 are all reserved —
310/// `enableCrossAddress` is permitted only for the Ironwood pool. Encoding the format in the type
311/// keeps the two flag-parsing paths from being confused (parallels [`ShieldedDataV6`]).
312#[derive(Copy, Clone, Debug, PartialEq, Eq)]
313pub struct FlagsV6(Flags);
314
315impl From<FlagsV6> for Flags {
316    fn from(flags: FlagsV6) -> Self {
317        flags.0
318    }
319}
320
321impl Flags {
322    /// The flag bits that are reserved (MUST be zero) in the pre-NU6.3 format.
323    const PRE_NU6_3_RESERVED: u8 = !(Self::ENABLE_SPENDS.bits() | Self::ENABLE_OUTPUTS.bits());
324
325    /// The flag bits that are reserved (MUST be zero) in the NU6.3 format.
326    const NU6_3_RESERVED: u8 = !(Self::ENABLE_SPENDS.bits()
327        | Self::ENABLE_OUTPUTS.bits()
328        | Self::ENABLE_CROSS_ADDRESS.bits());
329
330    /// Parses a flags byte, rejecting any bit set in the `reserved` mask.
331    ///
332    /// This is a generic helper that enforces whatever `reserved` mask the caller passes. The
333    /// specific consensus rule for which bits must be zero depends on the bundle format and is
334    /// documented at each call site (see the [`ZcashDeserialize`] impls for [`Flags`] and
335    /// [`FlagsV6`]).
336    fn from_byte(byte: u8, reserved: u8) -> Result<Self, SerializationError> {
337        if byte & reserved != 0 {
338            return Err(SerializationError::Parse("invalid reserved orchard flags"));
339        }
340
341        // `from_bits_truncate` keeps only known bits; the reserved-bit check above already
342        // rejected any bit not permitted by this format.
343        Ok(Self::from_bits_truncate(byte))
344    }
345}
346
347// We use the `bitflags 2.x` library to implement [`Flags`]. The
348// `2.x` version of the library uses a different serialization
349// format compared to `1.x`.
350// This manual implementation uses the `bitflags_serde_legacy` crate
351// to serialize `Flags` as `bitflags 1.x` would.
352impl serde::Serialize for Flags {
353    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
354        bitflags_serde_legacy::serialize(self, "Flags", serializer)
355    }
356}
357
358// We use the `bitflags 2.x` library to implement [`Flags`]. The
359// `2.x` version of the library uses a different deserialization
360// format compared to `1.x`.
361// This manual implementation uses the `bitflags_serde_legacy` crate
362// to deserialize `Flags` as `bitflags 1.x` would.
363impl<'de> serde::Deserialize<'de> for Flags {
364    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
365        bitflags_serde_legacy::deserialize("Flags", deserializer)
366    }
367}
368
369impl ZcashSerialize for Flags {
370    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
371        writer.write_u8(self.bits())?;
372
373        Ok(())
374    }
375}
376
377impl ZcashDeserialize for Flags {
378    /// # Consensus
379    ///
380    /// > [NU5 onward] In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard
381    /// > field MUST be zero.
382    ///
383    /// From NU6.3, the Ironwood flag byte uses bit 2 as `enableCrossAddress`, so only bits 3..7 are
384    /// reserved (see [`FlagsV6`]); the Orchard pool keeps bit 2 reserved in every tx version.
385    ///
386    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
387    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
388        // The default codec is the pre-NU6.3 format, used by v5 *and* v6 Orchard bundles, where
389        // bits 2..7 (including `enableCrossAddress`) are reserved and MUST be zero. Only the Ironwood
390        // bundle deserializes via the `FlagsV6` newtype, which permits bit 2.
391        Flags::from_byte(reader.read_u8()?, Flags::PRE_NU6_3_RESERVED)
392    }
393}
394
395impl ZcashDeserialize for FlagsV6 {
396    /// # Consensus
397    ///
398    /// From NU6.3, the Ironwood flag byte uses bit 2 as `enableCrossAddress`, so only bits 3..7 are
399    /// reserved and MUST be zero (cf. the Orchard-pool rule on [`Flags`], which keeps bit 2
400    /// reserved).
401    ///
402    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
403    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
404        // The NU6.3 Ironwood format: bit 2 (`enableCrossAddress`) is valid and only bits 3..7 are
405        // reserved.
406        Ok(FlagsV6(Flags::from_byte(
407            reader.read_u8()?,
408            Flags::NU6_3_RESERVED,
409        )?))
410    }
411}