Skip to main content

vector_core/crypto/
sha256.rs

1//! SHA-256 for bulk data: attachments, files and blobs.
2//!
3//! aarch64 hashes through `bitcoin_hashes`, which runtime-detects the ARMv8 SHA-256
4//! instructions; `sha2` 0.10 only uses them behind its `asm` feature. Elsewhere `sha2`
5//! stays: it runtime-detects SHA-NI too, and without SHA-NI its software path measured
6//! 9% faster than `bitcoin_hashes`' on a Cascade Lake Xeon.
7
8#[cfg(target_arch = "aarch64")]
9use bitcoin_hashes::HashEngine as _;
10#[cfg(not(target_arch = "aarch64"))]
11use sha2::Digest as _;
12
13/// An incremental SHA-256.
14#[derive(Clone, Default)]
15pub struct Sha256 {
16    #[cfg(target_arch = "aarch64")]
17    engine: bitcoin_hashes::sha256::HashEngine,
18    #[cfg(not(target_arch = "aarch64"))]
19    engine: sha2::Sha256,
20}
21
22impl Sha256 {
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    pub fn update(&mut self, data: impl AsRef<[u8]>) {
28        #[cfg(target_arch = "aarch64")]
29        self.engine.input(data.as_ref());
30        #[cfg(not(target_arch = "aarch64"))]
31        self.engine.update(data.as_ref());
32    }
33
34    pub fn finalize(self) -> [u8; 32] {
35        #[cfg(target_arch = "aarch64")]
36        {
37            self.engine.finalize().to_byte_array()
38        }
39        #[cfg(not(target_arch = "aarch64"))]
40        {
41            self.engine.finalize().into()
42        }
43    }
44}
45
46/// SHA-256 of `data` in one call.
47pub fn digest(data: &[u8]) -> [u8; 32] {
48    let mut h = Sha256::new();
49    h.update(data);
50    h.finalize()
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use sha2::Digest;
57
58    #[test]
59    fn matches_sha2_across_block_and_update_boundaries() {
60        assert_eq!(
61            super::super::hex::encode(&digest(b"abc")),
62            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
63        );
64        let data: Vec<u8> = (0..5000u32).map(|i| (i * 31 + 7) as u8).collect();
65        for len in [0usize, 1, 55, 56, 63, 64, 65, 127, 128, 1000, 5000] {
66            let want: [u8; 32] = sha2::Sha256::digest(&data[..len]).into();
67            assert_eq!(digest(&data[..len]), want, "len {len}");
68            for split in [0, 1, 63, 64, len / 2, len] {
69                let split = split.min(len);
70                let mut h = Sha256::new();
71                h.update(&data[..split]);
72                h.update(&data[split..len]);
73                assert_eq!(h.finalize(), want, "len {len} split {split}");
74            }
75        }
76    }
77}