arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/feistel/src/circuit.rs
feistel/src/circuit.rsRaw
  1//! For LICENSE check out https://github.com/0xPARC/plonky2-crypto-gadgets/blob/main/LICENSE
  2
  3use std::array;
  4
  5use plonky2::{
  6    field::{extension::Extendable, goldilocks_field::GoldilocksField as F},
  7    hash::hash_types::RichField,
  8    iop::{
  9        target::Target,
 10        witness::{PartialWitness, WitnessWrite},
 11    },
 12    plonk::circuit_builder::CircuitBuilder,
 13};
 14
 15pub(crate) const D: usize = 2;
 16
 17/// State on which the Feistel-like cipher operates
 18#[derive(Debug, Copy, Clone)]
 19pub struct FeistelStateTarget<const STATE_LEN: usize>([Target; STATE_LEN]);
 20
 21pub trait CircuitBuilderFeistel<F: RichField + Extendable<D>, const D: usize> {
 22    /// Feistel state target constructor
 23    fn add_feistel_state_target<const STATE_LEN: usize>(&mut self)
 24    -> FeistelStateTarget<STATE_LEN>;
 25
 26    /// Generates Feistel-like cipher as in `crate::feistel_cipher`
 27    fn feistel_cipher<const STATE_HALF_LEN: usize, const KEY_LEN: usize>(
 28        &mut self,
 29        state: FeistelStateTarget<{ 2 * STATE_HALF_LEN }>,
 30        key_schedule: &[[Target; KEY_LEN]],
 31        f: &impl Fn(&mut Self, Vec<Target>) -> [Target; STATE_HALF_LEN],
 32    ) -> FeistelStateTarget<{ 2 * STATE_HALF_LEN }>;
 33}
 34
 35impl CircuitBuilderFeistel<F, D> for CircuitBuilder<F, D> {
 36    fn add_feistel_state_target<const STATE_LEN: usize>(
 37        &mut self,
 38    ) -> FeistelStateTarget<STATE_LEN> {
 39        FeistelStateTarget(self.add_virtual_target_arr())
 40    }
 41
 42    fn feistel_cipher<const STATE_HALF_LEN: usize, const KEY_LEN: usize>(
 43        &mut self,
 44        state: FeistelStateTarget<{ 2 * STATE_HALF_LEN }>,
 45        key_schedule: &[[Target; KEY_LEN]],
 46        f: &impl Fn(&mut Self, Vec<Target>) -> [Target; STATE_HALF_LEN],
 47    ) -> FeistelStateTarget<{ 2 * STATE_HALF_LEN }> {
 48        if key_schedule.is_empty() {
 49            state
 50        } else {
 51            let k = key_schedule[0];
 52            let l = &state.0[..STATE_HALF_LEN];
 53            let r = &state.0[STATE_HALF_LEN..];
 54
 55            let new_l = r;
 56            let round_offset = f(self, [r, &k].concat());
 57            let new_r: [_; STATE_HALF_LEN] = array::from_fn(|i| self.add(l[i], round_offset[i]));
 58            self.feistel_cipher(
 59                FeistelStateTarget(array::from_fn(|i| {
 60                    if i < STATE_HALF_LEN {
 61                        new_l[i]
 62                    } else {
 63                        new_r[i - STATE_HALF_LEN]
 64                    }
 65                })),
 66                &key_schedule[1..],
 67                f,
 68            )
 69        }
 70    }
 71}
 72
 73pub trait PartialWitnessFeistel {
 74    /// Sets witnesses for a Feistel state target
 75    fn set_feistel_state_target<const STATE_SIZE: usize>(
 76        &mut self,
 77        target: &FeistelStateTarget<STATE_SIZE>,
 78        value: &[F; STATE_SIZE],
 79    ) -> anyhow::Result<()>;
 80}
 81
 82impl PartialWitnessFeistel for PartialWitness<F> {
 83    fn set_feistel_state_target<const STATE_SIZE: usize>(
 84        &mut self,
 85        target: &FeistelStateTarget<STATE_SIZE>,
 86        value: &[F; STATE_SIZE],
 87    ) -> anyhow::Result<()> {
 88        self.set_target_arr(&target.0, value)
 89    }
 90}
 91
 92#[cfg(test)]
 93mod tests {
 94    use plonky2::{
 95        field::goldilocks_field::GoldilocksField as F,
 96        hash::{
 97            hashing::hash_n_to_hash_no_pad,
 98            poseidon::{PoseidonHash, PoseidonPermutation},
 99        },
100        iop::witness::{PartialWitness, WitnessWrite},
101        plonk::{
102            circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
103            config::PoseidonGoldilocksConfig,
104        },
105    };
106
107    use super::D;
108    use crate::{
109        circuit::{CircuitBuilderFeistel, FeistelStateTarget, PartialWitnessFeistel},
110        feistel_cipher,
111        tests::random_fields,
112    };
113
114    #[test]
115    fn feistel_poseidon_check() -> anyhow::Result<()> {
116        const NR: usize = 32;
117        const STATE_HALF_LEN: usize = 4;
118        const KEY_LEN: usize = 4;
119
120        let config = CircuitConfig::standard_recursion_config();
121        let mut builder = CircuitBuilder::<F, D>::new(config);
122
123        let state_target: FeistelStateTarget<{ 2 * STATE_HALF_LEN }> =
124            builder.add_feistel_state_target();
125        let key_schedule_target: [[_; KEY_LEN]; NR] =
126            std::array::from_fn(|_| builder.add_virtual_target_arr());
127
128        let f = |builder: &mut CircuitBuilder<F, D>, targets: Vec<_>| {
129            builder
130                .hash_n_to_hash_no_pad::<PoseidonHash>(targets)
131                .elements
132        };
133
134        let out_state_target = builder.feistel_cipher(state_target, &key_schedule_target, &f);
135
136        let data = builder.build::<PoseidonGoldilocksConfig>();
137        (0..10).try_for_each(|_| {
138            let state: [_; 2 * STATE_HALF_LEN] = random_fields();
139            let key_schedule: [[_; KEY_LEN]; NR] = std::array::from_fn(|_| random_fields());
140            let eff = |fields: Vec<_>| {
141                hash_n_to_hash_no_pad::<F, PoseidonPermutation<_>>(&fields).elements
142            };
143            let out_state = feistel_cipher(state, &key_schedule, &eff);
144
145            let mut pw = PartialWitness::new();
146            pw.set_feistel_state_target(&state_target, &state)?;
147            std::iter::zip(key_schedule_target, key_schedule)
148                .try_for_each(|(t, v)| pw.set_target_arr(&t, &v))?;
149            pw.set_feistel_state_target(&out_state_target, &out_state)?;
150
151            let proof = data.prove(pw)?;
152            data.verify(proof)
153        })
154    }
155}