arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/feistel/src/lib.rs
feistel/src/lib.rsRaw
  1#![allow(incomplete_features)]
  2#![feature(generic_const_exprs)]
  3
  4pub mod circuit;
  5
  6use std::array;
  7
  8use plonky2::field::types::Field;
  9
 10/// Feistel-type cipher for finite fields, where XORing is replaced
 11/// with addition, cf. https://en.wikipedia.org/wiki/Feistel_cipher or
 12/// §3.5.1 of Stinson's *Cryptography* (3rd edition).
 13pub fn feistel_cipher<const STATE_HALF_LEN: usize, const KEY_LEN: usize, F: Field>(
 14    state: [F; 2 * STATE_HALF_LEN],
 15    key_schedule: &[[F; KEY_LEN]],
 16    f: &impl Fn(Vec<F>) -> [F; STATE_HALF_LEN],
 17) -> [F; 2 * STATE_HALF_LEN] {
 18    if key_schedule.is_empty() {
 19        state
 20    } else {
 21        let k = key_schedule[0];
 22        let l = &state[..STATE_HALF_LEN];
 23        let r = &state[STATE_HALF_LEN..];
 24
 25        let new_l = r;
 26        let round_offset = f([r, &k].concat());
 27        let new_r: [_; STATE_HALF_LEN] = array::from_fn(|i| l[i] + round_offset[i]);
 28
 29        feistel_cipher(
 30            array::from_fn(|i| {
 31                if i < STATE_HALF_LEN {
 32                    new_l[i]
 33                } else {
 34                    new_r[i - STATE_HALF_LEN]
 35                }
 36            }),
 37            &key_schedule[1..],
 38            f,
 39        )
 40    }
 41}
 42
 43/// Feistel-type inverse cipher for finite fields, where XORing is
 44/// replaced with subtraction.
 45pub fn feistel_inv_cipher<const STATE_HALF_LEN: usize, const KEY_LEN: usize, F: Field>(
 46    state: [F; 2 * STATE_HALF_LEN],
 47    reverse_key_schedule: &[[F; KEY_LEN]],
 48    f: &impl Fn(Vec<F>) -> [F; STATE_HALF_LEN],
 49) -> [F; 2 * STATE_HALF_LEN] {
 50    if reverse_key_schedule.is_empty() {
 51        state
 52    } else {
 53        let k = reverse_key_schedule[0];
 54        let l = &state[..STATE_HALF_LEN];
 55        let r = &state[STATE_HALF_LEN..];
 56
 57        let new_r = l;
 58        let round_offset = f([l, &k].concat());
 59        let new_l: [_; STATE_HALF_LEN] = array::from_fn(|i| r[i] - round_offset[i]);
 60
 61        feistel_inv_cipher(
 62            array::from_fn(|i| {
 63                if i < STATE_HALF_LEN {
 64                    new_l[i]
 65                } else {
 66                    new_r[i - STATE_HALF_LEN]
 67                }
 68            }),
 69            &reverse_key_schedule[1..],
 70            f,
 71        )
 72    }
 73}
 74
 75#[cfg(test)]
 76mod tests {
 77    use plonky2::{
 78        field::{
 79            goldilocks_field::GoldilocksField as F,
 80            types::{Field, Field64},
 81        },
 82        hash::{
 83            hashing::hash_n_to_hash_no_pad, keccak::KeccakPermutation,
 84            poseidon::PoseidonPermutation,
 85        },
 86    };
 87    use rand::{Rng, rngs::OsRng};
 88
 89    use crate::{feistel_cipher, feistel_inv_cipher};
 90
 91    pub(crate) fn random_fields<const N: usize>() -> [F; N] {
 92        std::array::from_fn(|_| F::from_canonical_u64(OsRng.gen_range(0..F::ORDER)))
 93    }
 94
 95    #[test]
 96    fn feistel_keccak_poseidon_roundtrip() {
 97        const NR: usize = 32;
 98        const STATE_HALF_LEN: usize = 4;
 99        const KEY_LEN: usize = 4;
100
101        let f_keccak =
102            |fields: Vec<_>| hash_n_to_hash_no_pad::<F, KeccakPermutation<_>>(&fields).elements;
103        let f_poseidon =
104            |fields: Vec<_>| hash_n_to_hash_no_pad::<F, PoseidonPermutation<_>>(&fields).elements;
105
106        [f_keccak, f_poseidon].into_iter().for_each(|f| {
107            (0..10).for_each(|_| {
108                let state: [_; 2 * STATE_HALF_LEN] = random_fields();
109                let key_schedule: [[_; KEY_LEN]; NR] = std::array::from_fn(|_| random_fields());
110
111                let out_state = feistel_cipher(state, &key_schedule, &f);
112                let reverse_key_schedule = key_schedule.into_iter().rev().collect::<Vec<_>>();
113                let maybe_original_state = feistel_inv_cipher(out_state, &reverse_key_schedule, &f);
114
115                assert_eq!(state, maybe_original_state);
116            })
117        })
118    }
119}