1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

//! This module contains an implementation of the `Hades252`
//! strategy algorithm specifically designed to work outside of
//! Rank 1 Constraint Systems (R1CS) or other custom Constraint
//! Systems such as Add/Mul/Custom plonk gate-circuits.
//!
//! The inputs of the permutation function have to be explicitly
//! over the BlsScalar Field of the bls12_381 curve so working over
//! `Fq = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`.

use crate::{round_constants::ROUND_CONSTANTS, PARTIAL_ROUNDS, TOTAL_FULL_ROUNDS};
use dusk_bls12_381::BlsScalar;

/// Strategy for zero-knowledge plonk circuits
#[cfg(feature = "std")]
pub mod gadget;

/// Strategy for scalars
pub mod scalar;

#[cfg(feature = "std")]
pub use gadget::GadgetStrategy;
pub use scalar::ScalarStrategy;

/// Defines the Hades252 strategy algorithm.
pub trait Strategy<T: Clone + Copy> {
    /// Fetch the next round constant from an iterator
    fn next_c<'b, I>(constants: &mut I) -> BlsScalar
    where
        I: Iterator<Item = &'b BlsScalar>,
    {
        constants
            .next()
            .copied()
            .expect("Hades252 out of ARK constants")
    }

    /// Add round keys to a set of `StrategyInput`.
    ///
    /// This round key addition also known as `ARK` is used to
    /// reach `Confusion and Diffusion` properties for the algorithm.
    ///
    /// Basically it allows to destroy any connection between the
    /// inputs and the outputs of the function.
    fn add_round_key<'b, I>(&mut self, constants: &mut I, words: &mut [T])
    where
        I: Iterator<Item = &'b BlsScalar>;

    /// Computes `input ^ 5 (mod Fp)`
    ///
    /// The modulo depends on the input you use. In our case
    /// the modulo is done in respect of the `bls12_381 scalar field`
    ///  == `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`.
    fn quintic_s_box(&mut self, value: &mut T);

    /// Multiply the values for MDS matrix during the
    /// full rounds application.
    fn mul_matrix<'b, I>(&mut self, constants: &mut I, values: &mut [T])
    where
        I: Iterator<Item = &'b BlsScalar>;

    /// Applies a `Partial Round` also known as a
    /// `Partial S-Box layer` to a set of inputs.
    ///
    /// ### A partial round has 3 steps on every iteration:
    ///
    /// - Add round keys to each word. Also known as `ARK`.
    /// - Apply `quintic S-Box` **just to the last element of
    /// the words generated from the first step.** This is also known
    /// as a `Sub Words` operation.
    /// - Multiplies the output words from the second step by
    /// the `MDS_MATRIX`.
    /// This is known as the `Mix Layer`.
    fn apply_partial_round<'b, I>(&mut self, constants: &mut I, words: &mut [T])
    where
        I: Iterator<Item = &'b BlsScalar>,
    {
        let last = words.len() - 1;

        // Add round keys to each word
        self.add_round_key(constants, words);

        // Then apply quintic s-box
        self.quintic_s_box(&mut words[last]);

        // Multiply this result by the MDS matrix
        self.mul_matrix(constants, words);
    }

    /// Applies a `Full Round` also known as a
    /// `Full S-Box layer` to a set of inputs.
    ///
    /// A full round has 3 steps on every iteration:
    ///
    /// - Add round keys to each word. Also known as `ARK`.
    /// - Apply `quintic S-Box` **to all of the words generated
    /// from the first step.**
    /// This is also known as a `Sub Words` operation.
    /// - Multiplies the output words from the second step by
    /// the `MDS_MATRIX`.
    /// This is known as the `Mix Layer`.
    fn apply_full_round<'a, I>(&mut self, constants: &mut I, words: &mut [T])
    where
        I: Iterator<Item = &'a BlsScalar>,
    {
        // Add round keys to each word
        self.add_round_key(constants, words);

        // Then apply quintic s-box
        words.iter_mut().for_each(|w| self.quintic_s_box(w));

        // Multiply this result by the MDS matrix
        self.mul_matrix(constants, words);
    }

    /// Applies a `permutation-round` of the `Hades252` strategy.
    ///
    /// It returns a vec of `WIDTH` outputs as a result which should be
    /// a randomly permuted version of the input.
    ///
    /// In general, the same round function is iterated enough times
    /// to make sure that any symmetries and structural properties that
    /// might exist in the round function vanish.
    ///
    /// This `permutation` is a 3-step process that:
    ///
    /// - Applies twice the half of the `FULL_ROUNDS`
    /// (which can be understood as linear ops).
    ///
    /// - In the middle step it applies the `PARTIAL_ROUDS`
    /// (which can be understood as non-linear ops).
    ///
    /// This structure allows to minimize the number of non-linear
    /// ops while mantaining the security.
    fn perm(&mut self, data: &mut [T]) {
        let mut constants = ROUND_CONSTANTS.iter();

        // Apply R_f full rounds
        for _ in 0..TOTAL_FULL_ROUNDS / 2 {
            self.apply_full_round(&mut constants, data);
        }

        // Apply R_P partial rounds
        for _ in 0..PARTIAL_ROUNDS {
            self.apply_partial_round(&mut constants, data);
        }

        // Apply R_f full rounds
        for _ in 0..TOTAL_FULL_ROUNDS / 2 {
            self.apply_full_round(&mut constants, data);
        }
    }

    /// Return the total rounds count
    fn rounds() -> usize {
        TOTAL_FULL_ROUNDS + PARTIAL_ROUNDS
    }
}