Skip to main content

sonobe_primitives/transcripts/griffin/
mod.rs

1//! Implementation of the Griffin circuit-friendly hash function and its
2//! parameter generation, as well as out-of-circuit widgets and in-circuit
3//! gadgets for permutation, hashing, sponges, and transcripts.
4//!
5//! According to the Griffin [paper], it is very efficient in terms of the
6//! number of constraints, but later an [attack] on Griffin and similar hash
7//! functions was discovered.
8//! Therefore, it is recommended to avoid using Griffin in production.
9//!
10//! The code is forked from the [implementation] in the Hash Functions for
11//! Zero-Knowledge Applications Zoo but uses arkworks instead of bellman as the
12//! underlying cryptographic library.
13//!
14//! [paper]: https://eprint.iacr.org/2022/403.pdf
15//! [attack]: https://eprint.iacr.org/2024/347.pdf
16//! [implementation]: https://extgit.isec.tugraz.at/krypto/zkfriendlyhashzoo
17
18// Below we attach Hash functions for Zero-Knowledge applications Zoo's original
19// license notice.
20//
21// Copyright (c) 2021 Graz University of Technology
22//
23// Permission is hereby granted, free of charge, to any person obtaining a copy
24// of this software and associated documentation files (the "Software"), to deal
25// in the Software without restriction, including without limitation the rights
26// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27// copies of the Software, and to permit persons to whom the Software is
28// furnished to do so, subject to the following conditions:
29//
30// The above copyright notice and this permission notice shall be included in
31// all copies or substantial portions of the Software.
32//
33// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39// SOFTWARE.
40
41use ark_ff::{LegendreSymbol, PrimeField, field_hashers::hash_to_field};
42use ark_r1cs_std::{
43    GR1CSVar,
44    alloc::AllocVar,
45    fields::{FieldVar, fp::FpVar},
46};
47use ark_relations::gr1cs::SynthesisError;
48use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
49use itertools::Itertools;
50use num_bigint::BigUint;
51use sha3::{
52    Shake128, Shake128Reader,
53    digest::{ExtendableOutput, Update, XofReader},
54};
55
56pub mod sponge;
57
58/// [`GriffinParams`] stores the full parameterisation of the Griffin
59/// permutation for a given prime field: state width `t`, S-box degree `d`,
60/// number of rounds, round constants, alpha/beta constants, and the MDS-like
61/// matrix.
62#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)]
63pub struct GriffinParams<F: PrimeField> {
64    round_constants: Vec<Vec<F>>,
65    t: usize,
66    d: usize,
67    d_inv: Vec<bool>,
68    rounds: usize,
69    alpha_beta: Vec<[F; 2]>,
70    mat: Vec<Vec<F>>,
71    rate: usize,
72    capacity: usize,
73}
74
75impl<F: PrimeField> GriffinParams<F> {
76    const INIT_SHAKE: &'static str = "Griffin";
77
78    /// [`GriffinParams::new`] constructs new Griffin parameters with the given
79    /// state width `t`, S-box degree `d`, and number of rounds `rounds`.
80    pub fn new(t: usize, d: usize, rounds: usize) -> Self {
81        // Equivalent to `assert!(t == 3 || t % 4 == 0);`, but bypass clippy's
82        // warning about `is_multiple_of`.
83        assert!(t == 3 || t & 3 == 0);
84        assert!(d == 3 || d == 5);
85        assert!(rounds >= 1);
86
87        let mut shake = Self::init_shake();
88
89        let d_inv = BigUint::from(d)
90            .modinv(&(-F::one()).into())
91            .unwrap()
92            .to_radix_be(2)
93            .into_iter()
94            .map(|i| i != 0)
95            .skip_while(|i| !i)
96            .collect();
97        let round_constants = Self::instantiate_rc(t, rounds, &mut shake);
98        let alpha_beta = Self::instantiate_alpha_beta(t, &mut shake);
99
100        let mat = Self::instantiate_matrix(t);
101
102        GriffinParams {
103            round_constants,
104            t,
105            d,
106            d_inv,
107            rounds,
108            alpha_beta,
109            mat,
110            rate: t - 1,
111            capacity: 1,
112        }
113    }
114
115    fn init_shake() -> Shake128Reader {
116        let mut shake = Shake128::default();
117        shake.update(Self::INIT_SHAKE.as_bytes());
118        for i in F::characteristic() {
119            shake.update(&i.to_le_bytes());
120        }
121        shake.finalize_xof()
122    }
123
124    fn instantiate_rc(t: usize, rounds: usize, shake: &mut Shake128Reader) -> Vec<Vec<F>> {
125        (0..rounds - 1)
126            .map(|_| (0..t).map(|_| hash_to_field::<_, _, 128>(shake)).collect())
127            .collect()
128    }
129
130    fn instantiate_alpha_beta(t: usize, shake: &mut Shake128Reader) -> Vec<[F; 2]> {
131        fn hash_to_non_zero_field<F: PrimeField>(reader: &mut impl XofReader) -> F {
132            loop {
133                let element = hash_to_field::<F, _, 128>(reader);
134                if !element.is_zero() {
135                    return element;
136                }
137            }
138        }
139
140        let mut alpha_beta = Vec::with_capacity(t - 2);
141
142        // random alpha/beta
143        loop {
144            let alpha = hash_to_non_zero_field::<F>(shake);
145            let mut beta = hash_to_non_zero_field::<F>(shake);
146            // distinct
147            while alpha == beta {
148                beta = hash_to_non_zero_field::<F>(shake);
149            }
150            let mut symbol = alpha;
151            symbol.square_in_place();
152            let mut tmp = beta;
153            tmp.double_in_place();
154            tmp.double_in_place();
155            symbol.sub_assign(&tmp);
156            if symbol.legendre() == LegendreSymbol::QuadraticNonResidue {
157                alpha_beta.push([alpha, beta]);
158                break;
159            }
160        }
161
162        // other alphas/betas
163        for i in 2..t - 1 {
164            let mut alpha = alpha_beta[0][0];
165            let mut beta = alpha_beta[0][1];
166            alpha.mul_assign(&F::from(i as u64));
167            beta.mul_assign(&F::from((i * i) as u64));
168            // distinct
169            while alpha == beta {
170                beta = hash_to_non_zero_field::<F>(shake);
171            }
172
173            #[cfg(debug_assertions)]
174            {
175                // check if really ok
176                let mut symbol = alpha;
177                symbol.square_in_place();
178                let mut tmp = beta;
179                tmp.double_in_place();
180                tmp.double_in_place();
181                symbol.sub_assign(&tmp);
182                assert_eq!(symbol.legendre(), LegendreSymbol::QuadraticNonResidue);
183            }
184
185            alpha_beta.push([alpha, beta]);
186        }
187
188        alpha_beta
189    }
190
191    fn instantiate_matrix(t: usize) -> Vec<Vec<F>> {
192        if t == 3 {
193            let row = vec![F::from(2), F::from(1), F::from(1)];
194            let t = row.len();
195            let mut mat: Vec<Vec<F>> = Vec::with_capacity(t);
196            let mut rot = row.to_owned();
197            mat.push(rot.clone());
198            for _ in 1..t {
199                rot.rotate_right(1);
200                mat.push(rot.clone());
201            }
202            mat
203        } else {
204            let row1 = vec![F::from(5), F::from(7), F::from(1), F::from(3)];
205            let row2 = vec![F::from(4), F::from(6), F::from(1), F::from(1)];
206            let row3 = vec![F::from(1), F::from(3), F::from(5), F::from(7)];
207            let row4 = vec![F::from(1), F::from(1), F::from(4), F::from(6)];
208            let c_mat = vec![row1, row2, row3, row4];
209            if t == 4 {
210                c_mat
211            } else {
212                assert_eq!(t % 4, 0);
213                let mut mat: Vec<Vec<F>> = vec![vec![F::zero(); t]; t];
214                for (row, matrow) in mat.iter_mut().enumerate().take(t) {
215                    for (col, matitem) in matrow.iter_mut().enumerate().take(t) {
216                        let row_mod = row % 4;
217                        let col_mod = col % 4;
218                        *matitem = c_mat[row_mod][col_mod];
219                        if row / 4 == col / 4 {
220                            matitem.add_assign(&c_mat[row_mod][col_mod]);
221                        }
222                    }
223                }
224                mat
225            }
226        }
227    }
228}
229
230/// [`Griffin`] implements the Griffin permutation and Griffin hash.
231pub struct Griffin;
232
233impl Griffin {
234    fn affine_3<F: PrimeField>(params: &GriffinParams<F>, input: &mut [F], round: usize) {
235        // multiplication by circ(2 1 1) is equal to state + sum(state)
236        let mut sum = input[0];
237        input.iter().skip(1).for_each(|el| sum.add_assign(el));
238
239        if round < params.rounds - 1 {
240            for (el, rc) in input
241                .iter_mut()
242                .zip_eq(params.round_constants[round].iter())
243            {
244                el.add_assign(&sum);
245                el.add_assign(rc); // add round constant
246            }
247        } else {
248            // no round constant
249            for el in input.iter_mut() {
250                el.add_assign(&sum);
251            }
252        }
253    }
254
255    fn affine_4<F: PrimeField>(params: &GriffinParams<F>, input: &mut [F], round: usize) {
256        let mut t_0 = input[0];
257        t_0.add_assign(&input[1]);
258        let mut t_1 = input[2];
259        t_1.add_assign(&input[3]);
260        let mut t_2 = input[1];
261        t_2.double_in_place();
262        t_2.add_assign(&t_1);
263        let mut t_3 = input[3];
264        t_3.double_in_place();
265        t_3.add_assign(&t_0);
266        let mut t_4 = t_1;
267        t_4.double_in_place();
268        t_4.double_in_place();
269        t_4.add_assign(&t_3);
270        let mut t_5 = t_0;
271        t_5.double_in_place();
272        t_5.double_in_place();
273        t_5.add_assign(&t_2);
274        let mut t_6 = t_3;
275        t_6.add_assign(&t_5);
276        let mut t_7 = t_2;
277        t_7.add_assign(&t_4);
278        input[0] = t_6;
279        input[1] = t_5;
280        input[2] = t_7;
281        input[3] = t_4;
282
283        if round < params.rounds - 1 {
284            for (i, rc) in input
285                .iter_mut()
286                .zip_eq(params.round_constants[round].iter())
287            {
288                i.add_assign(rc);
289            }
290        }
291    }
292
293    fn affine<F: PrimeField>(params: &GriffinParams<F>, input: &mut [F], round: usize) {
294        if params.t == 3 {
295            Griffin::affine_3(params, input, round);
296            return;
297        }
298        if params.t == 4 {
299            Griffin::affine_4(params, input, round);
300            return;
301        }
302
303        // first matrix
304        let t4 = params.t / 4;
305        for i in 0..t4 {
306            let start_index = i * 4;
307            let mut t_0 = input[start_index];
308            t_0.add_assign(&input[start_index + 1]);
309            let mut t_1 = input[start_index + 2];
310            t_1.add_assign(&input[start_index + 3]);
311            let mut t_2 = input[start_index + 1];
312            t_2.double_in_place();
313            t_2.add_assign(&t_1);
314            let mut t_3 = input[start_index + 3];
315            t_3.double_in_place();
316            t_3.add_assign(&t_0);
317            let mut t_4: F = t_1;
318            t_4.double_in_place();
319            t_4.double_in_place();
320            t_4.add_assign(&t_3);
321            let mut t_5 = t_0;
322            t_5.double_in_place();
323            t_5.double_in_place();
324            t_5.add_assign(&t_2);
325            input[start_index] = t_3 + t_5;
326            input[start_index + 1] = t_5;
327            input[start_index + 2] = t_2 + t_4;
328            input[start_index + 3] = t_4;
329        }
330
331        // second matrix
332        let mut stored = [F::zero(); 4];
333        for l in 0..4 {
334            stored[l] = input[l];
335            for j in 1..t4 {
336                stored[l].add_assign(&input[4 * j + l]);
337            }
338        }
339
340        for i in 0..input.len() {
341            input[i].add_assign(&stored[i % 4]);
342            if round < params.rounds - 1 {
343                input[i].add_assign(&params.round_constants[round][i]); // add round constant
344            }
345        }
346    }
347
348    fn non_linear<F: PrimeField>(params: &GriffinParams<F>, input: &mut [F]) {
349        // first two state words
350        input[0] = {
351            let mut res = F::one();
352            for &i in &params.d_inv {
353                res.square_in_place();
354                if i {
355                    res *= input[0];
356                }
357            }
358            res
359        };
360
361        let mut state = input[1];
362
363        input[1].square_in_place();
364        match params.d {
365            3 => {}
366            5 => {
367                input[1].square_in_place();
368            }
369            _ => panic!(),
370        }
371        input[1].mul_assign(&state);
372
373        let mut y01_i = input[1];
374        // rest of the state
375        for i in 2..input.len() {
376            y01_i += input[0];
377            let l = if i == 2 { y01_i } else { y01_i + state };
378            let ab = &params.alpha_beta[i - 2];
379            state = input[i];
380            input[i] *= l.square() + l * ab[0] + ab[1];
381        }
382    }
383
384    /// [`Griffin::permute`] applies the Griffin permutation to the given input
385    /// state `input` in place under parameters `params`.
386    pub fn permute<F: PrimeField>(params: &GriffinParams<F>, input: &mut [F]) {
387        Griffin::affine(params, input, params.rounds); // no RC
388
389        for r in 0..params.rounds {
390            Griffin::non_linear(params, input);
391            Griffin::affine(params, input, r);
392        }
393    }
394
395    /// [`Griffin::hash`] implements the Griffin hash function based on the
396    /// sponge construction, which produces a single field element as the digest
397    /// of the given message `message` under parameters `params`.
398    pub fn hash<F: PrimeField>(params: &GriffinParams<F>, message: &[F]) -> F {
399        let mut state = vec![F::zero(); params.t];
400        for chunk in message.chunks(params.rate) {
401            for i in 0..chunk.len() {
402                state[i] += &chunk[i];
403            }
404            Griffin::permute(params, &mut state)
405        }
406        state[0]
407    }
408}
409
410/// [`GriffinGadget`] implements the gadgets for Griffin permutation and Griffin
411/// hash.
412pub struct GriffinGadget;
413
414impl GriffinGadget {
415    fn non_linear<F: PrimeField>(
416        params: &GriffinParams<F>,
417        state: &[FpVar<F>],
418    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
419        let cs = state.cs();
420        let mut result = state.to_owned();
421        // x0
422        result[0] = FpVar::new_variable_with_inferred_mode(cs, || {
423            Ok({
424                {
425                    let v = result[0].value().unwrap_or_default();
426                    let mut res = F::one();
427                    for &i in &params.d_inv {
428                        res.square_in_place();
429                        if i {
430                            res *= v;
431                        }
432                    }
433                    res
434                }
435            })
436        })?;
437
438        let mut sq = result[0].square()?;
439        if params.d == 5 {
440            sq = sq.square()?;
441        }
442        result[0].mul_equals(&sq, &state[0])?;
443
444        // x1
445        let mut sq = result[1].square()?;
446        if params.d == 5 {
447            sq = sq.square()?;
448        }
449        result[1] *= sq;
450
451        let mut y01_i = result[1].clone();
452
453        // rest of the state
454        for i in 2..result.len() {
455            y01_i += &result[0];
456            let l = if i == 2 {
457                y01_i.clone()
458            } else {
459                &y01_i + &state[i - 1]
460            };
461            let ab = &params.alpha_beta[i - 2];
462            result[i] *= l.square()? + l * ab[0] + ab[1];
463        }
464
465        Ok(result)
466    }
467
468    /// [`GriffinGadget::permute`] applies the Griffin permutation to the given
469    /// input state variables `input` in place under parameters `params`.
470    pub fn permute<F: PrimeField>(
471        params: &GriffinParams<F>,
472        state: &[FpVar<F>],
473    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
474        let mut current_state = state.to_owned();
475        current_state = params
476            .mat
477            .iter()
478            .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum())
479            .collect();
480
481        for r in 0..params.rounds {
482            current_state = GriffinGadget::non_linear(params, &current_state)?;
483            current_state = params
484                .mat
485                .iter()
486                .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum())
487                .collect();
488            if r < params.rounds - 1 {
489                current_state = current_state
490                    .iter()
491                    .zip_eq(&params.round_constants[r])
492                    .map(|(c, rc)| c + *rc)
493                    .collect();
494            }
495        }
496        Ok(current_state)
497    }
498
499    /// [`GriffinGadget::hash`] implements the gadget for Griffin hash based on
500    /// the sponge construction, which produces a single field element variable
501    /// as the digest of the given message `message` under parameters `params`.
502    pub fn hash<F: PrimeField>(
503        params: &GriffinParams<F>,
504        message: &[FpVar<F>],
505    ) -> Result<FpVar<F>, SynthesisError> {
506        let mut state = vec![FpVar::zero(); params.t];
507        for chunk in message.chunks(params.rate) {
508            for i in 0..chunk.len() {
509                state[i] += &chunk[i];
510            }
511            state = GriffinGadget::permute(params, &state)?;
512        }
513        Ok(state[0].clone())
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use ark_bn254::Fr;
520    use ark_ff::UniformRand;
521    use ark_relations::gr1cs::ConstraintSystem;
522    use ark_std::{error::Error, rand::thread_rng};
523    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
524    use wasm_bindgen_test::wasm_bindgen_test as test;
525
526    use super::*;
527
528    #[test]
529    fn test() -> Result<(), Box<dyn Error>> {
530        let rng = &mut thread_rng();
531        let params = GriffinParams::new(24, 5, 9);
532        let t = params.t;
533        let x: Vec<Fr> = (0..t).map(|_| Fr::rand(rng)).collect();
534
535        let y = Griffin::hash(&params, &x);
536
537        let cs = ConstraintSystem::new_ref();
538        let x_var = Vec::new_witness(cs.clone(), || Ok(x.clone()))?;
539        let y_var = GriffinGadget::hash(&params, &x_var)?;
540        assert_eq!(y, y_var.value()?);
541        println!("{}", cs.num_constraints());
542        assert!(cs.is_satisfied()?);
543
544        Ok(())
545    }
546
547    #[test]
548    fn test_consistent_perm() {
549        let rng = &mut thread_rng();
550        let params = GriffinParams::new(3, 5, 12);
551        let t = params.t;
552        for _ in 0..5 {
553            let input1: Vec<_> = (0..t).map(|_| Fr::rand(rng)).collect();
554
555            let mut input2: Vec<_>;
556            loop {
557                input2 = (0..t).map(|_| Fr::rand(rng)).collect();
558                if input1 != input2 {
559                    break;
560                }
561            }
562
563            let mut perm1 = input1.clone();
564            let mut perm2 = input1.clone();
565            let mut perm3 = input2.clone();
566            Griffin::permute(&params, &mut perm1);
567            Griffin::permute(&params, &mut perm2);
568            Griffin::permute(&params, &mut perm3);
569            assert_eq!(perm1, perm2);
570            assert_ne!(perm1, perm3);
571        }
572    }
573
574    fn matmul<F: PrimeField>(input: &[F], mat: &[Vec<F>]) -> Vec<F> {
575        let t = mat.len();
576        debug_assert!(t == input.len());
577        let mut out = vec![F::zero(); t];
578        for row in 0..t {
579            for (col, inp) in input.iter().enumerate() {
580                let mut tmp = mat[row][col];
581                tmp *= inp;
582                out[row] += &tmp;
583            }
584        }
585        out
586    }
587
588    fn test_affine_opt<F: PrimeField>(t: usize) {
589        let rng = &mut thread_rng();
590        let params = GriffinParams::<F>::new(t, 5, 1);
591
592        let mat = &params.mat;
593
594        for _ in 0..5 {
595            let input: Vec<F> = (0..t).map(|_| F::rand(rng)).collect();
596
597            // affine 1
598            let output1 = matmul(&input, mat);
599            let mut output2 = input.to_owned();
600            Griffin::affine(&params, &mut output2, 1);
601            assert_eq!(output1, output2);
602        }
603    }
604
605    #[test]
606    fn test_affine_3() {
607        test_affine_opt::<Fr>(3);
608    }
609
610    #[test]
611    fn test_affine_4() {
612        test_affine_opt::<Fr>(4);
613    }
614
615    #[test]
616    fn test_affine_8() {
617        test_affine_opt::<Fr>(8);
618    }
619
620    #[test]
621    fn test_affine_60() {
622        test_affine_opt::<Fr>(60);
623    }
624}