1use binius_core::word::Word;
3use binius_frontend::{ChipGadget, CircuitBuilder, Hint, Wire, WitnessFiller};
4
5const IV: [u64; 8] = [
6 0x6a09e667f3bcc908,
7 0xbb67ae8584caa73b,
8 0x3c6ef372fe94f82b,
9 0xa54ff53a5f1d36f1,
10 0x510e527fade682d1,
11 0x9b05688c2b3e6c1f,
12 0x1f83d9abfb41bd6b,
13 0x5be0cd19137e2179,
14];
15
16const K: [u64; 80] = [
17 0x428a2f98d728ae22,
18 0x7137449123ef65cd,
19 0xb5c0fbcfec4d3b2f,
20 0xe9b5dba58189dbbc,
21 0x3956c25bf348b538,
22 0x59f111f1b605d019,
23 0x923f82a4af194f9b,
24 0xab1c5ed5da6d8118,
25 0xd807aa98a3030242,
26 0x12835b0145706fbe,
27 0x243185be4ee4b28c,
28 0x550c7dc3d5ffb4e2,
29 0x72be5d74f27b896f,
30 0x80deb1fe3b1696b1,
31 0x9bdc06a725c71235,
32 0xc19bf174cf692694,
33 0xe49b69c19ef14ad2,
34 0xefbe4786384f25e3,
35 0x0fc19dc68b8cd5b5,
36 0x240ca1cc77ac9c65,
37 0x2de92c6f592b0275,
38 0x4a7484aa6ea6e483,
39 0x5cb0a9dcbd41fbd4,
40 0x76f988da831153b5,
41 0x983e5152ee66dfab,
42 0xa831c66d2db43210,
43 0xb00327c898fb213f,
44 0xbf597fc7beef0ee4,
45 0xc6e00bf33da88fc2,
46 0xd5a79147930aa725,
47 0x06ca6351e003826f,
48 0x142929670a0e6e70,
49 0x27b70a8546d22ffc,
50 0x2e1b21385c26c926,
51 0x4d2c6dfc5ac42aed,
52 0x53380d139d95b3df,
53 0x650a73548baf63de,
54 0x766a0abb3c77b2a8,
55 0x81c2c92e47edaee6,
56 0x92722c851482353b,
57 0xa2bfe8a14cf10364,
58 0xa81a664bbc423001,
59 0xc24b8b70d0f89791,
60 0xc76c51a30654be30,
61 0xd192e819d6ef5218,
62 0xd69906245565a910,
63 0xf40e35855771202a,
64 0x106aa07032bbd1b8,
65 0x19a4c116b8d2d0c8,
66 0x1e376c085141ab53,
67 0x2748774cdf8eeb99,
68 0x34b0bcb5e19b48a8,
69 0x391c0cb3c5c95a63,
70 0x4ed8aa4ae3418acb,
71 0x5b9cca4f7763e373,
72 0x682e6ff3d6b2b8a3,
73 0x748f82ee5defb2fc,
74 0x78a5636f43172f60,
75 0x84c87814a1f0ab72,
76 0x8cc702081a6439ec,
77 0x90befffa23631e28,
78 0xa4506cebde82bde9,
79 0xbef9a3f7b2c67915,
80 0xc67178f2e372532b,
81 0xca273eceea26619c,
82 0xd186b8c721c0c207,
83 0xeada7dd6cde0eb1e,
84 0xf57d4f7fee6ed178,
85 0x06f067aa72176fba,
86 0x0a637dc5a2c898a6,
87 0x113f9804bef90dae,
88 0x1b710b35131c471b,
89 0x28db77f523047d84,
90 0x32caab7b40c72493,
91 0x3c9ebe0a15c9bebc,
92 0x431d67c49c100d4c,
93 0x4cc5d4becb3e42b6,
94 0x597f299cfc657e2a,
95 0x5fcb6fab3ad6faec,
96 0x6c44198c4a475817,
97];
98
99#[derive(Clone, Copy)]
105pub struct State(pub [Wire; 8]);
106
107impl State {
108 pub const fn new(wires: [Wire; 8]) -> Self {
109 State(wires)
110 }
111
112 pub fn public(builder: &CircuitBuilder) -> Self {
113 State(std::array::from_fn(|_| builder.add_inout()))
114 }
115
116 pub fn private(builder: &CircuitBuilder) -> Self {
117 State(std::array::from_fn(|_| builder.add_witness()))
118 }
119
120 pub fn iv(builder: &CircuitBuilder) -> Self {
121 State(std::array::from_fn(|i| builder.add_constant(Word(IV[i]))))
122 }
123}
124
125pub fn compress(builder: &CircuitBuilder, state_in: State, m: [Wire; 16]) -> State {
137 let inputs: Vec<Wire> = state_in.0.into_iter().chain(m).collect();
138
139 let outputs = builder.build_gadget(Sha512Compress, &[], &inputs);
140 State(std::array::from_fn(|i| outputs[i]))
141}
142
143pub struct Sha512Compress;
148
149impl Hint for Sha512Compress {
150 const NAME: &'static str = "binius.sha512_compress";
151
152 fn shape(&self, _dimensions: &[usize]) -> (usize, usize) {
153 (24, 8)
154 }
155
156 fn execute(&self, _dimensions: &[usize], inputs: &[Word], outputs: &mut [Word]) {
157 let state_in: [u64; 8] = std::array::from_fn(|i| inputs[i].as_u64());
158 let m: [u64; 16] = std::array::from_fn(|i| inputs[8 + i].as_u64());
159
160 for (slot, word) in outputs.iter_mut().zip(ref_compress(state_in, m)) {
161 *slot = Word(word);
162 }
163 }
164}
165
166impl ChipGadget for Sha512Compress {
167 fn build(&self, builder: &CircuitBuilder, _dimensions: &[usize], inputs: &[Wire]) -> Vec<Wire> {
168 let state_in = State(std::array::from_fn(|i| inputs[i]));
169 let m: [Wire; 16] = std::array::from_fn(|i| inputs[8 + i]);
170 compress_gates(builder, state_in, m).0.to_vec()
171 }
172}
173
174pub fn ref_compress(state_in: [u64; 8], m: [u64; 16]) -> [u64; 8] {
179 let mut w = [0u64; 80];
180 w[..16].copy_from_slice(&m);
181 for t in 16..80 {
182 let s0 = w[t - 15].rotate_right(1) ^ w[t - 15].rotate_right(8) ^ (w[t - 15] >> 7);
183 let s1 = w[t - 2].rotate_right(19) ^ w[t - 2].rotate_right(61) ^ (w[t - 2] >> 6);
184 w[t] = w[t - 16]
185 .wrapping_add(s0)
186 .wrapping_add(w[t - 7])
187 .wrapping_add(s1);
188 }
189
190 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state_in;
191 for t in 0..80 {
192 let big_s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
193 let ch = (e & f) ^ ((!e) & g);
194 let t1 = h
195 .wrapping_add(big_s1)
196 .wrapping_add(ch)
197 .wrapping_add(K[t])
198 .wrapping_add(w[t]);
199 let big_s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
200 let maj = (a & b) ^ (a & c) ^ (b & c);
201 let t2 = big_s0.wrapping_add(maj);
202 h = g;
203 g = f;
204 f = e;
205 e = d.wrapping_add(t1);
206 d = c;
207 c = b;
208 b = a;
209 a = t1.wrapping_add(t2);
210 }
211
212 [
213 state_in[0].wrapping_add(a),
214 state_in[1].wrapping_add(b),
215 state_in[2].wrapping_add(c),
216 state_in[3].wrapping_add(d),
217 state_in[4].wrapping_add(e),
218 state_in[5].wrapping_add(f),
219 state_in[6].wrapping_add(g),
220 state_in[7].wrapping_add(h),
221 ]
222}
223
224fn compress_gates(builder: &CircuitBuilder, state_in: State, m: [Wire; 16]) -> State {
226 let mut w = Vec::with_capacity(80);
235
236 w.extend_from_slice(&m);
238
239 for t in 16..80 {
241 let s0 = small_sigma_0(builder, w[t - 15]);
242 let s1 = small_sigma_1(builder, w[t - 2]);
243 let (p, _carry) = builder.iadd(w[t - 16], s0);
244 let (q, _carry) = builder.iadd(p, w[t - 7]);
245 let (w_t, _carry) = builder.iadd(q, s1);
246 w.push(w_t);
247 }
248
249 let w: &[Wire; 80] = (&*w).try_into().unwrap();
250 let mut state = state_in;
251 for t in 0..80 {
252 state = round(builder, t, state, w);
253 }
254
255 let (a_out, _carry) = builder.iadd(state_in.0[0], state.0[0]);
257 let (b_out, _carry) = builder.iadd(state_in.0[1], state.0[1]);
258 let (c_out, _carry) = builder.iadd(state_in.0[2], state.0[2]);
259 let (d_out, _carry) = builder.iadd(state_in.0[3], state.0[3]);
260 let (e_out, _carry) = builder.iadd(state_in.0[4], state.0[4]);
261 let (f_out, _carry) = builder.iadd(state_in.0[5], state.0[5]);
262 let (g_out, _carry) = builder.iadd(state_in.0[6], state.0[6]);
263 let (h_out, _carry) = builder.iadd(state_in.0[7], state.0[7]);
264
265 State([a_out, b_out, c_out, d_out, e_out, f_out, g_out, h_out])
266}
267
268pub fn pack_message_block(w: &mut WitnessFiller<'_>, m_wires: &[Wire; 16], block: [u8; 128]) {
273 for i in 0..16 {
274 let j = i * 8;
275 let limb = ((block[j] as u64) << 56)
277 | ((block[j + 1] as u64) << 48)
278 | ((block[j + 2] as u64) << 40)
279 | ((block[j + 3] as u64) << 32)
280 | ((block[j + 4] as u64) << 24)
281 | ((block[j + 5] as u64) << 16)
282 | ((block[j + 6] as u64) << 8)
283 | (block[j + 7] as u64);
284
285 w[m_wires[i]] = Word(limb);
286 }
287}
288
289fn round(builder: &CircuitBuilder, round: usize, state: State, w: &[Wire; 80]) -> State {
290 let State([a, b, c, d, e, f, g, h]) = state;
291
292 let big_sigma_e = big_sigma_1(builder, e);
293 let ch_efg = ch(builder, e, f, g);
294 let (t1a, _carry) = builder.iadd(h, big_sigma_e);
295 let (t1b, _carry) = builder.iadd(t1a, ch_efg);
296 let rc = builder.add_constant(Word(K[round]));
297 let (t1c, _carry) = builder.iadd(t1b, rc);
298 let (t1, _carry) = builder.iadd(t1c, w[round]);
299
300 let big_sigma_a = big_sigma_0(builder, a);
301 let maj_abc = maj(builder, a, b, c);
302 let (t2, _carry) = builder.iadd(big_sigma_a, maj_abc);
303
304 let h = g;
305 let g = f;
306 let f = e;
307 let (e, _carry) = builder.iadd(d, t1);
308 let d = c;
309 let c = b;
310 let b = a;
311 let (a, _carry) = builder.iadd(t1, t2);
312
313 State([a, b, c, d, e, f, g, h])
314}
315
316fn ch(builder: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
319 builder.bxor(z, builder.band(x, builder.bxor(y, z)))
320}
321
322fn maj(builder: &CircuitBuilder, x: Wire, y: Wire, z: Wire) -> Wire {
325 builder.bxor(builder.band(builder.bxor(x, z), builder.bxor(y, z)), z)
326}
327
328fn big_sigma_0(b: &CircuitBuilder, x: Wire) -> Wire {
330 let r1 = b.rotr(x, 28);
331 let r2 = b.rotr(x, 34);
332 let r3 = b.rotr(x, 39);
333 let x1 = b.bxor(r1, r2);
334 b.bxor(x1, r3)
335}
336
337fn big_sigma_1(b: &CircuitBuilder, x: Wire) -> Wire {
339 let r1 = b.rotr(x, 14);
340 let r2 = b.rotr(x, 18);
341 let r3 = b.rotr(x, 41);
342 let x1 = b.bxor(r1, r2);
343 b.bxor(x1, r3)
344}
345
346fn small_sigma_0(b: &CircuitBuilder, x: Wire) -> Wire {
348 let r1 = b.rotr(x, 1);
349 let r2 = b.rotr(x, 8);
350 let s1 = b.shr(x, 7);
351 let x1 = b.bxor(r1, r2);
352 b.bxor(x1, s1)
353}
354
355fn small_sigma_1(b: &CircuitBuilder, x: Wire) -> Wire {
357 let r1 = b.rotr(x, 19);
358 let r2 = b.rotr(x, 61);
359 let s1 = b.shr(x, 6);
360 let x1 = b.bxor(r1, r2);
361 b.bxor(x1, s1)
362}
363
364#[cfg(test)]
365mod tests {
366 use binius_core::word::Word;
367 use binius_frontend::{CircuitBuilder, Hint, Wire};
368 use proptest::prelude::*;
369
370 use super::{Sha512Compress, State, compress, compress_gates, pack_message_block};
371
372 #[test]
378 fn proof_preimage() {
379 let mut preimage: [u8; 128] = [0; 128];
381 preimage[0..3].copy_from_slice(b"abc");
382 preimage[3] = 0x80;
383 preimage[127] = 0x18;
384
385 #[rustfmt::skip]
386 let expected_state: [u64; 8] = [
387 0xddaf35a193617aba, 0xcc417349ae204131, 0x12e6fa4e89a97ea2, 0x0a9eeee64b55d39a,
388 0x2192992a274fc1a8, 0x36ba3c23a3feebbd, 0x454d4423643ce80e, 0x2a9ac94fa54ca49f,
389 ];
390
391 let circuit = CircuitBuilder::new();
392 let state = State::iv(&circuit);
393 let input: [Wire; 16] = std::array::from_fn(|_| circuit.add_witness());
394 let output: [Wire; 8] = std::array::from_fn(|_| circuit.add_inout());
395 let state_out = compress(&circuit, state, input);
396
397 for (i, (actual_x, expected_x)) in state_out.0.iter().zip(output).enumerate() {
398 circuit.assert_eq(format!("preimage_eq[{i}]"), *actual_x, expected_x);
399 }
400
401 let circuit = circuit.build();
402 let cs = circuit.constraint_system();
403 let mut w = circuit.new_witness_filler();
404
405 pack_message_block(&mut w, &input, preimage);
407
408 for (i, &output) in output.iter().enumerate() {
409 w[output] = Word(expected_state[i]);
410 }
411 circuit.populate_wire_witness(&mut w).unwrap();
412
413 cs.verify(&w.into_value_vec()).unwrap();
414 }
415
416 #[test]
417 fn sha512_chain() {
418 const N: usize = 3;
421 let circuit = CircuitBuilder::new();
422
423 let mut m_wires: Vec<[Wire; 16]> = Vec::with_capacity(N);
424
425 let mut state = State::iv(&circuit);
427 for i in 0..N {
428 let sha512_builder = circuit.subcircuit(format!("sha512[{i}]"));
431
432 let m: [Wire; 16] = if i == 0 {
435 std::array::from_fn(|_| sha512_builder.add_inout())
436 } else {
437 std::array::from_fn(|_| sha512_builder.add_witness())
438 };
439 state = compress(&sha512_builder, state, m);
440 m_wires.push(m);
441 }
442
443 let circuit = circuit.build();
444 let cs = circuit.constraint_system();
445 let mut w = circuit.new_witness_filler();
446
447 for m in &m_wires {
448 pack_message_block(&mut w, m, [0; 128]);
449 }
450 circuit.populate_wire_witness(&mut w).unwrap();
451
452 cs.verify(&w.into_value_vec()).unwrap();
453 }
454
455 #[test]
456 fn sha512_parallel() {
457 const N: usize = 3;
459 let circuit = CircuitBuilder::new();
460
461 let mut m_wires: Vec<[Wire; 16]> = Vec::with_capacity(N);
462
463 for i in 0..N {
464 let sha512_builder = circuit.subcircuit(format!("sha512[{i}]"));
466
467 let state = State::iv(&sha512_builder);
469 let m: [Wire; 16] = std::array::from_fn(|_| sha512_builder.add_inout());
470 let _state_out = compress(&sha512_builder, state, m);
471 m_wires.push(m);
472 }
473
474 let circuit = circuit.build();
475 let cs = circuit.constraint_system();
476 let mut w = circuit.new_witness_filler();
477
478 for m in &m_wires {
479 pack_message_block(&mut w, m, [0; 128]);
480 }
481 circuit.populate_wire_witness(&mut w).unwrap();
482 cs.verify(&w.into_value_vec()).unwrap();
483 }
484
485 fn run_compress_words(inputs: [u64; 24]) -> [u64; 8] {
487 let builder = CircuitBuilder::new();
488 let wires: [Wire; 24] = std::array::from_fn(|_| builder.add_witness());
489 let out = compress_gates(
490 &builder,
491 State(std::array::from_fn(|i| wires[i])),
492 std::array::from_fn(|i| wires[8 + i]),
493 );
494 for wire in out.0 {
495 builder.mark_inout(wire);
496 }
497
498 let circuit = builder.build();
499 let mut w = circuit.new_witness_filler();
500 for (wire, word) in std::iter::zip(wires, inputs) {
501 w[wire] = Word(word);
502 }
503 circuit.populate_wire_witness(&mut w).unwrap();
504
505 std::array::from_fn(|i| w[out.0[i]].as_u64())
506 }
507
508 proptest! {
509 #[test]
514 fn compress_hint_matches_its_gates(words in prop::collection::vec(any::<u64>(), 24)) {
515 let inputs: [u64; 24] = std::array::from_fn(|i| words[i]);
516
517 let mut hinted = [Word::ZERO; 8];
518 Sha512Compress.execute(&[], &inputs.map(Word), &mut hinted);
519
520 prop_assert_eq!(hinted.map(|word| word.as_u64()), run_compress_words(inputs));
521 }
522 }
523}