binius_hash/groestl/arch/
portable.rs1#![allow(clippy::needless_range_loop)]
4
5use std::array;
6
7use binius_field::{
8 arch::packed_aes_64::PackedAESBinaryField8x8b, AESTowerField8b, Field,
9 PackedAESBinaryField64x8b, PackedExtensionIndexable, PackedField,
10};
11use lazy_static::lazy_static;
12
13use super::groestl_table::TABLE;
14
15const ROUND_SIZE: usize = 10;
16
17#[inline(always)]
19const fn shift_p_func(row: usize, col: usize) -> usize {
20 let new_row = row;
21 let new_col = (row + col) % 8;
22 new_col * 8 + new_row
23}
24
25#[inline(always)]
27const fn shift_q_func(row: usize, col: usize) -> usize {
28 let new_row = row;
29 let new_col = (col + 2 * row - row / 4 + 1) % 8;
30 new_col * 8 + new_row
31}
32
33lazy_static! {
34 static ref ROW_0_SELECT: [PackedAESBinaryField64x8b; 10] = array::from_fn(|r| {
35 PackedAESBinaryField64x8b::from_fn(|i| {
36 let selector = i % 8;
37 if selector == 0 {
38 AESTowerField8b::from(r as u8)
39 } else {
40 AESTowerField8b::ZERO
41 }
42 })
43 });
44 static ref ROW_7_SELECT: [PackedAESBinaryField64x8b; 10] = array::from_fn(|r| {
45 PackedAESBinaryField64x8b::from_fn(|i| {
46 let selector = i % 8;
47 if selector == 7 {
48 AESTowerField8b::from(r as u8)
49 } else {
50 AESTowerField8b::ZERO
51 }
52 })
53 });
54 static ref ROUND_CONSTANT_P: PackedAESBinaryField64x8b =
55 PackedAESBinaryField64x8b::from_fn(|i| {
56 let selector = i % 8;
57 if selector == 0 {
58 AESTowerField8b::new(0x10 * (i / 8) as u8)
59 } else {
60 AESTowerField8b::ZERO
61 }
62 });
63 static ref ROUND_CONSTANT_Q: PackedAESBinaryField64x8b =
64 PackedAESBinaryField64x8b::from_fn(|i| {
65 let selector = i % 8;
66 if selector == 7 {
67 AESTowerField8b::new(0xff ^ (0x10 * (i / 8) as u8))
68 } else {
69 AESTowerField8b::new(0xff)
70 }
71 });
72}
73
74#[derive(Debug, Clone, Default)]
77pub struct Groestl256Core;
78
79impl Groestl256Core {
80 #[inline(always)]
81 fn add_round_constants_q(
82 &self,
83 x: PackedAESBinaryField64x8b,
84 r: usize,
85 ) -> PackedAESBinaryField64x8b {
86 x + ROW_7_SELECT[r] + *ROUND_CONSTANT_Q
87 }
88
89 #[inline(always)]
90 fn add_round_constants_p(
91 &self,
92 x: PackedAESBinaryField64x8b,
93 r: usize,
94 ) -> PackedAESBinaryField64x8b {
95 x + ROW_0_SELECT[r] + *ROUND_CONSTANT_P
96 }
97
98 #[inline(always)]
99 fn sub_mix_shift(
100 &self,
101 x: PackedAESBinaryField64x8b,
102 shift_func: fn(usize, usize) -> usize,
103 ) -> PackedAESBinaryField64x8b {
104 let x = [x];
105 let input: &[AESTowerField8b] = PackedAESBinaryField64x8b::unpack_base_scalars(&x);
106 let mut state_arr = [PackedAESBinaryField64x8b::zero()];
107 let state: &mut [AESTowerField8b] =
108 PackedAESBinaryField64x8b::unpack_base_scalars_mut(&mut state_arr);
109
110 for col in 0..8 {
111 let mut final_col: PackedAESBinaryField8x8b = PackedAESBinaryField8x8b::zero();
112 for row in 0..8 {
113 let shifted = shift_func(row, col);
114 final_col += PackedAESBinaryField8x8b::from_underlier(
115 TABLE[row][input[shifted].val() as usize],
116 );
117 }
118 let final_col = [final_col];
119 state[col * 8..col * 8 + 8]
120 .copy_from_slice(PackedAESBinaryField8x8b::unpack_base_scalars(&final_col));
121 }
122
123 state_arr[0]
124 }
125
126 pub fn permutation_pq(
129 &self,
130 p: PackedAESBinaryField64x8b,
131 q: PackedAESBinaryField64x8b,
132 ) -> (PackedAESBinaryField64x8b, PackedAESBinaryField64x8b) {
133 let mut p = p;
134 let mut q = q;
135 for r in 0..ROUND_SIZE {
136 p = self.add_round_constants_p(p, r);
137 q = self.add_round_constants_q(q, r);
138 p = self.sub_mix_shift(p, shift_p_func);
139 q = self.sub_mix_shift(q, shift_q_func);
140 }
141
142 (p, q)
143 }
144
145 pub fn permutation_p(&self, p: PackedAESBinaryField64x8b) -> PackedAESBinaryField64x8b {
148 let mut p = p;
149 for r in 0..ROUND_SIZE {
150 p = self.add_round_constants_p(p, r);
151 p = self.sub_mix_shift(p, shift_p_func);
152 }
153 p
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use std::array;
160
161 use super::*;
162
163 #[test]
164 fn test_permutation_peq() {
165 let expectedp: [u64; 8] = [
166 0x3c82be9a692fc68a,
167 0x0bcb7ee32d38376a,
168 0x02bc3221a92c42f5,
169 0xb00d24521eb9f4f6,
170 0xbe1e23fee0be4378,
171 0x7f8dc5bb346400d9,
172 0x5b54cf26259832b7,
173 0xb9ff91384b23b6ef,
174 ];
175 let expectedq: [u64; 8] = [
176 0x08cce1f96d30d072,
177 0xc59e24a275252ca5,
178 0x078b6474e25e7576,
179 0x29659cf868d046c1,
180 0x81703d4bbae7369b,
181 0x3d03ee6d9462745d,
182 0xa0688a2d116c3c6e,
183 0xb764b88eb2cc185f,
184 ];
185
186 let input: [PackedAESBinaryField64x8b; 2] = array::from_fn(|off| {
187 PackedAESBinaryField64x8b::from_fn(|i| AESTowerField8b::new((64 * off + i) as u8))
188 });
189
190 let instance = Groestl256Core;
191 let (pout, qout) = instance.permutation_pq(input[0], input[1]);
192
193 let pout = (0..8)
194 .map(|i| {
195 u64::from_be_bytes(
196 (0..8)
197 .map(|j| pout.get(i * 8 + j).val())
198 .collect::<Vec<_>>()
199 .try_into()
200 .unwrap(),
201 )
202 })
203 .collect::<Vec<_>>();
204 let pout: [u64; 8] = pout.try_into().unwrap();
205 assert_eq!(expectedp, pout);
206
207 let qout = (0..8)
208 .map(|i| {
209 u64::from_be_bytes(
210 (0..8)
211 .map(|j| qout.get(i * 8 + j).val())
212 .collect::<Vec<_>>()
213 .try_into()
214 .unwrap(),
215 )
216 })
217 .collect::<Vec<_>>();
218 let qout: [u64; 8] = qout.try_into().unwrap();
219 assert_eq!(expectedq, qout);
220 }
221}