1use binius_core::word::Word;
3use binius_frontend::{CircuitBuilder, Wire};
4
5fn ripemd160_compress(
30 builder: &CircuitBuilder,
31 state: [Wire; 5],
32 message_block: [Wire; 16],
33) -> [Wire; 5] {
34 const KL: [u32; 5] = [0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E];
36
37 const KR: [u32; 5] = [0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000];
39
40 const ZL: [usize; 80] = [
42 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, ];
48
49 const ZR: [usize; 80] = [
51 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, ];
57
58 const SL: [u32; 80] = [
60 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, ];
66
67 const SR: [u32; 80] = [
69 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, ];
75
76 let mut al = state[0];
78 let mut bl = state[1];
79 let mut cl = state[2];
80 let mut dl = state[3];
81 let mut el = state[4];
82
83 let mut ar = state[0];
85 let mut br = state[1];
86 let mut cr = state[2];
87 let mut dr = state[3];
88 let mut er = state[4];
89
90 for round_num in 0..80 {
92 let round_group = round_num / 16;
93
94 let func_l = match round_group {
96 0 => f,
97 1 => g,
98 2 => h,
99 3 => i,
100 4 => j,
101 _ => unreachable!(),
102 };
103
104 let new_bl = round(
105 builder,
106 al,
107 bl,
108 cl,
109 dl,
110 el,
111 message_block[ZL[round_num]],
112 SL[round_num],
113 KL[round_group],
114 func_l,
115 );
116
117 al = el;
119 el = dl;
120 dl = builder.rotl32(cl, 10);
121 cl = bl;
122 bl = new_bl;
123
124 let func_r = match round_group {
126 0 => j,
127 1 => i,
128 2 => h,
129 3 => g,
130 4 => f,
131 _ => unreachable!(),
132 };
133
134 let new_br = round(
135 builder,
136 ar,
137 br,
138 cr,
139 dr,
140 er,
141 message_block[ZR[round_num]],
142 SR[round_num],
143 KR[round_group],
144 func_r,
145 );
146
147 ar = er;
149 er = dr;
150 dr = builder.rotl32(cr, 10);
151 cr = br;
152 br = new_br;
153 }
154
155 [
159 builder.iadd_32(builder.iadd_32(state[1], cl), dr),
160 builder.iadd_32(builder.iadd_32(state[2], dl), er),
161 builder.iadd_32(builder.iadd_32(state[3], el), ar),
162 builder.iadd_32(builder.iadd_32(state[4], al), br),
163 builder.iadd_32(builder.iadd_32(state[0], bl), cr),
164 ]
165 .map(|w| builder.shr(builder.shl(w, 32), 32))
166}
167
168fn f(b: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
170 b.bxor(b.bxor(x, y), z)
171}
172
173fn g(b: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
175 b.bxor(z, b.band(x, b.bxor(y, z)))
176}
177
178fn h(b: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
180 b.bxor(b.bor(x, b.bnot(y)), z)
181}
182
183fn i(b: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
185 b.bxor(y, b.band(z, b.bxor(x, y)))
186}
187
188fn j(b: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
190 b.bxor(x, b.bor(y, b.bnot(z)))
191}
192
193#[allow(clippy::too_many_arguments)]
195fn round(
196 builder: &CircuitBuilder,
197 a: Wire,
198 b: Wire,
199 c: Wire,
200 d: Wire,
201 e: Wire,
202 x: Wire,
203 s: u32,
204 k: u32,
205 func: fn(&CircuitBuilder, Wire, Wire, Wire) -> Wire,
206) -> Wire {
207 let f_val = func(builder, b, c, d);
209 let t1 = builder.iadd_32(a, f_val);
210 let t2 = builder.iadd_32(t1, x);
211 let t = builder.iadd_32(t2, builder.add_constant_64(k as u64));
212
213 let t_rot = builder.rotl32(t, s);
215
216 builder.iadd_32(t_rot, e)
218}
219
220pub fn ripemd160_fixed(builder: &CircuitBuilder, message: &[Wire], len_bytes: usize) -> [Wire; 5] {
261 const H0: [u32; 5] = [
263 0x6745_2301,
264 0xEFCD_AB89,
265 0x98BA_DCFE,
266 0x1032_5476,
267 0xC3D2_E1F0,
268 ];
269
270 assert_eq!(
272 message.len(),
273 len_bytes.div_ceil(4),
274 "message.len() ({}) must equal len_bytes.div_ceil(4) ({})",
275 message.len(),
276 len_bytes.div_ceil(4)
277 );
278
279 assert!(
281 (len_bytes as u64)
282 .checked_mul(8)
283 .is_some_and(|bits| bits <= u32::MAX as u64),
284 "Message length in bits must fit in 32 bits"
285 );
286
287 let n_blocks = (len_bytes + 9).div_ceil(64);
292 let n_padded_words = n_blocks * 16; let mut padded_message = Vec::with_capacity(n_padded_words);
296
297 let n_message_words = len_bytes / 4;
299 let boundary_bytes = len_bytes % 4;
300
301 padded_message.extend_from_slice(&message[0..n_message_words]);
303
304 if boundary_bytes > 0 {
306 let last_word = message[n_message_words];
308
309 let shift_amount = boundary_bytes * 8;
312 let mask = builder.add_constant(Word((1u64 << shift_amount) - 1));
313 let masked = builder.band(last_word, mask);
314
315 let delimiter = builder.add_constant(Word(0x80u64 << shift_amount));
317 let boundary_word = builder.bxor(masked, delimiter);
318
319 padded_message.push(boundary_word);
320 } else {
321 padded_message.push(builder.add_constant(Word(0x80)));
323 }
324
325 let zero = builder.add_constant(Word::ZERO);
327 padded_message.resize(n_padded_words - 2, zero);
328
329 let bitlen = (len_bytes as u64) * 8;
331 padded_message.push(builder.add_constant(Word(bitlen & 0xFFFFFFFF))); padded_message.push(builder.add_constant(Word(bitlen >> 32))); let initial_state = [
336 builder.add_constant(Word(H0[0] as u64)),
337 builder.add_constant(Word(H0[1] as u64)),
338 builder.add_constant(Word(H0[2] as u64)),
339 builder.add_constant(Word(H0[3] as u64)),
340 builder.add_constant(Word(H0[4] as u64)),
341 ];
342
343 padded_message
345 .chunks_exact(16)
346 .enumerate()
347 .fold(initial_state, |state, (block_idx, block)| {
348 let block_message: [Wire; 16] = block
349 .try_into()
350 .expect("length of padded_message is a multiple of 16 by construction");
351
352 ripemd160_compress(
353 &builder.subcircuit(format!("ripemd160_compress[{block_idx}]")),
354 state,
355 block_message,
356 )
357 })
358}
359
360#[cfg(test)]
361mod tests {
362 use std::{array, iter, iter::repeat_with};
363
364 use rand::prelude::*;
365 use ripemd::Digest;
366
367 use super::*;
368
369 fn test_ripemd160_fixed_with_input(message: &[u8], expected_bytes: [u8; 20]) {
371 let b = CircuitBuilder::new();
372
373 let n_words = message.len().div_ceil(4);
375
376 let message_wires = repeat_with(|| b.add_inout())
377 .take(n_words)
378 .collect::<Vec<_>>();
379
380 let expected_digest_wires = array::from_fn::<_, 5, _>(|_| b.add_inout());
382
383 assert_eq!(message_wires.len(), message.len().div_ceil(4));
385 assert_eq!(message_wires.len(), n_words);
386 let computed_digest = ripemd160_fixed(&b, &message_wires, message.len());
387
388 for i in 0..5 {
390 b.assert_eq(format!("digest[{i}]"), computed_digest[i], expected_digest_wires[i]);
391 }
392
393 let circuit = b.build();
394 let cs = circuit.constraint_system();
395 let mut w = circuit.new_witness_filler();
396
397 for (&wire, chunk) in iter::zip(&message_wires, message.chunks(4)) {
399 let mut word_bytes = [0u8; 4];
400 word_bytes[..chunk.len()].copy_from_slice(chunk);
401 let word = u32::from_le_bytes(word_bytes);
402 w[wire] = Word(word as u64);
403 }
404
405 for (&wire, chunk) in iter::zip(&expected_digest_wires, expected_bytes.chunks(4)) {
407 let mut word_bytes = [0u8; 4];
408 word_bytes[..chunk.len()].copy_from_slice(chunk);
409 let word = u32::from_le_bytes(word_bytes);
410 w[wire] = Word(word as u64);
411 }
412
413 circuit.populate_wire_witness(&mut w).unwrap();
414 cs.verify(&w.into_value_vec()).unwrap();
415 }
416
417 #[test]
418 fn test_ripemd160_fixed_various_sizes() {
419 let mut rng = StdRng::seed_from_u64(0);
420
421 let sizes = vec![
423 0, 1, 3, 4, 5, 31, 32, 33, 55, 56, 63, 64, 65, 119, 120, 128, 256, ];
441
442 for size in sizes {
443 let mut message = vec![0u8; size];
445 rng.fill(&mut message[..]);
446
447 let expected = ripemd::Ripemd160::digest(&message);
449 let expected_bytes: [u8; 20] = expected.into();
450
451 test_ripemd160_fixed_with_input(&message, expected_bytes);
453 }
454 }
455
456 #[test]
457 #[should_panic(expected = "message.len() (1) must equal len_bytes.div_ceil(4) (2)")]
458 fn test_ripemd160_fixed_with_insufficient_wires() {
459 let builder = CircuitBuilder::new();
460
461 let message_wires: Vec<Wire> = vec![builder.add_witness()];
463
464 ripemd160_fixed(&builder, &message_wires, 5);
466 }
467}