iq_cometbft/
hash.rs

1//! Hash functions and their outputs
2
3use core::{
4    convert::TryFrom,
5    fmt::{self, Debug, Display},
6    str::FromStr,
7};
8
9use bytes::Bytes;
10use cometbft_proto::serializers::cow_str::CowStr;
11use cometbft_proto::Protobuf;
12use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
13use subtle_encoding::{base64, Encoding, Hex};
14
15use crate::{error::Error, prelude::*};
16
17/// Output size for the SHA-256 hash function
18pub const SHA256_HASH_SIZE: usize = 32;
19
20/// Hash algorithms
21#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
22pub enum Algorithm {
23    /// SHA-256
24    Sha256,
25}
26
27/// Hash digests
28#[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Default)]
29pub enum Hash {
30    /// SHA-256 hashes
31    Sha256([u8; SHA256_HASH_SIZE]),
32    /// Empty hash
33    #[default]
34    None,
35}
36
37impl Protobuf<Vec<u8>> for Hash {}
38
39/// Default conversion from `Vec<u8>` is SHA256 Hash or `None`
40impl TryFrom<Vec<u8>> for Hash {
41    type Error = Error;
42
43    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
44        if value.is_empty() {
45            return Ok(Hash::None);
46        }
47        Hash::from_bytes(Algorithm::Sha256, &value)
48    }
49}
50
51impl From<Hash> for Vec<u8> {
52    fn from(value: Hash) -> Self {
53        match value {
54            Hash::Sha256(s) => s.to_vec(),
55            Hash::None => vec![],
56        }
57    }
58}
59
60impl AsRef<[u8]> for Hash {
61    fn as_ref(&self) -> &[u8] {
62        match self {
63            Hash::Sha256(ref h) => h.as_ref(),
64            Hash::None => &[],
65        }
66    }
67}
68
69impl From<Hash> for Bytes {
70    fn from(h: Hash) -> Self {
71        Self::copy_from_slice(h.as_ref())
72    }
73}
74
75impl TryFrom<Bytes> for Hash {
76    type Error = Error;
77
78    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
79        Self::from_bytes(Algorithm::Sha256, value.as_ref())
80    }
81}
82
83impl Hash {
84    /// Create a new `Hash` with the given algorithm type
85    pub fn from_bytes(alg: Algorithm, bytes: &[u8]) -> Result<Hash, Error> {
86        if bytes.is_empty() {
87            return Ok(Hash::None);
88        }
89        match alg {
90            Algorithm::Sha256 => {
91                if bytes.len() == SHA256_HASH_SIZE {
92                    let mut h = [0u8; SHA256_HASH_SIZE];
93                    h.copy_from_slice(bytes);
94                    Ok(Hash::Sha256(h))
95                } else {
96                    Err(Error::invalid_hash_size())
97                }
98            },
99        }
100    }
101
102    /// Decode a `Hash` from upper-case hexadecimal
103    pub fn from_hex_upper(alg: Algorithm, s: &str) -> Result<Hash, Error> {
104        if s.is_empty() {
105            return Ok(Hash::None);
106        }
107        match alg {
108            Algorithm::Sha256 => {
109                let mut h = [0u8; SHA256_HASH_SIZE];
110                Hex::upper_case()
111                    .decode_to_slice(s.as_bytes(), &mut h)
112                    .map_err(Error::subtle_encoding)?;
113                Ok(Hash::Sha256(h))
114            },
115        }
116    }
117
118    /// Return the digest algorithm used to produce this hash
119    pub fn algorithm(self) -> Algorithm {
120        match self {
121            Hash::Sha256(_) => Algorithm::Sha256,
122            Hash::None => Algorithm::Sha256,
123        }
124    }
125
126    /// Borrow the `Hash` as a byte slice
127    pub fn as_bytes(&self) -> &[u8] {
128        match self {
129            Hash::Sha256(ref h) => h.as_ref(),
130            Hash::None => &[],
131        }
132    }
133
134    /// Convenience function to check for Hash::None
135    pub fn is_empty(&self) -> bool {
136        self == &Hash::None
137    }
138}
139
140impl Debug for Hash {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match self {
143            Hash::Sha256(_) => write!(f, "Hash::Sha256({self})"),
144            Hash::None => write!(f, "Hash::None"),
145        }
146    }
147}
148
149impl Display for Hash {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        let hex = match self {
152            Hash::Sha256(ref h) => Hex::upper_case().encode_to_string(h).unwrap(),
153            Hash::None => String::new(),
154        };
155
156        write!(f, "{hex}")
157    }
158}
159
160impl FromStr for Hash {
161    type Err = Error;
162
163    fn from_str(s: &str) -> Result<Self, Error> {
164        Self::from_hex_upper(Algorithm::Sha256, s)
165    }
166}
167
168impl<'de> Deserialize<'de> for Hash {
169    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
170        let hex = CowStr::deserialize(deserializer)?;
171
172        if hex.is_empty() {
173            Err(D::Error::custom("empty hash"))
174        } else {
175            Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{e}")))?)
176        }
177    }
178}
179
180impl Serialize for Hash {
181    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
182        self.to_string().serialize(serializer)
183    }
184}
185
186/// Serialization/deserialization for `Hash` that allows for empty hashes.
187pub mod allow_empty {
188    use super::*;
189
190    /// Serialize [`Hash`](enum@crate::hash::Hash) into a string.
191    pub fn serialize<S>(value: &Hash, serializer: S) -> Result<S::Ok, S::Error>
192    where
193        S: Serializer,
194    {
195        value.to_string().serialize(serializer)
196    }
197
198    /// Deserialize [`Hash`](enum@crate::hash::Hash) from a string, allowing for
199    /// empty hashes.
200    pub fn deserialize<'de, D>(deserializer: D) -> Result<Hash, D::Error>
201    where
202        D: Deserializer<'de>,
203    {
204        let hex = CowStr::deserialize(deserializer)?;
205        Hash::from_str(&hex).map_err(serde::de::Error::custom)
206    }
207}
208
209/// AppHash is usually a SHA256 hash, but in reality it can be any kind of data
210#[derive(Clone, PartialEq, Eq, Default)]
211pub struct AppHash(Vec<u8>);
212
213impl Protobuf<Vec<u8>> for AppHash {}
214
215impl TryFrom<Vec<u8>> for AppHash {
216    type Error = Error;
217
218    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
219        Ok(AppHash(value))
220    }
221}
222impl From<AppHash> for Vec<u8> {
223    fn from(value: AppHash) -> Self {
224        value.0
225    }
226}
227
228impl TryFrom<Bytes> for AppHash {
229    type Error = Error;
230
231    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
232        Ok(AppHash(value.to_vec()))
233    }
234}
235impl From<AppHash> for Bytes {
236    fn from(value: AppHash) -> Self {
237        value.0.into()
238    }
239}
240
241impl AppHash {
242    /// Return the hash bytes as a byte slice.
243    pub fn as_bytes(&self) -> &[u8] {
244        &self.0
245    }
246
247    /// Decode a `Hash` from upper-case hexadecimal
248    pub fn from_hex_upper(s: &str) -> Result<Self, Error> {
249        // TODO(tarcieri): use `is_multiple_of` when MSRV is 1.87
250        #[allow(clippy::manual_is_multiple_of)]
251        if s.len() % 2 != 0 {
252            return Err(Error::invalid_app_hash_length());
253        }
254        let mut h = vec![0; s.len() / 2];
255        Hex::upper_case()
256            .decode_to_slice(s.as_bytes(), &mut h)
257            .map_err(Error::subtle_encoding)?;
258        Ok(AppHash(h))
259    }
260
261    /// Decode a `Hash` from base64-encoded string
262    pub fn from_base64(s: &str) -> Result<Self, Error> {
263        let h = base64::decode(s).map_err(Error::subtle_encoding)?;
264        Ok(AppHash(h))
265    }
266}
267
268impl AsRef<[u8]> for AppHash {
269    fn as_ref(&self) -> &[u8] {
270        self.0.as_ref()
271    }
272}
273
274impl Debug for AppHash {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        write!(
277            f,
278            "AppHash({})",
279            Hex::upper_case().encode_to_string(&self.0).unwrap()
280        )
281    }
282}
283
284impl Display for AppHash {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        write!(
287            f,
288            "{}",
289            Hex::upper_case().encode_to_string(&self.0).unwrap()
290        )
291    }
292}
293
294impl FromStr for AppHash {
295    type Err = Error;
296
297    fn from_str(s: &str) -> Result<Self, Error> {
298        Self::from_hex_upper(s).or_else(|_| Self::from_base64(s))
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[derive(Debug, serde::Deserialize)]
307    struct AppHashTest {
308        #[serde(default)]
309        #[serde(with = "crate::serializers::apphash")]
310        pub app_hash: AppHash,
311    }
312
313    #[derive(Debug, serde::Deserialize)]
314    struct HashTest {
315        hash: Hash,
316        #[serde(with = "super::allow_empty")]
317        empty_hash: Hash,
318    }
319
320    #[test]
321    fn apphash_decode_base64() {
322        let test = serde_json::from_str::<AppHashTest>(
323            r#"{"app_hash":"MfX9f+bYoI8IioRb4YT/8/VhPvtNjgWFgTi4mmMSkBc="}"#,
324        )
325        .unwrap();
326
327        assert_eq!(
328            test.app_hash.as_ref(),
329            &[
330                0x31, 0xF5, 0xFD, 0x7F, 0xE6, 0xD8, 0xA0, 0x8F, 0x08, 0x8A, 0x84, 0x5B, 0xE1, 0x84,
331                0xFF, 0xF3, 0xF5, 0x61, 0x3E, 0xFB, 0x4D, 0x8E, 0x05, 0x85, 0x81, 0x38, 0xB8, 0x9A,
332                0x63, 0x12, 0x90, 0x17
333            ]
334        );
335    }
336
337    #[test]
338    fn apphash_decode_hex() {
339        let test = serde_json::from_str::<AppHashTest>(
340            r#"{"app_hash":"31F5FD7FE6D8A08F088A845BE184FFF3F5613EFB4D8E05858138B89A63129017"}"#,
341        )
342        .unwrap();
343
344        assert_eq!(
345            test.app_hash.as_ref(),
346            &[
347                0x31, 0xF5, 0xFD, 0x7F, 0xE6, 0xD8, 0xA0, 0x8F, 0x08, 0x8A, 0x84, 0x5B, 0xE1, 0x84,
348                0xFF, 0xF3, 0xF5, 0x61, 0x3E, 0xFB, 0x4D, 0x8E, 0x05, 0x85, 0x81, 0x38, 0xB8, 0x9A,
349                0x63, 0x12, 0x90, 0x17
350            ]
351        );
352    }
353
354    #[test]
355    fn hash_decode_hex() {
356        let s = r#"{
357            "hash": "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08",
358            "empty_hash": ""
359        }"#;
360
361        let expected_hash = &[
362            0x9F, 0x86, 0xD0, 0x81, 0x88, 0x4C, 0x7D, 0x65, 0x9A, 0x2F, 0xEA, 0xA0, 0xC5, 0x5A,
363            0xD0, 0x15, 0xA3, 0xBF, 0x4F, 0x1B, 0x2B, 0x0B, 0x82, 0x2C, 0xD1, 0x5D, 0x6C, 0x15,
364            0xB0, 0xF0, 0x0A, 0x08,
365        ];
366
367        let test = serde_json::from_str::<HashTest>(s).unwrap();
368        assert_eq!(test.hash.as_ref(), expected_hash);
369        assert_eq!(test.empty_hash, Hash::None);
370
371        // Test issue 1474
372        let json_value = serde_json::from_str::<serde_json::Value>(s).unwrap();
373        let test = serde_json::from_value::<HashTest>(json_value).unwrap();
374        assert_eq!(test.hash.as_ref(), expected_hash);
375        assert_eq!(test.empty_hash, Hash::None);
376    }
377}