Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
acir_to_constraint_buf.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
8
9#include <cstddef>
10#include <cstdint>
11#include <map>
12#include <tuple>
13#include <utility>
14
25
26namespace acir_format {
27
28using namespace bb;
29
39template <typename T>
40T deserialize_any_format(std::vector<uint8_t>&& buf,
41 std::function<T(msgpack::object const&)> decode_msgpack,
42 std::function<T(std::vector<uint8_t>)> decode_bincode)
43{
44 // We can't rely on exceptions to try to deserialize binpack, falling back to
45 // msgpack if it fails, because exceptions are (or were) not supported in Wasm
46 // and they are turned off in arch.cmake.
47 //
48 // For now our other option is to check if the data is valid msgpack,
49 // which slows things down, but we can't tell if the first byte of
50 // the data accidentally matches one of our format values.
51 //
52 // Unfortunately this doesn't seem to work either: `msgpack::parse`
53 // returns true for a `bincode` encoded program, and we have to check
54 // whether the value parsed is plausible.
55
56 if (!buf.empty()) {
57 // Once we remove support for legacy bincode format, we should expect to always
58 // have a format marker corresponding to acir::serialization::Format::Msgpack,
59 // but until then a match could be pure coincidence.
60 if (buf[0] == 2) {
61 // Skip the format marker to get the data.
62 const char* buffer = &reinterpret_cast<const char*>(buf.data())[1];
63 size_t size = buf.size() - 1;
64 msgpack::null_visitor probe;
65 if (msgpack::parse(buffer, size, probe)) {
66 auto oh = msgpack::unpack(buffer, size);
67 // This has to be on a separate line, see
68 // https://github.com/msgpack/msgpack-c/issues/695#issuecomment-393035172
69 auto o = oh.get();
70 // In experiments bincode data was parsed as 0.
71 // All the top level formats we look for are MAP types.
72 if (o.type == msgpack::type::MAP) {
73 return decode_msgpack(o);
74 }
75 }
76 }
77 // `buf[0] == 1` would indicate bincode starting with a format byte,
78 // but if it's a coincidence and it fails to parse then we can't recover
79 // from it, so let's just acknowledge that for now we don't want to
80 // exercise this code path and treat the whole data as bincode.
81 }
82 return decode_bincode(std::move(buf));
83}
84
89Acir::Program deserialize_program(std::vector<uint8_t>&& buf)
90{
91 return deserialize_any_format<Acir::Program>(
93 [](auto o) -> Acir::Program {
94 Acir::Program program;
95 try {
96 // Deserialize into a partial structure that ignores the Brillig parts,
97 // so that new opcodes can be added without breaking Barretenberg.
99 o.convert(program_wob);
100 program.functions = program_wob.functions;
101 } catch (const msgpack::type_error&) {
102 std::cerr << o << std::endl;
103 throw_or_abort("failed to convert msgpack data to Program");
104 }
105 return program;
106 },
108}
109
114{
115 return deserialize_any_format<Witnesses::WitnessStack>(
116 std::move(buf),
117 [](auto o) {
118 Witnesses::WitnessStack witness_stack;
119 try {
120 o.convert(witness_stack);
121 } catch (const msgpack::type_error&) {
122 std::cerr << o << std::endl;
123 throw_or_abort("failed to convert msgpack data to WitnessStack");
124 }
125 return witness_stack;
126 },
128}
129
130// TODO(tom): clean this up.
131uint256_t from_be_bytes(std::vector<uint8_t> const& bytes)
132{
133 BB_ASSERT_EQ(bytes.size(), 32U, "uint256 constructed from bytes array with invalid length");
134 uint256_t result = 0;
135 for (uint8_t byte : bytes) {
136 result <<= 8;
137 result |= byte;
138 }
139 return result;
140}
141
151{
152 poly_triple pt{
153 .a = 0,
154 .b = 0,
155 .c = 0,
156 .q_m = 0,
157 .q_l = 0,
158 .q_r = 0,
159 .q_o = 0,
160 .q_c = 0,
161 };
162
163 // Flags indicating whether each witness index for the present poly_tuple has been set
164 bool a_set = false;
165 bool b_set = false;
166 bool c_set = false;
167
168 // If necessary, set values for quadratic term (q_m * w_l * w_r)
169 BB_ASSERT_LTE(arg.mul_terms.size(), 1U, "We can only accommodate 1 quadratic term");
170 // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2}
171 if (!arg.mul_terms.empty()) {
172 const auto& mul_term = arg.mul_terms[0];
173 pt.q_m = from_be_bytes(std::get<0>(mul_term));
174 pt.a = std::get<1>(mul_term).value;
175 pt.b = std::get<2>(mul_term).value;
176 a_set = true;
177 b_set = true;
178 }
179
180 // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o
181 BB_ASSERT_LTE(arg.linear_combinations.size(), 3U, "We can only accommodate 3 linear terms");
182 for (const auto& linear_term : arg.linear_combinations) {
183 fr selector_value(from_be_bytes(std::get<0>(linear_term)));
184 uint32_t witness_idx = std::get<1>(linear_term).value;
185
186 // If the witness index has not yet been set or if the corresponding linear term is active, set the witness
187 // index and the corresponding selector value.
188 if (!a_set || pt.a == witness_idx) { // q_l * w_l
189 pt.a = witness_idx;
190 pt.q_l = selector_value;
191 a_set = true;
192 } else if (!b_set || pt.b == witness_idx) { // q_r * w_r
193 pt.b = witness_idx;
194 pt.q_r = selector_value;
195 b_set = true;
196 } else if (!c_set || pt.c == witness_idx) { // q_o * w_o
197 pt.c = witness_idx;
198 pt.q_o = selector_value;
199 c_set = true;
200 } else {
201 return poly_triple{
202 .a = 0,
203 .b = 0,
204 .c = 0,
205 .q_m = 0,
206 .q_l = 0,
207 .q_r = 0,
208 .q_o = 0,
209 .q_c = 0,
210 };
211 }
212 }
213
214 // Set constant value q_c
215 pt.q_c = from_be_bytes(arg.q_c);
216 return pt;
217}
218
220
223
231void assign_linear_term(mul_quad_<fr>& gate, int index, uint32_t witness_index, fr const& scaling)
232{
233 switch (index) {
234 case 0:
235 gate.a = witness_index;
236 gate.a_scaling = scaling;
237 break;
238 case 1:
239 gate.b = witness_index;
240 gate.b_scaling = scaling;
241 break;
242 case 2:
243 gate.c = witness_index;
244 gate.c_scaling = scaling;
245 break;
246 case 3:
247 gate.d = witness_index;
248 gate.d_scaling = scaling;
249 break;
250 default:
251 throw_or_abort("Unexpected index");
252 }
253}
254
257{
259 auto current_mul_term = arg.mul_terms.begin();
260 auto current_linear_term = arg.linear_combinations.begin();
261
262 // number of wires to use in the intermediate gate
263 int max_size = 4;
264 bool done = false;
265 // the intermediate 'big add' gates. The first one contains the constant term.
266 mul_quad_<fr> mul_gate = { .a = 0,
267 .b = 0,
268 .c = 0,
269 .d = 0,
270 .mul_scaling = fr::zero(),
271 .a_scaling = fr::zero(),
272 .b_scaling = fr::zero(),
273 .c_scaling = fr::zero(),
274 .d_scaling = fr::zero(),
275 .const_scaling = fr(from_be_bytes(arg.q_c)) };
276
277 // list of witnesses that are part of mul terms
278 std::set<uint32_t> all_mul_terms;
279 for (auto const& term : arg.mul_terms) {
280 all_mul_terms.insert(std::get<1>(term).value);
281 all_mul_terms.insert(std::get<2>(term).value);
282 }
283 // The 'mul term' witnesses that have been processed
284 std::set<uint32_t> processed_mul_terms;
285
286 while (!done) {
287 int i = 0; // index of the current free wire in the new intermediate gate
288
289 // we add a mul term (if there are some) to every intermediate gate
290 if (current_mul_term != arg.mul_terms.end()) {
291 mul_gate.mul_scaling = fr(from_be_bytes(std::get<0>(*current_mul_term)));
292 mul_gate.a = std::get<1>(*current_mul_term).value;
293 mul_gate.b = std::get<2>(*current_mul_term).value;
294 mul_gate.a_scaling = fr::zero();
295 mul_gate.b_scaling = fr::zero();
296 // Try to add corresponding linear terms, only if they were not already added
297 if (!processed_mul_terms.contains(mul_gate.a) || !processed_mul_terms.contains(mul_gate.b)) {
298 for (auto lin_term : arg.linear_combinations) {
299 auto w = std::get<1>(lin_term).value;
300 if (w == mul_gate.a) {
301 if (!processed_mul_terms.contains(mul_gate.a)) {
302 mul_gate.a_scaling = fr(from_be_bytes(std::get<0>(lin_term)));
303 processed_mul_terms.insert(w);
304 }
305 if (mul_gate.a == mul_gate.b) {
306 break;
307 }
308 } else if (w == mul_gate.b) {
309 if (!processed_mul_terms.contains(mul_gate.b)) {
310 mul_gate.b_scaling = fr(from_be_bytes(std::get<0>(lin_term)));
311 processed_mul_terms.insert(w);
312 }
313 break;
314 }
315 }
316 }
317 i = 2; // a and b are used because of the mul term
318 current_mul_term = std::next(current_mul_term);
319 }
320 // We need to process all the mul terms before being done.
321 done = current_mul_term == arg.mul_terms.end();
322
323 // Assign available wires with the remaining linear terms which are not also a 'mul term'
324 while (current_linear_term != arg.linear_combinations.end()) {
325 auto w = std::get<1>(*current_linear_term).value;
326 if (!all_mul_terms.contains(w)) {
327 if (i < max_size) {
329 mul_gate, i, w, fr(from_be_bytes(std::get<0>(*current_linear_term)))); // * fr(-1)));
330 ++i;
331 } else {
332 // No more available wire, but there is still some linear terms; we need another mul_gate
333 done = false;
334 break;
335 }
336 }
337 current_linear_term = std::next(current_linear_term);
338 }
339
340 // Index 4 of the next gate will be used
341 max_size = 3;
342 result.push_back(mul_gate);
343 mul_gate = { .a = 0,
344 .b = 0,
345 .c = 0,
346 .d = 0,
347 .mul_scaling = fr::zero(),
348 .a_scaling = fr::zero(),
349 .b_scaling = fr::zero(),
350 .c_scaling = fr::zero(),
351 .d_scaling = fr::zero(),
352 .const_scaling = fr::zero() };
353 }
354
355 return result;
356}
357
359{
360 mul_quad_<fr> quad{ .a = 0,
361 .b = 0,
362 .c = 0,
363 .d = 0,
364 .mul_scaling = 0,
365 .a_scaling = 0,
366 .b_scaling = 0,
367 .c_scaling = 0,
368 .d_scaling = 0,
369 .const_scaling = 0 };
370
371 // Flags indicating whether each witness index for the present mul_quad has been set
372 bool a_set = false;
373 bool b_set = false;
374 bool c_set = false;
375 bool d_set = false;
376 BB_ASSERT_LTE(arg.mul_terms.size(), 1U, "We can only accommodate 1 quadratic term");
377 // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2}
378 if (!arg.mul_terms.empty()) {
379 const auto& mul_term = arg.mul_terms[0];
380 quad.mul_scaling = from_be_bytes(std::get<0>(mul_term));
381 quad.a = std::get<1>(mul_term).value;
382 quad.b = std::get<2>(mul_term).value;
383 a_set = true;
384 b_set = true;
385 }
386 // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o
387 for (const auto& linear_term : arg.linear_combinations) {
388 fr selector_value(from_be_bytes(std::get<0>(linear_term)));
389 uint32_t witness_idx = std::get<1>(linear_term).value;
390
391 // If the witness index has not yet been set or if the corresponding linear term is active, set the witness
392 // index and the corresponding selector value.
393 if (!a_set || quad.a == witness_idx) {
394 quad.a = witness_idx;
395 quad.a_scaling = selector_value;
396 a_set = true;
397 } else if (!b_set || quad.b == witness_idx) {
398 quad.b = witness_idx;
399 quad.b_scaling = selector_value;
400 b_set = true;
401 } else if (!c_set || quad.c == witness_idx) {
402 quad.c = witness_idx;
403 quad.c_scaling = selector_value;
404 c_set = true;
405 } else if (!d_set || quad.d == witness_idx) {
406 quad.d = witness_idx;
407 quad.d_scaling = selector_value;
408 d_set = true;
409 } else {
410 // We cannot assign linear term to a constraint of width 4
411 return { .a = 0,
412 .b = 0,
413 .c = 0,
414 .d = 0,
415 .mul_scaling = 0,
416 .a_scaling = 0,
417 .b_scaling = 0,
418 .c_scaling = 0,
419 .d_scaling = 0,
420 .const_scaling = 0 };
421 }
422 }
423
424 // Set constant value q_c
425 quad.const_scaling = from_be_bytes(arg.q_c);
426 return quad;
427}
428
430{
431 for (const auto& linear_term : arg.value.linear_combinations) {
432 uint32_t witness_idx = std::get<1>(linear_term).value;
433 af.constrained_witness.insert(witness_idx);
434 }
435 for (const auto& linear_term : arg.value.mul_terms) {
436 uint32_t witness_idx = std::get<1>(linear_term).value;
437 af.constrained_witness.insert(witness_idx);
438 witness_idx = std::get<2>(linear_term).value;
439 af.constrained_witness.insert(witness_idx);
440 }
441}
442
444 poly_triple const& pt,
445 AcirFormat const& af)
446{
447 if (!arg.value.mul_terms.empty() || arg.value.linear_combinations.size() != 2) {
448 return { 0, 0 };
449 }
450 if (pt.q_l == -pt.q_r && pt.q_l != bb::fr::zero() && pt.q_c == bb::fr::zero()) {
451 // we require that one of the 2 witnesses to be constrained in an arithmetic gate
452 if (af.constrained_witness.contains(pt.a) || af.constrained_witness.contains(pt.b)) {
453 return { pt.a, pt.b };
454 }
455 }
456 return { 0, 0 };
457}
458
459void handle_arithmetic(Acir::Opcode::AssertZero const& arg, AcirFormat& af, size_t opcode_index)
460{
461 // If the expression fits in a polytriple, we use it.
462 if (arg.value.linear_combinations.size() <= 3 && arg.value.mul_terms.size() <= 1) {
464
465 auto assert_equal = is_assert_equal(arg, pt, af);
466 uint32_t w1 = std::get<0>(assert_equal);
467 uint32_t w2 = std::get<1>(assert_equal);
468 if (w1 != 0) {
469 if (w1 != w2) {
470 if (!af.constrained_witness.contains(pt.a)) {
471 // we mark it as constrained because it is going to be asserted to be equal to a constrained one.
472 af.constrained_witness.insert(pt.a);
473 // swap the witnesses so that the first one is always properly constrained.
474 auto tmp = pt.a;
475 pt.a = pt.b;
476 pt.b = tmp;
477 }
478 if (!af.constrained_witness.contains(pt.b)) {
479 // we mark it as constrained because it is going to be asserted to be equal to a constrained one.
480 af.constrained_witness.insert(pt.b);
481 }
482 // minimal_range of a witness is the smallest range of the witness and the witness that are
483 // 'assert_equal' to it
484 if (af.minimal_range.contains(pt.b) && af.minimal_range.contains(pt.a)) {
485 if (af.minimal_range[pt.a] < af.minimal_range[pt.b]) {
486 af.minimal_range[pt.a] = af.minimal_range[pt.b];
487 } else {
488 af.minimal_range[pt.b] = af.minimal_range[pt.a];
489 }
490 } else if (af.minimal_range.contains(pt.b)) {
491 af.minimal_range[pt.a] = af.minimal_range[pt.b];
492 } else if (af.minimal_range.contains(pt.a)) {
493 af.minimal_range[pt.b] = af.minimal_range[pt.a];
494 }
495
496 af.assert_equalities.push_back(pt);
497 af.original_opcode_indices.assert_equalities.push_back(opcode_index);
498 }
499 return;
500 }
501 // Even if the number of linear terms is less than 3, we might not be able to fit it into a width-3 arithmetic
502 // gate. This is the case if the linear terms are all distinct witness from the multiplication term. In that
503 // case, the serialize_arithmetic_gate() function will return a poly_triple with all 0's, and we use a width-4
504 // gate instead. We could probably always use a width-4 gate in fact.
505 if (pt == poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) {
507 af.original_opcode_indices.quad_constraints.push_back(opcode_index);
508
509 } else {
510 af.poly_triple_constraints.push_back(pt);
511 af.original_opcode_indices.poly_triple_constraints.push_back(opcode_index);
512 }
513 } else {
514 std::vector<mul_quad_<fr>> mul_quads;
515 // We try to use a single mul_quad gate to represent the expression.
516 if (arg.value.mul_terms.size() <= 1) {
517 auto quad = serialize_mul_quad_gate(arg.value);
518 // add it to the result vector if it worked
519 if (quad.a != 0 || !(quad.mul_scaling == fr(0)) || !(quad.a_scaling == fr(0))) {
520 mul_quads.push_back(quad);
521 }
522 }
523 if (mul_quads.empty()) {
524 // If not, we need to split the expression into multiple gates
525 mul_quads = split_into_mul_quad_gates(arg.value);
526 }
527 if (mul_quads.size() == 1) {
528 af.quad_constraints.push_back(mul_quads[0]);
529 af.original_opcode_indices.quad_constraints.push_back(opcode_index);
530 }
531 if (mul_quads.size() > 1) {
532 af.big_quad_constraints.push_back(mul_quads);
533 }
534 }
535 constrain_witnesses(arg, af);
536}
538{
539 auto input_witness = std::get<Acir::FunctionInput::Witness>(input.value);
540 return input_witness.value.value;
541}
542
544{
545 WitnessOrConstant result = std::visit(
546 [&](auto&& e) {
547 using T = std::decay_t<decltype(e)>;
550 .index = e.value.value,
551 .value = bb::fr::zero(),
552 .is_constant = false,
553 };
556 .index = 0,
557 .value = from_be_bytes(e.value),
558 .is_constant = true,
559 };
560 } else {
561 throw_or_abort("Unrecognized Acir::ConstantOrWitnessEnum variant.");
562 }
564 .index = 0,
565 .value = bb::fr::zero(),
566 .is_constant = true,
567 };
568 },
569 input.value);
570 return result;
571}
572
574{
575 std::visit(
576 [&](auto&& arg) {
577 using T = std::decay_t<decltype(arg)>;
579 auto lhs_input = parse_input(arg.lhs);
580 auto rhs_input = parse_input(arg.rhs);
582 .a = lhs_input,
583 .b = rhs_input,
584 .result = arg.output.value,
585 .num_bits = arg.num_bits,
586 .is_xor_gate = false,
587 });
588 af.constrained_witness.insert(af.logic_constraints.back().result);
589 af.original_opcode_indices.logic_constraints.push_back(opcode_index);
591 auto lhs_input = parse_input(arg.lhs);
592 auto rhs_input = parse_input(arg.rhs);
594 .a = lhs_input,
595 .b = rhs_input,
596 .result = arg.output.value,
597 .num_bits = arg.num_bits,
598 .is_xor_gate = true,
599 });
600 af.constrained_witness.insert(af.logic_constraints.back().result);
601 af.original_opcode_indices.logic_constraints.push_back(opcode_index);
603 auto witness_input = get_witness_from_function_input(arg.input);
605 .witness = witness_input,
606 .num_bits = arg.num_bits,
607 });
608 af.original_opcode_indices.range_constraints.push_back(opcode_index);
609 if (af.minimal_range.contains(witness_input)) {
610 if (af.minimal_range[witness_input] > arg.num_bits) {
611 af.minimal_range[witness_input] = arg.num_bits;
612 }
613 } else {
614 af.minimal_range[witness_input] = arg.num_bits;
615 }
618 .inputs = transform::map(arg.inputs, [](auto& e) { return parse_input(e); }),
619 .iv = transform::map(*arg.iv, [](auto& e) { return parse_input(e); }),
620 .key = transform::map(*arg.key, [](auto& e) { return parse_input(e); }),
621 .outputs = transform::map(arg.outputs, [](auto& e) { return e.value; }),
622 });
623 for (auto& output : af.aes128_constraints.back().outputs) {
624 af.constrained_witness.insert(output);
625 }
626 af.original_opcode_indices.aes128_constraints.push_back(opcode_index);
629 .inputs = transform::map(*arg.inputs, [](auto& e) { return parse_input(e); }),
630 .hash_values = transform::map(*arg.hash_values, [](auto& e) { return parse_input(e); }),
631 .result = transform::map(*arg.outputs, [](auto& e) { return e.value; }),
632 });
633 for (auto& output : af.sha256_compression.back().result) {
634 af.constrained_witness.insert(output);
635 }
636 af.original_opcode_indices.sha256_compression.push_back(opcode_index);
639 .inputs = transform::map(arg.inputs,
640 [](auto& e) {
641 return Blake2sInput{
642 .blackbox_input = parse_input(e),
643 .num_bits = 8,
644 };
645 }),
646 .result = transform::map(*arg.outputs, [](auto& e) { return e.value; }),
647 });
648 for (auto& output : af.blake2s_constraints.back().result) {
649 af.constrained_witness.insert(output);
650 }
651 af.original_opcode_indices.blake2s_constraints.push_back(opcode_index);
655 arg.inputs,
656 [](auto& e) { return Blake3Input{ .blackbox_input = parse_input(e), .num_bits = 8 }; }),
657 .result = transform::map(*arg.outputs, [](auto& e) { return e.value; }),
658 });
659 for (auto& output : af.blake3_constraints.back().result) {
660 af.constrained_witness.insert(output);
661 }
662 af.original_opcode_indices.blake3_constraints.push_back(opcode_index);
664 af.ecdsa_k1_constraints.push_back(EcdsaConstraint{
665 .hashed_message =
666 transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }),
667 .signature =
668 transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }),
669 .pub_x_indices =
670 transform::map(*arg.public_key_x, [](auto& e) { return get_witness_from_function_input(e); }),
671 .pub_y_indices =
672 transform::map(*arg.public_key_y, [](auto& e) { return get_witness_from_function_input(e); }),
673 .predicate = parse_input(arg.predicate),
674 .result = arg.output.value,
675 });
676 af.constrained_witness.insert(af.ecdsa_k1_constraints.back().result);
677 af.original_opcode_indices.ecdsa_k1_constraints.push_back(opcode_index);
679 af.ecdsa_r1_constraints.push_back(EcdsaConstraint{
680 .hashed_message =
681 transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }),
682 .signature =
683 transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }),
684 .pub_x_indices =
685 transform::map(*arg.public_key_x, [](auto& e) { return get_witness_from_function_input(e); }),
686 .pub_y_indices =
687 transform::map(*arg.public_key_y, [](auto& e) { return get_witness_from_function_input(e); }),
688 .predicate = parse_input(arg.predicate),
689 .result = arg.output.value,
690 });
691 af.constrained_witness.insert(af.ecdsa_r1_constraints.back().result);
692 af.original_opcode_indices.ecdsa_r1_constraints.push_back(opcode_index);
694 af.multi_scalar_mul_constraints.push_back(MultiScalarMul{
695 .points = transform::map(arg.points, [](auto& e) { return parse_input(e); }),
696 .scalars = transform::map(arg.scalars, [](auto& e) { return parse_input(e); }),
697 .predicate = parse_input(arg.predicate),
698 .out_point_x = (*arg.outputs)[0].value,
699 .out_point_y = (*arg.outputs)[1].value,
700 .out_point_is_infinite = (*arg.outputs)[2].value,
701 });
702 af.constrained_witness.insert(af.multi_scalar_mul_constraints.back().out_point_x);
703 af.constrained_witness.insert(af.multi_scalar_mul_constraints.back().out_point_y);
704 af.constrained_witness.insert(af.multi_scalar_mul_constraints.back().out_point_is_infinite);
705 af.original_opcode_indices.multi_scalar_mul_constraints.push_back(opcode_index);
707 auto input_1_x = parse_input((*arg.input1)[0]);
708 auto input_1_y = parse_input((*arg.input1)[1]);
709 auto input_1_infinite = parse_input((*arg.input1)[2]);
710 auto input_2_x = parse_input((*arg.input2)[0]);
711 auto input_2_y = parse_input((*arg.input2)[1]);
712 auto input_2_infinite = parse_input((*arg.input2)[2]);
713 auto predicate = parse_input(arg.predicate);
714
715 af.ec_add_constraints.push_back(EcAdd{
716 .input1_x = input_1_x,
717 .input1_y = input_1_y,
718 .input1_infinite = input_1_infinite,
719 .input2_x = input_2_x,
720 .input2_y = input_2_y,
721 .input2_infinite = input_2_infinite,
722 .predicate = predicate,
723 .result_x = (*arg.outputs)[0].value,
724 .result_y = (*arg.outputs)[1].value,
725 .result_infinite = (*arg.outputs)[2].value,
726 });
727 af.constrained_witness.insert(af.ec_add_constraints.back().result_x);
728 af.constrained_witness.insert(af.ec_add_constraints.back().result_y);
729 af.constrained_witness.insert(af.ec_add_constraints.back().result_infinite);
730 af.original_opcode_indices.ec_add_constraints.push_back(opcode_index);
732 af.keccak_permutations.push_back(Keccakf1600{
733 .state = transform::map(*arg.inputs, [](auto& e) { return parse_input(e); }),
734 .result = transform::map(*arg.outputs, [](auto& e) { return e.value; }),
735 });
736 for (auto& output : af.keccak_permutations.back().result) {
737 af.constrained_witness.insert(output);
738 }
739 af.original_opcode_indices.keccak_permutations.push_back(opcode_index);
741
742 auto input_key = get_witness_from_function_input(arg.key_hash);
743
744 auto proof_type_in = arg.proof_type;
745 auto predicate = parse_input(arg.predicate);
746 if (predicate.is_constant && predicate.value.is_zero()) {
747 // No constraint if the recursion is disabled
748 return;
749 }
750 auto c = RecursionConstraint{
751 .key = transform::map(arg.verification_key,
752 [](auto& e) { return get_witness_from_function_input(e); }),
753 .proof = transform::map(arg.proof, [](auto& e) { return get_witness_from_function_input(e); }),
754 .public_inputs =
755 transform::map(arg.public_inputs, [](auto& e) { return get_witness_from_function_input(e); }),
756 .key_hash = input_key,
757 .proof_type = proof_type_in,
758 .predicate = predicate,
759 };
760
761 // Add the recursion constraint to the appropriate container based on proof type
762 switch (c.proof_type) {
763 case HONK_ZK:
764 case HONK:
765 case ROLLUP_HONK:
766 case ROOT_ROLLUP_HONK:
767 af.honk_recursion_constraints.push_back(c);
768 af.original_opcode_indices.honk_recursion_constraints.push_back(opcode_index);
769 break;
770 case OINK:
771 case PG:
772 case PG_TAIL:
773 case PG_FINAL:
774 af.pg_recursion_constraints.push_back(c);
775 af.original_opcode_indices.pg_recursion_constraints.push_back(opcode_index);
776 break;
777 case AVM:
778 af.avm_recursion_constraints.push_back(c);
779 af.original_opcode_indices.avm_recursion_constraints.push_back(opcode_index);
780 break;
781 case CIVC:
782 af.civc_recursion_constraints.push_back(c);
783 af.original_opcode_indices.civc_recursion_constraints.push_back(opcode_index);
784 break;
785 default:
786 throw_or_abort("Invalid PROOF_TYPE in RecursionConstraint!");
787 }
789 af.poseidon2_constraints.push_back(Poseidon2Constraint{
790 .state = transform::map(arg.inputs, [](auto& e) { return parse_input(e); }),
791 .result = transform::map(arg.outputs, [](auto& e) { return e.value; }),
792 });
793 for (auto& output : af.poseidon2_constraints.back().result) {
794 af.constrained_witness.insert(output);
795 }
796 af.original_opcode_indices.poseidon2_constraints.push_back(opcode_index);
797 }
798 },
799 arg.value.value);
800}
801
803{
804 BlockConstraint block{ .init = {}, .trace = {}, .type = BlockType::ROM };
807
808 auto len = mem_init.init.size();
809 for (size_t i = 0; i < len; ++i) {
810 block.init.push_back(poly_triple{
811 .a = mem_init.init[i].value,
812 .b = 0,
813 .c = 0,
814 .q_m = 0,
815 .q_l = 1,
816 .q_r = 0,
817 .q_o = 0,
818 .q_c = 0,
819 });
820 }
821
822 // Databus is only supported for Goblin, non Goblin builders will treat call_data and return_data as normal
823 // array.
825 block.type = BlockType::CallData;
826 block.calldata_id = std::get<Acir::BlockType::CallData>(mem_init.block_type.value).value;
828 block.type = BlockType::ReturnData;
829 }
830
831 return block;
832}
833
834bool is_rom(Acir::MemOp const& mem_op)
835{
836 return mem_op.operation.mul_terms.empty() && mem_op.operation.linear_combinations.empty() &&
837 from_be_bytes(mem_op.operation.q_c) == 0;
838}
839
840uint32_t poly_to_witness(const poly_triple poly)
841{
842 if (poly.q_m == 0 && poly.q_r == 0 && poly.q_o == 0 && poly.q_l == 1 && poly.q_c == 0) {
843 return poly.a;
844 }
845 return 0;
846}
847
849{
850 uint8_t access_type = 1;
851 if (is_rom(mem_op.op)) {
852 access_type = 0;
853 }
854 if (access_type == 1) {
855 // We are not allowed to write on the databus
856 ASSERT((block.type != BlockType::CallData) && (block.type != BlockType::ReturnData));
857 block.type = BlockType::RAM;
858 }
859
860 // Update the ranges of the index using the array length
862 int bit_range = std::bit_width(block.init.size());
863 uint32_t index_witness = poly_to_witness(index);
864 if (index_witness != 0 && bit_range > 0) {
865 unsigned int u_bit_range = static_cast<unsigned int>(bit_range);
866 // Updates both af.minimal_range and af.index_range with u_bit_range when it is lower.
867 // By doing so, we keep these invariants:
868 // - minimal_range contains the smallest possible range for a witness
869 // - index_range constains the smallest range for a witness implied by any array operation
870 if (af.minimal_range.contains(index_witness)) {
871 if (af.minimal_range[index_witness] > u_bit_range) {
872 af.minimal_range[index_witness] = u_bit_range;
873 }
874 } else {
875 af.minimal_range[index_witness] = u_bit_range;
876 }
877 if (af.index_range.contains(index_witness)) {
878 if (af.index_range[index_witness] > u_bit_range) {
879 af.index_range[index_witness] = u_bit_range;
880 }
881 } else {
882 af.index_range[index_witness] = u_bit_range;
883 }
884 }
885
886 MemOp acir_mem_op =
887 MemOp{ .access_type = access_type, .index = index, .value = serialize_arithmetic_gate(mem_op.op.value) };
888 block.trace.push_back(acir_mem_op);
889}
890
892{
893 AcirFormat af;
894 // `varnum` is the true number of variables, thus we add one to the index which starts at zero
895 af.varnum = circuit.current_witness_index + 1;
896 af.num_acir_opcodes = static_cast<uint32_t>(circuit.opcodes.size());
897 af.public_inputs = join({ transform::map(circuit.public_parameters.value, [](auto e) { return e.value; }),
898 transform::map(circuit.return_values.value, [](auto e) { return e.value; }) });
899 // Map to a pair of: BlockConstraint, and list of opcodes associated with that BlockConstraint
900 // NOTE: We want to deterministically visit this map, so unordered_map should not be used.
902 for (size_t i = 0; i < circuit.opcodes.size(); ++i) {
903 const auto& gate = circuit.opcodes[i];
904 std::visit(
905 [&](auto&& arg) {
906 using T = std::decay_t<decltype(arg)>;
908 handle_arithmetic(arg, af, i);
910 handle_blackbox_func_call(arg, af, i);
911 } else if constexpr (std::is_same_v<T, Acir::Opcode::MemoryInit>) {
912 auto block = handle_memory_init(arg);
913 uint32_t block_id = arg.block_id.value;
914 block_id_to_block_constraint[block_id] = { block, /*opcode_indices=*/{ i } };
915 } else if constexpr (std::is_same_v<T, Acir::Opcode::MemoryOp>) {
916 auto block = block_id_to_block_constraint.find(arg.block_id.value);
917 if (block == block_id_to_block_constraint.end()) {
918 throw_or_abort("unitialized MemoryOp");
919 }
920 handle_memory_op(arg, af, block->second.first);
921 block->second.second.push_back(i);
922 }
923 },
924 gate.value);
925 }
926 for (const auto& [block_id, block] : block_id_to_block_constraint) {
927 // Note: the trace will always be empty for ReturnData since it cannot be explicitly read from in noir
928 if (!block.first.trace.empty() || block.first.type == BlockType::ReturnData ||
929 block.first.type == BlockType::CallData) {
930 af.block_constraints.push_back(block.first);
931 af.original_opcode_indices.block_constraints.push_back(block.second);
932 }
933 }
934 return af;
935}
936
938{
939 // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just
940 // `program_buf_to_acir_format` once Honk fully supports all ACIR test flows For now the backend still expects
941 // to work with a single ACIR function
942 auto program = deserialize_program(std::move(buf));
943 auto circuit = program.functions[0];
944
945 return circuit_serde_to_acir_format(circuit);
946}
947
957{
958 WitnessVector wv;
959 size_t index = 0;
960 for (const auto& e : witness_map.value) {
961 // ACIR uses a sparse format for WitnessMap where unused witness indices may be left unassigned.
962 // To ensure that witnesses sit at the correct indices in the `WitnessVector`, we fill any indices
963 // which do not exist within the `WitnessMap` with the dummy value of zero.
964 while (index < e.first.value) {
965 wv.emplace_back(0);
966 index++;
967 }
968 wv.emplace_back(from_be_bytes(e.second));
969 index++;
970 }
971 return wv;
972}
973
975{
976 // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just
977 // `witness_buf_to_witness_stack` once Honk fully supports all ACIR test flows. For now the backend still
978 // expects to work with the stop of the `WitnessStack`.
979 auto witness_stack = deserialize_witness_stack(std::move(buf));
980 auto w = witness_stack.stack[witness_stack.stack.size() - 1].witness;
981
983}
984
986{
987 auto program = deserialize_program(std::move(buf));
988
989 std::vector<AcirFormat> constraint_systems;
990 constraint_systems.reserve(program.functions.size());
991 for (auto const& function : program.functions) {
992 constraint_systems.emplace_back(circuit_serde_to_acir_format(function));
993 }
994
995 return constraint_systems;
996}
997
999{
1000 auto witness_stack = deserialize_witness_stack(std::move(buf));
1001 WitnessVectorStack witness_vector_stack;
1002 witness_vector_stack.reserve(witness_stack.stack.size());
1003 for (auto const& stack_item : witness_stack.stack) {
1004 witness_vector_stack.emplace_back(stack_item.index, witness_map_to_witness_vector(stack_item.witness));
1005 }
1006 return witness_vector_stack;
1007}
1008
1009AcirProgramStack get_acir_program_stack(std::string const& bytecode_path, std::string const& witness_path)
1010{
1011 vinfo("in get_acir_program_stack; witness path is ", witness_path);
1012 std::vector<uint8_t> bytecode = get_bytecode(bytecode_path);
1013 std::vector<AcirFormat> constraint_systems = program_buf_to_acir_format(std::move(bytecode));
1014 WitnessVectorStack witness_stack = [&]() {
1015 if (witness_path.empty()) {
1016 info("producing a stack of empties");
1017 WitnessVectorStack stack_of_empties{ constraint_systems.size(),
1018 std::make_pair(uint32_t(), WitnessVector()) };
1019 return stack_of_empties;
1020 }
1021 std::vector<uint8_t> witness_data = get_bytecode(witness_path);
1022 return witness_buf_to_witness_stack(std::move(witness_data));
1023 }();
1024
1025 return { std::move(constraint_systems), std::move(witness_stack) };
1026}
1027
1028} // namespace acir_format
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:163
#define ASSERT(expression,...)
Definition assert.hpp:77
#define vinfo(...)
Definition log.hpp:79
void info(Args... args)
Definition log.hpp:74
TestTraceContainer trace
uint8_t const * buf
Definition data_store.hpp:9
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
const auto init
Definition fr.bench.cpp:141
std::vector< uint8_t > get_bytecode(const std::string &bytecodePath)
uint32_t poly_to_witness(const poly_triple poly)
Acir::Program deserialize_program(std::vector< uint8_t > &&buf)
Deserializes a Program from bytes, trying msgpack or bincode formats.
T deserialize_any_format(std::vector< uint8_t > &&buf, std::function< T(msgpack::object const &)> decode_msgpack, std::function< T(std::vector< uint8_t >)> decode_bincode)
Deserialize buf either based on the first byte interpreted as a Noir serialization format byte,...
std::pair< uint32_t, uint32_t > is_assert_equal(Acir::Opcode::AssertZero const &arg, poly_triple const &pt, AcirFormat const &af)
void assign_linear_term(mul_quad_< fr > &gate, int index, uint32_t witness_index, fr const &scaling)
Assigns a linear term to a specific index in a mul_quad_ gate.
WitnessVector witness_buf_to_witness_data(std::vector< uint8_t > &&buf)
Converts from the ACIR-native WitnessStack format to Barretenberg's internal WitnessVector format.
void constrain_witnesses(Acir::Opcode::AssertZero const &arg, AcirFormat &af)
std::vector< mul_quad_< fr > > split_into_mul_quad_gates(Acir::Expression const &arg)
Accumulate the input expression into a serie of quad gates.
Witnesses::WitnessStack deserialize_witness_stack(std::vector< uint8_t > &&buf)
Deserializes a WitnessStack from bytes, trying msgpack or bincode formats.
AcirFormat circuit_serde_to_acir_format(Acir::Circuit const &circuit)
poly_triple serialize_arithmetic_gate(Acir::Expression const &arg)
Construct a poly_tuple for a standard width-3 arithmetic gate from its acir representation.
BlockConstraint handle_memory_init(Acir::Opcode::MemoryInit const &mem_init)
uint32_t get_witness_from_function_input(Acir::FunctionInput input)
WitnessVector witness_map_to_witness_vector(Witnesses::WitnessMap const &witness_map)
Converts from the ACIR-native WitnessMap format to Barretenberg's internal WitnessVector format.
AcirProgramStack get_acir_program_stack(std::string const &bytecode_path, std::string const &witness_path)
std::vector< std::pair< uint32_t, WitnessVector > > WitnessVectorStack
void handle_arithmetic(Acir::Opcode::AssertZero const &arg, AcirFormat &af, size_t opcode_index)
mul_quad_< fr > serialize_mul_quad_gate(Acir::Expression const &arg)
WitnessOrConstant< bb::fr > parse_input(Acir::FunctionInput input)
WitnessVectorStack witness_buf_to_witness_stack(std::vector< uint8_t > &&buf)
bool is_rom(Acir::MemOp const &mem_op)
void handle_memory_op(Acir::Opcode::MemoryOp const &mem_op, AcirFormat &af, BlockConstraint &block)
uint256_t from_be_bytes(std::vector< uint8_t > const &bytes)
std::vector< AcirFormat > program_buf_to_acir_format(std::vector< uint8_t > &&buf)
AcirFormat circuit_buf_to_acir_format(std::vector< uint8_t > &&buf)
bb::SlabVector< bb::fr > WitnessVector
void handle_blackbox_func_call(Acir::Opcode::BlackBoxFuncCall const &arg, AcirFormat &af, size_t opcode_index)
Cont< OutElem > map(Cont< InElem, Args... > const &in, F &&op)
Definition map.hpp:15
Entry point for Barretenberg command-line interface.
field< Bn254FrParams > fr
Definition fr.hpp:174
C join(std::initializer_list< C > to_join)
Definition container.hpp:26
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint8_t len
std::variant< Memory, CallData, ReturnData > value
Definition acir.hpp:3481
Acir::PublicInputs return_values
Definition acir.hpp:4504
std::vector< Acir::Opcode > opcodes
Definition acir.hpp:4501
uint32_t current_witness_index
Definition acir.hpp:4500
Acir::PublicInputs public_parameters
Definition acir.hpp:4503
std::vector< std::tuple< std::vector< uint8_t >, Acir::Witness > > linear_combinations
Definition acir.hpp:3567
std::vector< uint8_t > q_c
Definition acir.hpp:3568
std::vector< std::tuple< std::vector< uint8_t >, Acir::Witness, Acir::Witness > > mul_terms
Definition acir.hpp:3566
std::variant< Constant, Witness > value
Definition acir.hpp:2664
Acir::Expression value
Definition acir.hpp:3881
Acir::Expression operation
Definition acir.hpp:3879
Acir::Expression index
Definition acir.hpp:3880
Acir::Expression value
Definition acir.hpp:3908
Acir::BlackBoxFuncCall value
Definition acir.hpp:3928
std::vector< Acir::Witness > init
Definition acir.hpp:3973
Acir::BlockType block_type
Definition acir.hpp:3974
std::vector< Acir::Circuit > functions
Definition acir.hpp:4562
static Program bincodeDeserialize(std::vector< uint8_t >)
Definition acir.hpp:10961
std::vector< Acir::Circuit > functions
Definition acir.hpp:4586
std::vector< Acir::Witness > value
Definition acir.hpp:4479
std::map< Witnesses::Witness, std::vector< uint8_t > > value
static WitnessStack bincodeDeserialize(std::vector< uint8_t >)
std::vector< WitnessOrConstant< bb::fr > > inputs
std::vector< Blake2sConstraint > blake2s_constraints
std::vector< Sha256Compression > sha256_compression
bb::SlabVector< PolyTripleConstraint > poly_triple_constraints
bb::SlabVector< std::vector< bb::mul_quad_< bb::curve::BN254::ScalarField > > > big_quad_constraints
std::vector< LogicConstraint > logic_constraints
std::map< uint32_t, uint32_t > index_range
std::vector< Blake3Constraint > blake3_constraints
std::vector< RangeConstraint > range_constraints
std::vector< bb::poly_triple_< bb::curve::BN254::ScalarField > > assert_equalities
std::vector< AES128Constraint > aes128_constraints
std::map< uint32_t, uint32_t > minimal_range
AcirFormatOriginalOpcodeIndices original_opcode_indices
std::set< uint32_t > constrained_witness
bb::SlabVector< bb::mul_quad_< bb::curve::BN254::ScalarField > > quad_constraints
std::vector< BlockConstraint > block_constraints
std::vector< uint32_t > public_inputs
std::vector< std::vector< size_t > > block_constraints
Storage for constaint_systems/witnesses for a stack of acir programs.
std::vector< Blake2sInput > inputs
std::vector< Blake3Input > inputs
std::vector< bb::poly_triple > init
WitnessOrConstant< bb::fr > a
std::array< WitnessOrConstant< bb::fr >, 16 > inputs
static constexpr field zero()
void throw_or_abort(std::string const &err)