//! The Commitment enum, used for the corresponding block header field. use std::fmt; use hex::{FromHex, ToHex}; use thiserror::Error; use crate::{ block::{self, merkle::AuthDataRoot}, parameters::{Network, NetworkUpgrade, NetworkUpgrade::*}, sapling, }; /// Zcash blocks contain different kinds of commitments to their contents, /// depending on the network and height. /// /// The `Header.commitment_bytes` field is interpreted differently, based on the /// network and height. The interpretation changes in the network upgrade /// activation block, or in the block immediately after network upgrade /// activation. #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum Commitment { /// [Pre-Sapling] "A reserved field, to be ignored." /// /// This field is not verified. PreSaplingReserved([u8; 32]), /// [Sapling and Blossom] The final Sapling treestate of this block. /// /// The root LEBS2OSP256(rt) of the Sapling note commitment tree /// corresponding to the final Sapling treestate of this block. /// /// Subsequent `Commitment` variants also commit to the `FinalSaplingRoot`, /// via their `EarliestSaplingRoot` and `LatestSaplingRoot` fields. /// /// Since Zebra checkpoints on Canopy, we don't need to validate this /// field, but since it's included in the ChainHistoryRoot, we are /// already calculating it, so we might as well validate it. /// /// TODO: this field is verified during semantic verification FinalSaplingRoot(sapling::tree::Root), /// [Heartwood activation block] Reserved field. /// /// The value of this field MUST be all zeroes. /// /// This MUST NOT be interpreted as a root hash. /// See ZIP-221 for details. /// /// This field is verified in `Commitment::from_bytes`. ChainHistoryActivationReserved, /// [(Heartwood activation block + 1) to Canopy] The root of a Merkle /// Mountain Range chain history tree. /// /// This root hash commits to various features of the chain's history, /// including the Sapling commitment tree. This commitment supports the /// FlyClient protocol. See ZIP-221 for details. /// /// The commitment in each block covers the chain history from the most /// recent network upgrade, through to the previous block. In particular, /// an activation block commits to the entire previous network upgrade, and /// the block after activation commits only to the activation block. (And /// therefore transitively to all previous network upgrades covered by a /// chain history hash in their activation block, via the previous block /// hash field.) /// /// Since Zebra's mandatory checkpoint includes Canopy activation, we only /// need to verify the chain history root from `Canopy + 1 block` onwards, /// using a new history tree based on the `Canopy` activation block. /// /// NU5 and later upgrades use the [`Commitment::ChainHistoryBlockTxAuthCommitment`] /// variant. /// /// TODO: this field is verified during contextual verification ChainHistoryRoot(ChainHistoryMmrRootHash), /// [NU5 activation onwards] A commitment to: /// - the chain history Merkle Mountain Range tree, and /// - the auth data merkle tree covering this block. /// /// The chain history Merkle Mountain Range tree commits to the previous /// block and all ancestors in the current network upgrade. (A new chain /// history tree starts from each network upgrade's activation block.) /// /// The auth data merkle tree commits to this block. /// /// This commitment supports the FlyClient protocol and non-malleable /// transaction IDs. See ZIP-221 and ZIP-244 for details. /// /// See also the [`Commitment::ChainHistoryRoot`] variant. /// /// TODO: this field is verified during contextual verification ChainHistoryBlockTxAuthCommitment(ChainHistoryBlockTxAuthCommitmentHash), } /// The required value of reserved `Commitment`s. pub(crate) const CHAIN_HISTORY_ACTIVATION_RESERVED: [u8; 32] = [0; 32]; impl Commitment { /// Returns `bytes` as the Commitment variant for `network` and `height`. // // TODO: rename as from_bytes_in_serialized_order() pub(super) fn from_bytes( bytes: [u8; 32], network: Network, height: block::Height, ) -> Result { use Commitment::*; use CommitmentError::*; match NetworkUpgrade::current(network, height) { Genesis | BeforeOverwinter | Overwinter => Ok(PreSaplingReserved(bytes)), Sapling | Blossom => match sapling::tree::Root::try_from(bytes) { Ok(root) => Ok(FinalSaplingRoot(root)), _ => Err(InvalidSapingRootBytes), }, Heartwood if Some(height) == Heartwood.activation_height(network) => { if bytes == CHAIN_HISTORY_ACTIVATION_RESERVED { Ok(ChainHistoryActivationReserved) } else { Err(InvalidChainHistoryActivationReserved { actual: bytes }) } } Heartwood | Canopy => Ok(ChainHistoryRoot(ChainHistoryMmrRootHash(bytes))), Nu5 => Ok(ChainHistoryBlockTxAuthCommitment( ChainHistoryBlockTxAuthCommitmentHash(bytes), )), } } /// Returns the serialized bytes for this Commitment. // // TODO: refactor as bytes_in_serialized_order(&self) #[cfg(test)] pub(super) fn to_bytes(self) -> [u8; 32] { use Commitment::*; match self { PreSaplingReserved(bytes) => bytes, FinalSaplingRoot(hash) => hash.0.into(), ChainHistoryActivationReserved => CHAIN_HISTORY_ACTIVATION_RESERVED, ChainHistoryRoot(hash) => hash.0, ChainHistoryBlockTxAuthCommitment(hash) => hash.0, } } } /// The root hash of a Merkle Mountain Range chain history tree. // TODO: // - add methods for maintaining the MMR peaks, and calculating the root // hash from the current set of peaks // - move to a separate file #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct ChainHistoryMmrRootHash([u8; 32]); impl fmt::Display for ChainHistoryMmrRootHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.encode_hex::()) } } impl fmt::Debug for ChainHistoryMmrRootHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("ChainHistoryMmrRootHash") .field(&self.encode_hex::()) .finish() } } impl From<[u8; 32]> for ChainHistoryMmrRootHash { fn from(hash: [u8; 32]) -> Self { ChainHistoryMmrRootHash(hash) } } impl From for [u8; 32] { fn from(hash: ChainHistoryMmrRootHash) -> Self { hash.0 } } impl ChainHistoryMmrRootHash { /// Return the hash bytes in big-endian byte-order suitable for printing out byte by byte. /// /// Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. pub fn bytes_in_display_order(&self) -> [u8; 32] { let mut reversed_bytes = self.0; reversed_bytes.reverse(); reversed_bytes } /// Convert bytes in big-endian byte-order into a `ChainHistoryMmrRootHash`. /// /// Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. pub fn from_bytes_in_display_order( bytes_in_display_order: &[u8; 32], ) -> ChainHistoryMmrRootHash { let mut internal_byte_order = *bytes_in_display_order; internal_byte_order.reverse(); ChainHistoryMmrRootHash(internal_byte_order) } /// Returns the serialized bytes for this Commitment. pub fn bytes_in_serialized_order(&self) -> [u8; 32] { self.0 } } impl ToHex for &ChainHistoryMmrRootHash { fn encode_hex>(&self) -> T { self.bytes_in_display_order().encode_hex() } fn encode_hex_upper>(&self) -> T { self.bytes_in_display_order().encode_hex_upper() } } impl ToHex for ChainHistoryMmrRootHash { fn encode_hex>(&self) -> T { (&self).encode_hex() } fn encode_hex_upper>(&self) -> T { (&self).encode_hex_upper() } } impl FromHex for ChainHistoryMmrRootHash { type Error = <[u8; 32] as FromHex>::Error; fn from_hex>(hex: T) -> Result { let mut hash = <[u8; 32]>::from_hex(hex)?; hash.reverse(); Ok(hash.into()) } } /// A block commitment to chain history and transaction auth. /// - the chain history tree for all ancestors in the current network upgrade, /// and /// - the transaction authorising data in this block. /// /// Introduced in NU5. #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct ChainHistoryBlockTxAuthCommitmentHash([u8; 32]); impl fmt::Display for ChainHistoryBlockTxAuthCommitmentHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.encode_hex::()) } } impl fmt::Debug for ChainHistoryBlockTxAuthCommitmentHash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("ChainHistoryBlockTxAuthCommitmentHash") .field(&self.encode_hex::()) .finish() } } impl From<[u8; 32]> for ChainHistoryBlockTxAuthCommitmentHash { fn from(hash: [u8; 32]) -> Self { ChainHistoryBlockTxAuthCommitmentHash(hash) } } impl From for [u8; 32] { fn from(hash: ChainHistoryBlockTxAuthCommitmentHash) -> Self { hash.0 } } impl ChainHistoryBlockTxAuthCommitmentHash { /// Compute the block commitment from the history tree root and the /// authorization data root, as specified in [ZIP-244]. /// /// `history_tree_root` is the root of the history tree up to and including /// the *previous* block. /// `auth_data_root` is the root of the Merkle tree of authorizing data /// commmitments of each transaction in the *current* block. /// /// [ZIP-244]: https://zips.z.cash/zip-0244#block-header-changes pub fn from_commitments( history_tree_root: &ChainHistoryMmrRootHash, auth_data_root: &AuthDataRoot, ) -> Self { // > The value of this hash [hashBlockCommitments] is the BLAKE2b-256 hash personalized // > by the string "ZcashBlockCommit" of the following elements: // > hashLightClientRoot (as described in ZIP 221) // > hashAuthDataRoot (as described below) // > terminator [0u8;32] let hash_block_commitments: [u8; 32] = blake2b_simd::Params::new() .hash_length(32) .personal(b"ZcashBlockCommit") .to_state() .update(&<[u8; 32]>::from(*history_tree_root)[..]) .update(&<[u8; 32]>::from(*auth_data_root)) .update(&[0u8; 32]) .finalize() .as_bytes() .try_into() .expect("32 byte array"); Self(hash_block_commitments) } /// Return the hash bytes in big-endian byte-order suitable for printing out byte by byte. /// /// Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. pub fn bytes_in_display_order(&self) -> [u8; 32] { let mut reversed_bytes = self.0; reversed_bytes.reverse(); reversed_bytes } /// Convert bytes in big-endian byte-order into a `ChainHistoryBlockTxAuthCommitmentHash`. /// /// Zebra displays transaction and block hashes in big-endian byte-order, /// following the u256 convention set by Bitcoin and zcashd. pub fn from_bytes_in_display_order( bytes_in_display_order: &[u8; 32], ) -> ChainHistoryBlockTxAuthCommitmentHash { let mut internal_byte_order = *bytes_in_display_order; internal_byte_order.reverse(); ChainHistoryBlockTxAuthCommitmentHash(internal_byte_order) } /// Returns the serialized bytes for this Commitment. pub fn bytes_in_serialized_order(&self) -> [u8; 32] { self.0 } } impl ToHex for &ChainHistoryBlockTxAuthCommitmentHash { fn encode_hex>(&self) -> T { self.bytes_in_display_order().encode_hex() } fn encode_hex_upper>(&self) -> T { self.bytes_in_display_order().encode_hex_upper() } } impl ToHex for ChainHistoryBlockTxAuthCommitmentHash { fn encode_hex>(&self) -> T { (&self).encode_hex() } fn encode_hex_upper>(&self) -> T { (&self).encode_hex_upper() } } impl FromHex for ChainHistoryBlockTxAuthCommitmentHash { type Error = <[u8; 32] as FromHex>::Error; fn from_hex>(hex: T) -> Result { let mut hash = <[u8; 32]>::from_hex(hex)?; hash.reverse(); Ok(hash.into()) } } /// Errors that can occur when checking RootHash consensus rules. /// /// Each error variant corresponds to a consensus rule, so enumerating /// all possible verification failures enumerates the consensus rules we /// implement, and ensures that we don't reject blocks or transactions /// for a non-enumerated reason. #[allow(missing_docs)] #[derive(Error, Clone, Debug, PartialEq, Eq)] pub enum CommitmentError { #[error( "invalid final sapling root: expected {:?}, actual: {:?}", hex::encode(expected), hex::encode(actual) )] InvalidFinalSaplingRoot { // TODO: are these fields a security risk? If so, open a ticket to remove // similar fields across Zebra expected: [u8; 32], actual: [u8; 32], }, #[error("invalid chain history activation reserved block commitment: expected all zeroes, actual: {:?}", hex::encode(actual))] InvalidChainHistoryActivationReserved { actual: [u8; 32] }, #[error( "invalid chain history root: expected {:?}, actual: {:?}", hex::encode(expected), hex::encode(actual) )] InvalidChainHistoryRoot { expected: [u8; 32], actual: [u8; 32], }, #[error( "invalid block commitment root: expected {:?}, actual: {:?}", hex::encode(expected), hex::encode(actual) )] InvalidChainHistoryBlockTxAuthCommitment { expected: [u8; 32], actual: [u8; 32], }, #[error("missing required block height: block commitments can't be parsed without a block height, block hash: {block_hash:?}")] MissingBlockHeight { block_hash: block::Hash }, #[error("provided bytes are not a valid sapling root")] InvalidSapingRootBytes, }