Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
keccakf1600.cpp
Go to the documentation of this file.
2
3#include <cstddef>
4
5#include "barretenberg/aztec/aztec_constants.hpp"
8
9namespace bb::avm2::simulation {
10
11namespace {
12
19MemoryValue unconstrained_rotate_left(MemoryValue x, uint8_t len)
20{
21 // We avoid an undefined behavior in the shift below: "x_uint64_t >> (64 - len)"
22 // if it were evaluated with len = 0. (cpp standard on bitwise shifts requires rhs
23 // to be less than the number of bits in lhs).
24 if (len == 0) {
25 return x;
26 }
27
28 const auto x_uint64_t = x.as<uint64_t>();
29 BB_ASSERT_LT(len, 64, "Length out of bounds");
30 const auto out_uint64_t = (x_uint64_t << len) | x_uint64_t >> (64 - len);
31 return MemoryValue::from(out_uint64_t);
32}
33
37template <size_t N, size_t M>
38std::array<std::array<uint64_t, M>, N> two_dim_array_to_uint64(const std::array<std::array<MemoryValue, M>, N>& input)
39{
41 for (size_t i = 0; i < N; i++) {
42 for (size_t j = 0; j < M; j++) {
43 output[i][j] = input[i][j].template as<uint64_t>();
44 }
45 }
46 return output;
47}
48
52template <size_t N> std::array<uint64_t, N> array_to_uint64(const std::array<MemoryValue, N>& input)
53{
55 for (size_t i = 0; i < N; i++) {
56 output[i] = input[i].template as<uint64_t>();
57 }
58 return output;
59}
60
61} // namespace
62
77{
78 KeccakF1600Event keccakf1600_event;
80 keccakf1600_event.dst_addr = dst_addr;
81 keccakf1600_event.src_addr = src_addr;
82
83 try {
84 // We need to perform two bound checks to determine whether dst_addr and src_addr correspond to
85 // a memory slice which is out-of-range.
86 constexpr MemoryAddress HIGHEST_SLICE_ADDRESS = AVM_HIGHEST_MEM_ADDRESS - AVM_KECCAKF1600_STATE_SIZE + 1;
87
88 // We group both possible out-of-range errors in the same temporality group.
89 // Therefore, we perform both bound checks no matter what.
90 bool src_out_of_range = gt.gt(static_cast<uint128_t>(src_addr), static_cast<uint128_t>(HIGHEST_SLICE_ADDRESS));
91 bool dst_out_of_range = gt.gt(static_cast<uint128_t>(dst_addr), static_cast<uint128_t>(HIGHEST_SLICE_ADDRESS));
92
93 keccakf1600_event.src_out_of_range = src_out_of_range;
94 keccakf1600_event.dst_out_of_range = dst_out_of_range;
95 keccakf1600_event.space_id = memory.get_space_id();
96
97 if (src_out_of_range) {
98 throw KeccakF1600Exception(format("Read slice out of range: ", src_addr));
99 }
100 if (dst_out_of_range) {
101 throw KeccakF1600Exception(format("Write slice out of range: ", dst_addr));
102 }
103
104 // We work with MemoryValue as this type is required for bitwise operations handled
105 // by the bitwise sub-trace simulator. We continue by operating over Memory values and convert
106 // them back only at the end (event emission).
107 std::array<MemoryValue, AVM_KECCAKF1600_STATE_SIZE> src_mem_values{ MemoryValue::from<uint64_t>(0) };
108
109 // Slice read and tag check
110 for (size_t k = 0; k < AVM_KECCAKF1600_STATE_SIZE; k++) {
111 const auto addr = src_addr + static_cast<MemoryAddress>(k);
112 const MemoryValue& mem_val = memory.get(addr);
113 const MemoryTag tag = mem_val.get_tag();
114 src_mem_values[k] = mem_val;
115
116 if (tag != MemoryTag::U64) {
117 keccakf1600_event.tag_error = true;
118 keccakf1600_event.src_mem_values = src_mem_values;
119
121 format("Read slice tag invalid - addr: ", addr, " tag: ", static_cast<uint32_t>(tag)));
122 }
123 }
124
125 keccakf1600_event.src_mem_values = src_mem_values;
126
127 // Initialize state input values with values read from memory.
128 // Standard Keccak layout: memory[(y * 5) + x] = A[x][y], so linear index k maps to (x=k%5, y=k/5)
129 KeccakF1600StateMemValues state_input_values;
130 for (size_t k = 0; k < AVM_KECCAKF1600_STATE_SIZE; k++) {
131 state_input_values[k % 5][k / 5] = src_mem_values[k];
132 }
133
135
136 for (uint8_t round_idx = 0; round_idx < AVM_KECCAKF1600_NUM_ROUNDS; round_idx++) {
137 std::array<std::array<MemoryValue, 4>, 5> theta_xor_values;
138
139 // Theta xor computations. Each sheet's 4 steps form a dependency chain, so we walk
140 // step-major and SIMD-64 pair the same step across sheets ((0,1) and (2,3)); sheet 4 is
141 // scalar. (Must match keccakf1600.pil.)
142 // The running accumulator for sheet i at a given step is the previous step's xor result,
143 // or the sheet's first lane for step 0.
144 const auto theta_acc = [&](size_t i, size_t step) -> const MemoryValue& {
145 return step == 0 ? state_input_values[i][0] : theta_xor_values[i][step - 1];
146 };
147 for (size_t step = 0; step < 4; ++step) {
148 for (size_t s = 0; s + 1 < 4; s += 2) { // sheet pairs (0,1) and (2,3)
149 auto [c0, c1] = bitwise.simd_xor_op_64(theta_acc(s, step),
150 state_input_values[s][step + 1],
151 theta_acc(s + 1, step),
152 state_input_values[s + 1][step + 1]);
153 theta_xor_values[s][step] = c0;
154 theta_xor_values[s + 1][step] = c1;
155 }
156 theta_xor_values[4][step] = bitwise.xor_op(theta_acc(4, step), state_input_values[4][step + 1]);
157 }
158
159 // Theta xor values left rotated by 1
160 std::array<MemoryValue, 5> theta_xor_row_rotl1_values;
161 for (size_t i = 0; i < 5; ++i) {
162 theta_xor_row_rotl1_values[i] = unconstrained_rotate_left(theta_xor_values[i][3], 1);
163 }
164
165 // Theta combined xor computation. For sheet j the inputs are theta_xor[(j+4)%5][3] and
166 // rotl1(theta_xor_row[(j+1)%5]). SIMD-64 pairs sheets (0,1),(2,3); sheet 4 is scalar
167 // (must match keccakf1600.pil).
168 std::array<MemoryValue, 5> theta_combined_xor_values;
169 for (size_t i = 0; i + 1 < 5; i += 2) {
170 auto [c0, c1] = bitwise.simd_xor_op_64(theta_xor_values[(i + 4) % 5][3],
171 theta_xor_row_rotl1_values[(i + 1) % 5],
172 theta_xor_values[i % 5][3],
173 theta_xor_row_rotl1_values[(i + 1 + 1) % 5]);
174 theta_combined_xor_values[i] = c0;
175 theta_combined_xor_values[i + 1] = c1;
176 }
177 theta_combined_xor_values[4] = bitwise.xor_op(theta_xor_values[3][3], theta_xor_row_rotl1_values[0]);
178
179 // State theta values: state_theta[i][j] = state_in[i][j] XOR theta_combined_xor[i].
180 // SIMD-64: pair lanes in flat order idx = 5*i + j (must match keccakf1600.pil); the odd
181 // lane 24 (state index 44) is computed with a scalar XOR.
182 std::array<std::array<MemoryValue, 5>, 5> state_theta_values;
183 for (size_t idx = 0; idx + 1 < 25; idx += 2) {
184 const size_t i0 = idx / 5;
185 const size_t j0 = idx % 5;
186 const size_t i1 = (idx + 1) / 5;
187 const size_t j1 = (idx + 1) % 5;
188 auto [c0, c1] = bitwise.simd_xor_op_64(state_input_values[i0][j0],
189 theta_combined_xor_values[i0],
190 state_input_values[i1][j1],
191 theta_combined_xor_values[i1]);
192 state_theta_values[i0][j0] = c0;
193 state_theta_values[i1][j1] = c1;
194 }
195 state_theta_values[4][4] = bitwise.xor_op(state_input_values[4][4], theta_combined_xor_values[4]);
196
197 // State rho values
198 KeccakF1600StateMemValues state_rho_values;
199
200 // Handle range checks related to Rho round function.
201 // For i,j, such that 0 < rotation_len[i][j] <= 32, we range check
202 // the highest rotation_len[i][j] number of bits of state_theta_values[i][j].
203 // Otherwise, we range check the lowest 64 - rotation_len[i][j] bits.
204 for (size_t i = 0; i < 5; ++i) {
205 for (size_t j = 0; j < 5; ++j) {
206 const uint8_t& len = keccak_rotation_len[i][j];
207 // Compute state values after Rho function.
208 state_rho_values[i][j] = unconstrained_rotate_left(state_theta_values[i][j], len);
209 if (len > 0 && len <= 32) {
210 range_check.assert_range(state_theta_values[i][j].as<uint64_t>() >> (64 - len), len);
211 } else if (len > 32) {
212 range_check.assert_range(state_theta_values[i][j].as<uint64_t>() & ((1ULL << (64 - len)) - 1),
213 64 - len);
214 }
215 }
216 }
217
218 // state pi values
219 // state "not pi" values
220 KeccakF1600StateMemValues state_pi_values;
221 KeccakF1600StateMemValues state_pi_not_values;
222 for (size_t i = 0; i < 5; ++i) {
223 for (size_t j = 0; j < 5; ++j) {
224 state_pi_values[i][j] = state_rho_values[keccak_pi_rho_x_coords[i][j]][i];
225 state_pi_not_values[i][j] = ~state_pi_values[i][j];
226 }
227 }
228
229 // state "pi and" values: pi_and[i][j] = NOT(pi[(i+1)%5][j]) AND pi[(i+2)%5][j].
230 // SIMD-64 pairs lanes in flat order idx = 5*i + j; lane 24 (state index 44) is scalar.
231 // (Pairing must match keccakf1600.pil.) pi_and and chi are computed in separate passes so
232 // each phase can be paired independently.
233 KeccakF1600StateMemValues state_pi_and_values;
234 for (size_t idx = 0; idx + 1 < 25; idx += 2) {
235 const size_t i0 = idx / 5;
236 const size_t j0 = idx % 5;
237 const size_t i1 = (idx + 1) / 5;
238 const size_t j1 = (idx + 1) % 5;
239 auto [c0, c1] = bitwise.simd_and_op_64(state_pi_not_values[(i0 + 1) % 5][j0],
240 state_pi_values[(i0 + 2) % 5][j0],
241 state_pi_not_values[(i1 + 1) % 5][j1],
242 state_pi_values[(i1 + 2) % 5][j1]);
243 state_pi_and_values[i0][j0] = c0;
244 state_pi_and_values[i1][j1] = c1;
245 }
246 state_pi_and_values[4][4] =
247 bitwise.and_op(state_pi_not_values[(4 + 1) % 5][4], state_pi_values[(4 + 2) % 5][4]);
248
249 // state chi values: chi[i][j] = pi[i][j] XOR pi_and[i][j]. Same SIMD-64 pairing.
250 KeccakF1600StateMemValues state_chi_values;
251 for (size_t idx = 0; idx + 1 < 25; idx += 2) {
252 const size_t i0 = idx / 5;
253 const size_t j0 = idx % 5;
254 const size_t i1 = (idx + 1) / 5;
255 const size_t j1 = (idx + 1) % 5;
256 auto [c0, c1] = bitwise.simd_xor_op_64(state_pi_values[i0][j0],
257 state_pi_and_values[i0][j0],
258 state_pi_values[i1][j1],
259 state_pi_and_values[i1][j1]);
260 state_chi_values[i0][j0] = c0;
261 state_chi_values[i1][j1] = c1;
262 }
263 state_chi_values[4][4] = bitwise.xor_op(state_pi_values[4][4], state_pi_and_values[4][4]);
264
265 // state iota_00 value
266 // Recall that round starts with 1
267 MemoryValue iota_00_value =
268 bitwise.xor_op(state_chi_values[0][0], MemoryValue::from(keccak_round_constants[round_idx]));
269
270 rounds_data[round_idx] = {
271 .state = two_dim_array_to_uint64(state_input_values),
272 .theta_xor = two_dim_array_to_uint64(theta_xor_values),
273 .theta_xor_row_rotl1 = array_to_uint64(theta_xor_row_rotl1_values),
274 .theta_combined_xor = array_to_uint64(theta_combined_xor_values),
275 .state_theta = two_dim_array_to_uint64(state_theta_values),
276 .state_rho = two_dim_array_to_uint64(state_rho_values),
277 .state_pi_not = two_dim_array_to_uint64(state_pi_not_values),
278 .state_pi_and = two_dim_array_to_uint64(state_pi_and_values),
279 .state_chi = two_dim_array_to_uint64(state_chi_values),
280 .state_iota_00 = iota_00_value.as<uint64_t>(),
281 };
282
283 state_input_values = state_chi_values;
284 state_input_values[0][0] = iota_00_value;
285 }
286
287 // Slice write
288 for (size_t i = 0; i < 5; i++) {
289 for (size_t j = 0; j < 5; j++) {
290 memory.set(dst_addr + static_cast<MemoryAddress>((j * 5) + i), state_input_values[i][j]);
291 }
292 }
293
294 keccakf1600_event.rounds = rounds_data;
295 perm_events.emit(KeccakF1600Event(keccakf1600_event));
296 } catch (const KeccakF1600Exception& e) {
297 perm_events.emit(KeccakF1600Event(keccakf1600_event));
298 throw;
299 }
300}
301
302} // namespace bb::avm2::simulation
constexpr size_t N
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
static TaggedValue from(T value)
virtual uint32_t get_execution_id() const =0
EventEmitterInterface< KeccakF1600Event > & perm_events
void permutation(MemoryInterface &memory, MemoryAddress dst_addr, MemoryAddress src_addr) override
Perform the Keccak-f[1600] permutation (24 rounds) over a 25-word (5x5) 64-bit state.
ExecutionIdManagerInterface & execution_id_manager
std::string format(Args... args)
Definition log.hpp:23
uint32_t src_addr
uint32_t dst_addr
AVM range check gadget for witness generation.
constexpr std::array< std::array< uint8_t, 5 >, 5 > keccak_pi_rho_x_coords
constexpr std::array< uint64_t, 24 > keccak_round_constants
std::array< std::array< MemoryValue, 5 >, 5 > KeccakF1600StateMemValues
5x5 matrix of MemoryValue representing the Keccak state (used during simulation).
constexpr std::array< std::array< uint8_t, 5 >, 5 > keccak_rotation_len
TaggedValue MemoryValue
uint32_t MemoryAddress
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint8_t len
unsigned __int128 uint128_t
Definition serialize.hpp:45
Event emitted by the Keccak-f[1600] simulation for trace generation.
std::array< MemoryValue, AVM_KECCAKF1600_STATE_SIZE > src_mem_values
std::array< KeccakF1600RoundData, AVM_KECCAKF1600_NUM_ROUNDS > rounds
Per-round intermediate data.
Exception thrown on errors during the Keccak-f[1600] permutation.