poseidon: Remove `self` parameter from `Domain` trait methods

For almost all the sponge constructions defined in the Poseidon paper,
the domain can be defined completely statically. Variable-length hashing
requires knowledge of the message length, but that can be provided to
the fixed padding function in a subsequent commit, and in any case we
can't use variable-length inputs in a circuit.
This commit is contained in:
Jack Grigg 2021-12-09 20:23:27 +00:00
parent 9f654005c7
commit bfc65d5985
8 changed files with 77 additions and 80 deletions

View File

@ -101,10 +101,9 @@ where
},
)?;
let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init(
let hasher = Hash::<_, _, S, ConstantLength<L>, WIDTH, RATE>::init(
chip,
layouter.namespace(|| "init"),
ConstantLength::<L>,
)?;
let output = hasher.hash(layouter.namespace(|| "hash"), message)?;
@ -210,7 +209,7 @@ fn bench_poseidon<S, const WIDTH: usize, const RATE: usize, const L: usize>(
.collect::<Vec<_>>()
.try_into()
.unwrap();
let output = poseidon::Hash::<_, S, _, WIDTH, RATE>::init(ConstantLength::<L>).hash(message);
let output = poseidon::Hash::<_, S, ConstantLength<L>, WIDTH, RATE>::init().hash(message);
let circuit = HashCircuit::<S, WIDTH, RATE, L> {
message: Some(message),

View File

@ -21,7 +21,9 @@ fn bench_primitives(c: &mut Criterion) {
let message = [pallas::Base::random(rng), pallas::Base::random(rng)];
group.bench_function("2-to-1", |b| {
b.iter(|| poseidon::Hash::<_, P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message))
b.iter(|| {
poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message)
})
});
}

View File

@ -482,11 +482,11 @@ impl plonk::Circuit<pallas::Base> for Circuit {
// hash_old = poseidon_hash(nk, rho_old)
let hash_old = {
let poseidon_message = [nk.clone(), rho_old.clone()];
let poseidon_hasher = PoseidonHash::<_, _, poseidon::P128Pow5T3, _, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "Poseidon init"),
ConstantLength::<2>,
)?;
let poseidon_hasher =
PoseidonHash::<_, _, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "Poseidon init"),
)?;
poseidon_hasher.hash(
layouter.namespace(|| "Poseidon hash (nk, rho_old)"),
poseidon_message,

View File

@ -39,22 +39,19 @@ pub trait PoseidonInstructions<F: FieldExt, S: Spec<F, T, RATE>, const T: usize,
pub trait PoseidonSpongeInstructions<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<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, T, RATE>,
) -> Result<State<Self::Word, T>, Error>;
fn initial_state(&self, layouter: &mut impl Layouter<F>)
-> 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, T, RATE>,
initial_state: &State<Self::Word, T>,
input: &SpongeRate<Self::Word, RATE>,
) -> Result<State<Self::Word, T>, Error>;
@ -96,7 +93,7 @@ impl<
fn poseidon_sponge<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
@ -104,11 +101,10 @@ fn poseidon_sponge<
>(
chip: &PoseidonChip,
mut layouter: impl Layouter<F>,
domain: &D,
state: &mut State<PoseidonChip::Word, T>,
input: &SpongeRate<PoseidonChip::Word, RATE>,
) -> Result<SpongeRate<PoseidonChip::Word, RATE>, Error> {
*state = chip.pad_and_add(&mut layouter, domain, state, input)?;
*state = chip.pad_and_add(&mut layouter, state, input)?;
*state = chip.permute(&mut layouter, state)?;
Ok(PoseidonChip::get_output(state))
}
@ -117,7 +113,7 @@ fn poseidon_sponge<
#[derive(Debug)]
pub struct Sponge<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
M: SpongeMode,
D: Domain<F, T, RATE>,
@ -127,13 +123,12 @@ pub struct Sponge<
chip: PoseidonChip,
mode: M,
state: State<PoseidonChip::Word, T>,
domain: D,
_marker: PhantomData<M>,
_marker: PhantomData<(M, D)>,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
@ -141,25 +136,19 @@ impl<
> Sponge<F, PoseidonChip, S, Absorbing<PoseidonChip::Word, RATE>, D, T, RATE>
{
/// Constructs a new duplex sponge for the given Poseidon specification.
pub fn new(
chip: PoseidonChip,
mut layouter: impl Layouter<F>,
domain: D,
) -> Result<Self, Error> {
chip.initial_state(&mut layouter, &domain)
.map(|state| Sponge {
chip,
mode: Absorbing(
(0..RATE)
.map(|_| None)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
),
state,
domain,
_marker: PhantomData::default(),
})
pub fn new(chip: PoseidonChip, mut layouter: impl Layouter<F>) -> Result<Self, Error> {
chip.initial_state(&mut layouter).map(|state| Sponge {
chip,
mode: Absorbing(
(0..RATE)
.map(|_| None)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
),
state,
_marker: PhantomData::default(),
})
}
/// Absorbs an element into the sponge.
@ -179,7 +168,6 @@ impl<
let _ = poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&self.domain,
&mut self.state,
&self.mode.0,
)?;
@ -198,7 +186,6 @@ impl<
let mode = Squeezing(poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&self.domain,
&mut self.state,
&self.mode.0,
)?);
@ -207,7 +194,6 @@ impl<
chip: self.chip,
mode,
state: self.state,
domain: self.domain,
_marker: PhantomData::default(),
})
}
@ -215,7 +201,7 @@ impl<
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
@ -235,7 +221,6 @@ impl<
self.mode = Squeezing(poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&self.domain,
&mut self.state,
&self.mode.0,
)?);
@ -247,7 +232,7 @@ impl<
#[derive(Debug)]
pub struct Hash<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
@ -258,7 +243,7 @@ pub struct Hash<
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
@ -266,14 +251,14 @@ impl<
> Hash<F, PoseidonChip, S, D, T, RATE>
{
/// Initializes a new hasher.
pub fn init(chip: PoseidonChip, layouter: impl Layouter<F>, domain: D) -> Result<Self, Error> {
Sponge::new(chip, layouter, domain).map(|sponge| Hash { sponge })
pub fn init(chip: PoseidonChip, layouter: impl Layouter<F>) -> Result<Self, Error> {
Sponge::new(chip, layouter).map(|sponge| Hash { sponge })
}
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, ConstantLength<L>, T, RATE>,
S: Spec<F, T, RATE>,
const T: usize,
const RATE: usize,

View File

@ -268,17 +268,21 @@ impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize
}
}
impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize>
PoseidonSpongeInstructions<F, S, WIDTH, RATE> for Pow5Chip<F, WIDTH, RATE>
impl<
F: FieldExt,
S: Spec<F, WIDTH, RATE>,
D: Domain<F, WIDTH, RATE>,
const WIDTH: usize,
const RATE: usize,
> PoseidonSpongeInstructions<F, S, D, WIDTH, RATE> for Pow5Chip<F, WIDTH, RATE>
{
fn initial_state(
&self,
layouter: &mut impl Layouter<F>,
domain: &impl Domain<F, WIDTH, RATE>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
let state = layouter.assign_region(
|| format!("initial state for domain {:?}", domain),
|| format!("initial state for domain {}", D::name()),
|mut region| {
let mut state = Vec::with_capacity(WIDTH);
let mut load_state_word = |i: usize, value: F| -> Result<_, Error> {
@ -296,7 +300,7 @@ impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize
for i in 0..RATE {
load_state_word(i, F::zero())?;
}
load_state_word(RATE, domain.initial_capacity_element())?;
load_state_word(RATE, D::initial_capacity_element())?;
Ok(state)
},
@ -308,13 +312,12 @@ impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize
fn pad_and_add(
&self,
layouter: &mut impl Layouter<F>,
domain: &impl Domain<F, WIDTH, RATE>,
initial_state: &State<Self::Word, WIDTH>,
input: &SpongeRate<Self::Word, RATE>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
layouter.assign_region(
|| format!("pad-and-add for domain {:?}", domain),
|| format!("pad-and-add for domain {}", D::name()),
|mut region| {
config.s_pad_and_add.enable(&mut region, 1)?;
@ -334,7 +337,7 @@ impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize
(0..WIDTH).map(load_state_word).collect();
let initial_state = initial_state?;
let padding_values = domain.padding();
let padding_values = D::padding();
// Load the input and padding into this region.
let load_input_word = |i: usize| {
@ -783,10 +786,9 @@ mod tests {
},
)?;
let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init(
let hasher = Hash::<_, _, S, ConstantLength<L>, WIDTH, RATE>::init(
chip,
layouter.namespace(|| "init"),
ConstantLength::<L>,
)?;
let output = hasher.hash(layouter.namespace(|| "hash"), message)?;
@ -809,7 +811,7 @@ mod tests {
fn poseidon_hash() {
let message = [Fp::rand(), Fp::rand()];
let output =
poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength::<2>).hash(message);
poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init().hash(message);
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
@ -828,8 +830,8 @@ mod tests {
pallas::Base::from_repr(tv.input[0]).unwrap(),
pallas::Base::from_repr(tv.input[1]).unwrap(),
];
let output =
poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength).hash(message);
let output = poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init()
.hash(message);
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {

View File

@ -275,16 +275,19 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>
}
/// A domain in which a Poseidon hash function is being used.
pub trait Domain<F: FieldExt, const T: usize, const RATE: usize>: Copy + fmt::Debug {
pub trait Domain<F: FieldExt, const T: usize, const RATE: usize> {
/// The name of this domain, for debug formatting purposes.
fn name() -> String;
/// The initial capacity element, encoding this domain.
fn initial_capacity_element(&self) -> F;
fn initial_capacity_element() -> F;
/// The padding that will be added to each state word by [`Domain::pad_and_add`].
fn padding(&self) -> SpongeRate<F, RATE>;
fn padding() -> SpongeRate<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>, &SpongeRate<F, RATE>)>;
fn pad_and_add() -> Box<dyn Fn(&mut State<F, T>, &SpongeRate<F, RATE>)>;
}
/// A Poseidon hash function used with constant input length.
@ -296,13 +299,17 @@ pub struct ConstantLength<const L: usize>;
impl<F: FieldExt, const T: usize, const RATE: usize, const L: usize> Domain<F, T, RATE>
for ConstantLength<L>
{
fn initial_capacity_element(&self) -> F {
fn name() -> String {
format!("ConstantLength<{}>", L)
}
fn initial_capacity_element() -> F {
// Capacity value is $length \cdot 2^64 + (o-1)$ where o is the output length.
// We hard-code an output length of 1.
F::from_u128((L as u128) << 64)
}
fn padding(&self) -> SpongeRate<F, RATE> {
fn padding() -> SpongeRate<F, RATE> {
// For constant-input-length hashing, padding consists of the field elements being
// zero.
let mut padding = [None; RATE];
@ -312,7 +319,7 @@ impl<F: FieldExt, const T: usize, const RATE: usize, const L: usize> Domain<F, T
padding
}
fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeRate<F, RATE>)> {
fn pad_and_add() -> Box<dyn Fn(&mut State<F, T>, &SpongeRate<F, RATE>)> {
Box::new(|state, input| {
// `Iterator::zip` short-circuits when one iterator completes, so this will only
// mutate the rate portion of the state.
@ -336,7 +343,7 @@ pub struct Hash<
const RATE: usize,
> {
sponge: Sponge<F, S, Absorbing<F, RATE>, T, RATE>,
domain: D,
_domain: PhantomData<D>,
}
impl<
@ -353,7 +360,7 @@ impl<
.field("rate", &RATE)
.field("R_F", &S::full_rounds())
.field("R_P", &S::partial_rounds())
.field("domain", &self.domain)
.field("domain", &D::name())
.finish()
}
}
@ -367,10 +374,10 @@ impl<
> Hash<F, S, D, T, RATE>
{
/// Initializes a new hasher.
pub fn init(domain: D) -> Self {
pub fn init() -> Self {
Hash {
sponge: Sponge::new(domain.initial_capacity_element(), domain.pad_and_add()),
domain,
sponge: Sponge::new(D::initial_capacity_element(), D::pad_and_add()),
_domain: PhantomData::default(),
}
}
}
@ -400,7 +407,7 @@ mod tests {
let (round_constants, mds, _) = OrchardNullifier::constants();
let hasher = Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength);
let hasher = Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init();
let result = hasher.hash(message);
// The result should be equivalent to just directly applying the permutation and

View File

@ -292,7 +292,8 @@ mod tests {
Fp::from_repr(tv.input[1]).unwrap(),
];
let result = Hash::<_, super::P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message);
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}
@ -303,7 +304,8 @@ mod tests {
Fq::from_repr(tv.input[1]).unwrap(),
];
let result = Hash::<_, super::P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message);
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}

View File

@ -212,7 +212,7 @@ pub(crate) fn diversify_hash(d: &[u8; 11]) -> NonIdentityPallasPoint {
///
/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs
pub(crate) fn prf_nf(nk: pallas::Base, rho: pallas::Base) -> pallas::Base {
poseidon::Hash::<_, poseidon::P128Pow5T3, _, 3, 2>::init(poseidon::ConstantLength)
poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init()
.hash([nk, rho])
}