Nova/src/poseidon.rs
src/poseidon.rsRaw
1//! Poseidon Constants and Poseidon-based RO used in Nova
2use super::traits::{ROCircuitTrait, ROConstantsTrait, ROTrait};
3use bellperson::{
4 gadgets::{
5 boolean::{AllocatedBit, Boolean},
6 num::AllocatedNum,
7 },
8 ConstraintSystem, SynthesisError,
9};
10use core::marker::PhantomData;
11use ff::{PrimeField, PrimeFieldBits};
12use generic_array::typenum::U24;
13use neptune::{
14 circuit2::Elt,
15 poseidon::PoseidonConstants,
16 sponge::{
17 api::{IOPattern, SpongeAPI, SpongeOp},
18 circuit::SpongeCircuit,
19 vanilla::{Mode::Simplex, Sponge, SpongeTrait},
20 },
21 Strength,
22};
23use serde::{Deserialize, Serialize};
24
25/// All Poseidon Constants that are used in Nova
26#[derive(Clone, Serialize, Deserialize)]
27pub struct PoseidonConstantsCircuit<Scalar: PrimeField>(PoseidonConstants<Scalar, U24>);
28
29impl<Scalar> ROConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar>
30where
31 Scalar: PrimeField + PrimeFieldBits,
32{
33 /// Generate Poseidon constants
34 #[allow(clippy::new_without_default)]
35 fn new() -> Self {
36 Self(Sponge::<Scalar, U24>::api_constants(Strength::Standard))
37 }
38}
39
40/// A Poseidon-based RO to use outside circuits
41#[derive(Serialize, Deserialize)]
42pub struct PoseidonRO<Base, Scalar>
43where
44 Base: PrimeField + PrimeFieldBits,
45 Scalar: PrimeField + PrimeFieldBits,
46{
47 // Internal State
48 state: Vec<Base>,
49 constants: PoseidonConstantsCircuit<Base>,
50 num_absorbs: usize,
51 squeezed: bool,
52 _p: PhantomData<Scalar>,
53}
54
55impl<Base, Scalar> ROTrait<Base, Scalar> for PoseidonRO<Base, Scalar>
56where
57 Base: PrimeField + PrimeFieldBits + Serialize + for<'de> Deserialize<'de>,
58 Scalar: PrimeField + PrimeFieldBits,
59{
60 type Constants = PoseidonConstantsCircuit<Base>;
61
62 fn new(constants: PoseidonConstantsCircuit<Base>, num_absorbs: usize) -> Self {
63 Self {
64 state: Vec::new(),
65 constants,
66 num_absorbs,
67 squeezed: false,
68 _p: PhantomData::default(),
69 }
70 }
71
72 /// Absorb a new number into the state of the oracle
73 fn absorb(&mut self, e: Base) {
74 assert!(!self.squeezed, "Cannot absorb after squeezing");
75 self.state.push(e);
76 }
77
78 /// Compute a challenge by hashing the current state
79 fn squeeze(&mut self, num_bits: usize) -> Scalar {
80 // check if we have squeezed already
81 assert!(!self.squeezed, "Cannot squeeze again after squeezing");
82 self.squeezed = true;
83
84 let mut sponge = Sponge::new_with_constants(&self.constants.0, Simplex);
85 let acc = &mut ();
86 let parameter = IOPattern(vec![
87 SpongeOp::Absorb(self.num_absorbs as u32),
88 SpongeOp::Squeeze(1u32),
89 ]);
90
91 sponge.start(parameter, None, acc);
92 assert_eq!(self.num_absorbs, self.state.len());
93 SpongeAPI::absorb(&mut sponge, self.num_absorbs as u32, &self.state, acc);
94 let hash = SpongeAPI::squeeze(&mut sponge, 1, acc);
95 sponge.finish(acc).unwrap();
96
97 // Only return `num_bits`
98 let bits = hash[0].to_le_bits();
99 let mut res = Scalar::zero();
100 let mut coeff = Scalar::one();
101 for bit in bits[0..num_bits].into_iter() {
102 if *bit {
103 res += coeff;
104 }
105 coeff += coeff;
106 }
107 res
108 }
109}
110
111/// A Poseidon-based RO gadget to use inside the verifier circuit.
112#[derive(Serialize, Deserialize)]
113pub struct PoseidonROCircuit<Scalar>
114where
115 Scalar: PrimeField + PrimeFieldBits,
116{
117 // Internal state
118 state: Vec<AllocatedNum<Scalar>>,
119 constants: PoseidonConstantsCircuit<Scalar>,
120 num_absorbs: usize,
121 squeezed: bool,
122}
123
124impl<Scalar> ROCircuitTrait<Scalar> for PoseidonROCircuit<Scalar>
125where
126 Scalar: PrimeField + PrimeFieldBits + Serialize + for<'de> Deserialize<'de>,
127{
128 type Constants = PoseidonConstantsCircuit<Scalar>;
129
130 /// Initialize the internal state and set the poseidon constants
131 fn new(constants: PoseidonConstantsCircuit<Scalar>, num_absorbs: usize) -> Self {
132 Self {
133 state: Vec::new(),
134 constants,
135 num_absorbs,
136 squeezed: false,
137 }
138 }
139
140 /// Absorb a new number into the state of the oracle
141 fn absorb(&mut self, e: AllocatedNum<Scalar>) {
142 assert!(!self.squeezed, "Cannot absorb after squeezing");
143 self.state.push(e);
144 }
145
146 /// Compute a challenge by hashing the current state
147 fn squeeze<CS>(
148 &mut self,
149 mut cs: CS,
150 num_bits: usize,
151 ) -> Result<Vec<AllocatedBit>, SynthesisError>
152 where
153 CS: ConstraintSystem<Scalar>,
154 {
155 // check if we have squeezed already
156 assert!(!self.squeezed, "Cannot squeeze again after squeezing");
157 self.squeezed = true;
158 let parameter = IOPattern(vec![
159 SpongeOp::Absorb(self.num_absorbs as u32),
160 SpongeOp::Squeeze(1u32),
161 ]);
162 let mut ns = cs.namespace(|| "ns");
163
164 let hash = {
165 let mut sponge = SpongeCircuit::new_with_constants(&self.constants.0, Simplex);
166 let acc = &mut ns;
167 assert_eq!(self.num_absorbs, self.state.len());
168
169 sponge.start(parameter, None, acc);
170 neptune::sponge::api::SpongeAPI::absorb(
171 &mut sponge,
172 self.num_absorbs as u32,
173 &(0..self.state.len())
174 .map(|i| Elt::Allocated(self.state[i].clone()))
175 .collect::<Vec<Elt<Scalar>>>(),
176 acc,
177 );
178
179 let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc);
180 sponge.finish(acc).unwrap();
181 output
182 };
183
184 let hash = Elt::ensure_allocated(&hash[0], &mut ns.namespace(|| "ensure allocated"), true)?;
185
186 // return the hash as a vector of bits, truncated
187 Ok(
188 hash
189 .to_bits_le_strict(ns.namespace(|| "poseidon hash to boolean"))?
190 .iter()
191 .map(|boolean| match boolean {
192 Boolean::Is(ref x) => x.clone(),
193 _ => panic!("Wrong type of input. We should have never reached there"),
194 })
195 .collect::<Vec<AllocatedBit>>()[..num_bits]
196 .into(),
197 )
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 type S = pasta_curves::pallas::Scalar;
205 type B = pasta_curves::vesta::Scalar;
206 type G = pasta_curves::pallas::Point;
207 use crate::{
208 bellperson::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS,
209 gadgets::utils::le_bits_to_num,
210 };
211 use ff::Field;
212 use rand::rngs::OsRng;
213
214 #[test]
215 fn test_poseidon_ro() {
216 // Check that the number computed inside the circuit is equal to the number computed outside the circuit
217 let mut csprng: OsRng = OsRng;
218 let constants = PoseidonConstantsCircuit::new();
219 let num_absorbs = 32;
220 let mut ro: PoseidonRO<S, B> = PoseidonRO::new(constants.clone(), num_absorbs);
221 let mut ro_gadget: PoseidonROCircuit<S> = PoseidonROCircuit::new(constants, num_absorbs);
222 let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
223 for i in 0..num_absorbs {
224 let num = S::random(&mut csprng);
225 ro.absorb(num);
226 let num_gadget =
227 AllocatedNum::alloc(cs.namespace(|| format!("data {}", i)), || Ok(num)).unwrap();
228 num_gadget
229 .inputize(&mut cs.namespace(|| format!("input {}", i)))
230 .unwrap();
231 ro_gadget.absorb(num_gadget);
232 }
233 let num = ro.squeeze(NUM_CHALLENGE_BITS);
234 let num2_bits = ro_gadget.squeeze(&mut cs, NUM_CHALLENGE_BITS).unwrap();
235 let num2 = le_bits_to_num(&mut cs, num2_bits).unwrap();
236 assert_eq!(num.to_repr(), num2.get_value().unwrap().to_repr());
237 }
238}