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/// A bundle of [`Action`] descriptions and signature data.
24#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
25pub struct ShieldedData {
26    /// The orchard flags for this transaction.
27    /// Denoted as `flagsOrchard` in the spec.
28    pub flags: Flags,
29    /// The net value of Orchard spends minus outputs.
30    /// Denoted as `valueBalanceOrchard` in the spec.
31    pub value_balance: Amount,
32    /// The shared anchor for all `Spend`s in this transaction.
33    /// Denoted as `anchorOrchard` in the spec.
34    pub shared_anchor: tree::Root,
35    /// The aggregated zk-SNARK proof for all the actions in this transaction.
36    /// Denoted as `proofsOrchard` in the spec.
37    pub proof: Halo2Proof,
38    /// The Orchard Actions, in the order they appear in the transaction.
39    /// Denoted as `vActionsOrchard` and `vSpendAuthSigsOrchard` in the spec.
40    pub actions: AtLeastOne<AuthorizedAction>,
41    /// A signature on the transaction `sighash`.
42    /// Denoted as `bindingSigOrchard` in the spec.
43    pub binding_sig: Signature<Binding>,
44}
45
46impl fmt::Display for ShieldedData {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        let mut fmter = f.debug_struct("orchard::ShieldedData");
49
50        fmter.field("actions", &self.actions.len());
51        fmter.field("value_balance", &self.value_balance);
52        fmter.field("flags", &self.flags);
53
54        fmter.field("proof_len", &self.proof.zcash_serialized_size());
55
56        fmter.field("shared_anchor", &self.shared_anchor);
57
58        fmter.finish()
59    }
60}
61
62impl ShieldedData {
63    /// Iterate over the [`Action`]s for the [`AuthorizedAction`]s in this
64    /// transaction, in the order they appear in it.
65    pub fn actions(&self) -> impl Iterator<Item = &Action> {
66        self.actions.actions()
67    }
68
69    /// Collect the [`Nullifier`]s for this transaction.
70    pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
71        self.actions().map(|action| &action.nullifier)
72    }
73
74    /// Calculate the Action binding verification key.
75    ///
76    /// Getting the binding signature validating key from the Action description
77    /// value commitments and the balancing value implicitly checks that the
78    /// balancing value is consistent with the value transferred in the
79    /// Action descriptions, but also proves that the signer knew the
80    /// randomness used for the Action value commitments, which
81    /// prevents replays of Action descriptions that perform an output.
82    /// In Orchard, all Action descriptions have a spend authorization signature,
83    /// therefore the proof of knowledge of the value commitment randomness
84    /// is less important, but stills provides defense in depth, and reduces the
85    /// differences between Orchard and Sapling.
86    ///
87    /// The net value of Orchard spends minus outputs in a transaction
88    /// is called the balancing value, measured in zatoshi as a signed integer
89    /// cv_balance.
90    ///
91    /// Consistency of cv_balance with the value commitments in Action
92    /// descriptions is enforced by the binding signature.
93    ///
94    /// Instead of generating a key pair at random, we generate it as a function
95    /// of the value commitments in the Action descriptions of the transaction, and
96    /// the balancing value.
97    ///
98    /// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
99    pub fn binding_verification_key(&self) -> reddsa::VerificationKeyBytes<Binding> {
100        let cv: ValueCommitment = self.actions().map(|action| action.cv).sum();
101        let cv_balance: ValueCommitment =
102            ValueCommitment::new(pallas::Scalar::zero(), self.value_balance);
103
104        let key_bytes: [u8; 32] = (cv - cv_balance).into();
105        key_bytes.into()
106    }
107
108    /// Provide access to the `value_balance` field of the shielded data.
109    ///
110    /// Needed to calculate the sapling value balance.
111    pub fn value_balance(&self) -> Amount<NegativeAllowed> {
112        self.value_balance
113    }
114
115    /// Collect the cm_x's for this transaction, if it contains [`Action`]s with
116    /// outputs, in the order they appear in the transaction.
117    pub fn note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
118        self.actions().map(|action| &action.cm_x)
119    }
120}
121
122/// A trait for types that can provide Orchard actions.
123pub trait OrchardActions {
124    /// Returns an iterator over the actions in this type.
125    fn actions(&self) -> impl Iterator<Item = &Action> + '_;
126}
127
128impl OrchardActions for AtLeastOne<AuthorizedAction> {
129    /// Iterate over the [`Action`]s of each [`AuthorizedAction`].
130    fn actions(&self) -> impl Iterator<Item = &Action> + '_ {
131        self.iter()
132            .map(|authorized_action| &authorized_action.action)
133    }
134}
135
136/// An authorized action description.
137///
138/// Every authorized Orchard `Action` must have a corresponding `SpendAuth` signature.
139#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
140pub struct AuthorizedAction {
141    /// The action description of this Action.
142    pub action: Action,
143    /// The spend signature.
144    pub spend_auth_sig: Signature<SpendAuth>,
145}
146
147impl AuthorizedAction {
148    /// Split out the action and the signature for V5 transaction
149    /// serialization.
150    pub fn into_parts(self) -> (Action, Signature<SpendAuth>) {
151        (self.action, self.spend_auth_sig)
152    }
153
154    // Combine the action and the spend auth sig from V5 transaction
155    /// deserialization.
156    pub fn from_parts(action: Action, spend_auth_sig: Signature<SpendAuth>) -> AuthorizedAction {
157        AuthorizedAction {
158            action,
159            spend_auth_sig,
160        }
161    }
162}
163
164/// The size of a single Action
165///
166/// Actions are 5 * 32 + 580 + 80 bytes so the total size of each Action is 820 bytes.
167/// [7.5 Action Description Encoding and Consensus][ps]
168///
169/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
170pub const ACTION_SIZE: u64 = 5 * 32 + 580 + 80;
171
172/// The size of a single `Signature<SpendAuth>`.
173///
174/// Each Signature is 64 bytes.
175/// [7.1 Transaction Encoding and Consensus][ps]
176///
177/// [ps]: <https://zips.z.cash/protocol/nu5.pdf#actionencodingandconsensus>
178pub const SPEND_AUTH_SIG_SIZE: u64 = 64;
179
180/// The size of a single AuthorizedAction
181///
182/// Each serialized `Action` has a corresponding `Signature<SpendAuth>`.
183pub const AUTHORIZED_ACTION_SIZE: u64 = ACTION_SIZE + SPEND_AUTH_SIG_SIZE;
184
185/// The maximum number of orchard actions in a valid Zcash on-chain transaction V5.
186///
187/// If a transaction contains more actions than can fit in maximally large block, it might be
188/// valid on the network and in the mempool, but it can never be mined into a block. So
189/// rejecting these large edge-case transactions can never break consensus.
190impl TrustedPreallocate for Action {
191    fn max_allocation() -> u64 {
192        // Since a serialized Vec<AuthorizedAction> uses at least one byte for its length,
193        // and the signature is required,
194        // a valid max allocation can never exceed this size
195        const MAX: u64 = (MAX_BLOCK_BYTES - 1) / AUTHORIZED_ACTION_SIZE;
196        // # Consensus
197        //
198        // > [NU5 onward] nSpendsSapling, nOutputsSapling, and nActionsOrchard MUST all be less than 2^16.
199        //
200        // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
201        //
202        // This acts as nActionsOrchard and is therefore subject to the rule.
203        // The maximum value is actually smaller due to the block size limit,
204        // but we ensure the 2^16 limit with a static assertion.
205        static_assertions::const_assert!(MAX < (1 << 16));
206        MAX
207    }
208}
209
210impl TrustedPreallocate for Signature<SpendAuth> {
211    fn max_allocation() -> u64 {
212        // Each signature must have a corresponding action.
213        Action::max_allocation()
214    }
215}
216
217bitflags! {
218    /// Per-Transaction flags for Orchard.
219    ///
220    /// The spend and output flags are passed to the `Halo2Proof` verifier, which verifies
221    /// the relevant note spending and creation consensus rules.
222    ///
223    /// # Consensus
224    ///
225    /// > [NU5 onward] In a version 5 transaction, the reserved bits 2..7 of the flagsOrchard
226    /// > field MUST be zero.
227    ///
228    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
229    ///
230    /// ([`bitflags`](https://docs.rs/bitflags/1.2.1/bitflags/index.html) restricts its values to the
231    /// set of valid flags)
232    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
233    pub struct Flags: u8 {
234        /// Enable spending non-zero valued Orchard notes.
235        ///
236        /// "the `enableSpendsOrchard` flag, if present, MUST be 0 for coinbase transactions"
237        const ENABLE_SPENDS = 0b00000001;
238        /// Enable creating new non-zero valued Orchard notes.
239        const ENABLE_OUTPUTS = 0b00000010;
240    }
241}
242
243// We use the `bitflags 2.x` library to implement [`Flags`]. The
244// `2.x` version of the library uses a different serialization
245// format compared to `1.x`.
246// This manual implementation uses the `bitflags_serde_legacy` crate
247// to serialize `Flags` as `bitflags 1.x` would.
248impl serde::Serialize for Flags {
249    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
250        bitflags_serde_legacy::serialize(self, "Flags", serializer)
251    }
252}
253
254// We use the `bitflags 2.x` library to implement [`Flags`]. The
255// `2.x` version of the library uses a different deserialization
256// format compared to `1.x`.
257// This manual implementation uses the `bitflags_serde_legacy` crate
258// to deserialize `Flags` as `bitflags 1.x` would.
259impl<'de> serde::Deserialize<'de> for Flags {
260    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
261        bitflags_serde_legacy::deserialize("Flags", deserializer)
262    }
263}
264
265impl ZcashSerialize for Flags {
266    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
267        writer.write_u8(self.bits())?;
268
269        Ok(())
270    }
271}
272
273impl ZcashDeserialize for Flags {
274    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
275        // Consensus rule: "In a version 5 transaction,
276        // the reserved bits 2..7 of the flagsOrchard field MUST be zero."
277        // https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
278        Flags::from_bits(reader.read_u8()?)
279            .ok_or(SerializationError::Parse("invalid reserved orchard flags"))
280    }
281}