1mod constants;
3#[cfg(test)]
4mod reference;
5
6use binius_core::word::Word;
7use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
8use constants::{C240, R512, TWEAK_TYPE_CFG, TWEAK_TYPE_MSG, TWEAK_TYPE_OUT};
9
10pub struct Skein512 {
30 pub n_blocks: usize,
32 pub message: Vec<[Wire; 8]>,
34 pub digest: [Wire; 8],
36}
37
38impl Skein512 {
39 pub fn new(builder: &CircuitBuilder, n_blocks: usize) -> Self {
40 assert!(n_blocks > 0, "n_blocks must be positive");
41
42 let message: Vec<[Wire; 8]> = (0..n_blocks)
44 .map(|_| std::array::from_fn(|_| builder.add_witness()))
45 .collect();
46
47 let digest: [Wire; 8] = std::array::from_fn(|_| builder.add_witness());
49
50 Self::build_circuit(builder, &message, digest, n_blocks);
52
53 Self {
54 n_blocks,
55 message,
56 digest,
57 }
58 }
59
60 fn build_circuit(
61 builder: &CircuitBuilder,
62 message: &[[Wire; 8]],
63 digest: [Wire; 8],
64 n_blocks: usize,
65 ) {
66 let mut cfg = [0u8; 64];
73 cfg[0..4].copy_from_slice(&0x3341_4853u32.to_le_bytes()); cfg[4..6].copy_from_slice(&1u16.to_le_bytes()); cfg[6..8].copy_from_slice(&0u16.to_le_bytes()); cfg[8..16].copy_from_slice(&512u64.to_le_bytes()); cfg[16] = 0; cfg[17] = 0; cfg[18] = 0; let cfg_words: [Wire; 8] = std::array::from_fn(|i| {
84 let mut word_bytes = [0u8; 8];
85 word_bytes.copy_from_slice(&cfg[i * 8..(i + 1) * 8]);
86 let word_val = u64::from_le_bytes(word_bytes);
87 builder.add_constant_64(word_val)
88 });
89
90 let pos_lo = builder.add_constant_64(32);
92 let pos_hi = builder.add_constant_64(0);
93 let (t_low, t_high) = tweak(builder, pos_lo, pos_hi, true, true, TWEAK_TYPE_CFG);
94 let t_cfg_wires = [t_low, t_high];
95
96 let cv0: [Wire; 8] = std::array::from_fn(|_| builder.add_constant_64(0));
98
99 let config_ubi_out = ubi_block(builder, cv0, t_cfg_wires, cfg_words);
101
102 let mut current_cv = config_ubi_out;
107
108 for block_idx in 0..n_blocks {
110 let pos_end = ((block_idx + 1) * 64) as u64;
112
113 let is_first = block_idx == 0;
116 let is_final = false; let pos_lo = builder.add_constant_64(pos_end);
120 let pos_hi = builder.add_constant_64(0); let (t_low, t_high) =
122 tweak(builder, pos_lo, pos_hi, is_first, is_final, TWEAK_TYPE_MSG);
123 let t_msg_wires = [t_low, t_high];
124
125 current_cv = ubi_block(builder, current_cv, t_msg_wires, message[block_idx]);
127 }
128
129 let final_pos = (n_blocks * 64) as u64;
136
137 let empty_block: [Wire; 8] = std::array::from_fn(|_| builder.add_constant_64(0));
139
140 let pos_lo_final = builder.add_constant_64(final_pos);
142 let pos_hi_final = builder.add_constant_64(0);
143 let (t_low, t_high) = tweak(
144 builder,
145 pos_lo_final,
146 pos_hi_final,
147 false,
148 true, TWEAK_TYPE_MSG,
150 );
151 let t_msg_final_wires = [t_low, t_high];
152
153 current_cv = ubi_block(builder, current_cv, t_msg_final_wires, empty_block);
155
156 let mut out_bytes = [0u8; 64];
163 out_bytes[0..8].copy_from_slice(&0u64.to_le_bytes()); let out_block: [Wire; 8] = std::array::from_fn(|i| {
168 let mut word_bytes = [0u8; 8];
169 word_bytes.copy_from_slice(&out_bytes[i * 8..(i + 1) * 8]);
170 let word_val = u64::from_le_bytes(word_bytes);
171 builder.add_constant_64(word_val)
172 });
173
174 let pos_lo = builder.add_constant_64(8);
176 let pos_hi = builder.add_constant_64(0);
177 let (t_low, t_high) = tweak(builder, pos_lo, pos_hi, true, true, TWEAK_TYPE_OUT);
178 let t_out_wires = [t_low, t_high];
179
180 let computed_digest = ubi_block(builder, current_cv, t_out_wires, out_block);
182
183 builder.assert_eq_v("skein512_digest", computed_digest, digest);
188 }
189
190 pub fn populate_message(&self, w: &mut WitnessFiller<'_>, message_blocks: &[[u8; 64]]) {
192 assert_eq!(
193 message_blocks.len(),
194 self.n_blocks,
195 "Message blocks length {} != expected {}",
196 message_blocks.len(),
197 self.n_blocks
198 );
199
200 for (block_idx, block_bytes) in message_blocks.iter().enumerate() {
202 for (word_idx, word_bytes) in block_bytes.chunks(8).enumerate() {
203 let mut padded_bytes = [0u8; 8];
204 padded_bytes[..word_bytes.len()].copy_from_slice(word_bytes);
205 let word_val = u64::from_le_bytes(padded_bytes);
206 w[self.message[block_idx][word_idx]] = Word(word_val);
207 }
208 }
209 }
210
211 pub fn populate_digest(&self, w: &mut WitnessFiller<'_>, expected_digest: [u8; 64]) {
213 for (i, word_bytes) in expected_digest.chunks(8).enumerate() {
215 let word_val = u64::from_le_bytes(word_bytes.try_into().unwrap());
216 w[self.digest[i]] = Word(word_val);
217 }
218 }
219
220 pub const fn digest_wires(&self) -> [Wire; 8] {
222 self.digest
223 }
224
225 pub fn message_wires(&self) -> &[[Wire; 8]] {
227 &self.message
228 }
229}
230
231fn mix(circuit: &CircuitBuilder, a: Wire, b: Wire, r: u32) -> (Wire, Wire) {
247 let (a_out, _) = circuit.iadd(a, b);
249
250 let b_rotated = circuit.rotl(b, r);
252 let b_out = circuit.bxor(b_rotated, a_out);
253
254 (a_out, b_out)
255}
256
257const fn permute_512(_circuit: &CircuitBuilder, x: [Wire; 8]) -> [Wire; 8] {
268 [x[2], x[1], x[4], x[7], x[6], x[5], x[0], x[3]]
269}
270
271fn threefish_round(circuit: &CircuitBuilder, v_in: [Wire; 8], round_idx: usize) -> [Wire; 8] {
287 let r = R512[round_idx % 8];
289
290 let (mix0_a, mix0_b) = mix(circuit, v_in[0], v_in[1], r[0]);
293 let (mix1_a, mix1_b) = mix(circuit, v_in[2], v_in[3], r[1]);
294 let (mix2_a, mix2_b) = mix(circuit, v_in[4], v_in[5], r[2]);
295 let (mix3_a, mix3_b) = mix(circuit, v_in[6], v_in[7], r[3]);
296
297 let mixed_state = [
299 mix0_a, mix0_b, mix1_a, mix1_b, mix2_a, mix2_b, mix3_a, mix3_b, ];
304
305 permute_512(circuit, mixed_state)
307}
308
309fn threefish_subkey(circuit: &CircuitBuilder, s: usize, k: [Wire; 9], t: [Wire; 3]) -> [Wire; 8] {
324 let mut subkey = std::array::from_fn(|i| k[(s + i) % 9]);
326
327 let (sum5, _) = circuit.iadd(subkey[5], t[s % 3]);
330 subkey[5] = sum5;
331
332 let (sum6, _) = circuit.iadd(subkey[6], t[(s + 1) % 3]);
334 subkey[6] = sum6;
335
336 let round_constant = circuit.add_constant_64(s as u64);
338 let (sum7, _) = circuit.iadd(subkey[7], round_constant);
339 subkey[7] = sum7;
340
341 subkey
342}
343
344fn tweak(
358 circuit: &CircuitBuilder,
359 pos_bytes_lo: Wire,
360 mut pos_bytes_hi: Wire,
361 is_first: bool,
362 is_final: bool,
363 cfg: u64,
364) -> (Wire, Wire) {
365 let low_bytes_mask = circuit.add_constant_64(u32::MAX as u64);
366 pos_bytes_hi = circuit.band(pos_bytes_hi, low_bytes_mask);
367
368 let t_low = pos_bytes_lo;
369 let mut t_high = circuit.bxor(pos_bytes_hi, circuit.add_constant_64(cfg << 56));
376
377 if is_first {
378 t_high = circuit.bxor(t_high, circuit.add_constant_64(1 << 62));
379 }
380
381 if is_final {
382 t_high = circuit.bxor(t_high, circuit.add_constant_64(1 << 63));
383 }
384
385 (t_low, t_high)
386}
387
388fn ubi_block(
400 circuit: &CircuitBuilder,
401 chaining_value: [Wire; 8],
402 tweak: [Wire; 2],
403 block: [Wire; 8],
404) -> [Wire; 8] {
405 let out = Threefish512Block::new(circuit, chaining_value, tweak, block).v_out;
407 std::array::from_fn(|i| circuit.bxor(out[i], block[i]))
408}
409
410struct Threefish4RoundsWithInjection {
427 v_out: [Wire; 8],
428}
429
430impl Threefish4RoundsWithInjection {
431 fn new(
432 circuit: &CircuitBuilder,
433 v_in: [Wire; 8],
434 k: [Wire; 9],
435 t: [Wire; 3],
436 group_idx: usize,
437 ) -> Self {
438 let subkey = threefish_subkey(circuit, group_idx, k, t);
440 let mut v_out = std::array::from_fn(|i| {
441 let (sum, _) = circuit.iadd(v_in[i], subkey[i]);
442 sum
443 });
444
445 let base = group_idx * 4;
447 for round in 0..4 {
448 v_out = threefish_round(circuit, v_out, base + round);
449 }
450
451 Self { v_out }
452 }
453}
454
455struct Threefish512Block {
467 v_out: [Wire; 8],
468}
469
470impl Threefish512Block {
471 fn new(circuit: &CircuitBuilder, key: [Wire; 8], tweak: [Wire; 2], block: [Wire; 8]) -> Self {
472 let c240 = circuit.add_constant_64(C240);
474 let mut k_vec = Vec::with_capacity(9);
475 let mut sum = key[0];
476 k_vec.push(key[0]);
477 for i in 1..8 {
478 k_vec.push(key[i]);
479 sum = circuit.bxor(sum, key[i]);
480 }
481 k_vec.push(circuit.bxor(c240, sum));
482 let k: [Wire; 9] = k_vec.try_into().expect("Vec to array conversion");
483
484 let t0 = tweak[0];
486 let t1 = tweak[1];
487 let t2 = circuit.bxor(t0, t1);
488 let t = [t0, t1, t2];
489
490 let mut v = block;
492
493 for g in 0..18 {
495 let group = Threefish4RoundsWithInjection::new(circuit, v, k, t, g);
496 v = group.v_out;
497 }
498
499 let subkey = threefish_subkey(circuit, 18, k, t);
501 let v_out = std::array::from_fn(|i| {
502 let (sum, _) = circuit.iadd(v[i], subkey[i]);
503 sum
504 });
505
506 Self { v_out }
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use binius_frontend::CircuitBuilder;
513
514 use super::*;
515 use crate::skein512::reference;
516
517 fn test_skein512_with_blocks(message_blocks: &[[u8; 64]]) {
519 let n_blocks = message_blocks.len();
520 let builder = CircuitBuilder::new();
521
522 let skein = Skein512::new(&builder, n_blocks);
523 let circuit = builder.build();
524
525 let expected_digest = reference::skein512(message_blocks.as_flattened());
526
527 let mut w = circuit.new_witness_filler();
528 skein.populate_message(&mut w, message_blocks);
529 skein.populate_digest(&mut w, expected_digest);
530
531 circuit.populate_wire_witness(&mut w).unwrap();
532 let cs = circuit.constraint_system();
533 cs.verify(&w.into_value_vec()).unwrap();
534 }
535
536 #[test]
537 fn test_skein512_single_block() {
538 let mut block = [0u8; 64];
539 block[0..3].copy_from_slice(b"abc");
540
541 test_skein512_with_blocks(&[block]);
542 }
543
544 #[test]
545 fn test_skein512_64_byte_zeros() {
546 test_skein512_with_blocks(&[[0u8; 64]]);
547 }
548
549 #[test]
550 fn test_skein512_two_blocks() {
551 let mut block1 = [0xAAu8; 64];
552 let mut block2 = [0x55u8; 64];
553
554 block1[0] = 0x01;
555 block2[63] = 0xFF;
556
557 test_skein512_with_blocks(&[block1, block2]);
558 }
559
560 #[test]
561 fn test_skein512_multiple_blocks() {
562 let blocks = [
564 [0x00u8; 64], [0xFFu8; 64], {
567 let mut block = [0u8; 64];
568 for (i, byte) in block.iter_mut().enumerate() {
569 *byte = (i % 256) as u8;
570 }
571 block
572 },
573 {
574 let mut block = [0u8; 64];
575 block[0..26].copy_from_slice(b"abcdefghijklmnopqrstuvwxyz");
576 block
577 },
578 ];
579
580 test_skein512_with_blocks(&blocks);
581 }
582
583 #[test]
585 fn test_mix_correctness() {
586 let test_cases = [
587 (0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 0, "Zero rotation"),
589 (0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 1, "Single bit rotation"),
590 (0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 8, "Byte rotation"),
591 (0x0123456789ABCDEFu64, 0xFEDCBA9876543210u64, 32, "Half-word rotation"),
592 (0u64, 0u64, 0, "Both zero, no rotation"),
593 (u64::MAX, u64::MAX, 0, "Both max, no rotation"),
594 (0u64, u64::MAX, 32, "Zero and max"),
595 (u64::MAX, 0u64, 32, "Max and zero"),
596 (0x8000000000000000u64, 0x0000000000000001u64, 1, "MSB and LSB"),
597 (0x0000000000000001u64, 0x8000000000000000u64, 16, "LSB and MSB with rotation"),
598 ];
599
600 for (a_val, b_val, r, description) in test_cases {
601 let (expected_a, expected_b) = reference::mix(a_val, b_val, r);
602
603 let circuit = CircuitBuilder::new();
604
605 let a_wire = circuit.add_witness();
606 let b_wire = circuit.add_witness();
607
608 let (a_out, b_out) = mix(&circuit, a_wire, b_wire, r);
609
610 let expected_a_wire = circuit.add_constant(Word(expected_a));
611 let expected_b_wire = circuit.add_constant(Word(expected_b));
612
613 circuit.assert_eq(format!("{}_a_out", description), a_out, expected_a_wire);
614 circuit.assert_eq(format!("{}_b_out", description), b_out, expected_b_wire);
615
616 let built_circuit = circuit.build();
617 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
618
619 let mut witness = built_circuit.new_witness_filler();
620
621 witness[a_wire] = Word(a_val);
622 witness[b_wire] = Word(b_val);
623
624 built_circuit.populate_wire_witness(&mut witness).unwrap();
625
626 let cs = built_circuit.constraint_system();
627
628 cs.verify(&witness.into_value_vec())
629 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
630 }
631 }
632
633 #[test]
635 fn test_permute512_correctness() {
636 let test_cases = [
637 (
639 [
640 0x0123456789ABCDEFu64,
641 0xFEDCBA9876543210u64,
642 0x1111111111111111u64,
643 0x2222222222222222u64,
644 0x3333333333333333u64,
645 0x4444444444444444u64,
646 0x5555555555555555u64,
647 0x6666666666666666u64,
648 ],
649 "Sequential pattern",
650 ),
651 ([0xAAAAAAAAAAAAAAAAu64; 8], "All same values"),
652 ([0u64, 1u64, 2u64, 3u64, 4u64, 5u64, 6u64, 7u64], "Simple incremental"),
653 (
654 [
655 u64::MAX,
656 0u64,
657 u64::MAX,
658 0u64,
659 u64::MAX,
660 0u64,
661 u64::MAX,
662 0u64,
663 ],
664 "Alternating pattern",
665 ),
666 ];
667
668 for (input_vals, description) in test_cases {
669 let expected = reference::permute_512(input_vals);
670
671 let circuit = CircuitBuilder::new();
672
673 let input_wires = std::array::from_fn(|_| circuit.add_witness());
674
675 let permuted = permute_512(&circuit, input_wires);
676
677 for (i, &expected_val) in expected.iter().enumerate() {
678 let expected_wire = circuit.add_constant(Word(expected_val));
679 circuit.assert_eq(format!("{}[{}]", description, i), permuted[i], expected_wire);
680 }
681
682 let built_circuit = circuit.build();
683
684 let mut witness = built_circuit.new_witness_filler();
685
686 for (i, &val) in input_vals.iter().enumerate() {
687 witness[input_wires[i]] = Word(val);
688 }
689
690 built_circuit.populate_wire_witness(&mut witness).unwrap();
691
692 let cs = built_circuit.constraint_system();
693
694 cs.verify(&witness.into_value_vec())
695 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
696 }
697 }
698
699 #[test]
701 fn test_threefish_round_correctness() {
702 let test_cases = [
704 (
706 0,
707 [
708 0x0123456789ABCDEFu64,
709 0xFEDCBA9876543210u64,
710 0x1111111111111111u64,
711 0x2222222222222222u64,
712 0x3333333333333333u64,
713 0x4444444444444444u64,
714 0x5555555555555555u64,
715 0x6666666666666666u64,
716 ],
717 "Round 0 with sequential pattern",
718 ),
719 (
720 1,
721 [
722 0x0123456789ABCDEFu64,
723 0xFEDCBA9876543210u64,
724 0x1111111111111111u64,
725 0x2222222222222222u64,
726 0x3333333333333333u64,
727 0x4444444444444444u64,
728 0x5555555555555555u64,
729 0x6666666666666666u64,
730 ],
731 "Round 1 with sequential pattern",
732 ),
733 (
734 7,
735 [
736 0x0123456789ABCDEFu64,
737 0xFEDCBA9876543210u64,
738 0x1111111111111111u64,
739 0x2222222222222222u64,
740 0x3333333333333333u64,
741 0x4444444444444444u64,
742 0x5555555555555555u64,
743 0x6666666666666666u64,
744 ],
745 "Round 7 with sequential pattern",
746 ),
747 (
748 8,
749 [
750 0x0123456789ABCDEFu64,
751 0xFEDCBA9876543210u64,
752 0x1111111111111111u64,
753 0x2222222222222222u64,
754 0x3333333333333333u64,
755 0x4444444444444444u64,
756 0x5555555555555555u64,
757 0x6666666666666666u64,
758 ],
759 "Round 8 (same as round 0)",
760 ),
761 ];
762
763 for (round_idx, input_state, description) in test_cases {
764 let expected = reference::threefish_round(input_state, round_idx);
765
766 let circuit = CircuitBuilder::new();
767
768 let input_wires = std::array::from_fn(|_| circuit.add_witness());
769
770 let v_out = threefish_round(&circuit, input_wires, round_idx);
771
772 for i in 0..8 {
773 let expected_wire = circuit.add_constant(Word(expected[i]));
774 circuit.assert_eq(format!("{}[{}]", description, i), v_out[i], expected_wire);
775 }
776
777 let built_circuit = circuit.build();
778 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
779
780 let mut witness = built_circuit.new_witness_filler();
781
782 for (i, &val) in input_state.iter().enumerate() {
783 witness[input_wires[i]] = Word(val);
784 }
785
786 built_circuit.populate_wire_witness(&mut witness).unwrap();
787
788 let cs = built_circuit.constraint_system();
789
790 cs.verify(&witness.into_value_vec())
791 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
792 }
793 }
794
795 #[test]
797 fn test_threefish_subkey_correctness() {
798 let test_cases = [
799 (0, "First subkey (s=0)"),
801 (1, "Second subkey (s=1)"),
802 (5, "Middle subkey (s=5)"),
803 (9, "Wrap-around subkey (s=9)"),
804 (18, "Final subkey (s=18)"),
805 ];
806
807 for (s, description) in test_cases {
808 let k_vals = [
809 0x0123456789ABCDEFu64,
810 0xFEDCBA9876543210u64,
811 0x1111111111111111u64,
812 0x2222222222222222u64,
813 0x3333333333333333u64,
814 0x4444444444444444u64,
815 0x5555555555555555u64,
816 0x6666666666666666u64,
817 0x7777777777777777u64, ];
819
820 let t_vals = [
821 0xAAAAAAAAAAAAAAAAu64,
822 0xBBBBBBBBBBBBBBBBu64,
823 0x1111111111111111u64, ];
825
826 let expected = reference::threefish_subkey(s, k_vals, t_vals);
827
828 let circuit = CircuitBuilder::new();
829
830 let k_wires = std::array::from_fn(|_| circuit.add_witness());
831 let t_wires = std::array::from_fn(|_| circuit.add_witness());
832
833 let subkey = threefish_subkey(&circuit, s, k_wires, t_wires);
834
835 for (i, &expected_val) in expected.iter().enumerate() {
836 let expected_wire = circuit.add_constant(Word(expected_val));
837 circuit.assert_eq(format!("{}[{}]", description, i), subkey[i], expected_wire);
838 }
839
840 let built_circuit = circuit.build();
841 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
842
843 let mut witness = built_circuit.new_witness_filler();
844
845 for (i, &val) in k_vals.iter().enumerate() {
846 witness[k_wires[i]] = Word(val);
847 }
848 for (i, &val) in t_vals.iter().enumerate() {
849 witness[t_wires[i]] = Word(val);
850 }
851
852 built_circuit.populate_wire_witness(&mut witness).unwrap();
853
854 let cs = built_circuit.constraint_system();
855
856 cs.verify(&witness.into_value_vec())
857 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
858 }
859 }
860
861 #[test]
862 fn test_threefish_subkey_edge_cases() {
863 let test_cases = [
864 ([0u64; 9], [0u64; 3], "All zeros"),
866 ([u64::MAX; 9], [u64::MAX; 3], "All ones"),
867 ([0xDEADBEEFCAFEBABEu64; 9], [0x0123456789ABCDEFu64; 3], "Mixed pattern"),
868 ];
869
870 for (k_vals, t_vals, description) in test_cases {
871 let circuit = CircuitBuilder::new();
872
873 let k_wires = std::array::from_fn(|_| circuit.add_witness());
874 let t_wires = std::array::from_fn(|_| circuit.add_witness());
875
876 let s = 9;
877 let subkey = threefish_subkey(&circuit, s, k_wires, t_wires);
878 let expected = reference::threefish_subkey(s, k_vals, t_vals);
879
880 for i in 0..8 {
881 circuit.assert_eq(
882 format!("{}[{}]", description, i),
883 subkey[i],
884 circuit.add_constant(Word(expected[i])),
885 );
886 }
887
888 let built_circuit = circuit.build();
889 let mut witness = built_circuit.new_witness_filler();
890
891 for (i, &val) in k_vals.iter().enumerate() {
892 witness[k_wires[i]] = Word(val);
893 }
894 for (i, &val) in t_vals.iter().enumerate() {
895 witness[t_wires[i]] = Word(val);
896 }
897
898 built_circuit.populate_wire_witness(&mut witness).unwrap();
899
900 let cs = built_circuit.constraint_system();
901 cs.verify(&witness.into_value_vec())
902 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
903 }
904 }
905
906 #[test]
908 fn test_tweak_correctness() {
909 let test_cases = [
910 (0u128, false, false, "CFG: neither first nor final"),
912 (0u128, true, false, "CFG: first only"),
913 (0u128, false, true, "CFG: final only"),
914 (0u128, true, true, "CFG: both first and final"),
915 (64u128, false, false, "MSG 64 bytes: neither"),
916 (64u128, true, false, "MSG 64 bytes: first only"),
917 (64u128, false, true, "MSG 64 bytes: final only"),
918 (64u128, true, true, "MSG 64 bytes: both"),
919 (128u128, false, false, "OUT 128 bytes: neither"),
920 (128u128, true, false, "OUT 128 bytes: first only"),
921 (128u128, false, true, "OUT 128 bytes: final only"),
922 (128u128, true, true, "OUT 128 bytes: both"),
923 (0x123456789ABCDEu128, true, false, "MSG large pos: first only"),
925 (0x123456789ABCDEu128, false, true, "MSG large pos: final only"),
926 ];
927
928 fn test_tweak_inner(
929 pos_bytes: u128,
930 is_first: bool,
931 is_final: bool,
932 description: &str,
933 cfg: u64,
934 ) {
935 let expected = reference::tweak(
936 cfg,
937 pos_bytes as u64,
938 (pos_bytes >> 64) as u64,
939 is_first,
940 is_final,
941 );
942
943 let circuit = CircuitBuilder::new();
944 let pos_t0 = circuit.add_witness();
945 let pos_t1 = circuit.add_witness();
946
947 let (t_low, t_high) = tweak(&circuit, pos_t0, pos_t1, is_first, is_final, cfg);
948
949 let expected_t0 = circuit.add_constant(Word(expected[0]));
950 let expected_t1 = circuit.add_constant(Word(expected[1]));
951
952 circuit.assert_eq(format!("{}_t_low", description), t_low, expected_t0);
953 circuit.assert_eq(format!("{}_t_high", description), t_high, expected_t1);
954
955 let built_circuit = circuit.build();
956 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
957
958 let mut witness = built_circuit.new_witness_filler();
959
960 witness[pos_t0] = Word(pos_bytes as u64);
961 witness[pos_t1] = Word((pos_bytes >> 64) as u64);
962
963 built_circuit.populate_wire_witness(&mut witness).unwrap();
964
965 let cs = built_circuit.constraint_system();
966
967 cs.verify(&witness.into_value_vec())
968 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
969 }
970
971 for (pos_bytes, is_first, is_final, description) in test_cases {
972 test_tweak_inner(pos_bytes, is_first, is_final, description, TWEAK_TYPE_OUT);
973 }
974 }
975
976 #[test]
978 fn test_ubi_block_correctness() {
979 let test_cases = [
981 (
983 [
984 0x0123456789ABCDEFu64,
985 0xFEDCBA9876543210u64,
986 0x1111111111111111u64,
987 0x2222222222222222u64,
988 0x3333333333333333u64,
989 0x4444444444444444u64,
990 0x5555555555555555u64,
991 0x6666666666666666u64,
992 ],
993 [0xAAAAAAAAAAAAAAAAu64, 0xBBBBBBBBBBBBBBBBu64],
994 [
995 0x0F0E0D0C0B0A0908u64,
996 0x0706050403020100u64,
997 0x1111111111111111u64,
998 0x2222222222222222u64,
999 0x3333333333333333u64,
1000 0x4444444444444444u64,
1001 0x5555555555555555u64,
1002 0x6666666666666666u64,
1003 ],
1004 "Basic pattern",
1005 ),
1006 ([0u64; 8], [0u64; 2], [u64::MAX; 8], "All zeros chaining/tweak, all ones block"),
1007 ([u64::MAX; 8], [u64::MAX; 2], [0u64; 8], "All ones chaining/tweak, all zeros block"),
1008 ];
1009
1010 for (chaining_value, tweak, block, description) in test_cases {
1011 let expected = reference::ubi_block(chaining_value, tweak, block);
1012
1013 let circuit = CircuitBuilder::new();
1014 let chaining_wires = std::array::from_fn(|_| circuit.add_witness());
1015 let tweak_wires = std::array::from_fn(|_| circuit.add_witness());
1016 let block_wires = std::array::from_fn(|_| circuit.add_witness());
1017
1018 let g_out = ubi_block(&circuit, chaining_wires, tweak_wires, block_wires);
1019
1020 for i in 0..8 {
1021 let expected_wire = circuit.add_constant(Word(expected[i]));
1022 circuit.assert_eq(format!("{}[{}]", description, i), g_out[i], expected_wire);
1023 }
1024
1025 let built_circuit = circuit.build();
1026 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1027
1028 let mut witness = built_circuit.new_witness_filler();
1029
1030 for (i, &val) in chaining_value.iter().enumerate() {
1031 witness[chaining_wires[i]] = Word(val);
1032 }
1033 for (i, &val) in tweak.iter().enumerate() {
1034 witness[tweak_wires[i]] = Word(val);
1035 }
1036 for (i, &val) in block.iter().enumerate() {
1037 witness[block_wires[i]] = Word(val);
1038 }
1039
1040 built_circuit.populate_wire_witness(&mut witness).unwrap();
1041
1042 let cs = built_circuit.constraint_system();
1043 cs.verify(&witness.into_value_vec())
1044 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1045 }
1046 }
1047
1048 #[test]
1050 fn test_threefish_4rounds_with_injection_correctness() {
1051 let test_cases = [
1052 (
1054 0,
1055 [
1056 0x0123456789ABCDEFu64,
1057 0xFEDCBA9876543210u64,
1058 0x1111111111111111u64,
1059 0x2222222222222222u64,
1060 0x3333333333333333u64,
1061 0x4444444444444444u64,
1062 0x5555555555555555u64,
1063 0x6666666666666666u64,
1064 ],
1065 [
1066 0x0F0E0D0C0B0A0908u64,
1067 0x0706050403020100u64,
1068 0x1111111111111111u64,
1069 0x2222222222222222u64,
1070 0x3333333333333333u64,
1071 0x4444444444444444u64,
1072 0x5555555555555555u64,
1073 0x6666666666666666u64,
1074 0x7777777777777777u64,
1075 ],
1076 [
1077 0xAAAAAAAAAAAAAAAAu64,
1078 0xBBBBBBBBBBBBBBBBu64,
1079 0x1111111111111111u64,
1080 ],
1081 "Group 0, sequential pattern",
1082 ),
1083 (
1084 5,
1085 [
1086 0xDEADBEEFCAFEBABEu64,
1087 0x0123456789ABCDEFu64,
1088 0xFEDCBA9876543210u64,
1089 0x1111111111111111u64,
1090 0x2222222222222222u64,
1091 0x3333333333333333u64,
1092 0x4444444444444444u64,
1093 0x5555555555555555u64,
1094 ],
1095 [0x1111111111111111u64; 9],
1096 [
1097 0x2222222222222222u64,
1098 0x3333333333333333u64,
1099 0x1111111111111111u64,
1100 ],
1101 "Group 5, mixed pattern",
1102 ),
1103 (17, [0u64; 8], [u64::MAX; 9], [u64::MAX; 3], "Group 17, all zeros and all ones"),
1104 ];
1105
1106 for (group_idx, v_in, k, t, description) in test_cases {
1107 let expected = reference::threefish_4rounds_with_injection(v_in, k, t, group_idx);
1108
1109 let circuit = CircuitBuilder::new();
1110 let v_in_wires = std::array::from_fn(|_| circuit.add_witness());
1111 let k_wires = std::array::from_fn(|_| circuit.add_witness());
1112 let t_wires = std::array::from_fn(|_| circuit.add_witness());
1113
1114 let comp = Threefish4RoundsWithInjection::new(
1115 &circuit, v_in_wires, k_wires, t_wires, group_idx,
1116 );
1117
1118 for i in 0..8 {
1119 let expected_wire = circuit.add_constant(Word(expected[i]));
1120 circuit.assert_eq(format!("{}[{}]", description, i), comp.v_out[i], expected_wire);
1121 }
1122
1123 let built_circuit = circuit.build();
1124 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1125
1126 let mut witness = built_circuit.new_witness_filler();
1127
1128 for (i, &val) in v_in.iter().enumerate() {
1129 witness[v_in_wires[i]] = Word(val);
1130 }
1131 for (i, &val) in k.iter().enumerate() {
1132 witness[k_wires[i]] = Word(val);
1133 }
1134 for (i, &val) in t.iter().enumerate() {
1135 witness[t_wires[i]] = Word(val);
1136 }
1137
1138 built_circuit.populate_wire_witness(&mut witness).unwrap();
1139
1140 let cs = built_circuit.constraint_system();
1141 cs.verify(&witness.into_value_vec())
1142 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1143 }
1144 }
1145
1146 #[test]
1148 fn test_threefish512_block_correctness() {
1149 let test_cases = [
1151 (
1153 [
1154 0x0123456789ABCDEFu64,
1155 0xFEDCBA9876543210u64,
1156 0x1111111111111111u64,
1157 0x2222222222222222u64,
1158 0x3333333333333333u64,
1159 0x4444444444444444u64,
1160 0x5555555555555555u64,
1161 0x6666666666666666u64,
1162 ],
1163 [0xAAAAAAAAAAAAAAAAu64, 0xBBBBBBBBBBBBBBBBu64],
1164 [
1165 0x0F0E0D0C0B0A0908u64,
1166 0x0706050403020100u64,
1167 0x1111111111111111u64,
1168 0x2222222222222222u64,
1169 0x3333333333333333u64,
1170 0x4444444444444444u64,
1171 0x5555555555555555u64,
1172 0x6666666666666666u64,
1173 ],
1174 "Basic pattern",
1175 ),
1176 ([0u64; 8], [0u64; 2], [u64::MAX; 8], "All zeros key/tweak, all ones block"),
1177 ([u64::MAX; 8], [u64::MAX; 2], [0u64; 8], "All ones key/tweak, all zeros block"),
1178 ];
1179
1180 for (key, tweak, block, description) in test_cases {
1181 let expected = reference::threefish512_block(key, tweak, block);
1182
1183 let circuit = CircuitBuilder::new();
1184 let key_wires = std::array::from_fn(|_| circuit.add_witness());
1185 let tweak_wires = std::array::from_fn(|_| circuit.add_witness());
1186 let block_wires = std::array::from_fn(|_| circuit.add_witness());
1187
1188 let comp = Threefish512Block::new(&circuit, key_wires, tweak_wires, block_wires);
1189
1190 for i in 0..8 {
1191 let expected_wire = circuit.add_constant(Word(expected[i]));
1192 circuit.assert_eq(format!("{}[{}]", description, i), comp.v_out[i], expected_wire);
1193 }
1194
1195 let built_circuit = circuit.build();
1196 assert!(built_circuit.n_gates() > 0, "Circuit should have gates for {}", description);
1197
1198 let mut witness = built_circuit.new_witness_filler();
1199
1200 for (i, &val) in key.iter().enumerate() {
1201 witness[key_wires[i]] = Word(val);
1202 }
1203 for (i, &val) in tweak.iter().enumerate() {
1204 witness[tweak_wires[i]] = Word(val);
1205 }
1206 for (i, &val) in block.iter().enumerate() {
1207 witness[block_wires[i]] = Word(val);
1208 }
1209
1210 built_circuit.populate_wire_witness(&mut witness).unwrap();
1211
1212 let cs = built_circuit.constraint_system();
1213 cs.verify(&witness.into_value_vec())
1214 .unwrap_or_else(|_| panic!("Constraints verification failed for {}", description));
1215 }
1216 }
1217}