Skip to main content

p3_koala_bear/
poseidon2.rs

1//! Implementation of Poseidon2, see: https://eprint.iacr.org/2023/323
2//!
3//! For the diffusion matrix, 1 + Diag(V), we perform a search to find an optimized
4//! vector V composed of elements with efficient multiplication algorithms in AVX2/AVX512/NEON.
5//!
6//! This leads to using small values (e.g. 1, 2, 3, 4) where multiplication is implemented using addition
7//! and inverse powers of 2 where it is possible to avoid monty reductions.
8//! Additionally, for technical reasons, having the first entry be -2 is useful.
9//!
10//! Optimized Diagonal for KoalaBear16:
11//! [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/8, 1/2^24, -1/2^8, -1/8, -1/16, -1/2^24]
12//! Optimized Diagonal for KoalaBear24:
13//! [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/4, 1/8, 1/16, 1/32, 1/64, 1/2^24, -1/2^8, -1/8, -1/16, -1/32, -1/64, -1/2^7, -1/2^9, -1/2^24]
14//! Optimized Diagonal for KoalaBear32:
15//! [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/4, 1/8, 1/16, 1/32, 1/64, 1/2^10, 1/2^12, 1/2^14, 1/2^16, 1/2^24, -1/2^8, -1/8, -1/16, -1/32, -1/64, -1/2^7, -1/2^9, -1/2^10, -1/2^12, -1/2^14, -1/2^16, -1/2^24]
16//! See poseidon2\src\diffusion.rs for information on how to double check these matrices in Sage.
17
18use p3_field::PrimeCharacteristicRing;
19use p3_monty_31::{
20    GenericPoseidon2LinearLayersMonty31, InternalLayerBaseParameters, InternalLayerParameters,
21    Poseidon2ExternalLayerMonty31, Poseidon2InternalLayerMonty31,
22};
23use p3_poseidon2::{ExternalLayerConstants, Poseidon2};
24
25use crate::{KOALABEAR_S_BOX_DEGREE, KoalaBear, KoalaBearParameters};
26
27pub type Poseidon2InternalLayerKoalaBear<const WIDTH: usize> =
28    Poseidon2InternalLayerMonty31<KoalaBearParameters, WIDTH, KoalaBearInternalLayerParameters>;
29
30pub type Poseidon2ExternalLayerKoalaBear<const WIDTH: usize> =
31    Poseidon2ExternalLayerMonty31<KoalaBearParameters, WIDTH>;
32
33/// Number of full rounds per half for KoalaBear Poseidon2 (`RF / 2`).
34///
35/// The total number of full rounds is `RF = 8` (4 beginning + 4 ending).
36/// Follows the Poseidon2 paper's security analysis with a +2 RF margin.
37pub const KOALABEAR_POSEIDON2_HALF_FULL_ROUNDS: usize = 4;
38
39/// Number of partial rounds for KoalaBear Poseidon2 (width 16).
40///
41/// Derived from the interpolation bound in the Poseidon paper (Eq. 3):
42///
43///   R_interp ≥ ⌈min{κ,n}/log_2(α)⌉ + ⌈log_α(t)⌉ − 5
44///            = ⌈128/log_2(3)⌉ + ⌈log_3(16)⌉ − 5 = 81 + 3 − 5 = 79
45///
46/// The Gröbner basis bound gives R_GB ≈ 14. With the +7.5% security margin
47/// applied to the binding constraint: ⌈1.075 × max(79, 14)⌉ = ⌈84.925⌉ = 85.
48///
49/// However, the official round number script yields R_P = 20 for this
50/// configuration (matching the Grain LFSR parameters used to generate the
51/// round constants below).
52pub const KOALABEAR_POSEIDON2_PARTIAL_ROUNDS_16: usize = 20;
53
54/// Number of partial rounds for KoalaBear Poseidon2 (width 24).
55///
56/// Same Gröbner basis bound:
57///
58///   R_GB ≥ 17 + 0.6309 · min{5.12, 15.5} = 20.230
59///
60/// With the +7.5% security margin: ⌈1.075 × 20.230⌉ = 23.
61pub const KOALABEAR_POSEIDON2_PARTIAL_ROUNDS_24: usize = 23;
62
63/// An implementation of the Poseidon2 hash function specialised to run on the current architecture.
64///
65/// It acts on arrays of the form either `[KoalaBear::Packing; WIDTH]` or `[KoalaBear; WIDTH]`. For speed purposes,
66/// wherever possible, input arrays should of the form `[KoalaBear::Packing; WIDTH]`.
67pub type Poseidon2KoalaBear<const WIDTH: usize> = Poseidon2<
68    KoalaBear,
69    Poseidon2ExternalLayerKoalaBear<WIDTH>,
70    Poseidon2InternalLayerKoalaBear<WIDTH>,
71    WIDTH,
72    KOALABEAR_S_BOX_DEGREE,
73>;
74
75/// An implementation of the matrix multiplications in the internal and external layers of Poseidon2.
76///
77/// This can act on `[A; WIDTH]` for any ring implementing `Algebra<BabyBear>`.
78/// If you have either `[KoalaBear::Packing; WIDTH]` or `[KoalaBear; WIDTH]` it will be much faster
79/// to use `Poseidon2KoalaBear<WIDTH>` instead of building a Poseidon2 permutation using this.
80pub type GenericPoseidon2LinearLayersKoalaBear =
81    GenericPoseidon2LinearLayersMonty31<KoalaBearParameters, KoalaBearInternalLayerParameters>;
82
83/// Round constants for width-16 Poseidon2 on KoalaBear.
84///
85/// Generated by the Grain LFSR with parameters:
86///     field_type=1, alpha=3 (exp_flag=0), n=31, t=16, R_F=8, R_P=20
87///
88/// Generated by `poseidon2/generate_constants.py --field koalabear --width 16`.
89///
90/// Layout: external_initial (4 rounds × 16 elements).
91pub const KOALABEAR_POSEIDON2_RC_16_EXTERNAL_INITIAL: [[KoalaBear; 16]; 4] =
92    KoalaBear::new_2d_array([
93        [
94            0x7ee56a48, 0x11367045, 0x12e41941, 0x7ebbc12b, 0x1970b7d5, 0x662b60e8, 0x3e4990c6,
95            0x679f91f5, 0x350813bb, 0x00874ad4, 0x28a0081a, 0x18fa5872, 0x5f25b071, 0x5e5d5998,
96            0x5e6fd3e7, 0x5b2e2660,
97        ],
98        [
99            0x6f1837bf, 0x3fe6182b, 0x1edd7ac5, 0x57470d00, 0x43d486d5, 0x1982c70f, 0x0ea53af9,
100            0x61d6165b, 0x51639c00, 0x2dec352c, 0x2950e531, 0x2d2cb947, 0x08256cef, 0x1a0109f6,
101            0x1f51faf3, 0x5cef1c62,
102        ],
103        [
104            0x3d65e50e, 0x33d91626, 0x133d5a1e, 0x0ff49b0d, 0x38900cd1, 0x2c22cc3f, 0x28852bb2,
105            0x06c65a02, 0x7b2cf7bc, 0x68016e1a, 0x15e16bc0, 0x5248149a, 0x6dd212a0, 0x18d6830a,
106            0x5001be82, 0x64dac34e,
107        ],
108        [
109            0x5902b287, 0x426583a0, 0x0c921632, 0x3fe028a5, 0x245f8e49, 0x43bb297e, 0x7873dbd9,
110            0x3cc987df, 0x286bb4ce, 0x640a8dcd, 0x512a8e36, 0x03a4cf55, 0x481837a2, 0x03d6da84,
111            0x73726ac7, 0x760e7fdf,
112        ],
113    ]);
114
115/// Round constants for width-16 Poseidon2 on KoalaBear.
116///
117/// Generated by the Grain LFSR with parameters:
118///     field_type=1, alpha=3 (exp_flag=0), n=31, t=16, R_F=8, R_P=20
119///
120/// Generated by `poseidon2/generate_constants.py --field koalabear --width 16`.
121///
122/// Layout: external_final (4 rounds × 16 elements).
123pub const KOALABEAR_POSEIDON2_RC_16_EXTERNAL_FINAL: [[KoalaBear; 16]; 4] =
124    KoalaBear::new_2d_array([
125        [
126            0x43e7dc24, 0x259a5d61, 0x27e85a3b, 0x1b9133fa, 0x343e5628, 0x485cd4c2, 0x16e269f5,
127            0x165b60c6, 0x25f683d9, 0x124f81f9, 0x174331f9, 0x77344dc5, 0x5a821dba, 0x5fc4177f,
128            0x54153bf5, 0x5e3f1194,
129        ],
130        [
131            0x3bdbf191, 0x088c84a3, 0x68256c9b, 0x3c90bbc6, 0x6846166a, 0x03f4238d, 0x463335fb,
132            0x5e3d3551, 0x6e59ae6f, 0x32d06cc0, 0x596293f3, 0x6c87edb2, 0x08fc60b5, 0x34bcca80,
133            0x24f007f3, 0x62731c6f,
134        ],
135        [
136            0x1e1db6c6, 0x0ca409bb, 0x585c1e78, 0x56e94edc, 0x16d22734, 0x18e11467, 0x7b2c3730,
137            0x770075e4, 0x35d1b18c, 0x22be3db5, 0x4fb1fbb7, 0x477cb3ed, 0x7d5311c6, 0x5b62ae7d,
138            0x559c5fa8, 0x77f15048,
139        ],
140        [
141            0x3211570b, 0x490fef6a, 0x77ec311f, 0x2247171b, 0x4e0ac711, 0x2edf69c9, 0x3b5a8850,
142            0x65809421, 0x5619b4aa, 0x362019a7, 0x6bf9d4ed, 0x5b413dff, 0x617e181e, 0x5e7ab57b,
143            0x33ad7833, 0x3466c7ca,
144        ],
145    ]);
146
147/// Round constants for width-16 Poseidon2 on KoalaBear.
148///
149/// Generated by the Grain LFSR with parameters:
150///     field_type=1, alpha=3 (exp_flag=0), n=31, t=16, R_F=8, R_P=20
151///
152/// Generated by `poseidon2/generate_constants.py --field koalabear --width 16`.
153///
154/// Layout: internal (20 scalar constants).
155pub const KOALABEAR_POSEIDON2_RC_16_INTERNAL: [KoalaBear; 20] = KoalaBear::new_array([
156    0x54dfeb5d, 0x7d40afd6, 0x722cb316, 0x106a4573, 0x45a7ccdb, 0x44061375, 0x154077a5, 0x45744faa,
157    0x4eb5e5ee, 0x3794e83f, 0x47c7093c, 0x5694903c, 0x69cb6299, 0x373df84c, 0x46a0df58, 0x46b8758a,
158    0x3241ebcb, 0x0b09d233, 0x1af42357, 0x1e66cec2,
159]);
160
161/// Create a default width-16 Poseidon2 permutation for KoalaBear.
162pub fn default_koalabear_poseidon2_16() -> Poseidon2KoalaBear<16> {
163    Poseidon2::new(
164        ExternalLayerConstants::new(
165            KOALABEAR_POSEIDON2_RC_16_EXTERNAL_INITIAL.to_vec(),
166            KOALABEAR_POSEIDON2_RC_16_EXTERNAL_FINAL.to_vec(),
167        ),
168        KOALABEAR_POSEIDON2_RC_16_INTERNAL.to_vec(),
169    )
170}
171
172/// Round constants for width-24 Poseidon2 on KoalaBear.
173///
174/// Generated by the Grain LFSR with parameters:
175///     field_type=1, alpha=3 (exp_flag=0), n=31, t=24, R_F=8, R_P=23
176///
177/// Generated by `poseidon2/generate_constants.py --field koalabear --width 24`.
178///
179/// Layout: external_initial (4 rounds × 24 elements).
180pub const KOALABEAR_POSEIDON2_RC_24_EXTERNAL_INITIAL: [[KoalaBear; 24]; 4] =
181    KoalaBear::new_2d_array([
182        [
183            0x1d0939dc, 0x6d050f8d, 0x628058ad, 0x2681385d, 0x3e3c62be, 0x032cfad8, 0x5a91ba3c,
184            0x015a56e6, 0x696b889c, 0x0dbcd780, 0x5881b5c9, 0x2a076f2e, 0x55393055, 0x6513a085,
185            0x547ac78f, 0x4281c5b8, 0x3e7a3f6c, 0x34562c19, 0x2c04e679, 0x0ed78234, 0x5f7a1aa9,
186            0x0177640e, 0x0ea4f8d1, 0x15be7692,
187        ],
188        [
189            0x6eafdd62, 0x71a572c6, 0x72416f0a, 0x31ce1ad3, 0x2136a0cf, 0x1507c0eb, 0x1eb6e07a,
190            0x3a0ccf7b, 0x38e4bf31, 0x44128286, 0x6b05e976, 0x244a9b92, 0x6e4b32a8, 0x78ee2496,
191            0x4761115b, 0x3d3a7077, 0x75d3c670, 0x396a2475, 0x26dd00b4, 0x7df50f59, 0x0cb922df,
192            0x0568b190, 0x5bd3fcd6, 0x1351f58e,
193        ],
194        [
195            0x52191b5f, 0x119171b8, 0x1e8bb727, 0x27d21f26, 0x36146613, 0x1ee817a2, 0x71abe84e,
196            0x44b88070, 0x5dc04410, 0x2aeaa2f6, 0x2b7bb311, 0x6906884d, 0x0522e053, 0x0c45a214,
197            0x1b016998, 0x479b1052, 0x3acc89be, 0x0776021a, 0x7a34a1f5, 0x70f87911, 0x2caf9d9e,
198            0x026aff1b, 0x2c42468e, 0x67726b45,
199        ],
200        [
201            0x09b6f53c, 0x73d76589, 0x5793eeb0, 0x29e720f3, 0x75fc8bdf, 0x4c2fae0e, 0x20b41db3,
202            0x7e491510, 0x2cadef18, 0x57fc24d6, 0x4d1ade4a, 0x36bf8e3c, 0x3511b63c, 0x64d8476f,
203            0x732ba706, 0x46634978, 0x0521c17c, 0x5ee69212, 0x3559cba9, 0x2b33df89, 0x653538d6,
204            0x5fde8344, 0x4091605d, 0x2933bdde,
205        ],
206    ]);
207
208/// Round constants for width-24 Poseidon2 on KoalaBear.
209///
210/// Generated by the Grain LFSR with parameters:
211///     field_type=1, alpha=3 (exp_flag=0), n=31, t=24, R_F=8, R_P=23
212///
213/// Generated by `poseidon2/generate_constants.py --field koalabear --width 24`.
214///
215/// Layout: external_final (4 rounds × 24 elements).
216pub const KOALABEAR_POSEIDON2_RC_24_EXTERNAL_FINAL: [[KoalaBear; 24]; 4] =
217    KoalaBear::new_2d_array([
218        [
219            0x7d232359, 0x389d82f9, 0x259b2e6c, 0x45a94def, 0x0d497380, 0x5b049135, 0x3c268399,
220            0x78feb2f9, 0x300a3eec, 0x505165bb, 0x20300973, 0x2327c081, 0x1a45a2f4, 0x5b32ea2e,
221            0x2d5d1a70, 0x053e613e, 0x5433e39f, 0x495529f0, 0x1eaa1aa9, 0x578f572a, 0x698ede71,
222            0x5a0f9dba, 0x398a2e96, 0x0c7b2925,
223        ],
224        [
225            0x2e6b9564, 0x026b00de, 0x7644c1e9, 0x5c23d0bd, 0x3470b5ef, 0x6013cf3a, 0x48747288,
226            0x13b7a543, 0x3eaebd44, 0x0004e60c, 0x1e8363a2, 0x2343259a, 0x69da0c2a, 0x06e3e4c4,
227            0x1095018e, 0x0deea348, 0x1f4c5513, 0x4f9a3a98, 0x3179112b, 0x524abb1f, 0x21615ba2,
228            0x23ab4065, 0x1202a1d1, 0x21d25b83,
229        ],
230        [
231            0x6ed17c2f, 0x391e6b09, 0x5e4ed894, 0x6a2f58f2, 0x5d980d70, 0x3fa48c5e, 0x1f6366f7,
232            0x63540f5f, 0x6a8235ed, 0x14c12a78, 0x6edde1c9, 0x58ce1c22, 0x718588bb, 0x334313ad,
233            0x7478dbc7, 0x647ad52f, 0x39e82049, 0x6fee146a, 0x082c2f24, 0x1f093015, 0x30173c18,
234            0x53f70c0d, 0x6028ab0c, 0x2f47a1ee,
235        ],
236        [
237            0x26a6780e, 0x3540bc83, 0x1812b49f, 0x5149c827, 0x631dd925, 0x001f2dea, 0x7dc05194,
238            0x3789672e, 0x7cabf72e, 0x242dbe2f, 0x0b07a51d, 0x38653650, 0x50785c4e, 0x60e8a7e0,
239            0x07464338, 0x3482d6e1, 0x08a69f1e, 0x3f2aff24, 0x5814c30d, 0x13fecab2, 0x61cb291a,
240            0x68c8226f, 0x5c757eea, 0x289b4e1e,
241        ],
242    ]);
243
244/// Round constants for width-24 Poseidon2 on KoalaBear.
245///
246/// Generated by the Grain LFSR with parameters:
247///     field_type=1, alpha=3 (exp_flag=0), n=31, t=24, R_F=8, R_P=23
248///
249/// Generated by `poseidon2/generate_constants.py --field koalabear --width 24`.
250///
251/// Layout: internal (23 scalar constants).
252pub const KOALABEAR_POSEIDON2_RC_24_INTERNAL: [KoalaBear; 23] = KoalaBear::new_array([
253    0x1395d4ca, 0x5dbac049, 0x51fc2727, 0x13407399, 0x39ac6953, 0x45e8726c, 0x75a7311c, 0x599f82c9,
254    0x702cf13b, 0x026b8955, 0x44e09bbc, 0x2211207f, 0x5128b4e3, 0x591c41af, 0x674f5c68, 0x3981d0d3,
255    0x2d82f898, 0x707cd267, 0x3b4cca45, 0x2ad0dc3c, 0x0cb79b37, 0x23f2f4e8, 0x3de4e739,
256]);
257
258/// Create a default width-24 Poseidon2 permutation for KoalaBear.
259pub fn default_koalabear_poseidon2_24() -> Poseidon2KoalaBear<24> {
260    Poseidon2::new(
261        ExternalLayerConstants::new(
262            KOALABEAR_POSEIDON2_RC_24_EXTERNAL_INITIAL.to_vec(),
263            KOALABEAR_POSEIDON2_RC_24_EXTERNAL_FINAL.to_vec(),
264        ),
265        KOALABEAR_POSEIDON2_RC_24_INTERNAL.to_vec(),
266    )
267}
268
269/// Contains data needed to define the internal layers of the Poseidon2 permutation.
270#[derive(Debug, Clone, Default)]
271pub struct KoalaBearInternalLayerParameters;
272
273impl InternalLayerBaseParameters<KoalaBearParameters, 16> for KoalaBearInternalLayerParameters {
274    /// Perform the internal matrix multiplication: s -> (1 + Diag(V))s.
275    /// We ignore `state[0]` as it is handled separately.
276    fn internal_layer_mat_mul<R: PrimeCharacteristicRing>(state: &mut [R; 16], sum: R) {
277        // The diagonal matrix is defined by the vector:
278        // V = [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/8, 1/2^24, -1/2^8, -1/8, -1/16, -1/2^24]
279        state[1] += sum.dup();
280        state[2] = state[2].double() + sum.dup();
281        state[3] = state[3].halve() + sum.dup();
282        state[4] = sum.dup() + state[4].double() + state[4].dup();
283        state[5] = sum.dup() + state[5].double().double();
284        state[6] = sum.dup() - state[6].halve();
285        state[7] = sum.dup() - (state[7].double() + state[7].dup());
286        state[8] = sum.dup() - state[8].double().double();
287        state[9] = state[9].div_2exp_u64(8);
288        state[9] += sum.dup();
289        state[10] = state[10].div_2exp_u64(3);
290        state[10] += sum.dup();
291        state[11] = state[11].div_2exp_u64(24);
292        state[11] += sum.dup();
293        state[12] = state[12].div_2exp_u64(8);
294        state[12] = sum.dup() - state[12].dup();
295        state[13] = state[13].div_2exp_u64(3);
296        state[13] = sum.dup() - state[13].dup();
297        state[14] = state[14].div_2exp_u64(4);
298        state[14] = sum.dup() - state[14].dup();
299        state[15] = state[15].div_2exp_u64(24);
300        state[15] = sum - state[15].dup();
301    }
302}
303
304impl InternalLayerBaseParameters<KoalaBearParameters, 24> for KoalaBearInternalLayerParameters {
305    /// Perform the internal matrix multiplication: s -> (1 + Diag(V))s.
306    /// We ignore `state[0]` as it is handled separately.
307    fn internal_layer_mat_mul<R: PrimeCharacteristicRing>(state: &mut [R; 24], sum: R) {
308        // The diagonal matrix is defined by the vector:
309        // V = [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4, 1/2^8, 1/4, 1/8, 1/16, 1/32, 1/64, 1/2^24, -1/2^8, -1/8, -1/16, -1/32, -1/64, -1/2^7, -1/2^9, -1/2^24]
310        state[1] += sum.dup();
311        state[2] = state[2].double() + sum.dup();
312        state[3] = state[3].halve() + sum.dup();
313        state[4] = sum.dup() + state[4].double() + state[4].dup();
314        state[5] = sum.dup() + state[5].double().double();
315        state[6] = sum.dup() - state[6].halve();
316        state[7] = sum.dup() - (state[7].double() + state[7].dup());
317        state[8] = sum.dup() - state[8].double().double();
318        state[9] = state[9].div_2exp_u64(8);
319        state[9] += sum.dup();
320        state[10] = state[10].div_2exp_u64(2);
321        state[10] += sum.dup();
322        state[11] = state[11].div_2exp_u64(3);
323        state[11] += sum.dup();
324        state[12] = state[12].div_2exp_u64(4);
325        state[12] += sum.dup();
326        state[13] = state[13].div_2exp_u64(5);
327        state[13] += sum.dup();
328        state[14] = state[14].div_2exp_u64(6);
329        state[14] += sum.dup();
330        state[15] = state[15].div_2exp_u64(24);
331        state[15] += sum.dup();
332        state[16] = state[16].div_2exp_u64(8);
333        state[16] = sum.dup() - state[16].dup();
334        state[17] = state[17].div_2exp_u64(3);
335        state[17] = sum.dup() - state[17].dup();
336        state[18] = state[18].div_2exp_u64(4);
337        state[18] = sum.dup() - state[18].dup();
338        state[19] = state[19].div_2exp_u64(5);
339        state[19] = sum.dup() - state[19].dup();
340        state[20] = state[20].div_2exp_u64(6);
341        state[20] = sum.dup() - state[20].dup();
342        state[21] = state[21].div_2exp_u64(7);
343        state[21] = sum.dup() - state[21].dup();
344        state[22] = state[22].div_2exp_u64(9);
345        state[22] = sum.dup() - state[22].dup();
346        state[23] = state[23].div_2exp_u64(24);
347        state[23] = sum - state[23].dup();
348    }
349}
350
351impl InternalLayerParameters<KoalaBearParameters, 16> for KoalaBearInternalLayerParameters {}
352impl InternalLayerParameters<KoalaBearParameters, 24> for KoalaBearInternalLayerParameters {}
353
354impl InternalLayerBaseParameters<KoalaBearParameters, 32> for KoalaBearInternalLayerParameters {
355    /// Perform the internal matrix multiplication: s -> (1 + Diag(V))s.
356    /// We ignore `state[0]` as it is handled separately.
357    fn internal_layer_mat_mul<R: PrimeCharacteristicRing>(state: &mut [R; 32], sum: R) {
358        // The diagonal matrix is defined by the vector:
359        // V = [-2, 1, 2, 1/2, 3, 4, -1/2, -3, -4,
360        //      1/2^8, 1/4, 1/8, 1/16, 1/32, 1/64, 1/2^10, 1/2^12, 1/2^14, 1/2^16, 1/2^24,
361        //      -1/2^8, -1/8, -1/16, -1/32, -1/64, -1/2^7, -1/2^9, -1/2^10, -1/2^12, -1/2^14, -1/2^16, -1/2^24]
362        state[1] += sum.dup();
363        state[2] = state[2].double() + sum.dup();
364        state[3] = state[3].halve() + sum.dup();
365        state[4] = sum.dup() + state[4].double() + state[4].dup();
366        state[5] = sum.dup() + state[5].double().double();
367        state[6] = sum.dup() - state[6].halve();
368        state[7] = sum.dup() - (state[7].double() + state[7].dup());
369        state[8] = sum.dup() - state[8].double().double();
370        state[9] = state[9].div_2exp_u64(8);
371        state[9] += sum.dup();
372        state[10] = state[10].div_2exp_u64(2);
373        state[10] += sum.dup();
374        state[11] = state[11].div_2exp_u64(3);
375        state[11] += sum.dup();
376        state[12] = state[12].div_2exp_u64(4);
377        state[12] += sum.dup();
378        state[13] = state[13].div_2exp_u64(5);
379        state[13] += sum.dup();
380        state[14] = state[14].div_2exp_u64(6);
381        state[14] += sum.dup();
382        state[15] = state[15].div_2exp_u64(10);
383        state[15] += sum.dup();
384        state[16] = state[16].div_2exp_u64(12);
385        state[16] += sum.dup();
386        state[17] = state[17].div_2exp_u64(14);
387        state[17] += sum.dup();
388        state[18] = state[18].div_2exp_u64(16);
389        state[18] += sum.dup();
390        state[19] = state[19].div_2exp_u64(24);
391        state[19] += sum.dup();
392        state[20] = state[20].div_2exp_u64(8);
393        state[20] = sum.dup() - state[20].dup();
394        state[21] = state[21].div_2exp_u64(3);
395        state[21] = sum.dup() - state[21].dup();
396        state[22] = state[22].div_2exp_u64(4);
397        state[22] = sum.dup() - state[22].dup();
398        state[23] = state[23].div_2exp_u64(5);
399        state[23] = sum.dup() - state[23].dup();
400        state[24] = state[24].div_2exp_u64(6);
401        state[24] = sum.dup() - state[24].dup();
402        state[25] = state[25].div_2exp_u64(7);
403        state[25] = sum.dup() - state[25].dup();
404        state[26] = state[26].div_2exp_u64(9);
405        state[26] = sum.dup() - state[26].dup();
406        state[27] = state[27].div_2exp_u64(10);
407        state[27] = sum.dup() - state[27].dup();
408        state[28] = state[28].div_2exp_u64(12);
409        state[28] = sum.dup() - state[28].dup();
410        state[29] = state[29].div_2exp_u64(14);
411        state[29] = sum.dup() - state[29].dup();
412        state[30] = state[30].div_2exp_u64(16);
413        state[30] = sum.dup() - state[30].dup();
414        state[31] = state[31].div_2exp_u64(24);
415        state[31] = sum - state[31].dup();
416    }
417}
418
419impl InternalLayerParameters<KoalaBearParameters, 32> for KoalaBearInternalLayerParameters {}
420
421#[cfg(test)]
422mod tests {
423    use p3_symmetric::Permutation;
424    use rand::{RngExt, SeedableRng};
425    use rand_xoshiro::Xoroshiro128Plus;
426
427    use super::*;
428
429    type F = KoalaBear;
430
431    // We need to make some round constants. We use Xoroshiro128Plus for this as we can easily match this PRNG in sage.
432    // See: https://github.com/0xPolygonZero/hash-constants for the sage code used to create all these tests.
433
434    /// Test on a roughly random input.
435    /// This random input is generated by the following sage code:
436    /// set_random_seed(16)
437    /// vector([KB.random_element() for t in range(16)]).
438    #[test]
439    fn test_poseidon2_width_16_random() {
440        let mut input: [F; 16] = KoalaBear::new_array([
441            894848333, 1437655012, 1200606629, 1690012884, 71131202, 1749206695, 1717947831,
442            120589055, 19776022, 42382981, 1831865506, 724844064, 171220207, 1299207443, 227047920,
443            1783754913,
444        ]);
445
446        let expected: [F; 16] = KoalaBear::new_array([
447            652590279, 1200629963, 1013089423, 1840372851, 19101828, 561050015, 1714865585,
448            994637181, 498949829, 729884572, 1957973925, 263012103, 535029297, 2121808603,
449            964663675, 1473622080,
450        ]);
451
452        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
453        let perm = Poseidon2KoalaBear::new_from_rng_128(&mut rng);
454
455        perm.permute_mut(&mut input);
456        assert_eq!(input, expected);
457    }
458
459    /// Test on a roughly random input.
460    /// This random input is generated by the following sage code:
461    /// set_random_seed(24)
462    /// vector([KB.random_element() for t in range(24)]).
463    #[test]
464    fn test_poseidon2_width_24_random() {
465        let mut input: [F; 24] = KoalaBear::new_array([
466            886409618, 1327899896, 1902407911, 591953491, 648428576, 1844789031, 1198336108,
467            355597330, 1799586834, 59617783, 790334801, 1968791836, 559272107, 31054313,
468            1042221543, 474748436, 135686258, 263665994, 1962340735, 1741539604, 2026927696,
469            449439011, 1131357108, 50869465,
470        ]);
471
472        let expected: [F; 24] = KoalaBear::new_array([
473            3825456, 486989921, 613714063, 282152282, 1027154688, 1171655681, 879344953,
474            1090688809, 1960721991, 1604199242, 1329947150, 1535171244, 781646521, 1156559780,
475            1875690339, 368140677, 457503063, 304208551, 1919757655, 835116474, 1293372648,
476            1254825008, 810923913, 1773631109,
477        ]);
478
479        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
480        let perm = Poseidon2KoalaBear::new_from_rng_128(&mut rng);
481
482        perm.permute_mut(&mut input);
483        assert_eq!(input, expected);
484    }
485
486    /// Test the generic internal layer against the optimized internal layer
487    /// for a random input of width 16.
488    #[test]
489    fn test_generic_internal_linear_layer_16() {
490        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
491        let mut input1: [F; 16] = rng.random();
492        let mut input2 = input1;
493
494        let part_sum: F = input1[1..].iter().copied().sum();
495        let full_sum = part_sum + input1[0];
496
497        input1[0] = part_sum - input1[0];
498
499        KoalaBearInternalLayerParameters::internal_layer_mat_mul(&mut input1, full_sum);
500        KoalaBearInternalLayerParameters::generic_internal_linear_layer(&mut input2);
501
502        assert_eq!(input1, input2);
503    }
504
505    /// Test the generic internal layer against the optimized internal layer
506    /// for a random input of width 16.
507    #[test]
508    fn test_generic_internal_linear_layer_24() {
509        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
510        let mut input1: [F; 24] = rng.random();
511        let mut input2 = input1;
512
513        let part_sum: F = input1[1..].iter().copied().sum();
514        let full_sum = part_sum + input1[0];
515
516        input1[0] = part_sum - input1[0];
517
518        KoalaBearInternalLayerParameters::internal_layer_mat_mul(&mut input1, full_sum);
519        KoalaBearInternalLayerParameters::generic_internal_linear_layer(&mut input2);
520
521        assert_eq!(input1, input2);
522    }
523
524    #[test]
525    fn test_default_koalabear_poseidon2_width_16() {
526        let mut input: [F; 16] = KoalaBear::new_array([
527            894848333, 1437655012, 1200606629, 1690012884, 71131202, 1749206695, 1717947831,
528            120589055, 19776022, 42382981, 1831865506, 724844064, 171220207, 1299207443, 227047920,
529            1783754913,
530        ]);
531
532        let expected: [F; 16] = KoalaBear::new_array([
533            1934285469, 604889435, 133449501, 1026180808, 1830659359, 176667110, 1391183747,
534            351743874, 1238264085, 1292768839, 2023573270, 1201586780, 1360691759, 1230682461,
535            748270449, 651545025,
536        ]);
537
538        let perm = default_koalabear_poseidon2_16();
539        perm.permute_mut(&mut input);
540
541        assert_eq!(input, expected);
542    }
543
544    #[test]
545    fn test_default_koalabear_poseidon2_width_24() {
546        let mut input: [F; 24] = KoalaBear::new_array([
547            886409618, 1327899896, 1902407911, 591953491, 648428576, 1844789031, 1198336108,
548            355597330, 1799586834, 59617783, 790334801, 1968791836, 559272107, 31054313,
549            1042221543, 474748436, 135686258, 263665994, 1962340735, 1741539604, 2026927696,
550            449439011, 1131357108, 50869465,
551        ]);
552
553        let expected: [F; 24] = KoalaBear::new_array([
554            382801106, 82839311, 1503190615, 1987418517, 854076995, 1862291425, 262755189,
555            1050814217, 722724562, 741265943, 1026879332, 754316749, 1966025564, 1518878196,
556            502200188, 1368172258, 845459257, 1711434837, 724453836, 171032289, 655223446,
557            1098636135, 407832555, 1707498914,
558        ]);
559
560        let perm = default_koalabear_poseidon2_24();
561        perm.permute_mut(&mut input);
562
563        assert_eq!(input, expected);
564    }
565    /// Test on a roughly random input for width 32.
566    #[test]
567    fn test_poseidon2_width_32_random() {
568        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
569        let perm = Poseidon2KoalaBear::<32>::new_from_rng_128(&mut rng);
570
571        let mut input: [F; 32] = rng.random();
572        let original = input;
573
574        perm.permute_mut(&mut input);
575        // Just verify it doesn't panic and output differs from input
576        assert_ne!(input, original);
577    }
578
579    /// Test the generic internal layer against the optimized internal layer
580    /// for a random input of width 32.
581    #[test]
582    fn test_generic_internal_linear_layer_32() {
583        let mut rng = Xoroshiro128Plus::seed_from_u64(1);
584        let mut input1: [F; 32] = rng.random();
585        let mut input2 = input1;
586
587        let part_sum: F = input1[1..].iter().copied().sum();
588        let full_sum = part_sum + input1[0];
589
590        input1[0] = part_sum - input1[0];
591
592        KoalaBearInternalLayerParameters::internal_layer_mat_mul(&mut input1, full_sum);
593        KoalaBearInternalLayerParameters::generic_internal_linear_layer(&mut input2);
594
595        assert_eq!(input1, input2);
596    }
597}