warg_crypto/hash/
mod.rs

1use anyhow::Error;
2use once_cell::sync::Lazy;
3use serde::{Deserialize, Serialize};
4use std::{fmt, str::FromStr};
5use thiserror::Error;
6
7mod dynamic;
8mod r#static;
9
10pub use digest::{Digest, Output};
11pub use dynamic::{AnyHash, AnyHashError};
12pub use r#static::Hash;
13pub use sha2::Sha256;
14
15use crate::VisitBytes;
16
17use self::r#static::IncorrectLengthError;
18
19#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21#[non_exhaustive]
22pub enum HashAlgorithm {
23    Sha256,
24}
25
26impl fmt::Display for HashAlgorithm {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            HashAlgorithm::Sha256 => write!(f, "sha256"),
30        }
31    }
32}
33
34impl FromStr for HashAlgorithm {
35    type Err = Error;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        match s {
39            "sha256" => Ok(HashAlgorithm::Sha256),
40            _ => Err(Error::msg(format!("Illegal hash algorithm '{}'", s))),
41        }
42    }
43}
44
45static EMPTY_TREE_HASH: Lazy<Vec<Hash<Sha256>>> = Lazy::new(|| {
46    let mut v: Vec<Hash<Sha256>> = Vec::with_capacity(257);
47    fn empty_tree_hash<D: SupportedDigest>(v: &mut Vec<Hash<D>>, height: u32) -> Hash<D> {
48        let hash: Hash<D> = if height == 0 {
49            hash_empty()
50        } else {
51            let last_hash = empty_tree_hash(v, height - 1);
52            hash_branch(&last_hash, &last_hash)
53        };
54        v.push(hash.clone());
55        hash
56    }
57    empty_tree_hash(&mut v, 256);
58    v
59});
60
61// If updating this function, also update `hash_empty` in transparency map
62pub(crate) fn hash_empty<D: SupportedDigest>() -> Hash<D> {
63    hash_leaf(())
64}
65
66// If updating this function, also update `hash_leaf` in transparency map
67pub(crate) fn hash_leaf<D, V>(value: V) -> Hash<D>
68where
69    D: SupportedDigest,
70    V: VisitBytes,
71{
72    Hash::of(&(0b0, value))
73}
74
75// If updating this function, also update `hash_branch` in transparency map
76pub(crate) fn hash_branch<D>(lhs: &Hash<D>, rhs: &Hash<D>) -> Hash<D>
77where
78    D: SupportedDigest,
79{
80    Hash::of((0b1, lhs, rhs))
81}
82
83pub trait SupportedDigest: Digest + private::Sealed + Default + Sized + 'static {
84    const ALGORITHM: HashAlgorithm;
85    fn empty_tree_hash(height: usize) -> &'static Hash<Self>;
86}
87
88impl SupportedDigest for Sha256 {
89    const ALGORITHM: HashAlgorithm = HashAlgorithm::Sha256;
90    fn empty_tree_hash(height: usize) -> &'static Hash<Sha256> {
91        &EMPTY_TREE_HASH[height]
92    }
93}
94
95mod private {
96    use sha2::Sha256;
97
98    pub trait Sealed {}
99    impl Sealed for Sha256 {}
100}
101
102impl<D: SupportedDigest> From<Hash<D>> for AnyHash {
103    fn from(value: Hash<D>) -> Self {
104        (&value).into()
105    }
106}
107
108impl<D: SupportedDigest> From<&Hash<D>> for AnyHash {
109    fn from(value: &Hash<D>) -> Self {
110        AnyHash {
111            algo: D::ALGORITHM,
112            bytes: value.digest.to_vec(),
113        }
114    }
115}
116
117#[derive(Error, Debug)]
118pub enum HashError {
119    #[error("mismatched hash algorithm: expected {expected}, got {actual}")]
120    MismatchedAlgorithms {
121        expected: HashAlgorithm,
122        actual: HashAlgorithm,
123    },
124
125    #[error("expected {expected} bytes for hash algorithm {algo}, got {actual}")]
126    IncorrectLength {
127        expected: usize,
128        algo: HashAlgorithm,
129        actual: usize,
130    },
131}
132
133impl<D: SupportedDigest> TryFrom<AnyHash> for Hash<D> {
134    type Error = HashError;
135
136    fn try_from(value: AnyHash) -> Result<Self, Self::Error> {
137        if value.algorithm() == D::ALGORITHM {
138            let len = value.bytes.len();
139            match Hash::try_from(value.bytes) {
140                Ok(hash) => Ok(hash),
141                Err(IncorrectLengthError) => Err(HashError::IncorrectLength {
142                    expected: <D as Digest>::output_size(),
143                    algo: D::ALGORITHM,
144                    actual: len,
145                }),
146            }
147        } else {
148            Err(HashError::MismatchedAlgorithms {
149                expected: D::ALGORITHM,
150                actual: value.algorithm(),
151            })
152        }
153    }
154}