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