You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
6.8 KiB

//! Poseidon Constants and Poseidon-based RO used in Nova
use super::traits::{ROCircuitTrait, ROConstantsTrait, ROTrait};
use bellperson::{
gadgets::{
boolean::{AllocatedBit, Boolean},
num::AllocatedNum,
},
ConstraintSystem, SynthesisError,
};
use core::marker::PhantomData;
use ff::{PrimeField, PrimeFieldBits};
use generic_array::typenum::U24;
use neptune::{
circuit2::Elt,
poseidon::PoseidonConstants,
sponge::{
api::{IOPattern, SpongeAPI, SpongeOp},
circuit::SpongeCircuit,
vanilla::{Mode::Simplex, Sponge, SpongeTrait},
},
Strength,
};
/// All Poseidon Constants that are used in Nova
#[derive(Clone)]
pub struct PoseidonConstantsCircuit<Scalar: PrimeField>(PoseidonConstants<Scalar, U24>);
impl<Scalar> ROConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
/// Generate Poseidon constants
#[allow(clippy::new_without_default)]
fn new() -> Self {
Self(Sponge::<Scalar, U24>::api_constants(Strength::Standard))
}
}
/// A Poseidon-based RO to use outside circuits
pub struct PoseidonRO<Base, Scalar>
where
Base: PrimeField + PrimeFieldBits,
Scalar: PrimeField + PrimeFieldBits,
{
// Internal State
state: Vec<Base>,
constants: PoseidonConstantsCircuit<Base>,
num_absorbs: usize,
squeezed: bool,
_p: PhantomData<Scalar>,
}
impl<Base, Scalar> ROTrait<Base, Scalar> for PoseidonRO<Base, Scalar>
where
Base: PrimeField + PrimeFieldBits,
Scalar: PrimeField + PrimeFieldBits,
{
type Constants = PoseidonConstantsCircuit<Base>;
fn new(constants: PoseidonConstantsCircuit<Base>, num_absorbs: usize) -> Self {
Self {
state: Vec::new(),
constants,
num_absorbs,
squeezed: false,
_p: PhantomData::default(),
}
}
/// Absorb a new number into the state of the oracle
fn absorb(&mut self, e: Base) {
assert!(!self.squeezed, "Cannot absorb after squeezing");
self.state.push(e);
}
/// Compute a challenge by hashing the current state
fn squeeze(&mut self, num_bits: usize) -> Scalar {
// check if we have squeezed already
assert!(!self.squeezed, "Cannot squeeze again after squeezing");
self.squeezed = true;
let mut sponge = Sponge::new_with_constants(&self.constants.0, Simplex);
let acc = &mut ();
let parameter = IOPattern(vec![
SpongeOp::Absorb(self.num_absorbs as u32),
SpongeOp::Squeeze(1u32),
]);
sponge.start(parameter, Some(1u32), acc);
assert_eq!(self.num_absorbs, self.state.len());
SpongeAPI::absorb(&mut sponge, self.num_absorbs as u32, &self.state, acc);
let hash = SpongeAPI::squeeze(&mut sponge, 1, acc);
sponge.finish(acc).unwrap();
// Only return `num_bits`
let bits = hash[0].to_le_bits();
let mut res = Scalar::zero();
let mut coeff = Scalar::one();
for bit in bits[0..num_bits].into_iter() {
if *bit {
res += coeff;
}
coeff += coeff;
}
res
}
}
/// A Poseidon-based RO gadget to use inside the verifier circuit.
pub struct PoseidonROCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
// Internal state
state: Vec<AllocatedNum<Scalar>>,
constants: PoseidonConstantsCircuit<Scalar>,
num_absorbs: usize,
squeezed: bool,
}
impl<Scalar> ROCircuitTrait<Scalar> for PoseidonROCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
type Constants = PoseidonConstantsCircuit<Scalar>;
/// Initialize the internal state and set the poseidon constants
fn new(constants: PoseidonConstantsCircuit<Scalar>, num_absorbs: usize) -> Self {
Self {
state: Vec::new(),
constants,
num_absorbs,
squeezed: false,
}
}
/// Absorb a new number into the state of the oracle
fn absorb(&mut self, e: AllocatedNum<Scalar>) {
assert!(!self.squeezed, "Cannot absorb after squeezing");
self.state.push(e);
}
/// Compute a challenge by hashing the current state
fn squeeze<CS>(
&mut self,
mut cs: CS,
num_bits: usize,
) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Scalar>,
{
// check if we have squeezed already
assert!(!self.squeezed, "Cannot squeeze again after squeezing");
self.squeezed = true;
let parameter = IOPattern(vec![
SpongeOp::Absorb(self.num_absorbs as u32),
SpongeOp::Squeeze(1u32),
]);
let mut ns = cs.namespace(|| "ns");
let hash = {
let mut sponge = SpongeCircuit::new_with_constants(&self.constants.0, Simplex);
let acc = &mut ns;
assert_eq!(self.num_absorbs, self.state.len());
sponge.start(parameter, Some(1u32), acc);
neptune::sponge::api::SpongeAPI::absorb(
&mut sponge,
self.num_absorbs as u32,
&(0..self.state.len())
.map(|i| Elt::Allocated(self.state[i].clone()))
.collect::<Vec<Elt<Scalar>>>(),
acc,
);
let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc);
sponge.finish(acc).unwrap();
output
};
let hash = Elt::ensure_allocated(&hash[0], &mut ns.namespace(|| "ensure allocated"), true)?;
// return the hash as a vector of bits, truncated
Ok(
hash
.to_bits_le_strict(ns.namespace(|| "poseidon hash to boolean"))?
.iter()
.map(|boolean| match boolean {
Boolean::Is(ref x) => x.clone(),
_ => panic!("Wrong type of input. We should have never reached there"),
})
.collect::<Vec<AllocatedBit>>()[..num_bits]
.into(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
type S = pasta_curves::pallas::Scalar;
type B = pasta_curves::vesta::Scalar;
type G = pasta_curves::pallas::Point;
use crate::{
bellperson::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS,
gadgets::utils::le_bits_to_num,
};
use ff::Field;
use rand::rngs::OsRng;
#[test]
fn test_poseidon_ro() {
// Check that the number computed inside the circuit is equal to the number computed outside the circuit
let mut csprng: OsRng = OsRng;
let constants = PoseidonConstantsCircuit::new();
let num_absorbs = 32;
let mut ro: PoseidonRO<S, B> = PoseidonRO::new(constants.clone(), num_absorbs);
let mut ro_gadget: PoseidonROCircuit<S> = PoseidonROCircuit::new(constants, num_absorbs);
let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
for i in 0..num_absorbs {
let num = S::random(&mut csprng);
ro.absorb(num);
let num_gadget =
AllocatedNum::alloc(cs.namespace(|| format!("data {}", i)), || Ok(num)).unwrap();
num_gadget
.inputize(&mut cs.namespace(|| format!("input {}", i)))
.unwrap();
ro_gadget.absorb(num_gadget);
}
let num = ro.squeeze(NUM_CHALLENGE_BITS);
let num2_bits = ro_gadget.squeeze(&mut cs, NUM_CHALLENGE_BITS).unwrap();
let num2 = le_bits_to_num(&mut cs, num2_bits).unwrap();
assert_eq!(num.to_repr(), num2.get_value().unwrap().to_repr());
}
}