Skip to main content

nix_compat/nixhash/
sha256.rs

1use std::fmt;
2use std::io::Write;
3
4use data_encoding::{HEXLOWER, HEXUPPER};
5use sha2::Digest;
6
7use crate::nixhash::HashAlgo;
8
9use super::NixHash;
10
11type Sha256Array = [u8; Sha256::digest_length()];
12
13/// A SHA256 hash.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Sha256(Sha256Array);
16impl Sha256 {
17    pub const fn digest_length() -> usize {
18        HashAlgo::Sha256.digest_length()
19    }
20
21    pub const fn new(digest: Sha256Array) -> Self {
22        Self(digest)
23    }
24
25    /// Shorthand for digesting a byte slice.
26    ///
27    /// # Examples
28    /// ```
29    /// use nix_compat::nixhash::Sha256;
30    ///
31    /// let sha256 = Sha256::digest_bytes("abc");
32    /// assert_eq!(format!("{sha256:x}"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
33    /// ```
34    pub fn digest_bytes<C: AsRef<[u8]>>(content: C) -> Self {
35        let mut w = Sha256Digester::new();
36        w.update(content.as_ref());
37        w.finalize()
38    }
39
40    /// Hashes formatted string data with SHA-256, without an intermediate buffer.
41    ///
42    /// Analogous to [`std::fmt::format`].
43    ///
44    /// # Examples
45    /// ```
46    /// use nix_compat::nixhash::Sha256;
47    ///
48    /// let sha256 = Sha256::digest_display(format_args!("{}bc", "a"));
49    /// assert_eq!(format!("{sha256:x}"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
50    /// ```
51    pub fn digest_display<D: fmt::Display>(value: D) -> Self {
52        let mut w = Sha256Digester::new();
53        write!(&mut w, "{value}").unwrap();
54        w.finalize()
55    }
56
57    pub const fn as_bytes(&self) -> &[u8] {
58        &self.0
59    }
60
61    pub const fn into_bytes(self) -> Sha256Array {
62        self.0
63    }
64}
65
66impl fmt::LowerHex for Sha256 {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "{}", HEXLOWER.encode_display(self.as_bytes()))
69    }
70}
71
72impl fmt::UpperHex for Sha256 {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", HEXUPPER.encode_display(self.as_bytes()))
75    }
76}
77
78impl AsRef<[u8]> for Sha256 {
79    fn as_ref(&self) -> &[u8] {
80        self.as_bytes()
81    }
82}
83
84impl std::borrow::Borrow<[u8]> for Sha256 {
85    fn borrow(&self) -> &[u8] {
86        self.as_bytes()
87    }
88}
89
90impl std::ops::Deref for Sha256 {
91    type Target = [u8];
92
93    fn deref(&self) -> &Self::Target {
94        self.as_bytes()
95    }
96}
97
98impl From<Sha256Array> for Sha256 {
99    fn from(digest: Sha256Array) -> Self {
100        Sha256::new(digest)
101    }
102}
103
104impl From<Sha256> for Sha256Array {
105    fn from(value: Sha256) -> Self {
106        value.into_bytes()
107    }
108}
109
110impl From<Sha256> for NixHash {
111    fn from(value: Sha256) -> Self {
112        NixHash::Sha256(value.into_bytes())
113    }
114}
115
116impl PartialEq<Sha256Array> for Sha256 {
117    fn eq(&self, other: &Sha256Array) -> bool {
118        &self.0 == other
119    }
120}
121
122impl PartialEq<Sha256> for Sha256Array {
123    fn eq(&self, other: &Sha256) -> bool {
124        self == &other.0
125    }
126}
127
128impl PartialEq<[u8]> for Sha256 {
129    fn eq(&self, other: &[u8]) -> bool {
130        self.0 == other
131    }
132}
133
134impl PartialEq<Sha256> for [u8] {
135    fn eq(&self, other: &Sha256) -> bool {
136        self == other.0
137    }
138}
139
140impl PartialEq<NixHash> for Sha256 {
141    fn eq(&self, other: &NixHash) -> bool {
142        matches!(other, NixHash::Sha256(sha256) if self == sha256)
143    }
144}
145
146impl PartialEq<Sha256> for NixHash {
147    fn eq(&self, other: &Sha256) -> bool {
148        matches!(self, NixHash::Sha256(sha256) if other == sha256)
149    }
150}
151
152/// A digester that takes in bytes and ultimately produces a [`Sha256`].
153///
154/// # Examples
155/// ```
156/// use nix_compat::nixhash::{Sha256, Sha256Digester};
157///
158/// let one_shot = Sha256::digest_bytes("hello, world");
159///
160/// let mut ctx = Sha256Digester::new();
161/// ctx.update("hello");
162/// ctx.update(", ");
163/// ctx.update("world");
164/// let multi_path = ctx.finalize();
165///
166/// assert_eq!(one_shot, multi_path);
167/// ```
168pub struct Sha256Digester(sha2::Sha256);
169impl Sha256Digester {
170    /// Returns a new digester
171    pub fn new() -> Self {
172        Self(sha2::Sha256::new())
173    }
174
175    /// Updates the digester with the provided bytes
176    pub fn update<C: AsRef<[u8]>>(&mut self, data: C) {
177        self.0.update(data);
178    }
179
180    /// Finalize the digest and return the produced [`Sha256`].
181    pub fn finalize(self) -> Sha256 {
182        let digest: [u8; HashAlgo::Sha256.digest_length()] = self.0.finalize().into();
183        Sha256::new(digest)
184    }
185}
186
187impl Default for Sha256Digester {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193impl Write for Sha256Digester {
194    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
195        self.update(buf);
196        Ok(buf.len())
197    }
198
199    fn flush(&mut self) -> std::io::Result<()> {
200        Ok(())
201    }
202}
203
204/// Analogous to [`std::format`], but returning only the SHA-256 digest of the formatted string.
205///
206/// # Examples
207/// ```
208/// use nix_compat::format_sha256;
209/// let sha256 = format_sha256!("{}bc", "a");
210/// assert_eq!(format!("{sha256:x}"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
211/// ```
212#[macro_export]
213macro_rules! format_sha256 {
214    ($($args:tt)*) => {
215        ::nix_compat::nixhash::Sha256::digest_display(format_args!($($args)*))
216    };
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use hex_literal::hex;
223
224    struct Sha256Formats {
225        input: &'static str,
226        hash: Sha256,
227        base16: &'static str,
228    }
229
230    /// value taken from: https://tools.ietf.org/html/rfc4634
231    const SHA256_ABC: Sha256Formats = Sha256Formats {
232        input: "abc",
233        hash: Sha256::new(hex!(
234            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
235        )),
236        base16: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
237    };
238
239    /// value taken from: https://tools.ietf.org/html/rfc4634
240    const SHA256_LONG: Sha256Formats = Sha256Formats {
241        input: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
242        hash: Sha256::new(hex!(
243            "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
244        )),
245        base16: "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
246    };
247
248    #[rstest_reuse::template]
249    #[rstest::rstest]
250    #[case::abc(SHA256_ABC)]
251    #[case::long(SHA256_LONG)]
252    fn hash_formats(#[case] hash: Sha256Formats) {}
253
254    /// Test `fmt::LowerHex` implementation
255    #[rstest_reuse::apply(hash_formats)]
256    fn lower_hex(#[case] hash: Sha256Formats) {
257        let actual = format!("{:x}", hash.hash);
258        assert_eq!(hash.base16, actual);
259    }
260
261    /// Test `fmt::UpperHex` implementation
262    #[rstest_reuse::apply(hash_formats)]
263    fn upper_hex(#[case] hash: Sha256Formats) {
264        let expected = hash.base16.to_uppercase();
265        let actual = format!("{:X}", hash.hash);
266        assert_eq!(expected, actual);
267    }
268
269    /// Test `Sha256::digest_bytes` implementation
270    #[rstest_reuse::apply(hash_formats)]
271    fn digest_bytes(#[case] hash: Sha256Formats) {
272        let actual = Sha256::digest_bytes(hash.input);
273        assert_eq!(hash.hash, actual);
274    }
275
276    /// Test `Sha256::digest_display` implementation
277    #[rstest_reuse::apply(hash_formats)]
278    fn digest_display(#[case] hash: Sha256Formats) {
279        let actual = Sha256::digest_display(format_args!("{}", hash.input));
280        assert_eq!(hash.hash, actual);
281    }
282
283    /// Test `format_sha256` macro implementation
284    #[rstest_reuse::apply(hash_formats)]
285    fn format_sha256_test(#[case] hash: Sha256Formats) {
286        let actual = format_sha256!("{}", hash.input);
287        assert_eq!(hash.hash, actual);
288    }
289
290    /// Test `Sha256Digester` implementation
291    #[rstest_reuse::apply(hash_formats)]
292    fn digester(#[case] hash: Sha256Formats) {
293        let mut ctx = Sha256Digester::new();
294        ctx.update(hash.input);
295        let actual = ctx.finalize();
296        assert_eq!(hash.hash, actual);
297    }
298
299    /// Test `Sha256Digester` `std::io::Write` implementation
300    #[rstest_reuse::apply(hash_formats)]
301    fn digester_write(#[case] hash: Sha256Formats) {
302        let mut ctx = Sha256Digester::new();
303        ctx.write_all(hash.input.as_bytes()).unwrap();
304        let actual = ctx.finalize();
305        assert_eq!(hash.hash, actual);
306    }
307}