1use std::{array, iter};
26
27use binius_core::word::Word;
28use binius_frontend::{ChipGadget, CircuitBuilder, Hint, Wire};
29
30use super::constants::{IV, SIGMA};
31use crate::util::clear_high_bits;
32
33#[allow(clippy::too_many_arguments)]
42fn g(
43 builder: &CircuitBuilder,
44 v: &mut [Wire; 16],
45 a: usize,
46 b: usize,
47 c: usize,
48 d: usize,
49 x: Wire,
50 y: Wire,
51) {
52 v[a] = builder.iadd_32(builder.iadd_32(v[a], v[b]), x);
54 v[d] = builder.rotr32(builder.bxor(v[d], v[a]), 16);
55 v[c] = builder.iadd_32(v[c], v[d]);
57 v[b] = builder.rotr32(builder.bxor(v[b], v[c]), 12);
58 v[a] = builder.iadd_32(builder.iadd_32(v[a], v[b]), y);
60 v[d] = builder.rotr32(builder.bxor(v[d], v[a]), 8);
61 v[c] = builder.iadd_32(v[c], v[d]);
63 v[b] = builder.rotr32(builder.bxor(v[b], v[c]), 7);
64}
65
66fn round(builder: &CircuitBuilder, v: &mut [Wire; 16], m: &[Wire; 16], round_idx: usize) {
71 let s = &SIGMA[round_idx];
72 g(builder, v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
74 g(builder, v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
75 g(builder, v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
76 g(builder, v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
77 g(builder, v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
79 g(builder, v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
80 g(builder, v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
81 g(builder, v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
82}
83
84fn init_v(
97 builder: &CircuitBuilder,
98 h: [Wire; 8],
99 iv: [Wire; 8],
100 t_lo: Wire,
101 t_hi: Wire,
102 last: Wire,
103) -> [Wire; 16] {
104 let mut v: [Wire; 16] = array::from_fn(|i| if i < 8 { h[i] } else { iv[i - 8] });
105 v[12] = builder.bxor(v[12], t_lo);
107 v[13] = builder.bxor(v[13], t_hi);
109 v[14] = builder.bxor(v[14], last);
111 v
112}
113
114fn compress_core(
123 builder: &CircuitBuilder,
124 h: [Wire; 8],
125 mut v: [Wire; 16],
126 m: [Wire; 16],
127) -> [Wire; 8] {
128 for round_idx in 0..10 {
129 round(builder, &mut v, &m, round_idx);
130 }
131 array::from_fn(|i| builder.bxor(h[i], builder.bxor(v[i], v[i + 8])))
133}
134
135pub fn blake2s_compress(
159 builder: &CircuitBuilder,
160 h: [Wire; 8],
161 m: [Wire; 16],
162 t_lo: Wire,
163 t_hi: Wire,
164 last: Wire,
165) -> [Wire; 8] {
166 let iv: [Wire; 8] = array::from_fn(|i| builder.add_constant(Word(IV[i] as u64)));
169 let v = init_v(builder, h, iv, t_lo, t_hi, last);
170 compress_core(builder, h, v, m)
171}
172
173pub fn blake2s_compress_2x(
205 builder: &CircuitBuilder,
206 h: [Wire; 8],
207 m: [Wire; 16],
208 t_lo: Wire,
209 t_hi: Wire,
210 last: Wire,
211) -> [Wire; 8] {
212 let inputs: Vec<Wire> = h.into_iter().chain(m).chain([t_lo, t_hi, last]).collect();
213 let outputs = builder.build_gadget(Blake2sCompress2x, &[], &inputs);
214 array::from_fn(|i| outputs[i])
215}
216
217pub struct Blake2sCompress2x;
224
225impl Hint for Blake2sCompress2x {
226 const NAME: &'static str = "binius.blake2s_compress_2x";
227
228 fn shape(&self, _dimensions: &[usize]) -> (usize, usize) {
229 (27, 8)
230 }
231
232 fn execute(&self, _dimensions: &[usize], inputs: &[Word], outputs: &mut [Word]) {
233 let compress_lane = |i: usize| {
239 let lane = |word: Word| (word.as_u64() >> (32 * i)) as u32;
240 let h: [u32; 8] = array::from_fn(|j| lane(inputs[j]));
241 let m: [u32; 16] = array::from_fn(|j| lane(inputs[8 + j]));
242 ref_compress(h, m, lane(inputs[24]), lane(inputs[25]), lane(inputs[26]))
243 };
244
245 let (lane_0, lane_1) = (compress_lane(0), compress_lane(1));
247 for (slot, (low, high)) in iter::zip(outputs, iter::zip(lane_0, lane_1)) {
248 *slot = Word(low as u64 | ((high as u64) << 32));
249 }
250 }
251}
252
253impl ChipGadget for Blake2sCompress2x {
254 fn build(&self, builder: &CircuitBuilder, _dimensions: &[usize], inputs: &[Wire]) -> Vec<Wire> {
255 let h: [Wire; 8] = array::from_fn(|i| inputs[i]);
258 let m: [Wire; 16] = array::from_fn(|i| inputs[8 + i]);
259 compress_2x_gates(builder, h, m, inputs[24], inputs[25], inputs[26]).to_vec()
260 }
261}
262
263fn compress_2x_gates(
268 builder: &CircuitBuilder,
269 h: [Wire; 8],
270 m: [Wire; 16],
271 t_lo: Wire,
272 t_hi: Wire,
273 last: Wire,
274) -> [Wire; 8] {
275 let iv_2x: [Wire; 8] = array::from_fn(|i| {
277 let w = IV[i] as u64;
278 builder.add_constant(Word(w | (w << 32)))
279 });
280 let v = init_v(builder, h, iv_2x, t_lo, t_hi, last);
281 compress_core(builder, h, v, m)
282}
283
284pub fn blake2s_compress_2x_seq(
340 builder: &CircuitBuilder,
341 h: [Wire; 8],
342 blocks: [[Wire; 16]; 2],
343 t_los: [Wire; 2],
344 t_his: [Wire; 2],
345 lasts: [Wire; 2],
346) -> [Wire; 8] {
347 let mut hint_inputs = Vec::with_capacity(27);
355 hint_inputs.extend_from_slice(&h);
356 hint_inputs.extend_from_slice(&blocks[0]);
357 hint_inputs.push(t_los[0]);
358 hint_inputs.push(t_his[0]);
359 hint_inputs.push(lasts[0]);
360 let merged_vec = builder.call_hint(Blake2sCompressHint, &[], &hint_inputs);
361 let merged: [Wire; 8] = array::from_fn(|i| merged_vec[i]);
362
363 let pack = |lo: Wire, hi: Wire| builder.bxor(lo, builder.shl(hi, 32));
371 let clear = |w: Wire| clear_high_bits(builder, w, 32);
372
373 let merged_block: [Wire; 16] = array::from_fn(|i| pack(clear(blocks[1][i]), blocks[0][i]));
376 let merged_t_lo = pack(clear(t_los[1]), t_los[0]);
377 let merged_t_hi = pack(clear(t_his[1]), t_his[0]);
378 let merged_last = pack(clear(lasts[1]), lasts[0]);
379
380 let out =
381 blake2s_compress_2x(builder, merged, merged_block, merged_t_lo, merged_t_hi, merged_last);
382
383 for (m, (s, o)) in iter::zip(merged, iter::zip(h, out)) {
401 let expected = builder.bxor(builder.shl(s, 32), builder.shr(o, 32));
402 builder.assert_eq("blake2s_compress_2x_seq.merged_state", m, expected);
403 }
404
405 out
406}
407
408struct Blake2sCompressHint;
428
429impl Hint for Blake2sCompressHint {
430 const NAME: &'static str = "binius.blake2s_compress";
431
432 fn shape(&self, _dimensions: &[usize]) -> (usize, usize) {
433 (27, 8)
434 }
435
436 fn execute(&self, _dimensions: &[usize], inputs: &[Word], outputs: &mut [Word]) {
437 let h: [u32; 8] = array::from_fn(|i| inputs[i].as_u64() as u32);
439 let m: [u32; 16] = array::from_fn(|i| inputs[8 + i].as_u64() as u32);
440 let t_lo = inputs[24].as_u64() as u32;
441 let t_hi = inputs[25].as_u64() as u32;
442 let last = inputs[26].as_u64() as u32;
443
444 let out = ref_compress(h, m, t_lo, t_hi, last);
447 for (i, slot) in outputs.iter_mut().enumerate() {
448 *slot = Word(out[i] as u64 | ((h[i] as u64) << 32));
449 }
450 }
451}
452
453pub fn ref_compress(h: [u32; 8], m: [u32; 16], t_lo: u32, t_hi: u32, last: u32) -> [u32; 8] {
471 const fn ref_g(v: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) {
472 v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
474 v[d] = (v[d] ^ v[a]).rotate_right(16);
475 v[c] = v[c].wrapping_add(v[d]);
477 v[b] = (v[b] ^ v[c]).rotate_right(12);
478 v[a] = v[a].wrapping_add(v[b]).wrapping_add(y);
480 v[d] = (v[d] ^ v[a]).rotate_right(8);
481 v[c] = v[c].wrapping_add(v[d]);
483 v[b] = (v[b] ^ v[c]).rotate_right(7);
484 }
485
486 let mut v = [0u32; 16];
487 v[..8].copy_from_slice(&h);
489 v[8..].copy_from_slice(&IV);
490 v[12] ^= t_lo;
492 v[13] ^= t_hi;
493 v[14] ^= last;
494
495 for s in &SIGMA {
497 ref_g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
498 ref_g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
499 ref_g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
500 ref_g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
501 ref_g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
502 ref_g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
503 ref_g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
504 ref_g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
505 }
506
507 array::from_fn(|i| h[i] ^ v[i] ^ v[i + 8])
509}
510
511#[cfg(test)]
512mod tests {
513 use binius_frontend::CircuitBuilder;
514 use hex_literal::hex;
515 use proptest::prelude::*;
516
517 use super::*;
518
519 fn run_compress(h: [u32; 8], m: [u32; 16], t_lo: u32, t_hi: u32, last: u32) -> [u32; 8] {
526 let builder = CircuitBuilder::new();
527 let h_wires: [Wire; 8] = array::from_fn(|_| builder.add_witness());
528 let m_wires: [Wire; 16] = array::from_fn(|_| builder.add_witness());
529 let t_lo_w = builder.add_witness();
530 let t_hi_w = builder.add_witness();
531 let last_w = builder.add_witness();
532
533 let out = blake2s_compress(&builder, h_wires, m_wires, t_lo_w, t_hi_w, last_w);
536 let out_inout: [Wire; 8] = array::from_fn(|_| builder.add_inout());
537 for i in 0..8 {
538 builder.assert_eq("out_match", out[i], out_inout[i]);
539 }
540
541 let circuit = builder.build();
542 let mut w = circuit.new_witness_filler();
543 for i in 0..8 {
544 w[h_wires[i]] = Word(h[i] as u64);
545 }
546 for i in 0..16 {
547 w[m_wires[i]] = Word(m[i] as u64);
548 }
549 w[t_lo_w] = Word(t_lo as u64);
550 w[t_hi_w] = Word(t_hi as u64);
551 w[last_w] = Word(last as u64);
552
553 let expected = ref_compress(h, m, t_lo, t_hi, last);
554 for i in 0..8 {
555 w[out_inout[i]] = Word(expected[i] as u64);
556 }
557 circuit.populate_wire_witness(&mut w).unwrap();
558 array::from_fn(|i| w[out_inout[i]].0 as u32)
559 }
560
561 #[test]
562 fn rfc7693_appendix_b_trace_matches_spec() {
563 let h: [u32; 8] = [
576 IV[0] ^ 0x0101_0020,
577 IV[1],
578 IV[2],
579 IV[3],
580 IV[4],
581 IV[5],
582 IV[6],
583 IV[7],
584 ];
585 let mut m = [0u32; 16];
588 m[0] = 0x0063_6261;
589 let (t_lo, t_hi, last) = (3u32, 0u32, 0xFFFF_FFFFu32);
591
592 let expected: [u32; 8] = [
593 0x8C5E_8C50,
594 0xE214_7C32,
595 0xA32B_A7E1,
596 0x2F45_EB4E,
597 0x208B_4537,
598 0x293A_D69E,
599 0x4C9B_994D,
600 0x8259_6786,
601 ];
602 assert_eq!(ref_compress(h, m, t_lo, t_hi, last), expected);
603 assert_eq!(run_compress(h, m, t_lo, t_hi, last), expected);
604
605 let digest_bytes: Vec<u8> = expected.iter().flat_map(|w| w.to_le_bytes()).collect();
607 assert_eq!(
608 digest_bytes,
609 hex!("508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982")
610 );
611 }
612
613 fn pack2x(lo: u32, hi: u32) -> u64 {
616 (lo as u64) | ((hi as u64) << 32)
617 }
618
619 fn unpack2x(w: u64) -> (u32, u32) {
620 (w as u32, (w >> 32) as u32)
621 }
622
623 fn run_compress_2x(
626 h: [[u32; 8]; 2],
627 m: [[u32; 16]; 2],
628 t_lo: [u32; 2],
629 t_hi: [u32; 2],
630 last: [u32; 2],
631 ) -> [[u32; 8]; 2] {
632 let builder = CircuitBuilder::new();
633 let h_wires: [Wire; 8] = array::from_fn(|_| builder.add_witness());
634 let m_wires: [Wire; 16] = array::from_fn(|_| builder.add_witness());
635 let t_lo_w = builder.add_witness();
636 let t_hi_w = builder.add_witness();
637 let last_w = builder.add_witness();
638
639 let out = blake2s_compress_2x(&builder, h_wires, m_wires, t_lo_w, t_hi_w, last_w);
640 let out_inout: [Wire; 8] = array::from_fn(|_| builder.add_inout());
641 for i in 0..8 {
642 builder.assert_eq("out_match_2x", out[i], out_inout[i]);
643 }
644
645 let circuit = builder.build();
647 let mut w = circuit.new_witness_filler();
648 for i in 0..8 {
649 w[h_wires[i]] = Word(pack2x(h[0][i], h[1][i]));
650 }
651 for i in 0..16 {
652 w[m_wires[i]] = Word(pack2x(m[0][i], m[1][i]));
653 }
654 w[t_lo_w] = Word(pack2x(t_lo[0], t_lo[1]));
655 w[t_hi_w] = Word(pack2x(t_hi[0], t_hi[1]));
656 w[last_w] = Word(pack2x(last[0], last[1]));
657
658 let exp0 = ref_compress(h[0], m[0], t_lo[0], t_hi[0], last[0]);
659 let exp1 = ref_compress(h[1], m[1], t_lo[1], t_hi[1], last[1]);
660 for i in 0..8 {
661 w[out_inout[i]] = Word(pack2x(exp0[i], exp1[i]));
662 }
663 circuit.populate_wire_witness(&mut w).unwrap();
664
665 let mut actual = [[0u32; 8]; 2];
667 for i in 0..8 {
668 let (lo, hi) = unpack2x(w[out_inout[i]].0);
669 actual[0][i] = lo;
670 actual[1][i] = hi;
671 }
672 actual
673 }
674
675 #[test]
676 fn compress_2x_distinct_lanes() {
677 let h0: [u32; 8] = [
683 IV[0] ^ 0x0101_0020,
684 IV[1],
685 IV[2],
686 IV[3],
687 IV[4],
688 IV[5],
689 IV[6],
690 IV[7],
691 ];
692 let mut m0 = [0u32; 16];
693 m0[0] = 0x0063_6261;
694
695 let h1: [u32; 8] = [
696 0xDEAD_BEEF,
697 0xCAFE_BABE,
698 0x1234_5678,
699 0x9ABC_DEF0,
700 0x0BAD_F00D,
701 0xFEED_FACE,
702 0x0123_4567,
703 0x89AB_CDEF,
704 ];
705 let m1: [u32; 16] = array::from_fn(|i| (i as u32).wrapping_mul(0x0101_0101));
706
707 let actual = run_compress_2x([h0, h1], [m0, m1], [3, 64], [0, 0], [0xFFFF_FFFF, 0]);
708 assert_eq!(actual[0], ref_compress(h0, m0, 3, 0, 0xFFFF_FFFF));
709 assert_eq!(actual[1], ref_compress(h1, m1, 64, 0, 0));
710 }
711
712 #[test]
713 fn compress_2x_lane_independence() {
714 let h: [u32; 8] = [
722 IV[0] ^ 0x0101_0020,
723 IV[1],
724 IV[2],
725 IV[3],
726 IV[4],
727 IV[5],
728 IV[6],
729 IV[7],
730 ];
731 let mut m = [0u32; 16];
732 m[0] = 0x0063_6261;
733 let actual =
734 run_compress_2x([h, h], [m, [0u32; 16]], [3, 0], [0, 0], [0xFFFF_FFFF, 0xFFFF_FFFF]);
735 assert_eq!(actual[0], ref_compress(h, m, 3, 0, 0xFFFF_FFFF));
736 assert_eq!(actual[1], ref_compress(h, [0u32; 16], 0, 0, 0xFFFF_FFFF));
737 }
738
739 #[allow(clippy::too_many_arguments)]
744 fn run_compress_2x_seq(
745 h: [u32; 8],
746 m1: [u32; 16],
747 m2: [u32; 16],
748 t_lo1: u32,
749 t_hi1: u32,
750 last1: u32,
751 t_lo2: u32,
752 t_hi2: u32,
753 last2: u32,
754 ) -> ([u32; 8], [u32; 8]) {
755 let builder = CircuitBuilder::new();
756 let h_wires: [Wire; 8] = array::from_fn(|_| builder.add_witness());
757 let m1_wires: [Wire; 16] = array::from_fn(|_| builder.add_witness());
758 let m2_wires: [Wire; 16] = array::from_fn(|_| builder.add_witness());
759 let t_lo1_w = builder.add_witness();
760 let t_hi1_w = builder.add_witness();
761 let last1_w = builder.add_witness();
762 let t_lo2_w = builder.add_witness();
763 let t_hi2_w = builder.add_witness();
764 let last2_w = builder.add_witness();
765
766 let out = blake2s_compress_2x_seq(
767 &builder,
768 h_wires,
769 [m1_wires, m2_wires],
770 [t_lo1_w, t_lo2_w],
771 [t_hi1_w, t_hi2_w],
772 [last1_w, last2_w],
773 );
774 let out_inout: [Wire; 8] = array::from_fn(|_| builder.add_inout());
775 for i in 0..8 {
776 builder.assert_eq("out_match_2x_seq", out[i], out_inout[i]);
777 }
778
779 let circuit = builder.build();
780 let mut w = circuit.new_witness_filler();
781 for i in 0..8 {
782 w[h_wires[i]] = Word(h[i] as u64);
783 }
784 for i in 0..16 {
785 w[m1_wires[i]] = Word(m1[i] as u64);
786 w[m2_wires[i]] = Word(m2[i] as u64);
787 }
788 w[t_lo1_w] = Word(t_lo1 as u64);
789 w[t_hi1_w] = Word(t_hi1 as u64);
790 w[last1_w] = Word(last1 as u64);
791 w[t_lo2_w] = Word(t_lo2 as u64);
792 w[t_hi2_w] = Word(t_hi2 as u64);
793 w[last2_w] = Word(last2 as u64);
794
795 let s1 = ref_compress(h, m1, t_lo1, t_hi1, last1);
798 let s2 = ref_compress(s1, m2, t_lo2, t_hi2, last2);
799 for i in 0..8 {
800 w[out_inout[i]] = Word(pack2x(s2[i], s1[i]));
801 }
802 circuit.populate_wire_witness(&mut w).unwrap();
803
804 let mut s2_out = [0u32; 8];
805 let mut s1_out = [0u32; 8];
806 for i in 0..8 {
807 let (lo, hi) = unpack2x(w[out_inout[i]].0);
808 s2_out[i] = lo;
809 s1_out[i] = hi;
810 }
811 (s2_out, s1_out)
812 }
813
814 #[test]
815 fn compress_2x_seq_chains_two_blocks() {
816 let h: [u32; 8] = [
818 IV[0] ^ 0x0101_0020,
819 IV[1],
820 IV[2],
821 IV[3],
822 IV[4],
823 IV[5],
824 IV[6],
825 IV[7],
826 ];
827 let m1 = [0u32; 16];
828 let m2: [u32; 16] = array::from_fn(|i| i as u32);
829 let (s2, s1) = run_compress_2x_seq(h, m1, m2, 64, 0, 0, 100, 0, 0xFFFF_FFFF);
830 let exp_s1 = ref_compress(h, m1, 64, 0, 0);
831 let exp_s2 = ref_compress(exp_s1, m2, 100, 0, 0xFFFF_FFFF);
832 assert_eq!(s1, exp_s1);
833 assert_eq!(s2, exp_s2);
834 }
835
836 #[test]
837 fn compress_2x_seq_distinct_params() {
838 let h: [u32; 8] = [
847 0xDEAD_BEEF,
848 0xCAFE_BABE,
849 0x1234_5678,
850 0x9ABC_DEF0,
851 0x0BAD_F00D,
852 0xFEED_FACE,
853 0x0123_4567,
854 0x89AB_CDEF,
855 ];
856 let m1: [u32; 16] = array::from_fn(|i| (i as u32).wrapping_mul(0xDEAD_BEEF));
857 let m2: [u32; 16] = array::from_fn(|i| (i as u32).wrapping_mul(0x0101_0101));
858 let (s2, s1) = run_compress_2x_seq(h, m1, m2, 64, 0, 0, 40, 0, 0xFFFF_FFFF);
859 let exp_s1 = ref_compress(h, m1, 64, 0, 0);
860 let exp_s2 = ref_compress(exp_s1, m2, 40, 0, 0xFFFF_FFFF);
861 assert_eq!(s1, exp_s1);
862 assert_eq!(s2, exp_s2);
863 }
864
865 fn run_compress_2x_words(inputs: [u64; 27]) -> [u64; 8] {
868 let builder = CircuitBuilder::new();
869 let wires: [Wire; 27] = array::from_fn(|_| builder.add_witness());
870 let out = compress_2x_gates(
871 &builder,
872 array::from_fn(|i| wires[i]),
873 array::from_fn(|i| wires[8 + i]),
874 wires[24],
875 wires[25],
876 wires[26],
877 );
878 for wire in out {
879 builder.mark_inout(wire);
880 }
881
882 let circuit = builder.build();
883 let mut w = circuit.new_witness_filler();
884 for (wire, word) in iter::zip(wires, inputs) {
885 w[wire] = Word(word);
886 }
887 circuit.populate_wire_witness(&mut w).unwrap();
888
889 array::from_fn(|i| w[out[i]].as_u64())
890 }
891
892 fn word32() -> impl Strategy<Value = u32> {
900 prop_oneof![
901 3 => any::<u32>(),
902 1 => prop_oneof![Just(0), Just(1), Just(u32::MAX), Just(1 << 31), Just(u32::MAX >> 1)],
903 ]
904 }
905
906 fn h8() -> impl Strategy<Value = [u32; 8]> {
907 prop::array::uniform8(word32())
908 }
909
910 fn block16() -> impl Strategy<Value = [u32; 16]> {
911 prop::array::uniform16(word32())
912 }
913
914 proptest! {
915 #![proptest_config(ProptestConfig::with_cases(16))]
919
920 #[test]
921 fn compress_matches_reference(
922 h in h8(), m in block16(), t_lo in any::<u32>(), t_hi in any::<u32>(), last in any::<u32>(),
923 ) {
924 prop_assert_eq!(run_compress(h, m, t_lo, t_hi, last), ref_compress(h, m, t_lo, t_hi, last));
925 }
926
927 #[test]
928 fn compress_2x_lanes_are_independent(
929 h0 in h8(), h1 in h8(), m0 in block16(), m1 in block16(),
930 t0 in any::<u32>(), t1 in any::<u32>(), l0 in any::<u32>(), l1 in any::<u32>(),
931 ) {
932 let actual = run_compress_2x([h0, h1], [m0, m1], [t0, t1], [0, 0], [l0, l1]);
937 prop_assert_eq!(actual[0], ref_compress(h0, m0, t0, 0, l0));
938 prop_assert_eq!(actual[1], ref_compress(h1, m1, t1, 0, l1));
939 }
940
941 #[test]
942 fn compress_2x_hint_matches_its_gates(words in prop::collection::vec(any::<u64>(), 27)) {
943 let inputs: [u64; 27] = array::from_fn(|i| words[i]);
949
950 let mut hinted = [Word::ZERO; 8];
951 Blake2sCompress2x.execute(&[], &inputs.map(Word), &mut hinted);
952
953 prop_assert_eq!(hinted.map(|word| word.as_u64()), run_compress_2x_words(inputs));
954 }
955
956 #[test]
957 fn compress_2x_seq_matches_two_chained_references(
958 h in h8(), m1 in block16(), m2 in block16(),
959 t1 in any::<u32>(), l1 in any::<u32>(), t2 in any::<u32>(), l2 in any::<u32>(),
960 ) {
961 let (s2, s1) = run_compress_2x_seq(h, m1, m2, t1, 0, l1, t2, 0, l2);
969 let exp_s1 = ref_compress(h, m1, t1, 0, l1);
970 prop_assert_eq!(s1, exp_s1);
971 prop_assert_eq!(s2, ref_compress(exp_s1, m2, t2, 0, l2));
972 }
973 }
974}