neptune/poseidon_alt.rs
1//! This module contains the 'correct' and 'dynamic' versions of Poseidon hashing.
2//! These are tested (in `poseidon::test`) to be equivalent to the 'static optimized' version
3//! used for actual hashing by the neptune library.
4use crate::poseidon::{Arity, Poseidon};
5use crate::{matrix, quintic_s_box};
6use ff::PrimeField;
7
8////////////////////////////////////////////////////////////////////////////////
9/// Correct
10///
11/// This code path implements a naive and evidently correct poseidon hash.
12
13/// The returned element is the second poseidon element, the first is the arity tag.
14pub(crate) fn hash_correct<F, A>(p: &mut Poseidon<'_, F, A>) -> F
15where
16 F: PrimeField,
17 A: Arity<F>,
18{
19 // This counter is incremented when a round constants is read. Therefore, the round constants never repeat.
20 // The first full round should use the initial constants.
21 full_round(p);
22
23 for _ in 1..p.constants.half_full_rounds {
24 full_round(p);
25 }
26
27 partial_round(p);
28
29 for _ in 1..p.constants.partial_rounds {
30 partial_round(p);
31 }
32
33 for _ in 0..p.constants.half_full_rounds {
34 full_round(p);
35 }
36
37 p.elements[1]
38}
39
40pub(crate) fn full_round<F, A>(p: &mut Poseidon<'_, F, A>)
41where
42 F: PrimeField,
43 A: Arity<F>,
44{
45 // Apply the quintic S-Box to all elements, after adding the round key.
46 // Round keys are added in the S-box to match circuits (where the addition is free)
47 // and in preparation for the shift to adding round keys after (rather than before) applying the S-box.
48
49 let pre_round_keys = p
50 .constants
51 .round_constants
52 .as_ref()
53 .unwrap()
54 .iter()
55 .skip(p.constants_offset)
56 .map(Some);
57
58 p.elements
59 .iter_mut()
60 .zip(pre_round_keys)
61 .for_each(|(l, pre)| {
62 quintic_s_box(l, pre, None);
63 });
64
65 p.constants_offset += p.elements.len();
66
67 // M(B)
68 // Multiply the elements by the constant MDS matrix
69 p.product_mds();
70}
71
72/// The partial round is the same as the full round, with the difference that we apply the S-Box only to the first bitflags poseidon leaf.
73pub(crate) fn partial_round<F, A>(p: &mut Poseidon<'_, F, A>)
74where
75 F: PrimeField,
76 A: Arity<F>,
77{
78 // Every element of the hash buffer is incremented by the round constants
79 add_round_constants(p);
80
81 // Apply the quintic S-Box to the first element
82 quintic_s_box(&mut p.elements[0], None, None);
83
84 // Multiply the elements by the constant MDS matrix
85 p.product_mds();
86}
87
88////////////////////////////////////////////////////////////////////////////////
89/// Dynamic
90///
91/// This code path implements a code path which dynamically calculates compressed round constants one-deep.
92/// It serves as a bridge between the 'correct' and fully, statically optimized implementations.
93/// Comments reference notation also expanded in matrix.rs and help clarify the relationship between
94/// our optimizations and those described in the paper.
95
96pub(crate) fn hash_optimized_dynamic<F, A>(p: &mut Poseidon<'_, F, A>) -> F
97where
98 F: PrimeField,
99 A: Arity<F>,
100{
101 // The first full round should use the initial constants.
102 full_round_dynamic(p, true, true);
103
104 for _ in 1..(p.constants.half_full_rounds) {
105 full_round_dynamic(p, false, true);
106 }
107
108 partial_round_dynamic(p);
109
110 for _ in 1..p.constants.partial_rounds {
111 partial_round(p);
112 }
113
114 for _ in 0..p.constants.half_full_rounds {
115 full_round_dynamic(p, true, false);
116 }
117
118 p.elements[1]
119}
120
121pub(crate) fn full_round_dynamic<F, A>(
122 p: &mut Poseidon<'_, F, A>,
123 add_current_round_keys: bool,
124 absorb_next_round_keys: bool,
125) where
126 F: PrimeField,
127 A: Arity<F>,
128{
129 // NOTE: decrease in performance is expected when using this pathway.
130 // We seek to preserve correctness while transforming the algorithm to an eventually more performant one.
131
132 // Round keys are added in the S-box to match circuits (where the addition is free).
133 // If requested, add round keys synthesized from following round after (rather than before) applying the S-box.
134 let pre_round_keys = p
135 .constants
136 .round_constants
137 .as_ref()
138 .unwrap()
139 .iter()
140 .skip(p.constants_offset)
141 .map(|x| {
142 if add_current_round_keys {
143 Some(x)
144 } else {
145 None
146 }
147 });
148
149 if absorb_next_round_keys {
150 // Using the notation from `test_inverse` in matrix.rs:
151 // S
152 let post_vec = p
153 .constants
154 .round_constants
155 .as_ref()
156 .unwrap()
157 .iter()
158 .skip(
159 p.constants_offset
160 + if add_current_round_keys {
161 p.elements.len()
162 } else {
163 0
164 },
165 )
166 .take(p.elements.len())
167 .copied()
168 .collect::<Vec<_>>();
169
170 // Compute the constants which should be added *before* the next `product_mds`.
171 // in order to have the same effect as adding the given constants *after* the next `product_mds`.
172
173 // M^-1(S)
174 let inverted_vec = matrix::left_apply_matrix(&p.constants.mds_matrices.m_inv, &post_vec);
175
176 // M(M^-1(S))
177 let original = matrix::left_apply_matrix(&p.constants.mds_matrices.m, &inverted_vec);
178
179 // S = M(M^-1(S))
180 assert_eq!(&post_vec, &original, "Oh no, the inversion trick failed.");
181
182 let post_round_keys = inverted_vec.iter();
183
184 // S-Box Output = B.
185 // With post-add, result is B + M^-1(S).
186 p.elements
187 .iter_mut()
188 .zip(pre_round_keys.zip(post_round_keys))
189 .for_each(|(l, (pre, post))| {
190 quintic_s_box(l, pre, Some(post));
191 });
192 } else {
193 p.elements
194 .iter_mut()
195 .zip(pre_round_keys)
196 .for_each(|(l, pre)| {
197 quintic_s_box(l, pre, None);
198 });
199 }
200 let mut consumed = 0;
201 if add_current_round_keys {
202 consumed += p.elements.len()
203 };
204 if absorb_next_round_keys {
205 consumed += p.elements.len()
206 };
207 p.constants_offset += consumed;
208
209 // If absorb_next_round_keys
210 // M(B + M^-1(S)
211 // else
212 // M(B)
213 // Multiply the elements by the constant MDS matrix
214 p.product_mds();
215}
216
217pub(crate) fn partial_round_dynamic<F, A>(p: &mut Poseidon<'_, F, A>)
218where
219 F: PrimeField,
220 A: Arity<F>,
221{
222 // Apply the quintic S-Box to the first element
223 quintic_s_box(&mut p.elements[0], None, None);
224
225 // Multiply the elements by the constant MDS matrix
226 p.product_mds();
227}
228
229/// For every leaf, add the round constants with index defined by the constants offset, and increment the
230/// offset.
231fn add_round_constants<F, A>(p: &mut Poseidon<'_, F, A>)
232where
233 F: PrimeField,
234 A: Arity<F>,
235{
236 for (element, round_constant) in p.elements.iter_mut().zip(
237 p.constants
238 .round_constants
239 .as_ref()
240 .unwrap()
241 .iter()
242 .skip(p.constants_offset),
243 ) {
244 element.add_assign(round_constant);
245 }
246
247 p.constants_offset += p.elements.len();
248}