arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/poseidon-cipher/src/circuit.rs
poseidon-cipher/src/circuit.rsRaw
  1//! For LICENSE check out https://github.com/0xPARC/plonky2-crypto-gadgets/blob/main/LICENSE
  2//!
  3//! Implements the Plonky2 circuits for Poseidon encryption as described at
  4//! https://drive.google.com/file/d/1EVrP3DzoGbmzkRmYnyEDcIQcXVU7GlOd/view .
  5
  6use std::array;
  7
  8use anyhow::Result;
  9use plonky2::{
 10    field::{
 11        extension::{FieldExtension, quintic::QuinticExtension},
 12        goldilocks_field::GoldilocksField as F,
 13        types::{Field, Field64},
 14    },
 15    hash::poseidon::PoseidonHash,
 16    iop::{
 17        target::Target,
 18        witness::{PartialWitness, WitnessWrite},
 19    },
 20    plonk::circuit_builder::CircuitBuilder,
 21};
 22use pod2::backends::plonky2::{
 23    basetypes::D,
 24    primitives::ec::{
 25        curve::{CircuitBuilderElliptic, Point, PointTarget, WitnessWriteCurve},
 26        field::{CircuitBuilderNNF, OEFTarget},
 27    },
 28};
 29
 30use crate::{Fq, TWO128};
 31
 32// Fq Target
 33type FqT = OEFTarget<5, QuinticExtension<F>>;
 34
 35pub struct PoseidonEncryptTarget<const L: usize>
 36where
 37    [(); L + 1]:,
 38{
 39    ks: PointTarget,
 40    m: [FqT; L],
 41    nonce: [Target; 2],
 42    ct: [FqT; L + 1],
 43}
 44
 45impl<const L: usize> PoseidonEncryptTarget<L>
 46where
 47    [(); L + 1]:,
 48{
 49    pub fn build(builder: &mut CircuitBuilder<F, D>) -> Self {
 50        assert!(L.is_multiple_of(3));
 51        assert!(L < F::ORDER as usize);
 52        let fqt_zero = const_fqt_zero(builder);
 53
 54        // add targets
 55        let ks: PointTarget = builder.add_virtual_point_target();
 56        let m: [FqT; L] = array::from_fn(|_| builder.add_virtual_nnf_target());
 57        let nonce: [Target; 2] = builder.add_virtual_target_arr::<2>();
 58        let mut ct: [FqT; L + 1] = array::from_fn(|_| fqt_zero.clone());
 59
 60        // build the circuit logic
 61        let f_zero = builder.constant(F::ZERO);
 62        let n = FqT::new([nonce[0], nonce[1], f_zero, f_zero, f_zero]);
 63        let two128: Fq = TWO128;
 64        let l_two128: Fq = Fq::from_basefield_array([
 65            F::from_canonical_u64(L as u64),
 66            F::ZERO,
 67            F::ZERO,
 68            F::ZERO,
 69            F::ZERO,
 70        ]) * two128;
 71        let l_two128_target: FqT = const_fqt_from_fq(builder, l_two128);
 72        let nl128: FqT = builder.nnf_add(&n, &l_two128_target);
 73        let fq_zero: FqT = FqT::new([f_zero, f_zero, f_zero, f_zero, f_zero]);
 74        let mut s: [FqT; 4] = [fq_zero, ks.x.clone(), ks.u.clone(), nl128];
 75
 76        for i in 0..L / 3 {
 77            s = hash_state_target(builder, s);
 78
 79            // absorb
 80            s[1] = builder.nnf_add(&s[1], &m[i * 3]);
 81            s[2] = builder.nnf_add(&s[2], &m[i * 3 + 1]);
 82            s[3] = builder.nnf_add(&s[3], &m[i * 3 + 2]);
 83
 84            // release
 85            ct[i * 3] = s[1].clone();
 86            ct[i * 3 + 1] = s[2].clone();
 87            ct[i * 3 + 2] = s[3].clone();
 88        }
 89        s = hash_state_target(builder, s);
 90        ct[L] = s[1].clone();
 91
 92        Self { ks, m, nonce, ct }
 93    }
 94
 95    pub fn set_targets(
 96        &self,
 97        pw: &mut PartialWitness<F>,
 98        ks: Point,
 99        m: &[Fq],
100        nonce: [F; 2],
101        ct: &[Fq],
102    ) -> Result<()> {
103        assert_eq!(m.len(), L);
104        assert_eq!(m.len() + 1, ct.len());
105
106        pw.set_point_target(&self.ks, &ks)?;
107        #[allow(clippy::needless_range_loop)]
108        for i in 0..L {
109            for j in 0..5 {
110                pw.set_target(self.m[i].components[j], m[i].0[j])?;
111            }
112        }
113        pw.set_target_arr(&self.nonce, &nonce)?;
114        #[allow(clippy::needless_range_loop)]
115        for i in 0..L + 1 {
116            for j in 0..5 {
117                pw.set_target(self.ct[i].components[j], ct[i].0[j])?;
118            }
119        }
120
121        Ok(())
122    }
123}
124
125fn hash_state_target(builder: &mut CircuitBuilder<F, D>, s: [FqT; 4]) -> [FqT; 4] {
126    let elems: [Target; 4 * 5] = array::from_fn(|i| s[i / 5].components[i % 5]);
127    let h = builder.hash_n_to_m_no_pad::<PoseidonHash>(elems.to_vec(), 4 * 5);
128    array::from_fn(|i| FqT::new(array::from_fn::<_, 5, _>(|j| h[j + 5 * i])))
129}
130
131fn const_fqt_from_fq(builder: &mut CircuitBuilder<F, D>, fq: Fq) -> FqT {
132    let t: [Target; 5] = array::from_fn(|i| builder.constant(fq.0[i]));
133    FqT::new(t)
134}
135fn const_fqt_from_f(builder: &mut CircuitBuilder<F, D>, f: F) -> FqT {
136    let v = builder.constant(f);
137    let zero = builder.constant(F::ZERO);
138    FqT::new([v, zero, zero, zero, zero])
139}
140fn const_fqt_zero(builder: &mut CircuitBuilder<F, D>) -> FqT {
141    let zero = builder.constant(F::ZERO);
142    FqT::new([zero, zero, zero, zero, zero])
143}
144
145#[cfg(test)]
146mod tests {
147    use anyhow::Result;
148    use plonky2::{
149        field::{goldilocks_field::GoldilocksField as F, types::Sample},
150        iop::witness::PartialWitness,
151        plonk::{
152            circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
153            config::PoseidonGoldilocksConfig,
154        },
155    };
156
157    use super::*;
158
159    #[test]
160    fn test_encrypt() -> Result<()> {
161        let mut rng = rand::thread_rng();
162        const L: usize = 129; // (multiple of 3)
163
164        let (_k, K) = crate::new_key();
165        let ks = crate::expanded_key(K);
166        let nonce: [F; 2] = [F::rand(), F::rand()];
167        let msg: Vec<Fq> = (0..L).map(|_| Fq::sample(&mut rng)).collect();
168        let l = msg.len();
169
170        let ct = crate::encrypt(ks, &msg, nonce);
171
172        let m = crate::decrypt(ks, &ct, nonce, l);
173        assert_eq!(m, msg);
174
175        // circuit declaration
176        let config = CircuitConfig::standard_recursion_config();
177        let mut builder = CircuitBuilder::<F, D>::new(config);
178
179        let cipher_targets = PoseidonEncryptTarget::<L>::build(&mut builder);
180
181        println!(
182            "PoseidonEncrypt circuit (L:{}) num_gates: {}",
183            L,
184            builder.num_gates()
185        );
186        let data = builder.build::<PoseidonGoldilocksConfig>();
187
188        // set values to circuit
189        let mut pw = PartialWitness::<F>::new();
190        cipher_targets.set_targets(&mut pw, ks, &msg, nonce, &ct)?;
191
192        let proof = data.prove(pw)?;
193        data.verify(proof)
194    }
195}