zebra_chain/block/
commitment.rs

1//! The Commitment enum, used for the corresponding block header field.
2
3use std::fmt;
4
5use hex::{FromHex, ToHex};
6use thiserror::Error;
7
8use crate::{
9    block::{self, merkle::AuthDataRoot},
10    parameters::{
11        Network,
12        NetworkUpgrade::{self, *},
13    },
14    sapling,
15    serialization::BytesInDisplayOrder,
16};
17
18/// Zcash blocks contain different kinds of commitments to their contents,
19/// depending on the network and height.
20///
21/// The `Header.commitment_bytes` field is interpreted differently, based on the
22/// network and height. The interpretation changes in the network upgrade
23/// activation block, or in the block immediately after network upgrade
24/// activation.
25#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
26pub enum Commitment {
27    /// [Pre-Sapling] "A reserved field, to be ignored."
28    ///
29    /// This field is not verified.
30    PreSaplingReserved([u8; 32]),
31
32    /// [Sapling and Blossom] The final Sapling treestate of this block.
33    ///
34    /// The root LEBS2OSP256(rt) of the Sapling note commitment tree
35    /// corresponding to the final Sapling treestate of this block.
36    ///
37    /// Subsequent `Commitment` variants also commit to the `FinalSaplingRoot`,
38    /// via their `EarliestSaplingRoot` and `LatestSaplingRoot` fields.
39    ///
40    /// Since Zebra checkpoints on Canopy, we don't need to validate this
41    /// field, but since it's included in the ChainHistoryRoot, we are
42    /// already calculating it, so we might as well validate it.
43    ///
44    /// TODO: this field is verified during semantic verification
45    FinalSaplingRoot(sapling::tree::Root),
46
47    /// [Heartwood activation block] Reserved field.
48    ///
49    /// The value of this field MUST be all zeroes.
50    ///
51    /// This MUST NOT be interpreted as a root hash.
52    /// See ZIP-221 for details.
53    ///
54    /// This field is verified in `Commitment::from_bytes`.
55    ChainHistoryActivationReserved,
56
57    /// [(Heartwood activation block + 1) to Canopy] The root of a Merkle
58    /// Mountain Range chain history tree.
59    ///
60    /// This root hash commits to various features of the chain's history,
61    /// including the Sapling commitment tree. This commitment supports the
62    /// FlyClient protocol. See ZIP-221 for details.
63    ///
64    /// The commitment in each block covers the chain history from the most
65    /// recent network upgrade, through to the previous block. In particular,
66    /// an activation block commits to the entire previous network upgrade, and
67    /// the block after activation commits only to the activation block. (And
68    /// therefore transitively to all previous network upgrades covered by a
69    /// chain history hash in their activation block, via the previous block
70    /// hash field.)
71    ///
72    /// Since Zebra's mandatory checkpoint includes Canopy activation, we only
73    /// need to verify the chain history root from `Canopy + 1 block` onwards,
74    /// using a new history tree based on the `Canopy` activation block.
75    ///
76    /// NU5 and later upgrades use the [`Commitment::ChainHistoryBlockTxAuthCommitment`]
77    /// variant.
78    ///
79    /// TODO: this field is verified during contextual verification
80    ChainHistoryRoot(ChainHistoryMmrRootHash),
81
82    /// [NU5 activation onwards] A commitment to:
83    /// - the chain history Merkle Mountain Range tree, and
84    /// - the auth data merkle tree covering this block.
85    ///
86    /// The chain history Merkle Mountain Range tree commits to the previous
87    /// block and all ancestors in the current network upgrade. (A new chain
88    /// history tree starts from each network upgrade's activation block.)
89    ///
90    /// The auth data merkle tree commits to this block.
91    ///
92    /// This commitment supports the FlyClient protocol and non-malleable
93    /// transaction IDs. See ZIP-221 and ZIP-244 for details.
94    ///
95    /// See also the [`Commitment::ChainHistoryRoot`] variant.
96    ///
97    /// TODO: this field is verified during contextual verification
98    ChainHistoryBlockTxAuthCommitment(ChainHistoryBlockTxAuthCommitmentHash),
99}
100
101/// The required value of reserved `Commitment`s.
102pub const CHAIN_HISTORY_ACTIVATION_RESERVED: [u8; 32] = [0; 32];
103
104impl Commitment {
105    /// Returns `bytes` as the Commitment variant for `network` and `height`.
106    //
107    // TODO: rename as from_bytes_in_serialized_order()
108    pub(super) fn from_bytes(
109        bytes: [u8; 32],
110        network: &Network,
111        height: block::Height,
112    ) -> Result<Commitment, CommitmentError> {
113        use Commitment::*;
114        use CommitmentError::*;
115
116        match NetworkUpgrade::current_with_activation_height(network, height) {
117            (Genesis | BeforeOverwinter | Overwinter, _) => Ok(PreSaplingReserved(bytes)),
118            (Sapling | Blossom, _) => match sapling::tree::Root::try_from(bytes) {
119                Ok(root) => Ok(FinalSaplingRoot(root)),
120                _ => Err(InvalidSapingRootBytes),
121            },
122            (Heartwood, activation_height) if height == activation_height => {
123                if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED {
124                    Ok(ChainHistoryActivationReserved)
125                } else {
126                    Err(InvalidChainHistoryActivationReserved { actual: bytes })
127                }
128            }
129            // It's possible for the current network upgrade to be Heartwood or any network upgrade after Heartwood at
130            // the Heartwood activation height on Regtest or configured test networks. The reserved chain history root
131            // activation bytes should still be used pre-NU5.
132            //
133            // See <https://zips.z.cash/zip-0221> and the [protocol specification ยง7.6](https://zips.z.cash/protocol/protocol.pdf).
134            (Canopy, _) if Some(height) == Heartwood.activation_height(network) => {
135                if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED {
136                    Ok(ChainHistoryActivationReserved)
137                } else {
138                    Err(InvalidChainHistoryActivationReserved { actual: bytes })
139                }
140            }
141            (Heartwood | Canopy, _) => Ok(ChainHistoryRoot(ChainHistoryMmrRootHash(bytes))),
142            (Nu5 | Nu6 | Nu6_1 | Nu7, _) => Ok(ChainHistoryBlockTxAuthCommitment(
143                ChainHistoryBlockTxAuthCommitmentHash(bytes),
144            )),
145
146            #[cfg(zcash_unstable = "zfuture")]
147            (ZFuture, _) => Ok(ChainHistoryBlockTxAuthCommitment(
148                ChainHistoryBlockTxAuthCommitmentHash(bytes),
149            )),
150        }
151    }
152
153    /// Returns the serialized bytes for this Commitment.
154    //
155    // TODO: refactor as bytes_in_serialized_order(&self)
156    #[cfg(test)]
157    pub(super) fn to_bytes(self) -> [u8; 32] {
158        use Commitment::*;
159
160        match self {
161            PreSaplingReserved(bytes) => bytes,
162            FinalSaplingRoot(hash) => hash.0.into(),
163            ChainHistoryActivationReserved => CHAIN_HISTORY_ACTIVATION_RESERVED,
164            ChainHistoryRoot(hash) => hash.0,
165            ChainHistoryBlockTxAuthCommitment(hash) => hash.0,
166        }
167    }
168}
169
170/// The root hash of a Merkle Mountain Range chain history tree.
171// TODO:
172//    - add methods for maintaining the MMR peaks, and calculating the root
173//      hash from the current set of peaks
174//    - move to a separate file
175#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Default)]
176pub struct ChainHistoryMmrRootHash([u8; 32]);
177
178impl fmt::Display for ChainHistoryMmrRootHash {
179    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180        f.write_str(&self.encode_hex::<String>())
181    }
182}
183
184impl fmt::Debug for ChainHistoryMmrRootHash {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        f.debug_tuple("ChainHistoryMmrRootHash")
187            .field(&self.encode_hex::<String>())
188            .finish()
189    }
190}
191
192impl From<[u8; 32]> for ChainHistoryMmrRootHash {
193    fn from(hash: [u8; 32]) -> Self {
194        ChainHistoryMmrRootHash(hash)
195    }
196}
197
198impl From<ChainHistoryMmrRootHash> for [u8; 32] {
199    fn from(hash: ChainHistoryMmrRootHash) -> Self {
200        hash.0
201    }
202}
203
204impl BytesInDisplayOrder<true> for ChainHistoryMmrRootHash {
205    fn bytes_in_serialized_order(&self) -> [u8; 32] {
206        self.0
207    }
208
209    fn from_bytes_in_serialized_order(bytes: [u8; 32]) -> Self {
210        ChainHistoryMmrRootHash(bytes)
211    }
212}
213
214impl ToHex for &ChainHistoryMmrRootHash {
215    fn encode_hex<T: FromIterator<char>>(&self) -> T {
216        self.bytes_in_display_order().encode_hex()
217    }
218
219    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
220        self.bytes_in_display_order().encode_hex_upper()
221    }
222}
223
224impl ToHex for ChainHistoryMmrRootHash {
225    fn encode_hex<T: FromIterator<char>>(&self) -> T {
226        (&self).encode_hex()
227    }
228
229    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
230        (&self).encode_hex_upper()
231    }
232}
233
234impl FromHex for ChainHistoryMmrRootHash {
235    type Error = <[u8; 32] as FromHex>::Error;
236
237    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
238        let mut hash = <[u8; 32]>::from_hex(hex)?;
239        hash.reverse();
240
241        Ok(hash.into())
242    }
243}
244
245/// A block commitment to chain history and transaction auth.
246/// - the chain history tree for all ancestors in the current network upgrade,
247///   and
248/// - the transaction authorising data in this block.
249///
250/// Introduced in NU5.
251#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
252pub struct ChainHistoryBlockTxAuthCommitmentHash([u8; 32]);
253
254impl fmt::Display for ChainHistoryBlockTxAuthCommitmentHash {
255    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
256        f.write_str(&self.encode_hex::<String>())
257    }
258}
259
260impl fmt::Debug for ChainHistoryBlockTxAuthCommitmentHash {
261    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262        f.debug_tuple("ChainHistoryBlockTxAuthCommitmentHash")
263            .field(&self.encode_hex::<String>())
264            .finish()
265    }
266}
267
268impl From<[u8; 32]> for ChainHistoryBlockTxAuthCommitmentHash {
269    fn from(hash: [u8; 32]) -> Self {
270        ChainHistoryBlockTxAuthCommitmentHash(hash)
271    }
272}
273
274impl From<ChainHistoryBlockTxAuthCommitmentHash> for [u8; 32] {
275    fn from(hash: ChainHistoryBlockTxAuthCommitmentHash) -> Self {
276        hash.0
277    }
278}
279
280impl BytesInDisplayOrder<true> for ChainHistoryBlockTxAuthCommitmentHash {
281    fn bytes_in_serialized_order(&self) -> [u8; 32] {
282        self.0
283    }
284
285    fn from_bytes_in_serialized_order(bytes: [u8; 32]) -> Self {
286        ChainHistoryBlockTxAuthCommitmentHash(bytes)
287    }
288}
289
290impl ChainHistoryBlockTxAuthCommitmentHash {
291    /// Compute the block commitment from the history tree root and the
292    /// authorization data root, as specified in [ZIP-244].
293    ///
294    /// `history_tree_root` is the root of the history tree up to and including
295    /// the *previous* block.
296    /// `auth_data_root` is the root of the Merkle tree of authorizing data
297    /// commmitments of each transaction in the *current* block.
298    ///
299    ///  [ZIP-244]: https://zips.z.cash/zip-0244#block-header-changes
300    pub fn from_commitments(
301        history_tree_root: &ChainHistoryMmrRootHash,
302        auth_data_root: &AuthDataRoot,
303    ) -> Self {
304        // > The value of this hash [hashBlockCommitments] is the BLAKE2b-256 hash personalized
305        // > by the string "ZcashBlockCommit" of the following elements:
306        // >   hashLightClientRoot (as described in ZIP 221)
307        // >   hashAuthDataRoot    (as described below)
308        // >   terminator          [0u8;32]
309        let hash_block_commitments: [u8; 32] = blake2b_simd::Params::new()
310            .hash_length(32)
311            .personal(b"ZcashBlockCommit")
312            .to_state()
313            .update(&<[u8; 32]>::from(*history_tree_root)[..])
314            .update(&<[u8; 32]>::from(*auth_data_root))
315            .update(&[0u8; 32])
316            .finalize()
317            .as_bytes()
318            .try_into()
319            .expect("32 byte array");
320        Self(hash_block_commitments)
321    }
322}
323
324impl ToHex for &ChainHistoryBlockTxAuthCommitmentHash {
325    fn encode_hex<T: FromIterator<char>>(&self) -> T {
326        self.bytes_in_display_order().encode_hex()
327    }
328
329    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
330        self.bytes_in_display_order().encode_hex_upper()
331    }
332}
333
334impl ToHex for ChainHistoryBlockTxAuthCommitmentHash {
335    fn encode_hex<T: FromIterator<char>>(&self) -> T {
336        (&self).encode_hex()
337    }
338
339    fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
340        (&self).encode_hex_upper()
341    }
342}
343
344impl FromHex for ChainHistoryBlockTxAuthCommitmentHash {
345    type Error = <[u8; 32] as FromHex>::Error;
346
347    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
348        let mut hash = <[u8; 32]>::from_hex(hex)?;
349        hash.reverse();
350
351        Ok(hash.into())
352    }
353}
354
355/// Errors that can occur when checking RootHash consensus rules.
356///
357/// Each error variant corresponds to a consensus rule, so enumerating
358/// all possible verification failures enumerates the consensus rules we
359/// implement, and ensures that we don't reject blocks or transactions
360/// for a non-enumerated reason.
361#[allow(missing_docs)]
362#[derive(Error, Clone, Debug, PartialEq, Eq)]
363pub enum CommitmentError {
364    #[error(
365        "invalid final sapling root: expected {:?}, actual: {:?}",
366        hex::encode(expected),
367        hex::encode(actual)
368    )]
369    InvalidFinalSaplingRoot {
370        // TODO: are these fields a security risk? If so, open a ticket to remove
371        // similar fields across Zebra
372        expected: [u8; 32],
373        actual: [u8; 32],
374    },
375
376    #[error("invalid chain history activation reserved block commitment: expected all zeroes, actual: {:?}",  hex::encode(actual))]
377    InvalidChainHistoryActivationReserved { actual: [u8; 32] },
378
379    #[error(
380        "invalid chain history root: expected {:?}, actual: {:?}",
381        hex::encode(expected),
382        hex::encode(actual)
383    )]
384    InvalidChainHistoryRoot {
385        expected: [u8; 32],
386        actual: [u8; 32],
387    },
388
389    #[error(
390        "invalid block commitment root: expected {:?}, actual: {:?}",
391        hex::encode(expected),
392        hex::encode(actual)
393    )]
394    InvalidChainHistoryBlockTxAuthCommitment {
395        expected: [u8; 32],
396        actual: [u8; 32],
397    },
398
399    #[error("missing required block height: block commitments can't be parsed without a block height, block hash: {block_hash:?}")]
400    MissingBlockHeight { block_hash: block::Hash },
401
402    #[error("provided bytes are not a valid sapling root")]
403    InvalidSapingRootBytes,
404}