mirror of https://github.com/zcash/halo2.git
PoseidonDuplexInstructions
This commit is contained in:
parent
a69d76113f
commit
38dd7b791d
|
@ -11,7 +11,7 @@ use halo2::{
|
|||
mod pow5t3;
|
||||
pub use pow5t3::{Pow5T3Chip, Pow5T3Config};
|
||||
|
||||
use crate::primitives::poseidon::{Spec, State};
|
||||
use crate::primitives::poseidon::{Domain, Spec, SpongeState, State};
|
||||
|
||||
/// The set of circuit instructions required to use the Poseidon permutation.
|
||||
pub trait PoseidonInstructions<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>:
|
||||
|
@ -27,3 +27,33 @@ pub trait PoseidonInstructions<F: FieldExt, S: Spec<F, T, RATE>, const T: usize,
|
|||
initial_state: &State<Self::Word, T>,
|
||||
) -> Result<State<Self::Word, T>, Error>;
|
||||
}
|
||||
|
||||
/// The set of circuit instructions required to use the [`Duplex`] and [`Hash`] gadgets.
|
||||
///
|
||||
/// [`Hash`]: self::Hash
|
||||
pub trait PoseidonDuplexInstructions<
|
||||
F: FieldExt,
|
||||
S: Spec<F, T, RATE>,
|
||||
const T: usize,
|
||||
const RATE: usize,
|
||||
>: PoseidonInstructions<F, S, T, RATE>
|
||||
{
|
||||
/// Returns the initial empty state for the given domain.
|
||||
fn initial_state(
|
||||
&self,
|
||||
layouter: &mut impl Layouter<F>,
|
||||
domain: &impl Domain<F, S, T, RATE>,
|
||||
) -> Result<State<Self::Word, T>, Error>;
|
||||
|
||||
/// Pads the given input (according to the specified domain) and adds it to the state.
|
||||
fn pad_and_add(
|
||||
&self,
|
||||
layouter: &mut impl Layouter<F>,
|
||||
domain: &impl Domain<F, S, T, RATE>,
|
||||
initial_state: &State<Self::Word, T>,
|
||||
input: &SpongeState<Self::Word, RATE>,
|
||||
) -> Result<State<Self::Word, T>, Error>;
|
||||
|
||||
/// Extracts sponge output from the given state.
|
||||
fn get_output(state: &State<Self::Word, T>) -> SpongeState<Self::Word, RATE>;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ use halo2::{
|
|||
poly::Rotation,
|
||||
};
|
||||
|
||||
use super::PoseidonInstructions;
|
||||
use crate::primitives::poseidon::{Mds, Spec, State};
|
||||
use super::{PoseidonDuplexInstructions, PoseidonInstructions};
|
||||
use crate::primitives::poseidon::{Domain, Mds, Spec, SpongeState, State};
|
||||
|
||||
const WIDTH: usize = 3;
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub struct Pow5T3Config<F: FieldExt> {
|
|||
rc_b: [Column<Fixed>; WIDTH],
|
||||
s_full: Selector,
|
||||
s_partial: Selector,
|
||||
s_pad_and_add: Selector,
|
||||
|
||||
half_full_rounds: usize,
|
||||
half_partial_rounds: usize,
|
||||
|
@ -54,9 +55,6 @@ impl<F: FieldExt> Pow5T3Chip<F> {
|
|||
let half_partial_rounds = S::partial_rounds() / 2;
|
||||
let (round_constants, m_reg, m_inv) = spec.constants();
|
||||
|
||||
let state_permutation =
|
||||
Permutation::new(meta, &[state[0].into(), state[1].into(), state[2].into()]);
|
||||
|
||||
let partial_sbox = meta.advice_column();
|
||||
|
||||
let rc_a = [
|
||||
|
@ -70,8 +68,26 @@ impl<F: FieldExt> Pow5T3Chip<F> {
|
|||
meta.fixed_column(),
|
||||
];
|
||||
|
||||
// This allows state words to be initialized (by constraining them equal to fixed
|
||||
// values), and used in a permutation from an arbitrary region. rc_a is used in
|
||||
// every permutation round, while rc_b is empty in the initial and final full
|
||||
// rounds, so we use rc_b as "scratch space" for fixed values (enabling potential
|
||||
// layouter optimisations).
|
||||
let state_permutation = Permutation::new(
|
||||
meta,
|
||||
&[
|
||||
state[0].into(),
|
||||
state[1].into(),
|
||||
state[2].into(),
|
||||
rc_b[0].into(),
|
||||
rc_b[1].into(),
|
||||
rc_b[2].into(),
|
||||
],
|
||||
);
|
||||
|
||||
let s_full = meta.selector();
|
||||
let s_partial = meta.selector();
|
||||
let s_pad_and_add = meta.selector();
|
||||
|
||||
let alpha = [5, 0, 0, 0];
|
||||
let pow_5 = |v: Expression<F>| {
|
||||
|
@ -151,6 +167,32 @@ impl<F: FieldExt> Pow5T3Chip<F> {
|
|||
]
|
||||
});
|
||||
|
||||
meta.create_gate("pad-and-add", |meta| {
|
||||
let initial_state_0 = meta.query_advice(state[0], Rotation::prev());
|
||||
let initial_state_1 = meta.query_advice(state[1], Rotation::prev());
|
||||
let initial_state_2 = meta.query_advice(state[2], Rotation::prev());
|
||||
let input_0 = meta.query_advice(state[0], Rotation::cur());
|
||||
let input_1 = meta.query_advice(state[1], Rotation::cur());
|
||||
let output_state_0 = meta.query_advice(state[0], Rotation::next());
|
||||
let output_state_1 = meta.query_advice(state[1], Rotation::next());
|
||||
let output_state_2 = meta.query_advice(state[2], Rotation::next());
|
||||
|
||||
let s_pad_and_add = meta.query_selector(s_pad_and_add, Rotation::cur());
|
||||
|
||||
let pad_and_add = |initial_state, input, output_state| {
|
||||
// We pad the input by storing the required padding in fixed columns and
|
||||
// then constraining the corresponding input columns to be equal to it.
|
||||
s_pad_and_add.clone() * (initial_state + input - output_state)
|
||||
};
|
||||
|
||||
vec![
|
||||
pad_and_add(initial_state_0, input_0, output_state_0),
|
||||
pad_and_add(initial_state_1, input_1, output_state_1),
|
||||
// The capacity element is never altered by the input.
|
||||
s_pad_and_add * (initial_state_2 - output_state_2),
|
||||
]
|
||||
});
|
||||
|
||||
Pow5T3Config {
|
||||
state,
|
||||
state_permutation,
|
||||
|
@ -159,6 +201,7 @@ impl<F: FieldExt> Pow5T3Chip<F> {
|
|||
rc_b,
|
||||
s_full,
|
||||
s_partial,
|
||||
s_pad_and_add,
|
||||
half_full_rounds,
|
||||
half_partial_rounds,
|
||||
alpha,
|
||||
|
@ -234,7 +277,141 @@ impl<F: FieldExt, S: Spec<F, WIDTH, 2>> PoseidonInstructions<F, S, WIDTH, 2> for
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
impl<F: FieldExt, S: Spec<F, WIDTH, 2>> PoseidonDuplexInstructions<F, S, WIDTH, 2>
|
||||
for Pow5T3Chip<F>
|
||||
{
|
||||
fn initial_state(
|
||||
&self,
|
||||
layouter: &mut impl Layouter<F>,
|
||||
domain: &impl Domain<F, S, WIDTH, 2>,
|
||||
) -> Result<State<Self::Word, WIDTH>, Error> {
|
||||
let config = self.config();
|
||||
layouter.assign_region(
|
||||
|| format!("initial state for domain {:?}", domain),
|
||||
|mut region| {
|
||||
let mut load_state_word = |i: usize, value: F| {
|
||||
let var = region.assign_advice(
|
||||
|| format!("state_{}", i),
|
||||
config.state[i],
|
||||
0,
|
||||
|| Ok(value),
|
||||
)?;
|
||||
let fixed = region.assign_fixed(
|
||||
|| format!("state_{}", i),
|
||||
config.rc_b[i],
|
||||
0,
|
||||
|| Ok(value),
|
||||
)?;
|
||||
region.constrain_equal(&config.state_permutation, var, fixed)?;
|
||||
Ok(StateWord {
|
||||
var,
|
||||
value: Some(value),
|
||||
})
|
||||
};
|
||||
|
||||
Ok([
|
||||
load_state_word(0, F::zero())?,
|
||||
load_state_word(1, F::zero())?,
|
||||
load_state_word(2, domain.initial_capacity_element())?,
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn pad_and_add(
|
||||
&self,
|
||||
layouter: &mut impl Layouter<F>,
|
||||
domain: &impl Domain<F, S, WIDTH, 2>,
|
||||
initial_state: &State<Self::Word, WIDTH>,
|
||||
input: &SpongeState<Self::Word, 2>,
|
||||
) -> Result<State<Self::Word, WIDTH>, Error> {
|
||||
let config = self.config();
|
||||
layouter.assign_region(
|
||||
|| format!("pad-and-add for domain {:?}", domain),
|
||||
|mut region| {
|
||||
config.s_pad_and_add.enable(&mut region, 1)?;
|
||||
|
||||
// Load the initial state into this region.
|
||||
let mut load_state_word = |i: usize| {
|
||||
let value = initial_state[i].value;
|
||||
let var = region.assign_advice(
|
||||
|| format!("load state_{}", i),
|
||||
config.state[i],
|
||||
0,
|
||||
|| value.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
region.constrain_equal(&config.state_permutation, initial_state[i].var, var)?;
|
||||
Ok(StateWord { var, value })
|
||||
};
|
||||
let initial_state = [
|
||||
load_state_word(0)?,
|
||||
load_state_word(1)?,
|
||||
load_state_word(2)?,
|
||||
];
|
||||
|
||||
let padding_values = domain.padding();
|
||||
|
||||
// Load the input and padding into this region.
|
||||
let mut load_input_word = |i: usize| {
|
||||
let (constraint_var, value) = match (input[i], padding_values[i]) {
|
||||
(Some(word), None) => (word.var, word.value),
|
||||
(None, Some(padding_value)) => {
|
||||
let padding_var = region.assign_fixed(
|
||||
|| format!("load pad_{}", i),
|
||||
config.rc_b[i],
|
||||
1,
|
||||
|| Ok(padding_value),
|
||||
)?;
|
||||
(padding_var, Some(padding_value))
|
||||
}
|
||||
_ => panic!("Input and padding don't match"),
|
||||
};
|
||||
let var = region.assign_advice(
|
||||
|| format!("load input_{}", i),
|
||||
config.state[i],
|
||||
1,
|
||||
|| value.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
region.constrain_equal(&config.state_permutation, constraint_var, var)?;
|
||||
|
||||
Ok(StateWord { var, value })
|
||||
};
|
||||
let input = [load_input_word(0)?, load_input_word(1)?];
|
||||
|
||||
// Constrain the output.
|
||||
let mut constrain_output_word = |i: usize| {
|
||||
let value = initial_state[i].value.and_then(|initial_word| {
|
||||
input
|
||||
.get(i)
|
||||
.map(|word| word.value)
|
||||
// The capacity element is never altered by the input.
|
||||
.unwrap_or_else(|| Some(F::zero()))
|
||||
.map(|input_word| initial_word + input_word)
|
||||
});
|
||||
let var = region.assign_advice(
|
||||
|| format!("load output_{}", i),
|
||||
config.state[i],
|
||||
2,
|
||||
|| value.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
Ok(StateWord { var, value })
|
||||
};
|
||||
|
||||
Ok([
|
||||
constrain_output_word(0)?,
|
||||
constrain_output_word(1)?,
|
||||
constrain_output_word(2)?,
|
||||
])
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_output(state: &State<Self::Word, WIDTH>) -> SpongeState<Self::Word, 2> {
|
||||
[Some(state[0]), Some(state[1])]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StateWord<F: FieldExt> {
|
||||
var: Cell,
|
||||
value: Option<F>,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::array;
|
||||
use std::fmt;
|
||||
use std::iter;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
@ -237,11 +238,13 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> Duplex
|
|||
|
||||
/// A domain in which a Poseidon hash function is being used.
|
||||
pub trait Domain<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>:
|
||||
Copy
|
||||
Copy + fmt::Debug
|
||||
{
|
||||
/// The initial capacity element, encoding this domain.
|
||||
fn initial_capacity_element(&self) -> F;
|
||||
|
||||
fn padding(&self) -> SpongeState<F, RATE>;
|
||||
|
||||
/// Returns a function that will update the given state with the given input to a
|
||||
/// duplex permutation round, applying padding according to this domain specification.
|
||||
fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>;
|
||||
|
@ -262,6 +265,16 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize, const
|
|||
F::from_u128((L as u128) << 64)
|
||||
}
|
||||
|
||||
fn padding(&self) -> SpongeState<F, RATE> {
|
||||
// For constant-input-length hashing, padding consists of the field elements being
|
||||
// zero.
|
||||
let mut padding = [None; RATE];
|
||||
for word in padding.iter_mut().skip(L) {
|
||||
*word = Some(F::zero());
|
||||
}
|
||||
padding
|
||||
}
|
||||
|
||||
fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)> {
|
||||
Box::new(|state, input| {
|
||||
// `Iterator::zip` short-circuits when one iterator completes, so this will only
|
||||
|
|
Loading…
Reference in New Issue