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