1use 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#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
26pub enum Commitment {
27 PreSaplingReserved([u8; 32]),
31
32 FinalSaplingRoot(sapling::tree::Root),
46
47 ChainHistoryActivationReserved,
56
57 ChainHistoryRoot(ChainHistoryMmrRootHash),
81
82 ChainHistoryBlockTxAuthCommitment(ChainHistoryBlockTxAuthCommitmentHash),
99}
100
101pub const CHAIN_HISTORY_ACTIVATION_RESERVED: [u8; 32] = [0; 32];
103
104impl Commitment {
105 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 (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 #[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#[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#[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 pub fn from_commitments(
301 history_tree_root: &ChainHistoryMmrRootHash,
302 auth_data_root: &AuthDataRoot,
303 ) -> Self {
304 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#[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 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}