Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
aes128.test.cpp
Go to the documentation of this file.
1#include "aes128.hpp"
8
9#include <gtest/gtest.h>
10
11using namespace bb;
12
13// ============================================================================
14// Gadget tests — exercised against both UltraCircuitBuilder and MegaCircuitBuilder
15// ============================================================================
16
17template <class Builder> class StdlibAes128 : public ::testing::Test {};
18
19using BuilderTypes = ::testing::Types<bb::UltraCircuitBuilder, bb::MegaCircuitBuilder>;
21
22// Helper function to create field element as either constant or witness
23template <class Builder>
31
32// Helper function to convert byte array to uint256_t
33static uint256_t convert_bytes_to_uint256(const uint8_t* data)
34{
35 uint256_t converted(0);
36 for (uint64_t i = 0; i < 16; ++i) {
37 uint256_t to_add = uint256_t((uint64_t)(data[i])) << uint256_t((15 - i) * 8);
38 converted += to_add;
39 }
40 return converted;
41}
42
43// Test function for a specific combination of witness/constant inputs
44template <class Builder> void test_aes128_combination(bool key_as_witness, bool iv_as_witness, bool input_as_witness)
45{
47
48 uint8_t key[16]{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
49 uint8_t out[64]{ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
50 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2,
51 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16,
52 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7 };
53 uint8_t iv[16]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
54 uint8_t in[64]{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
55 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
56 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
57 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 };
58
59 auto builder = Builder();
60
61 // Create input blocks with specified witness/constant configuration
62 std::vector<field_pt> in_field;
63 for (size_t i = 0; i < 4; ++i) {
64 in_field.push_back(create_field_element(builder, convert_bytes_to_uint256(in + i * 16), input_as_witness));
65 }
66
67 // Create key and IV with specified witness/constant configuration
68 field_pt key_field = create_field_element(builder, convert_bytes_to_uint256(key), key_as_witness);
69 field_pt iv_field = create_field_element(builder, convert_bytes_to_uint256(iv), iv_as_witness);
70
71 // Expected results
72 std::vector<fr> expected{ convert_bytes_to_uint256(out),
73 convert_bytes_to_uint256(out + 16),
74 convert_bytes_to_uint256(out + 32),
75 convert_bytes_to_uint256(out + 48) };
76
77 // Run AES encryption
78 const auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field);
79
80 // Verify results
81 for (size_t i = 0; i < 4; ++i) {
82 EXPECT_EQ(result[i].get_value(), expected[i])
83 << "Combination: key=" << (key_as_witness ? "witness" : "constant")
84 << ", iv=" << (iv_as_witness ? "witness" : "constant")
85 << ", input=" << (input_as_witness ? "witness" : "constant");
86 }
87
88 // Verify circuit is valid (only if there are witnesses)
89 if (key_as_witness || iv_as_witness || input_as_witness) {
90 bool proof_result = CircuitChecker::check(builder);
91 EXPECT_EQ(proof_result, true) << "Circuit check failed for combination: key="
92 << (key_as_witness ? "witness" : "constant")
93 << ", iv=" << (iv_as_witness ? "witness" : "constant")
94 << ", input=" << (input_as_witness ? "witness" : "constant");
95 }
96}
97
98// Test function for mixed input blocks (some constant, some witness)
99template <class Builder>
100void test_aes128_mixed_input(bool key_as_witness, bool iv_as_witness, const std::vector<bool>& input_block_config)
101{
103
104 uint8_t key[16]{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
105 uint8_t out[64]{ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
106 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2,
107 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16,
108 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7 };
109 uint8_t iv[16]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
110 uint8_t in[64]{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
111 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
112 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
113 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 };
114
115 auto builder = Builder();
116
117 // Create input blocks with mixed witness/constant configuration
118 std::vector<field_pt> in_field;
119 for (size_t i = 0; i < input_block_config.size(); ++i) {
120 in_field.push_back(create_field_element(builder, convert_bytes_to_uint256(in + i * 16), input_block_config[i]));
121 }
122
123 // Create key and IV with specified witness/constant configuration
124 field_pt key_field = create_field_element(builder, convert_bytes_to_uint256(key), key_as_witness);
125 field_pt iv_field = create_field_element(builder, convert_bytes_to_uint256(iv), iv_as_witness);
126
127 // Expected results
128 std::vector<fr> expected;
129 for (size_t i = 0; i < input_block_config.size(); ++i) {
130 expected.push_back(convert_bytes_to_uint256(out + i * 16));
131 }
132
133 // Run AES encryption
134 const auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field);
135
136 // Verify results
137 for (size_t i = 0; i < result.size(); ++i) {
138 EXPECT_EQ(result[i].get_value(), expected[i]) << "Mixed input test failed for block " << i;
139 }
140
141 // Verify circuit is valid (only if there are witnesses)
142 bool has_witness = key_as_witness || iv_as_witness;
143 for (bool is_witness : input_block_config) {
144 if (is_witness) {
145 has_witness = true;
146 break;
147 }
148 }
149
150 if (has_witness) {
151 bool proof_result = CircuitChecker::check(builder);
152 EXPECT_EQ(proof_result, true) << "Circuit check failed for mixed input test";
153 }
154}
155
156TYPED_TEST(StdlibAes128, encrypt_64_bytes_all_witness)
157{
158 test_aes128_combination<TypeParam>(true, true, true);
159}
160
161TYPED_TEST(StdlibAes128, encrypt_64_bytes_all_constant)
162{
163 test_aes128_combination<TypeParam>(false, false, false);
164}
165
166TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_witness_iv_constant_input_constant)
167{
168 test_aes128_combination<TypeParam>(true, false, false);
169}
170
171TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_constant_iv_witness_input_constant)
172{
173 test_aes128_combination<TypeParam>(false, true, false);
174}
175
176TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_constant_iv_constant_input_witness)
177{
178 test_aes128_combination<TypeParam>(false, false, true);
179}
180
181TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_witness_iv_witness_input_constant)
182{
183 test_aes128_combination<TypeParam>(true, true, false);
184}
185
186TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_witness_iv_constant_input_witness)
187{
188 test_aes128_combination<TypeParam>(true, false, true);
189}
190
191TYPED_TEST(StdlibAes128, encrypt_64_bytes_key_constant_iv_witness_input_witness)
192{
193 test_aes128_combination<TypeParam>(false, true, true);
194}
195
196// Original test for backward compatibility
197TYPED_TEST(StdlibAes128, encrypt_64_bytes_original)
198{
199 using Builder = TypeParam;
202
203 uint8_t key[16]{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
204 uint8_t out[64]{ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
205 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2,
206 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16,
207 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7 };
208 uint8_t iv[16]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
209 uint8_t in[64]{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
210 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
211 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
212 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 };
213
214 const auto convert_bytes = [](uint8_t* data) {
215 uint256_t converted(0);
216 for (uint64_t i = 0; i < 16; ++i) {
217 uint256_t to_add = uint256_t((uint64_t)(data[i])) << uint256_t((15 - i) * 8);
218 converted += to_add;
219 }
220 return converted;
221 };
222
223 auto builder = Builder();
224
225 std::vector<field_pt> in_field{
226 witness_pt(&builder, fr(convert_bytes(in))),
227 witness_pt(&builder, fr(convert_bytes(in + 16))),
228 witness_pt(&builder, fr(convert_bytes(in + 32))),
229 witness_pt(&builder, fr(convert_bytes(in + 48))),
230 };
231
232 field_pt key_field(witness_pt(&builder, fr(convert_bytes(key))));
233 field_pt iv_field(witness_pt(&builder, fr(convert_bytes(iv))));
234
235 std::vector<fr> expected{
236 convert_bytes(out), convert_bytes(out + 16), convert_bytes(out + 32), convert_bytes(out + 48)
237 };
238
239 const auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field);
240
241 for (size_t i = 0; i < 4; ++i) {
242 EXPECT_EQ(result[i].get_value(), expected[i]);
243 }
244
245 std::cout << "num gates = " << builder.get_num_finalized_gates_inefficient() << std::endl;
246
247 bool proof_result = CircuitChecker::check(builder);
248 EXPECT_EQ(proof_result, true);
249}
250
251// Mixed input tests - different combinations of constant/witness blocks
252TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_first_witness_rest_constant)
253{
254 test_aes128_mixed_input<TypeParam>(false, false, { true, false, false, false });
255}
256
257TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_alternating_witness_constant)
258{
259 test_aes128_mixed_input<TypeParam>(false, false, { true, false, true, false });
260}
261
262TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_first_constant_rest_witness)
263{
264 test_aes128_mixed_input<TypeParam>(false, false, { false, true, true, true });
265}
266
267TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_key_witness_mixed_blocks)
268{
269 test_aes128_mixed_input<TypeParam>(true, false, { true, false, true, false });
270}
271
272TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_iv_witness_mixed_blocks)
273{
274 test_aes128_mixed_input<TypeParam>(false, true, { false, true, false, true });
275}
276
277TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_key_iv_witness_mixed_blocks)
278{
279 test_aes128_mixed_input<TypeParam>(true, true, { true, false, true, false });
280}
281
282TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_all_witness_blocks)
283{
284 test_aes128_mixed_input<TypeParam>(false, false, { true, true, true, true });
285}
286
287TYPED_TEST(StdlibAes128, encrypt_64_bytes_mixed_input_all_constant_blocks)
288{
289 test_aes128_mixed_input<TypeParam>(false, false, { false, false, false, false });
290}
291
292// ============================================================================
293// Sparse Form XOR Tests
294// ============================================================================
295// In AES circuits, XOR is performed via addition in base-9 sparse form:
296// - Each bit of a byte becomes a digit (0 or 1) in a base-9 number
297// - Adding two sparse values: digits become 0, 1, or 2
298// - Normalization maps: even digits → 0, odd digits → 1 (exactly XOR semantics)
299//
300// This allows XOR to be computed as: sparse(a) + sparse(b), then normalize
301//
302// These tests exercise the plookup lookup tables and `numeric::map_*` helpers
303// rather than the aes128 gadget template itself, so they are Ultra-only.
304// ============================================================================
305
306constexpr uint64_t AES_SPARSE_BASE = 9;
307
313TEST(stdlib_aes128_sparse, sparse_form_roundtrip)
314{
315 // Test all possible byte values
316 for (uint64_t byte = 0; byte < 256; ++byte) {
317 uint256_t sparse = numeric::map_into_sparse_form<AES_SPARSE_BASE>(byte);
318 uint64_t recovered = numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse);
319 EXPECT_EQ(recovered, byte) << "Roundtrip failed for byte 0x" << std::hex << byte;
320 }
321}
322
328TEST(stdlib_aes128_sparse, sparse_addition_equals_xor_native)
329{
330 // Test a variety of byte pairs
332 { 0x00, 0x00 }, // 0 XOR 0 = 0
333 { 0xFF, 0x00 }, // FF XOR 0 = FF
334 { 0xFF, 0xFF }, // FF XOR FF = 0
335 { 0xAA, 0x55 }, // Alternating bits: AA XOR 55 = FF
336 { 0x0F, 0xF0 }, // 0F XOR F0 = FF
337 { 0x12, 0x34 }, // Random: 12 XOR 34 = 26
338 { 0x2b, 0x6b }, // From AES test vectors: key[0] XOR plaintext[0]
339 };
340
341 for (const auto& [a, b] : test_cases) {
342 uint8_t expected_xor = a ^ b;
343
344 // Convert to sparse form
345 uint256_t sparse_a = numeric::map_into_sparse_form<AES_SPARSE_BASE>(a);
346 uint256_t sparse_b = numeric::map_into_sparse_form<AES_SPARSE_BASE>(b);
347
348 // Add in sparse form (this is the circuit operation)
349 uint256_t sparse_sum = sparse_a + sparse_b;
350
351 // The sum needs normalization. In the circuit, this is done via plookup.
352 // For native testing, we can verify digit-by-digit:
353 // Each digit d in sparse_sum should normalize to (d % 2)
354 uint256_t normalized = 0;
355 uint256_t power = 1;
356 uint256_t temp = sparse_sum;
357
358 for (size_t i = 0; i < 8; ++i) {
359 uint64_t digit = (temp % AES_SPARSE_BASE).data[0];
360 uint64_t normalized_digit = digit % 2; // even→0, odd→1
361 normalized += power * normalized_digit;
362 power *= AES_SPARSE_BASE;
363 temp /= AES_SPARSE_BASE;
364 }
365
366 // Convert normalized result back to byte
367 uint64_t actual_xor = numeric::map_from_sparse_form<AES_SPARSE_BASE>(normalized);
368
369 EXPECT_EQ(actual_xor, expected_xor) << "Sparse XOR failed for 0x" << std::hex << (int)a << " ^ 0x" << (int)b
370 << ": expected 0x" << (int)expected_xor << ", got 0x" << actual_xor;
371 }
372}
373
381TEST(stdlib_aes128_sparse, sparse_form_lookup_table)
382{
386
388
389 // Take an input to the AES (16 bytes)
390 uint8_t lhs[16] = {
391 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
392 };
393 uint8_t rhs[16] = {
394 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
395 };
396
397 uint8_t expected_xor[16];
398 for (size_t i = 0; i < 16; ++i) {
399 expected_xor[i] = lhs[i] ^ rhs[i];
400 }
401
402 field_ct expected_xor_field = witness_ct(&builder, fr(convert_bytes_to_uint256(expected_xor)));
403
404 // pack the 16 bytes into a field_ct
405 field_ct lhs_field = witness_ct(&builder, fr(convert_bytes_to_uint256(lhs)));
406 field_ct rhs_field = witness_ct(&builder, fr(convert_bytes_to_uint256(rhs)));
407
408 // Now use the AES input table to convert lhs and rhs to sparse form using the AES input table
411
412 // Add the sparse fields together
413 for (size_t i = 0; i < 16; ++i) {
414 lhs_sparse_fields[i] = lhs_sparse_fields[i] + rhs_sparse_fields[i];
415 // Normalize the result
416 lhs_sparse_fields[i] = plookup_read::read_from_1_to_2_table(plookup::AES_NORMALIZE, lhs_sparse_fields[i]);
417 }
418 auto output_bytes = stdlib::aes128::convert_from_sparse_bytes(&builder, std::span{ lhs_sparse_fields });
419
420 output_bytes.assert_equal(expected_xor_field);
421
422 EXPECT_TRUE(CircuitChecker::check(builder));
423 EXPECT_FALSE(builder.failed());
424}
425
431TEST(stdlib_aes128_sparse, sparse_xor_circuit)
432{
436
438
439 // Test cases: pairs of bytes and their expected XOR
441 { 0x00, 0x00, 0x00 }, { 0xFF, 0xFF, 0x00 }, { 0xAA, 0x55, 0xFF },
442 { 0x12, 0x34, 0x26 }, { 0x2b, 0x6b, 0x40 }, // key[0] XOR plaintext[0] from NIST vectors
443 };
444
445 for (const auto& [a, b, expected] : test_cases) {
446 // Convert bytes to sparse form
447 uint256_t sparse_a = numeric::map_into_sparse_form<AES_SPARSE_BASE>(a);
448 uint256_t sparse_b = numeric::map_into_sparse_form<AES_SPARSE_BASE>(b);
449
450 // Create circuit witnesses
451 field_ct field_a = witness_ct(&builder, fr(sparse_a));
452 field_ct field_b = witness_ct(&builder, fr(sparse_b));
453
454 // Add in circuit (this is the XOR operation before normalization)
455 field_ct sum = field_a + field_b;
456
457 // Normalize via plookup (this completes the XOR)
459
460 // Convert result back to byte and verify
461 uint64_t sparse_result = uint256_t(normalized.get_value()).data[0];
462 uint8_t actual = static_cast<uint8_t>(numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse_result));
463
464 EXPECT_EQ(actual, expected) << "Circuit sparse XOR failed for 0x" << std::hex << (int)a << " ^ 0x" << (int)b;
465 }
466
467 EXPECT_TRUE(CircuitChecker::check(builder));
468}
469
476TEST(stdlib_aes128_sparse, sparse_multi_xor_circuit)
477{
481
483
484 // Test 3-way and 4-way XOR
485 uint8_t a = 0x12, b = 0x34, c = 0x56, d = 0x78;
486
487 // Expected results
488 uint8_t expected_3way = a ^ b ^ c; // 0x12 ^ 0x34 ^ 0x56 = 0x70
489 uint8_t expected_4way = a ^ b ^ c ^ d; // 0x12 ^ 0x34 ^ 0x56 ^ 0x78 = 0x08
490
491 // Convert to sparse form
492 field_ct sparse_a = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(a)));
493 field_ct sparse_b = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(b)));
494 field_ct sparse_c = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(c)));
495 field_ct sparse_d = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(d)));
496
497 // 3-way XOR: add all three, then normalize
498 field_ct sum_3way = sparse_a + sparse_b + sparse_c;
500
501 uint64_t sparse_result_3way = uint256_t(normalized_3way.get_value()).data[0];
502 uint8_t actual_3way = static_cast<uint8_t>(numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse_result_3way));
503 EXPECT_EQ(actual_3way, expected_3way) << "3-way sparse XOR failed";
504
505 // 4-way XOR: add all four, then normalize
506 field_ct sum_4way = sparse_a + sparse_b + sparse_c + sparse_d;
508
509 uint64_t sparse_result_4way = uint256_t(normalized_4way.get_value()).data[0];
510 uint8_t actual_4way = static_cast<uint8_t>(numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse_result_4way));
511 EXPECT_EQ(actual_4way, expected_4way) << "4-way sparse XOR failed";
512
513 EXPECT_TRUE(CircuitChecker::check(builder));
514}
515
523TEST(stdlib_aes128_sparse, sparse_addition_limit)
524{
528
530
531 // Add 8 copies of 0xFF (all bits set)
532 // Each digit will be 8 (the maximum safe value in base-9)
533 uint8_t value = 0xFF;
534 field_ct sparse_value = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(value)));
535
536 field_ct accumulated = sparse_value;
537 for (size_t i = 1; i < 8; ++i) {
538 accumulated = accumulated + sparse_value;
539 }
540
541 // XOR of 8 copies of 0xFF = 0x00 (even count = all zeros)
543
544 uint64_t sparse_result = uint256_t(normalized.get_value()).data[0];
545 uint8_t actual = static_cast<uint8_t>(numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse_result));
546
547 EXPECT_EQ(actual, 0x00) << "8-way XOR of 0xFF should be 0x00";
548
549 // Add 7 copies of 0xFF = 0xFF (odd count = all ones)
550 field_ct accumulated_7 = sparse_value;
551 for (size_t i = 1; i < 7; ++i) {
552 accumulated_7 = accumulated_7 + sparse_value;
553 }
554
556
557 uint64_t sparse_result_7 = uint256_t(normalized_7.get_value()).data[0];
558 uint8_t actual_7 = static_cast<uint8_t>(numeric::map_from_sparse_form<AES_SPARSE_BASE>(sparse_result_7));
559
560 EXPECT_EQ(actual_7, 0xFF) << "7-way XOR of 0xFF should be 0xFF";
561
562 EXPECT_TRUE(CircuitChecker::check(builder));
563}
564
571TEST(stdlib_aes128_sparse, sparse_addition_overflow)
572{
576
578
579 // Add 9 copies of 0xFF (all bits set)
580 // Each digit will be 9 (the minimum value to overflow)
581 uint8_t value = 0xFF;
582 field_ct sparse_value = witness_ct(&builder, fr(numeric::map_into_sparse_form<AES_SPARSE_BASE>(value)));
583
584 field_ct accumulated = sparse_value;
585 for (size_t i = 1; i < 9; ++i) {
586 accumulated = accumulated + sparse_value;
587 }
588
589 // 9-way addition of 0xFF overflows base-9 sparse form (digit value 9 >= base)
590 // This should throw an exception because the value exceeds the lookup table range
591 EXPECT_THROW(plookup_read::read_from_1_to_2_table(plookup::AES_NORMALIZE, accumulated), std::runtime_error)
592 << "9-way XOR of 0xFF should overflow and throw";
593}
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
void assert_equal(const field_t &rhs, std::string const &msg="field_t::assert_equal") const
Copy constraint: constrain that *this field is equal to rhs element.
Definition field.cpp:942
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:838
static field_pt read_from_1_to_2_table(const plookup::MultiTableId id, const field_pt &key_a)
Definition plookup.cpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
const std::vector< MemoryValue > data
FF a
FF b
ECCVMCircuitBuilder Builder
stdlib::witness_t< bb::UltraCircuitBuilder > witness_pt
stdlib::witness_t< Builder > witness_ct
@ AES_NORMALIZE
Definition types.hpp:101
std::array< field_t< Builder >, 16 > convert_into_sparse_bytes(Builder *ctx, const field_t< Builder > &block_data)
Converts a 128-bit block into 16 sparse-form bytes via AES_INPUT plookup table.
Definition aes128.cpp:41
field_t< Builder > convert_from_sparse_bytes(Builder *ctx, block_span< Builder > sparse_bytes)
Definition aes128.cpp:57
std::vector< field_t< Builder > > encrypt_buffer_cbc(const std::vector< field_t< Builder > > &input, const field_t< Builder > &iv, const field_t< Builder > &key)
Main public interface: AES-128 CBC encryption.
Definition aes128.cpp:353
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
Inner sum(Cont< Inner, Args... > const &in)
Definition container.hpp:70
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
::testing::Types< UltraCircuitBuilder, MegaCircuitBuilder > BuilderTypes
void test_aes128_mixed_input(bool key_as_witness, bool iv_as_witness, const std::vector< bool > &input_block_config)
constexpr uint64_t AES_SPARSE_BASE
void test_aes128_combination(bool key_as_witness, bool iv_as_witness, bool input_as_witness)
stdlib::field_t< Builder > create_field_element(Builder &builder, const uint256_t &value, bool as_witness)