Skip to main content

bh_sd_jwt/traits/
hasher.rs

1// Copyright (C) 2020-2026  The Blockhouse Technology Limited (TBTL).
2//
3// This program is free software: you can redistribute it and/or modify it
4// under the terms of the GNU Affero General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or (at your
6// option) any later version.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10// or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
11// License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16use std::str::FromStr;
17
18use bherror::Error;
19use serde::{Deserialize, Serialize};
20
21use crate::DecodingError;
22
23/// The hash algorithm identifier for `SHA-256` as specified in the
24/// "*Hash Name String*" column of the *IANA* [Named Information Hash Algorithm
25/// Registry].
26///
27/// [Named Information Hash Algorithm Registry]: https://www.iana.org/assignments/named-information/named-information.xhtml
28pub(crate) const SHA_256_ALG_NAME: &str = "sha-256";
29
30/// An identifier of the algorithm used for hashing. All the algorithm variants
31/// are deemed secure for the `SD-JWT` purposes.
32///
33/// The string value of the algorithm is used in the `_sd_alg` field of the
34/// `SD-JWT`, formatted as specified in the *IANA* [Named Information Hash
35/// Algorithm Registry].
36///
37/// The default algorithm is `SHA-256`, as specified [here].
38///
39/// The [`HashingAlgorithm`] can be parsed from string, expecting the same
40/// format as specified above.
41///
42/// [Named Information Hash Algorithm Registry]: https://www.iana.org/assignments/named-information/named-information.xhtml
43/// [here]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-07#name-hash-function-claim
44#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45pub enum HashingAlgorithm {
46    /// SHA-256 algorithm for hashing.
47    #[serde(rename = "sha-256")]
48    #[default]
49    Sha256,
50}
51
52impl HashingAlgorithm {
53    /// Returns the string value of the algorithm, formatted as specified in the
54    /// *IANA* [Named Information Hash Algorithm Registry].
55    ///
56    /// [Named Information Hash Algorithm Registry]: https://www.iana.org/assignments/named-information/named-information.xhtml
57    pub fn as_str(&self) -> &'static str {
58        match self {
59            HashingAlgorithm::Sha256 => SHA_256_ALG_NAME,
60        }
61    }
62}
63
64impl FromStr for HashingAlgorithm {
65    type Err = bherror::Error<DecodingError>;
66
67    fn from_str(value: &str) -> Result<Self, Self::Err> {
68        match value {
69            SHA_256_ALG_NAME => Ok(Self::Sha256),
70            _ => Err(Error::root(DecodingError::InvalidHashAlgorithmName(
71                value.to_owned(),
72            ))),
73        }
74    }
75}
76
77impl std::fmt::Display for HashingAlgorithm {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.write_str(self.as_str())
80    }
81}
82
83/// The trait used for calculating hash digest.
84///
85/// The algorithm used for calculating the digest needs to be the one returned
86/// from the [`Hasher::algorithm`] method.
87///
88/// The trait is automatically implemented for `&dyn Hasher`, `Box<dyn Hasher>`,
89/// `&H`, and `Box<H>`, where `H` implements `Hasher`.
90pub trait Hasher: Send + Sync {
91    /// Returns the algorithm used for calculating the hash digest within the
92    /// [`Hasher::digest`] method.
93    fn algorithm(&self) -> HashingAlgorithm;
94
95    /// Computes the hash digest of the given `input` using the algorithm as
96    /// returned from the [`Hasher::algorithm`] method.
97    fn digest(&self, input: &[u8]) -> Vec<u8>;
98}
99
100impl<H: Hasher> Hasher for &H {
101    fn algorithm(&self) -> HashingAlgorithm {
102        (*self).algorithm()
103    }
104
105    fn digest(&self, input: &[u8]) -> Vec<u8> {
106        (*self).digest(input)
107    }
108}
109
110impl<H: Hasher> Hasher for Box<H> {
111    fn algorithm(&self) -> HashingAlgorithm {
112        self.as_ref().algorithm()
113    }
114
115    fn digest(&self, input: &[u8]) -> Vec<u8> {
116        self.as_ref().digest(input)
117    }
118}
119
120impl Hasher for &dyn Hasher {
121    fn algorithm(&self) -> HashingAlgorithm {
122        (*self).algorithm()
123    }
124
125    fn digest(&self, input: &[u8]) -> Vec<u8> {
126        (*self).digest(input)
127    }
128}
129
130impl Hasher for Box<dyn Hasher> {
131    fn algorithm(&self) -> HashingAlgorithm {
132        self.as_ref().algorithm()
133    }
134
135    fn digest(&self, input: &[u8]) -> Vec<u8> {
136        self.as_ref().digest(input)
137    }
138}
139
140#[cfg(test)]
141pub(crate) mod tests {
142    use super::*;
143
144    #[test]
145    fn hashing_algorithm_sha256_serializes_correctly() {
146        let alg = HashingAlgorithm::Sha256;
147        let expected = format!("\"{}\"", SHA_256_ALG_NAME);
148
149        let serialized = serde_json::to_string(&alg).unwrap();
150        assert_eq!(serialized, expected);
151
152        let deserialized: HashingAlgorithm = serde_json::from_str(&expected).unwrap();
153        assert_eq!(deserialized, alg);
154    }
155}