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}