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, two_128};
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 = two_128();
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}