|
Barretenberg
The ZK-SNARK library at the core of Aztec
|
Poseidon2 is a SNARK-friendly cryptographic hash designed to be efficient inside prime-field arithmetic circuits. It follows the Poseidon2 paper and refines the original Poseidon hash.
This implementation includes:
The sponge absorbs input elements into an internal state, applies permutations, and squeezes output elements.
Let the input be
\[ \mathbf{a} = (a_0, a_1, \dots, a_{N-1}). \]
Partition it into blocks of size \(r=3\):
\[ B_j = (a_{3j},, a_{3j+1},, a_{3j+2}) \quad\text{(pad missing entries with 0)},\qquad m = \left\lceil \frac{N}{3}\right\rceil . \]
In Poseidon paper, the padding scheme for variable input length hashing suggests padding with \( 10^\ast\).
"Domain Separation for Poseidon" section (see 4.2 in Poseidon) suggests using domain separation IV defined as follows
\[ \mathrm{IV} = (\texttt{input_length}^{64}) \]
Initialize the state:
\[ \mathbf{s}^{(0)} = (0,0,0,\mathrm{IV}). \]
Since we only use Poseidon2 sponge with variable length inputs and the length is a part of domain separation, we can pad the inputs with \( 0^\ast \), which would not lead to collisions (tested here).
Note that we initialize \( \mathrm{IV} \) as a fixed witness. It ensures that the first invocation of the Poseidon2 permutation leads to a state where all entries are normalized witnesses, i.e. they have multiplicative_constant equal 1, and additive_constant equal 0.
For each block \(j=0,\dots,m-1\),
\[ \mathbf{s}^{(j+1)} = P\left(\mathbf{s}^{(j)} + (B_j,0)\right), \]
where \(P\) is the Poseidon2 permutation and \((B_j,0)\) is an array of size \( 4 \) with \(r\) state elements and a \(0\) capacity limb.
After absorption, produce one output field element via one duplex step:
\[ y_0 = \big(P(\mathbf{s}^{(m)})\big)_0. \]
Each permutation consists of:
Note that in general, step 1 requires 6 arithmetic gates, the steps 2-4 create total number of rounds + 3 gates. Hence a single invocation of Poseidon2 Permutation results in 73 gates.
As proposed in Section 5.1 of Poseidon2 paper, we set
\[ M_E = \begin{bmatrix} 5 & 7 & 1 & 3 \\ 4 & 6 & 1 & 1 \\ 1 & 3 & 5 & 7 \\ 1 & 1 & 4 & 6 \end{bmatrix} \]
\[ M_I = \begin{bmatrix} D_1 & 1 & 1 & 1 \\ 1 & D_2 & 1 & 1 \\ 1 & 1 & D_3 & 1 \\ 1 & 1 & 1 & D_4 \end{bmatrix} \]
Implementation note: The code stores internal_matrix_diagonal_minus_one[i] = D_i - 1 (not the actual diagonal values \(D_i\)). This is because the algorithm computes \(v_i = (D_i - 1) \cdot u_i + \text{sum}\) where \(\text{sum} = u_1 + u_2 + u_3 + u_4\), which equals \(D_i \cdot u_i + (\text{sum of other elements})\).
The constants are generated using the sage script authored by Markus Schofnegger from Horizen Labs.
Based on Section 3.2 of Poseidon2 paper.
Given \( R_P = 56 \), \( R_F = 8\), \( d = 5\), \( \log_2(p) \approx 254 \), we get \( 128 \) bits of security.
For an external round with state \( \mathbf{u}=(u_1,u_2,u_3,u_4) \), define \( \mathbf{v}=M_E\cdot\mathbf{u}\). Poseidon2 External Relation enforces that the permuted values equal the values in the next row (accessed via shifts):
\[ v_k = w_{k,\mathrm{shift}} \qquad \text{for } k \in \{1,2,3,4\}. \]
We encode four independent constraints under a selector \( q_{\mathrm{poseidon2_external}}\) and aggregate them with independent challenges \( \alpha_i = \alpha_{i, Poseidon2_ext}\) from SubrelationSeparators:
\[ q_{\mathrm{poseidon2_external}}\cdot \Big( \alpha_0\big(v_1 - w_{1,\mathrm{shift}}\big) + \alpha_1\big(v_2 - w_{2,\mathrm{shift}}\big) + \alpha_2\big(v_3 - w_{3,\mathrm{shift}}\big) + \alpha_3\big(v_4 - w_{4,\mathrm{shift}}\big) \Big) = 0. \]
To ensure that the relation holds point-wise on the hypercube, the equation above is also multiplied by the appropriate scaling factor arising from GateSeparatorPolynomial.
Internal rounds follow the same pattern, using \( M_I \) and the partial S-box on the first element.
Hashing a single field element costs \( 73 \) gates. As above, let \( N > 1\) be the input size. Define \( m = \lceil N/3 \rceil \) and let \( N_3 = N\pmod{3} \). The number of gates depends on the number of padded fields equal to \( N_3 \). If \( N_3 = 0\), we get
\[ 1 + 73\cdot m + 3\cdot (m - 1) \]
gates, otherwise we get
\[ 1 + 73\cdot m + 3\cdot (m - 2) + N_3.\]
According to TACEO blog post Poseidon{2} for Noir, a single permutation cost for \( t = 4 \) implemented without Poseidon2 custom gates is \( 2313 \) gates.