1use binius_core::word::Word;
8use binius_frontend::{CircuitBuilder, Wire};
9
10use super::sha512::sha512_fixed;
11
12pub fn hmac_sha512_fixed(
30 builder: &mut CircuitBuilder,
31 key: &[Wire],
32 message: &[Wire],
33 message_len_bytes: usize,
34) -> [Wire; 8] {
35 const BLOCK_SIZE_WORDS: usize = 16; const IPAD_BYTE: u8 = 0x36;
37 const OPAD_BYTE: u8 = 0x5C;
38
39 assert!(
41 key.len() <= BLOCK_SIZE_WORDS,
42 "Key length {} words exceeds maximum {} words (128 bytes)",
43 key.len(),
44 BLOCK_SIZE_WORDS
45 );
46
47 assert_eq!(
49 message.len(),
50 message_len_bytes.div_ceil(8),
51 "Message wire count {} doesn't match message length {} bytes",
52 message.len(),
53 message_len_bytes
54 );
55
56 let mut padded_key = Vec::with_capacity(BLOCK_SIZE_WORDS);
58 padded_key.extend_from_slice(key);
59 let zero = builder.add_constant(Word::ZERO);
60 padded_key.resize(BLOCK_SIZE_WORDS, zero);
61
62 let ipad_word = builder.add_constant(Word(u64::from_le_bytes([IPAD_BYTE; 8])));
64 let opad_word = builder.add_constant(Word(u64::from_le_bytes([OPAD_BYTE; 8])));
65
66 let key_xor_ipad = padded_key
68 .iter()
69 .map(|&key_word| builder.bxor(key_word, ipad_word))
70 .collect::<Vec<_>>();
71
72 let key_xor_opad = padded_key
74 .iter()
75 .map(|&key_word| builder.bxor(key_word, opad_word))
76 .collect::<Vec<_>>();
77
78 let mut inner_input = Vec::with_capacity(BLOCK_SIZE_WORDS + message.len());
80 inner_input.extend_from_slice(&key_xor_ipad);
81 inner_input.extend_from_slice(message);
82 let inner_len_bytes = 128 + message_len_bytes; let inner_hash = sha512_fixed(builder, &inner_input, inner_len_bytes);
84
85 let mut outer_input = Vec::with_capacity(BLOCK_SIZE_WORDS + 8);
87 outer_input.extend_from_slice(&key_xor_opad);
88 outer_input.extend_from_slice(&inner_hash);
89 let outer_len_bytes = 128 + 64; sha512_fixed(builder, &outer_input, outer_len_bytes)
91}
92
93#[cfg(test)]
94mod tests {
95 use std::{array, iter::repeat_with};
96
97 use binius_core::word::Word;
98 use hmac::{Hmac, KeyInit, Mac};
99 use rand::prelude::*;
100 use sha2::Sha512;
101
102 use super::*;
103
104 type HmacSha512 = Hmac<Sha512>;
105
106 #[test]
107 fn test_hmac_sha512_random_messages() {
108 let mut rng = StdRng::seed_from_u64(0);
109
110 for _ in 0..10 {
111 let key_len = rng.random_range(1..=16) * 8; let mut key_bytes = vec![0u8; key_len];
114 rng.fill(&mut key_bytes[..]);
115
116 let message_len = rng.random_range(0..=256);
118 let mut message_bytes = vec![0u8; message_len];
119 rng.fill(&mut message_bytes[..]);
120
121 let mut mac = HmacSha512::new_from_slice(&key_bytes).unwrap();
123 mac.update(&message_bytes);
124 let expected = mac.finalize().into_bytes();
125
126 let mut builder = CircuitBuilder::new();
128
129 let mut key_wires = Vec::new();
131 for _ in 0..key_bytes.len().div_ceil(8) {
132 key_wires.push(builder.add_witness());
133 }
134
135 let message_wires = repeat_with(|| builder.add_witness())
137 .take(message_len.div_ceil(8))
138 .collect::<Vec<_>>();
139
140 let output =
142 hmac_sha512_fixed(&mut builder, &key_wires, &message_wires, message_bytes.len());
143
144 let expected_wires = array::from_fn::<_, 8, _>(|_| builder.add_witness());
146
147 for (i, (&computed, &expected)) in output.iter().zip(expected_wires.iter()).enumerate()
149 {
150 builder.assert_eq(format!("hmac_output_{i}"), computed, expected);
151 }
152
153 let circuit = builder.build();
154 let mut witness = circuit.new_witness_filler();
155
156 for (i, chunk) in key_bytes.chunks(8).enumerate() {
158 let mut word_bytes = [0u8; 8];
159 word_bytes[..chunk.len()].copy_from_slice(chunk);
160 let word = u64::from_be_bytes(word_bytes);
161 witness[key_wires[i]] = Word(word);
162 }
163
164 for (i, chunk) in message_bytes.chunks(8).enumerate() {
166 let mut word_bytes = [0u8; 8];
167 word_bytes[..chunk.len()].copy_from_slice(chunk);
168 let word = u64::from_be_bytes(word_bytes);
169 witness[message_wires[i]] = Word(word);
170 }
171
172 for (i, chunk) in expected.chunks(8).enumerate() {
174 let word = u64::from_be_bytes(chunk.try_into().unwrap());
175 witness[expected_wires[i]] = Word(word);
176 }
177
178 circuit
179 .populate_wire_witness(&mut witness)
180 .expect("Circuit should populate witnesses successfully");
181
182 let cs = circuit.constraint_system();
184 cs.verify(&witness.into_value_vec())
185 .expect("Circuit constraints should be satisfied with random data");
186 }
187 }
188
189 #[test]
190 #[should_panic(expected = "Key length 17 words exceeds maximum 16 words")]
191 fn test_hmac_key_too_large() {
192 let mut builder = CircuitBuilder::new();
193
194 let mut key_wires = Vec::new();
196 for _ in 0..17 {
197 key_wires.push(builder.add_constant(Word::ZERO));
198 }
199
200 let message = vec![builder.add_constant(Word::ZERO)];
201
202 hmac_sha512_fixed(&mut builder, &key_wires, &message, 8);
204 }
205
206 #[test]
207 #[should_panic(expected = "Message wire count 2 doesn't match message length 7 bytes")]
208 fn test_hmac_message_wire_mismatch() {
209 let mut builder = CircuitBuilder::new();
210
211 let key = vec![builder.add_constant(Word::ZERO)];
212 let message = vec![
213 builder.add_constant(Word::ZERO),
214 builder.add_constant(Word::ZERO),
215 ];
216
217 hmac_sha512_fixed(&mut builder, &key, &message, 7);
219 }
220}