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}