openvm_sha2_circuit/sha2_chips/
config.rs

1use openvm_sha2_air::{Sha256Config, Sha384Config, Sha512Config};
2use sha2::{
3    compress256, compress512, digest::generic_array::GenericArray, Digest, Sha256, Sha384, Sha512,
4};
5
6use crate::{Sha2BlockHasherVmConfig, Sha2MainChipConfig};
7
8pub const SHA2_REGISTER_READS: usize = 3;
9pub const SHA2_READ_SIZE: usize = 4;
10pub const SHA2_WRITE_SIZE: usize = 4;
11
12pub trait Sha2Config: Sha2MainChipConfig + Sha2BlockHasherVmConfig {
13    /// Number of bits used to store the message length (part of the message padding)
14    const MESSAGE_LENGTH_BITS: usize;
15
16    /// Number of bytes in the digest
17    const DIGEST_BYTES: usize;
18
19    // Preconditions:
20    // - state.len() >= Self::STATE_BYTES
21    // - input.len() == Self::BLOCK_BYTES
22    fn compress(state: &mut [u8], input: &[u8]);
23
24    // returns the digest as big-endian words
25    fn hash(message: &[u8]) -> Vec<u8>;
26}
27
28impl Sha2Config for Sha256Config {
29    const MESSAGE_LENGTH_BITS: usize = 64;
30
31    // the digest is the whole state
32    const DIGEST_BYTES: usize = Sha256Config::STATE_BYTES;
33
34    fn compress(state: &mut [u8], input: &[u8]) {
35        debug_assert!(state.len() >= Sha256Config::STATE_BYTES);
36        debug_assert!(input.len() == Sha256Config::BLOCK_BYTES);
37
38        // SAFETY:
39        //   This is safe because state points to a [u32; 8].
40        //   The only reason we have a &[u8] instead is that we read it from a record, where
41        //   we store the state as bytes since we don't know the word size at compile time (u32 for
42        //   Sha256, u64 for Sha512)
43        let state_u32s: &mut [u32; 8] = unsafe { &mut *(state.as_mut_ptr() as *mut [u32; 8]) };
44
45        let input_array = GenericArray::from_slice(input);
46
47        compress256(state_u32s, &[*input_array]);
48    }
49
50    // returns the digest as big-endian words
51    fn hash(message: &[u8]) -> Vec<u8> {
52        Sha256::digest(message).to_vec()
53    }
54}
55
56impl Sha2Config for Sha512Config {
57    const MESSAGE_LENGTH_BITS: usize = 128;
58
59    // the digest is the whole state
60    const DIGEST_BYTES: usize = Sha512Config::STATE_BYTES;
61
62    fn compress(state: &mut [u8], input: &[u8]) {
63        debug_assert!(state.len() >= Sha512Config::STATE_BYTES);
64        debug_assert!(input.len() == Sha512Config::BLOCK_BYTES);
65
66        // `state` may only be 4-byte aligned, so copy through an aligned `u64` buffer.
67        // We store the state as bytes in the record because the word size is
68        // not known at compile time (u32 for Sha256, u64 for Sha512).
69        let mut state_u64s = [0u64; 8];
70        for (w, chunk) in state_u64s.iter_mut().zip(state.chunks_exact(8)) {
71            *w = u64::from_ne_bytes(chunk.try_into().unwrap());
72        }
73
74        let input_array = GenericArray::from_slice(input);
75
76        compress512(&mut state_u64s, &[*input_array]);
77
78        for (w, chunk) in state_u64s.iter().zip(state.chunks_exact_mut(8)) {
79            chunk.copy_from_slice(&w.to_ne_bytes());
80        }
81    }
82
83    // returns the digest as big-endian words
84    fn hash(message: &[u8]) -> Vec<u8> {
85        Sha512::digest(message).to_vec()
86    }
87}
88
89impl Sha2Config for Sha384Config {
90    const MESSAGE_LENGTH_BITS: usize = Sha512Config::MESSAGE_LENGTH_BITS;
91
92    // SHA-384 truncates the output to 48 bytes
93    const DIGEST_BYTES: usize = 48;
94
95    fn compress(state: &mut [u8], input: &[u8]) {
96        Sha512Config::compress(state, input);
97    }
98
99    fn hash(message: &[u8]) -> Vec<u8> {
100        Sha384::digest(message).to_vec()
101    }
102}