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::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;
 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_l = FqT::new([
 63            nonce[0],
 64            nonce[1],
 65            builder.constant(F::from_canonical_u64(L as u64)),
 66            f_zero,
 67            f_zero,
 68        ]);
 69        let fq_zero: FqT = FqT::new([f_zero, f_zero, f_zero, f_zero, f_zero]);
 70        let mut s: [FqT; 4] = [fq_zero, ks.x.clone(), ks.u.clone(), n_l];
 71
 72        for i in 0..L / 3 {
 73            s = hash_state_target(builder, s);
 74
 75            // absorb
 76            s[1] = builder.nnf_add(&s[1], &m[i * 3]);
 77            s[2] = builder.nnf_add(&s[2], &m[i * 3 + 1]);
 78            s[3] = builder.nnf_add(&s[3], &m[i * 3 + 2]);
 79
 80            // release
 81            ct[i * 3] = s[1].clone();
 82            ct[i * 3 + 1] = s[2].clone();
 83            ct[i * 3 + 2] = s[3].clone();
 84        }
 85        s = hash_state_target(builder, s);
 86        ct[L] = s[1].clone();
 87
 88        Self { ks, m, nonce, ct }
 89    }
 90
 91    pub fn set_targets(
 92        &self,
 93        pw: &mut PartialWitness<F>,
 94        ks: Point,
 95        m: &[Fq],
 96        nonce: [F; 2],
 97        ct: &[Fq],
 98    ) -> Result<()> {
 99        assert_eq!(m.len(), L);
100        assert_eq!(m.len() + 1, ct.len());
101
102        pw.set_point_target(&self.ks, &ks)?;
103        #[allow(clippy::needless_range_loop)]
104        for i in 0..L {
105            for j in 0..5 {
106                pw.set_target(self.m[i].components[j], m[i].0[j])?;
107            }
108        }
109        pw.set_target_arr(&self.nonce, &nonce)?;
110        #[allow(clippy::needless_range_loop)]
111        for i in 0..L + 1 {
112            for j in 0..5 {
113                pw.set_target(self.ct[i].components[j], ct[i].0[j])?;
114            }
115        }
116
117        Ok(())
118    }
119}
120
121fn hash_state_target(builder: &mut CircuitBuilder<F, D>, s: [FqT; 4]) -> [FqT; 4] {
122    let elems: [Target; 4 * 5] = array::from_fn(|i| s[i / 5].components[i % 5]);
123    let h = builder.hash_n_to_m_no_pad::<PoseidonHash>(elems.to_vec(), 4 * 5);
124    array::from_fn(|i| FqT::new(array::from_fn::<_, 5, _>(|j| h[j + 5 * i])))
125}
126
127fn const_fqt_from_fq(builder: &mut CircuitBuilder<F, D>, fq: Fq) -> FqT {
128    let t: [Target; 5] = array::from_fn(|i| builder.constant(fq.0[i]));
129    FqT::new(t)
130}
131fn const_fqt_from_f(builder: &mut CircuitBuilder<F, D>, f: F) -> FqT {
132    let v = builder.constant(f);
133    let zero = builder.constant(F::ZERO);
134    FqT::new([v, zero, zero, zero, zero])
135}
136fn const_fqt_zero(builder: &mut CircuitBuilder<F, D>) -> FqT {
137    let zero = builder.constant(F::ZERO);
138    FqT::new([zero, zero, zero, zero, zero])
139}
140
141#[cfg(test)]
142mod tests {
143    use anyhow::Result;
144    use plonky2::{
145        field::{goldilocks_field::GoldilocksField as F, types::Sample},
146        iop::witness::PartialWitness,
147        plonk::{
148            circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
149            config::PoseidonGoldilocksConfig,
150        },
151    };
152
153    use super::*;
154
155    #[test]
156    fn test_encrypt() -> Result<()> {
157        let mut rng = rand::thread_rng();
158        const L: usize = 129; // (multiple of 3)
159
160        let (_k, K) = crate::new_key();
161        let ks = crate::expanded_key(K);
162        let nonce: [F; 2] = [F::rand(), F::rand()];
163        let msg: Vec<Fq> = (0..L).map(|_| Fq::sample(&mut rng)).collect();
164        let l = msg.len();
165
166        let ct = crate::encrypt(ks, &msg, nonce);
167
168        let m = crate::decrypt(ks, &ct, nonce, l);
169        assert_eq!(m, msg);
170
171        // circuit declaration
172        let config = CircuitConfig::standard_recursion_config();
173        let mut builder = CircuitBuilder::<F, D>::new(config);
174
175        let cipher_targets = PoseidonEncryptTarget::<L>::build(&mut builder);
176
177        println!(
178            "PoseidonEncrypt circuit (L:{}) num_gates: {}",
179            L,
180            builder.num_gates()
181        );
182        let data = builder.build::<PoseidonGoldilocksConfig>();
183
184        // set values to circuit
185        let mut pw = PartialWitness::<F>::new();
186        cipher_targets.set_targets(&mut pw, ks, &msg, nonce, &ct)?;
187
188        let proof = data.prove(pw)?;
189        data.verify(proof)
190    }
191}