Skip to content

Commit a5d6397

Browse files
authored
Add preliminary Poseidon CRH w/ constraints (#28)
* Add poseidon * Initial poseidon impl, missing contraints for permute/hash * Formatting * Add poseidon constraints and evaluate, missing statics allocation * Remove is_zero, since inverse applies constraint * Add statics * Fmt * no_std fix * Fix errors * Fmt * Update Cargo.toml
1 parent e944fb1 commit a5d6397

File tree

6 files changed

+616
-1
lines changed

6 files changed

+616
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ r1cs = [ "ark-r1cs-std", "tracing", "ark-nonnative-field" ]
4545
ark-ed-on-bls12-381 = { git = "https://github.com/arkworks-rs/curves", default-features = false, features = [ "r1cs" ] }
4646
ark-bls12-377 = { git = "https://github.com/arkworks-rs/curves", default-features = false, features = [ "curve", "r1cs" ] }
4747
ark-mnt4-298 = { git = "https://github.com/arkworks-rs/curves", default-features = false, features = [ "curve", "r1cs" ] }
48-
ark-mnt6-298 = { git = "https://github.com/arkworks-rs/curves", default-features = false, features = [ "r1cs" ] }
48+
ark-mnt6-298 = { git = "https://github.com/arkworks-rs/curves", default-features = false, features = [ "r1cs" ] }

src/crh/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use ark_std::rand::Rng;
55
pub mod bowe_hopwood;
66
pub mod injective_map;
77
pub mod pedersen;
8+
pub mod poseidon;
89

910
use crate::Error;
1011

src/crh/poseidon/constraints.rs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
use super::sbox::constraints::SboxConstraints;
2+
use super::PoseidonRoundParams;
3+
use super::{Poseidon, PoseidonCRH};
4+
use crate::FixedLengthCRHGadget;
5+
use ark_ff::PrimeField;
6+
use ark_r1cs_std::fields::fp::FpVar;
7+
use ark_r1cs_std::uint8::UInt8;
8+
use ark_r1cs_std::ToConstraintFieldGadget;
9+
use ark_r1cs_std::{alloc::AllocVar, fields::FieldVar, prelude::*};
10+
use ark_relations::r1cs::{Namespace, SynthesisError};
11+
12+
use ark_std::marker::PhantomData;
13+
use core::borrow::Borrow;
14+
15+
#[derive(Derivative, Clone)]
16+
pub struct PoseidonRoundParamsVar<F: PrimeField, P: PoseidonRoundParams<F>> {
17+
params: Poseidon<F, P>,
18+
}
19+
20+
pub struct PoseidonCRHGadget<F: PrimeField, P: PoseidonRoundParams<F>> {
21+
field: PhantomData<F>,
22+
params: PhantomData<PoseidonRoundParamsVar<F, P>>,
23+
}
24+
25+
impl<F: PrimeField, P: PoseidonRoundParams<F>> PoseidonRoundParamsVar<F, P> {
26+
fn permute(&self, input: Vec<FpVar<F>>) -> Result<Vec<FpVar<F>>, SynthesisError> {
27+
let width = P::WIDTH;
28+
assert_eq!(input.len(), width);
29+
30+
let full_rounds_beginning = P::FULL_ROUNDS_BEGINNING;
31+
let partial_rounds = P::PARTIAL_ROUNDS;
32+
let full_rounds_end = P::FULL_ROUNDS_END;
33+
34+
let mut input_vars: Vec<FpVar<F>> = input;
35+
36+
let mut round_keys_offset = 0;
37+
38+
// ------------ First rounds with full SBox begin --------------------
39+
40+
for _k in 0..full_rounds_beginning {
41+
// TODO: Check if Scalar::default() can be replaced by FpVar<F>::one() or FpVar<F>::zero()
42+
let mut sbox_outputs: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
43+
44+
// Substitution (S-box) layer
45+
for i in 0..width {
46+
let round_key = self.params.round_keys[round_keys_offset];
47+
sbox_outputs[i] = P::SBOX
48+
.synthesize_sbox(input_vars[i].clone(), round_key)?
49+
.into();
50+
51+
round_keys_offset += 1;
52+
}
53+
54+
// TODO: Check if Scalar::default() can be replaced by FpVar<F>::one()
55+
let mut next_input_vars: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
56+
57+
self.apply_linear_layer(
58+
width,
59+
sbox_outputs,
60+
&mut next_input_vars,
61+
&self.params.mds_matrix,
62+
);
63+
64+
for i in 0..width {
65+
// replace input_vars with next_input_vars
66+
input_vars[i] = next_input_vars.remove(0);
67+
}
68+
}
69+
70+
// ------------ First rounds with full SBox begin --------------------
71+
72+
// ------------ Middle rounds with partial SBox begin --------------------
73+
74+
for _k in full_rounds_beginning..(full_rounds_beginning + partial_rounds) {
75+
let mut sbox_outputs: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
76+
77+
// Substitution (S-box) layer
78+
for i in 0..width {
79+
let round_key = self.params.round_keys[round_keys_offset];
80+
81+
// apply Sbox to only 1 element of the state.
82+
// Here the last one is chosen but the choice is arbitrary.
83+
if i == width - 1 {
84+
sbox_outputs[i] = P::SBOX
85+
.synthesize_sbox(input_vars[i].clone(), round_key)?
86+
.into();
87+
} else {
88+
sbox_outputs[i] = input_vars[i].clone() + round_key;
89+
}
90+
91+
round_keys_offset += 1;
92+
}
93+
94+
// Linear layer
95+
// TODO: Check if Scalar::default() can be replaced by FpVar<F>::one()
96+
let mut next_input_vars: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
97+
98+
self.apply_linear_layer(
99+
width,
100+
sbox_outputs,
101+
&mut next_input_vars,
102+
&self.params.mds_matrix,
103+
);
104+
105+
for i in 0..width {
106+
// replace input_vars with simplified next_input_vars
107+
input_vars[i] = next_input_vars.remove(0);
108+
}
109+
}
110+
111+
// ------------ Middle rounds with partial SBox end --------------------
112+
113+
// ------------ Last rounds with full SBox begin --------------------
114+
115+
for _k in (full_rounds_beginning + partial_rounds)
116+
..(full_rounds_beginning + partial_rounds + full_rounds_end)
117+
{
118+
// TODO: Check if Scalar::default() can be replaced by FpVar<F>::one()
119+
let mut sbox_outputs: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
120+
121+
// Substitution (S-box) layer
122+
for i in 0..width {
123+
let round_key = self.params.round_keys[round_keys_offset];
124+
sbox_outputs[i] = P::SBOX
125+
.synthesize_sbox(input_vars[i].clone(), round_key)?
126+
.into();
127+
128+
round_keys_offset += 1;
129+
}
130+
131+
// Linear layer
132+
// TODO: Check if Scalar::default() can be replaced by FpVar<F>::one()
133+
let mut next_input_vars: Vec<FpVar<F>> = vec![FpVar::<F>::one(); width];
134+
135+
self.apply_linear_layer(
136+
width,
137+
sbox_outputs,
138+
&mut next_input_vars,
139+
&self.params.mds_matrix,
140+
);
141+
142+
for i in 0..width {
143+
// replace input_vars with next_input_vars
144+
input_vars[i] = next_input_vars.remove(0);
145+
}
146+
}
147+
148+
// ------------ Last rounds with full SBox end --------------------
149+
150+
Ok(input_vars)
151+
}
152+
153+
fn apply_linear_layer(
154+
&self,
155+
width: usize,
156+
sbox_outs: Vec<FpVar<F>>,
157+
next_inputs: &mut Vec<FpVar<F>>,
158+
mds_matrix: &Vec<Vec<F>>,
159+
) {
160+
for j in 0..width {
161+
for i in 0..width {
162+
next_inputs[i] = next_inputs[i].clone()
163+
+ sbox_outs[j].clone() * &FpVar::<F>::Constant(mds_matrix[i][j]);
164+
}
165+
}
166+
}
167+
168+
fn hash_2(
169+
&self,
170+
xl: FpVar<F>,
171+
xr: FpVar<F>,
172+
statics: Vec<FpVar<F>>,
173+
) -> Result<FpVar<F>, SynthesisError> {
174+
let width = P::WIDTH;
175+
// Only 2 inputs to the permutation are set to the input of this hash
176+
// function.
177+
assert_eq!(statics.len(), width - 2);
178+
179+
// Always keep the 1st input as 0
180+
let mut inputs = vec![statics[0].to_owned()];
181+
inputs.push(xl);
182+
inputs.push(xr);
183+
184+
// statics correspond to committed variables with values as PADDING_CONST
185+
// and 0s and randomness as 0
186+
for i in 1..statics.len() {
187+
inputs.push(statics[i].to_owned());
188+
}
189+
let permutation_output = self.permute(inputs)?;
190+
Ok(permutation_output[1].clone())
191+
}
192+
193+
fn hash_4(
194+
&self,
195+
input: &[FpVar<F>],
196+
statics: Vec<FpVar<F>>,
197+
) -> Result<FpVar<F>, SynthesisError> {
198+
assert_eq!(input.len(), 4);
199+
let width = P::WIDTH;
200+
// Only 4 inputs to the permutation are set to the input of this hash
201+
// function.
202+
assert_eq!(statics.len(), width - 4);
203+
// Always keep the 1st input as 0
204+
let mut inputs = vec![statics[0].to_owned()];
205+
inputs.push(input[0].clone());
206+
inputs.push(input[1].clone());
207+
inputs.push(input[2].clone());
208+
inputs.push(input[3].clone());
209+
210+
// statics correspond to committed variables with values as PADDING_CONST
211+
// and 0s and randomness as 0
212+
for i in 1..statics.len() {
213+
inputs.push(statics[i].to_owned());
214+
}
215+
216+
let permutation_output = self.permute(inputs)?;
217+
Ok(permutation_output[1].to_owned())
218+
}
219+
}
220+
221+
// https://github.com/arkworks-rs/r1cs-std/blob/master/src/bits/uint8.rs#L343
222+
impl<F: PrimeField, P: PoseidonRoundParams<F>> FixedLengthCRHGadget<PoseidonCRH<F, P>, F>
223+
for PoseidonCRHGadget<F, P>
224+
{
225+
type OutputVar = FpVar<F>;
226+
type ParametersVar = PoseidonRoundParamsVar<F, P>;
227+
228+
fn evaluate(
229+
parameters: &Self::ParametersVar,
230+
input: &[UInt8<F>],
231+
) -> Result<Self::OutputVar, SynthesisError> {
232+
let f_var_vec: Vec<FpVar<F>> = input.to_constraint_field()?;
233+
234+
// Choice is arbitrary
235+
let padding_const: F = F::from(101u32);
236+
let zero_const: F = F::zero();
237+
238+
let statics = match f_var_vec.len() {
239+
2 => {
240+
vec![
241+
FpVar::<F>::Constant(zero_const),
242+
FpVar::<F>::Constant(padding_const),
243+
FpVar::<F>::Constant(zero_const),
244+
FpVar::<F>::Constant(zero_const),
245+
]
246+
}
247+
4 => {
248+
vec![
249+
FpVar::<F>::Constant(zero_const),
250+
FpVar::<F>::Constant(padding_const),
251+
]
252+
}
253+
_ => panic!("incorrect number (elements) for poseidon hash"),
254+
};
255+
256+
let result = match f_var_vec.len() {
257+
2 => parameters.hash_2(f_var_vec[0].clone(), f_var_vec[1].clone(), statics),
258+
4 => parameters.hash_4(&f_var_vec, statics),
259+
_ => panic!("incorrect number (elements) for poseidon hash"),
260+
};
261+
Ok(result.unwrap_or(Self::OutputVar::zero()))
262+
}
263+
}
264+
265+
impl<F: PrimeField, P: PoseidonRoundParams<F>> AllocVar<Poseidon<F, P>, F>
266+
for PoseidonRoundParamsVar<F, P>
267+
{
268+
#[tracing::instrument(target = "r1cs", skip(_cs, f))]
269+
fn new_variable<T: Borrow<Poseidon<F, P>>>(
270+
_cs: impl Into<Namespace<F>>,
271+
f: impl FnOnce() -> Result<T, SynthesisError>,
272+
_mode: AllocationMode,
273+
) -> Result<Self, SynthesisError> {
274+
let params = f()?.borrow().clone();
275+
Ok(Self { params })
276+
}
277+
}

0 commit comments

Comments
 (0)