binius_circuits/keccak/
fixed_length.rs1use binius_core::word::Word;
4use binius_frontend::{CircuitBuilder, Wire};
5
6use super::{
7 N_WORDS_PER_BLOCK, N_WORDS_PER_DIGEST, N_WORDS_PER_STATE, RATE_BYTES, permutation::keccak_f1600,
8};
9
10pub fn keccak256(
41 builder: &CircuitBuilder,
42 message: &[Wire],
43 len_bytes: usize,
44) -> [Wire; N_WORDS_PER_DIGEST] {
45 assert_eq!(
47 message.len(),
48 len_bytes.div_ceil(8),
49 "message.len() ({}) must equal len_bytes.div_ceil(8) ({})",
50 message.len(),
51 len_bytes.div_ceil(8)
52 );
53
54 let n_blocks = (len_bytes + 1).div_ceil(RATE_BYTES);
56 let n_padded_words = n_blocks * N_WORDS_PER_BLOCK;
57
58 let mut padded_message = Vec::with_capacity(n_padded_words);
60
61 if len_bytes.is_multiple_of(8) {
64 padded_message.extend_from_slice(message);
66 padded_message.push(builder.add_constant(Word(0x01)));
68 } else {
69 padded_message.extend_from_slice(&message[..message.len() - 1]);
71
72 let last_idx = message.len() - 1;
74 let byte_in_word = len_bytes % 8;
75
76 let mask = (1u64 << (byte_in_word * 8)) - 1;
79 let masked_word = builder.band(message[last_idx], builder.add_constant(Word(mask)));
80
81 let padding_bit = 1u64 << (byte_in_word * 8);
83 let boundary_word = builder.bxor(masked_word, builder.add_constant(Word(padding_bit)));
84 padded_message.push(boundary_word);
85 }
86
87 let zero = builder.add_constant(Word::ZERO);
89 padded_message.resize(n_padded_words, zero);
90
91 let last_byte_mask = 0x80u64 << 56; let last_idx = n_padded_words - 1;
95 padded_message[last_idx] =
96 builder.bxor(padded_message[last_idx], builder.add_constant(Word(last_byte_mask)));
97
98 let zero = builder.add_constant(Word::ZERO);
100 let mut state = [zero; N_WORDS_PER_STATE];
101
102 for block in padded_message.chunks(N_WORDS_PER_BLOCK) {
104 for (i, &word) in block.iter().enumerate() {
106 state[i] = builder.bxor(state[i], word);
107 }
108
109 keccak_f1600(builder, &mut state);
111 }
112
113 [state[0], state[1], state[2], state[3]]
115}
116
117#[cfg(test)]
118mod tests {
119 use binius_frontend::CircuitBuilder;
120 use rand::prelude::*;
121 use rstest::rstest;
122 use sha3::{Digest, Keccak256};
123
124 use super::*;
125
126 #[rstest]
127 #[case(0)] #[case(1)] #[case(8)] #[case(135)] #[case(136)] #[case(137)] #[case(272)] #[case(500)] fn test_keccak256_fixed(#[case] message_len_bytes: usize) {
136 let seed = message_len_bytes as u64;
138 let mut rng = StdRng::seed_from_u64(seed);
139 let mut message = vec![0u8; message_len_bytes];
140 rng.fill_bytes(&mut message);
141
142 let mut hasher = Keccak256::new();
144 hasher.update(&message);
145 let expected_digest: [u8; 32] = hasher.finalize().into();
146
147 let builder = CircuitBuilder::new();
149
150 let n_words = message_len_bytes.div_ceil(8);
152 let message_wires: Vec<_> = (0..n_words).map(|_| builder.add_witness()).collect();
153
154 let expected_digest_wires: [Wire; 4] = std::array::from_fn(|_| builder.add_witness());
156
157 let computed_digest = keccak256(&builder, &message_wires, message_len_bytes);
159
160 for i in 0..4 {
162 builder.assert_eq(format!("digest[{i}]"), computed_digest[i], expected_digest_wires[i]);
163 }
164
165 let circuit = builder.build();
167 let cs = circuit.constraint_system();
168 let mut witness = circuit.new_witness_filler();
169
170 for (i, chunk) in message.chunks(8).enumerate() {
172 let mut word_bytes = [0u8; 8];
173 word_bytes[..chunk.len()].copy_from_slice(chunk);
174 let word = u64::from_le_bytes(word_bytes);
175 witness[message_wires[i]] = Word(word);
176 }
177
178 for (i, chunk) in expected_digest.chunks(8).enumerate() {
180 let word = u64::from_le_bytes(chunk.try_into().unwrap());
181 witness[expected_digest_wires[i]] = Word(word);
182 }
183
184 circuit.populate_wire_witness(&mut witness).unwrap();
185 cs.verify(&witness.into_value_vec())
186 .expect("Circuit constraints should be satisfied");
187 }
188
189 #[test]
190 #[should_panic(expected = "message.len() (1) must equal len_bytes.div_ceil(8) (2)")]
191 fn test_keccak256_fixed_wrong_wire_count() {
192 let builder = CircuitBuilder::new();
193
194 let message_wires = vec![builder.add_witness()];
196
197 keccak256(&builder, &message_wires, 10);
199 }
200
201 #[test]
202 fn test_keccak256_fixed_exact_wire_count() {
203 let builder = CircuitBuilder::new();
204
205 let empty: Vec<Wire> = vec![];
207 let _ = keccak256(&builder, &empty, 0);
208
209 let one_wire = vec![builder.add_witness()];
211 let _ = keccak256(&builder, &one_wire, 8);
212
213 let two_wires = vec![builder.add_witness(), builder.add_witness()];
215 let _ = keccak256(&builder, &two_wires, 10);
216
217 let three_wires = vec![
219 builder.add_witness(),
220 builder.add_witness(),
221 builder.add_witness(),
222 ];
223 let _ = keccak256(&builder, &three_wires, 17);
224 }
225}