Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bool.test.cpp
Go to the documentation of this file.
1#include "bool.hpp"
6#include <gtest/gtest.h>
7#include <tuple>
8
9using namespace bb;
10
11#pragma GCC diagnostic ignored "-Wunused-const-variable"
12
13namespace {
15}
17template <class Builder_> class BoolTest : public ::testing::Test {
18 public:
19 using Builder = Builder_;
22
23 // These tree boolean flags cover all possible combinations for a valid input.
24 struct BoolInput {
25 bool is_const; // witness_index = IS_CONSTANT
26 bool value; // w_a
27 bool is_inverted; // i_a
28 };
29
30 // A helper to produce all possible inputs for a given operand.
33 size_t idx = 0;
34 for (bool is_const : { false, true }) {
35 for (bool value : { false, true }) {
36 for (bool is_inverted : { false, true }) {
37 inputs[idx++] = BoolInput{ is_const, value, is_inverted };
38 }
39 }
40 }
41 return inputs;
42 }();
43 // A helper to create a bool_t element from the given flags
45 {
47 return in.is_inverted ? !b : b;
48 };
49
50 void test_binary_op(std::string const& op_name,
51 const std::function<bool_ct(const bool_ct&, const bool_ct&)>& op,
52 const std::function<bool(bool, bool)>& expected_op)
53 {
55
56 for (auto& lhs : all_inputs) {
57 for (auto& rhs : all_inputs) {
60
61 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
62
63 if (!a.is_constant() && !b.is_constant()) {
64 a.set_origin_tag(submitted_value_origin_tag);
65 b.set_origin_tag(challenge_origin_tag);
66 }
67 bool_ct c = op(a, b);
68
69 bool expected = expected_op(lhs.value ^ lhs.is_inverted, rhs.value ^ rhs.is_inverted);
70
71 EXPECT_EQ(c.get_value(), expected)
72 << "Failed on " << op_name << " with inputs: lhs = {const=" << lhs.is_const << ", val=" << lhs.value
73 << ", inv=" << lhs.is_inverted << "}, rhs = {const=" << rhs.is_const << ", val=" << rhs.value
74 << ", inv=" << rhs.is_inverted << "}";
75
76 if (a.is_constant() && b.is_constant()) {
77 EXPECT_TRUE(c.is_constant());
78 }
79
80 if (!a.is_constant() && !b.is_constant()) {
81 // The result of a binary op on two witnesses must be a witness
82 EXPECT_TRUE(!c.is_constant());
83 // Check that the tags are propagated
84 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
85 }
86
87 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
88 // An extra gate is created iff both operands are witnesses
89 EXPECT_EQ(diff, static_cast<size_t>(!a.is_constant() && !b.is_constant()));
90 }
91 }
92
93 EXPECT_TRUE(CircuitChecker::check(builder));
94 };
95
97 {
99 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
100 bool_ct a_true(true);
101 bool_ct a_false(false);
102 EXPECT_TRUE(a_true.get_value());
103 EXPECT_FALSE(a_false.get_value());
104 EXPECT_TRUE(a_true.is_constant() && a_false.is_constant());
105 EXPECT_TRUE(!a_true.is_inverted() && !a_false.is_inverted());
106 // No gates have been added
107 EXPECT_TRUE(num_gates_start == builder.get_estimated_num_finalized_gates());
108 }
109
111 {
113 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
114 const size_t witness_idx_zero = builder.add_variable(bb::fr(0));
115 const size_t witness_idx_one = builder.add_variable(bb::fr(1));
116 const size_t non_bool_witness_idx = builder.add_variable(bb::fr(15));
117
118 bool_ct bool_witness = bool_ct::from_witness_index_unsafe(&builder, witness_idx_zero);
119 EXPECT_EQ(bool_witness.get_value(), false);
120
121 bool_witness = bool_ct::from_witness_index_unsafe(&builder, witness_idx_one);
122 EXPECT_EQ(bool_witness.get_value(), true);
123 // No gates are added.
124 EXPECT_EQ(builder.get_estimated_num_finalized_gates() - num_gates_start, 0);
125
126 // Out-of-circuit failure when witness points to a non-bool value.
127 EXPECT_THROW_OR_ABORT(bool_witness = bool_ct::from_witness_index_unsafe(&builder, non_bool_witness_idx),
128 "bool_t: creating a witness bool from a non-boolean value");
129 }
131 {
133 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
134
135 bool_ct a_true = witness_ct(&builder, 1);
136 bool_ct a_false = witness_ct(&builder, 0);
137 EXPECT_TRUE(a_true.get_value());
138 EXPECT_FALSE(a_false.get_value());
139 EXPECT_TRUE(!a_true.is_constant() && !a_false.is_constant());
140 EXPECT_TRUE(!a_true.is_inverted() && !a_false.is_inverted());
141 // Each witness bool must be constrained => expect 2 gates being added
142 EXPECT_TRUE(builder.get_estimated_num_finalized_gates() - num_gates_start == 2);
143 EXPECT_TRUE(CircuitChecker::check(builder));
144
145 // Test failure
146 bool_ct a_incorrect;
147 uint256_t random_value(engine.get_random_uint256());
148
149 if (random_value * random_value - random_value != 0) {
150 EXPECT_THROW_OR_ABORT(a_incorrect = witness_ct(&builder, random_value),
151 "((other.witness == bb::fr::one()) || (other.witness == bb::fr::zero()))");
152 };
153 }
154
156 {
157 const bool use_range_constraint = true;
158
159 for (size_t num_inputs = 1; num_inputs < 50; num_inputs++) {
161 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
162
163 std::vector<uint32_t> indices;
164 for (size_t idx = 0; idx < num_inputs; idx++) {
165 indices.push_back(
166 bool_ct(witness_ct(&builder, idx % 2), use_range_constraint).get_normalized_witness_index());
167 }
168
169 const size_t sorted_list_size = num_inputs + 2;
170
171 // Pin down the gate numbers. The point is that it is more efficient to use this constructor to constrain a
172 // batch of bool_t elements. 4 is a special case, otherwise the number of gates is just
173 // ceil(sorted_list_size) + 2.
174 size_t expected = (sorted_list_size == 4) ? 4 : ((sorted_list_size + 3) / 4) + 2;
175
176 EXPECT_EQ(builder.get_estimated_num_finalized_gates() - num_gates_start, expected);
177
178 builder.create_unconstrained_gates(indices);
179
180 EXPECT_TRUE(CircuitChecker::check(builder));
181 }
182
183 // Failure test
185 EXPECT_THROW_OR_ABORT(auto new_bool = bool_ct(witness_ct(&builder, 2), use_range_constraint),
186 "bool_t: witness value is not 0 or 1");
187 }
188 void test_AND()
189 {
191 "AND", [](const bool_ct& a, const bool_ct& b) { return a & b; }, [](bool a, bool b) { return a && b; });
192 }
193
194 void test_xor()
195 {
197 "XOR", [](const bool_ct& a, const bool_ct& b) { return a ^ b; }, [](bool a, bool b) { return a ^ b; });
198 }
199
200 void test_OR()
201 {
203 "OR", [](const bool_ct& a, const bool_ct& b) { return a || b; }, [](bool a, bool b) { return a || b; });
204 }
205
206 void test_EQ()
207 {
209 "==", [](const bool_ct& a, const bool_ct& b) { return a == b; }, [](bool a, bool b) { return a == b; });
210 }
211
212 void test_NEQ()
213 {
215 "!=", [](const bool_ct& a, const bool_ct& b) { return a != b; }, [](bool a, bool b) { return a != b; });
216 }
217
219 {
221 "=>",
222 [](const bool_ct& a, const bool_ct& b) { return a.implies(b); },
223 [](bool a, bool b) { return !a || b; });
224 }
225
227 {
229 "<=>",
230 [](const bool_ct& a, const bool_ct& b) { return a.implies_both_ways(b); },
231 [](bool a, bool b) { return !(a ^ b); });
232 }
233
235 {
236
237 for (auto& lhs : all_inputs) {
238 for (auto& rhs : all_inputs) {
240
243
244 if (a.is_constant() && b.is_constant() && !(!a.get_value() || b.get_value())) {
245 EXPECT_THROW_OR_ABORT(a.must_imply(b), R"(\‍(lhs\.get_value\‍(\) == rhs\.get_value\‍(\)\))");
246 } else {
247 bool result_is_constant = (!a || b).is_constant();
248
249 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
250
251 if (!a.is_constant() && !b.is_constant()) {
252 a.set_origin_tag(submitted_value_origin_tag);
253 b.set_origin_tag(challenge_origin_tag);
254 }
255 a.must_imply(b);
256 // !a || b
257 // b = 1 =>
258 bool expected = !(lhs.value ^ lhs.is_inverted) || rhs.value ^ rhs.is_inverted;
259
260 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
261
262 if (!a.is_constant() && !b.is_constant()) {
263 EXPECT_EQ(diff, 2);
264 }
265 // Due to optimizations, the result of a => b can be a constant, in this case, the the assert_equal
266 // reduces to an out-of-circuit ASSERT
267 if (result_is_constant) {
268 EXPECT_EQ(diff, 0);
269 }
270
271 if (!result_is_constant && a.is_constant() && !b.is_constant()) {
272 // we only add gates if the value `true` is not flipped to `false` and we need to add a new
273 // constant == 1, which happens iff `b` is not inverted.
274 EXPECT_EQ(diff, static_cast<size_t>(!b.is_inverted()));
275 }
276
277 if (!result_is_constant && !a.is_constant() && b.is_constant()) {
278 // we only add gates if the value `true` is not flipped to `false` and we need to add a new
279 // constant == 1, which happens iff `a` is inverted.
280 EXPECT_EQ(diff, static_cast<size_t>(a.is_inverted()));
281 }
282 EXPECT_EQ(CircuitChecker::check(builder), expected);
283 }
284 }
285 }
286 }
287
289 {
290 for (auto lhs : all_inputs) {
291 for (auto rhs : all_inputs) {
292 for (auto predicate : all_inputs) {
294
297 bool_ct condition = create_bool_ct(predicate, &builder);
298
299 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
300 if (!a.is_constant() && !b.is_constant()) {
301 condition.set_origin_tag(submitted_value_origin_tag);
302 a.set_origin_tag(challenge_origin_tag);
303 b.set_origin_tag(next_challenge_tag);
304 }
305
306 bool_ct result = bool_ct::conditional_assign(condition, a, b);
307 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
308 if (!a.is_constant() && !b.is_constant()) {
309 EXPECT_EQ(result.get_origin_tag(), first_second_third_merged_tag);
310 }
311 bool expected = (condition.get_value()) ? a.get_value() : b.get_value();
312 EXPECT_EQ(result.get_value(), expected);
313
314 if (condition.is_constant()) {
315 EXPECT_EQ(diff, 0);
316 }
317
318 EXPECT_TRUE(CircuitChecker::check(builder));
319 }
320 }
321 }
322 }
323
325 {
326 for (auto a_raw : all_inputs) {
327 auto builder = Builder();
328
329 bool_ct a = create_bool_ct(a_raw, &builder);
330
331 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
332 if (!a.is_constant()) {
333 a.set_origin_tag(submitted_value_origin_tag);
334 }
335 bool_ct c = a.normalize();
336 EXPECT_EQ(c.get_value(), a.get_value());
337 if (!a.is_constant()) {
338 EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag);
339 }
340 EXPECT_EQ(c.is_inverted(), false);
341 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
342 // Note that although `normalize()` returns value, it flips the `is_inverted()` flag of `a` if it was
343 // `true`.
344 EXPECT_EQ(diff, static_cast<size_t>(!a.is_constant() && a_raw.is_inverted));
345 EXPECT_TRUE(CircuitChecker::check(builder));
346 }
347 }
348
350 {
351
352 for (auto lhs : all_inputs) {
353 for (auto rhs : all_inputs) {
354
356
359
360 bool failed = a.get_value() != b.get_value();
361
362 if (!a.is_constant() && !b.is_constant()) {
363 a.assert_equal(b);
364 // CircuitChecker is not verifying the permutation relation
365 EXPECT_EQ(builder.failed(), failed);
366 } else if (!a.is_constant() || !b.is_constant()) {
367 a.assert_equal(b);
368 EXPECT_EQ(CircuitChecker::check(builder), !failed);
369 } else {
370 if (failed) {
371 EXPECT_THROW_OR_ABORT(a.assert_equal(b), R"(\‍(lhs\.get_value\‍(\) == rhs\.get_value\‍(\)\))");
372 }
373 }
374 }
375 }
376 }
377
379 {
380 auto builder = Builder();
381
382 auto gates_before = builder.get_estimated_num_finalized_gates();
383
386
387 a.set_origin_tag(submitted_value_origin_tag);
388 b.set_origin_tag(challenge_origin_tag);
389
390 a = a ^ b; // a = 1
391 EXPECT_EQ(a.get_value(), 1);
392
393 // Tags are merged on XOR
394 EXPECT_EQ(a.get_origin_tag(), first_two_merged_tag);
395
396 b = !b; // b = 1 (witness 0)
397 EXPECT_EQ(b.get_value(), 1);
398
399 // Tag is preserved on NOT
400 EXPECT_EQ(b.get_origin_tag(), challenge_origin_tag);
401
402 a.set_origin_tag(submitted_value_origin_tag);
403
404 bool_ct d = (a == b); //
405 EXPECT_EQ(d.get_value(), 1);
406
407 // Tags are merged on ==
408 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
409
410 d = false; // d = 0
411 d.set_origin_tag(challenge_origin_tag);
412 EXPECT_EQ(d.get_value(), 0);
413
414 bool_ct e = a | d; // e = 1 = a
415 EXPECT_EQ(e.get_value(), 1);
416
417 // Tags are merged on OR
418 EXPECT_EQ(e.get_origin_tag(), first_two_merged_tag);
419
420 bool_ct f = e ^ b; // f = 0
421 EXPECT_EQ(f.get_value(), 0);
422
423 f.set_origin_tag(challenge_origin_tag);
424 d = (!f) & a; // d = 1
425 EXPECT_EQ(d.get_value(), 1);
426
427 // Tags are merged on AND
428 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
429
430 bool result = CircuitChecker::check(builder);
431 EXPECT_EQ(result, true);
432
433 auto gates_after = builder.get_estimated_num_finalized_gates();
434 EXPECT_EQ(gates_after - gates_before, 6UL);
435 }
436
437 // Check that (a && (b || c)) ^ (d => f) <=> ((a && b) || (a && c)) ^ (!d || f)) for all inputs.
439 {
440 for (const auto& a_input : all_inputs) {
441 for (const auto& b_input : all_inputs) {
442 for (const auto& c_input : all_inputs) {
443 for (const auto& d_input : all_inputs) {
444 for (const auto& f_input : all_inputs) {
446
447 // Construct bool_cts from inputs
448 bool_ct a = create_bool_ct(a_input, &builder);
449 bool_ct b = create_bool_ct(b_input, &builder);
450 bool_ct c = create_bool_ct(c_input, &builder);
451 bool_ct d = create_bool_ct(d_input, &builder);
452 bool_ct f = create_bool_ct(f_input, &builder);
453
454 // === Formula 1 ===
455 bool_ct lhs = (a && (b || c)) ^ (d.implies(f));
456 bool_ct rhs = ((a && b) || (a && c)) ^ (!d || f);
457
458 // Equivalence check
459 bool_ct equivalent = lhs.implies_both_ways(rhs);
460 if (!equivalent.get_value()) {
461 info("FAIL:");
462 info("a: ", a.get_value(), " b: ", b.get_value(), " c: ", c.get_value());
463 info("d: ", d.get_value(), " f: ", f.get_value());
464 }
465
466 EXPECT_EQ(equivalent.get_value(), true);
467 EXPECT_TRUE(CircuitChecker::check(builder));
468 }
469 }
470 }
471 }
472 }
473 }
474};
475
476using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder>;
477
479TYPED_TEST(BoolTest, ConstructFromConstBool)
480{
481 TestFixture::test_construct_from_const_bool();
482}
483
484TYPED_TEST(BoolTest, ConstructFromWitness)
485{
486 TestFixture::test_construct_from_witness();
487}
488TYPED_TEST(BoolTest, ConstructFromWitnessRangeConstraint)
489{
490 TestFixture::test_construct_from_witness_range_constraint();
491}
492
493TYPED_TEST(BoolTest, Normalization)
494{
495 TestFixture::test_normalize();
496}
498{
499 TestFixture::test_xor();
500}
501
503{
504 TestFixture::test_AND();
505}
506
508{
509 TestFixture::test_OR();
510}
511
513{
514 TestFixture::test_EQ();
515}
516
518{
519 TestFixture::test_NEQ();
520}
521
523{
524 TestFixture::test_implies();
525}
526
527TYPED_TEST(BoolTest, ImpliesBothWays)
528{
529 TestFixture::test_implies_both_ways();
530}
531
533{
534 TestFixture::test_must_imply();
535}
536
537TYPED_TEST(BoolTest, ConditionalAssign)
538{
539 TestFixture::test_conditional_assign();
540}
541
542TYPED_TEST(BoolTest, TestBasicOperationsTags)
543{
544 TestFixture::test_basic_operations_tags();
545}
546
547TYPED_TEST(BoolTest, TestSimpleProof)
548{
549 TestFixture::test_simple_proof();
550}
551TYPED_TEST(BoolTest, AssertEqual)
552{
553 TestFixture::test_assert_equal();
554}
#define EXPECT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:185
void test_xor()
void test_construct_from_witness_index()
void test_implies_both_ways()
void test_normalize()
void test_implies()
void test_NEQ()
void test_conditional_assign()
void test_binary_op(std::string const &op_name, const std::function< bool_ct(const bool_ct &, const bool_ct &)> &op, const std::function< bool(bool, bool)> &expected_op)
Definition bool.test.cpp:50
void test_OR()
void test_AND()
void test_must_imply()
Builder_ Builder
Definition bool.test.cpp:19
void test_basic_operations_tags()
void test_simple_proof()
void test_construct_from_const_bool()
Definition bool.test.cpp:96
std::array< BoolInput, 8 > all_inputs
Definition bool.test.cpp:31
void test_EQ()
void test_construct_from_witness_range_constraint()
stdlib::witness_t< Builder > witness_ct
Definition bool.test.cpp:20
void test_construct_from_witness()
static bool_ct create_bool_ct(const BoolInput &in, Builder *builder)
Definition bool.test.cpp:44
void test_assert_equal()
stdlib::bool_t< Builder > bool_ct
Definition bool.test.cpp:21
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
Implements boolean logic in-circuit.
Definition bool.hpp:59
bool get_value() const
Definition bool.hpp:111
bool is_constant() const
Definition bool.hpp:113
void set_origin_tag(const OriginTag &new_tag) const
Definition bool.hpp:128
bool_t implies(const bool_t &other) const
Implements implication operator in circuit.
Definition bool.cpp:478
bool is_inverted() const
Definition bool.hpp:114
bool_t normalize() const
A bool_t element is normalized if witness_inverted == false. For a given *this, output its normalized...
Definition bool.cpp:505
static bool_t conditional_assign(const bool_t< Builder > &predicate, const bool_t &lhs, const bool_t &rhs)
Implements the ternary operator - if predicate == true then return lhs, else return rhs.
Definition bool.cpp:456
static bool_t from_witness_index_unsafe(Builder *ctx, uint32_t witness_index)
Create a bool_t from a witness index that is known to contain a constrained bool value.
Definition bool.cpp:96
bool_t implies_both_ways(const bool_t &other) const
Implements a "double-implication" (<=>), a.k.a "iff", a.k.a. "biconditional".
Definition bool.cpp:495
OriginTag get_origin_tag() const
Definition bool.hpp:129
void info(Args... args)
Definition log.hpp:74
AluTraceBuilder builder
Definition alu.test.cpp:123
FF a
FF b
testing::Types< bb::UltraCircuitBuilder, bb::MegaCircuitBuilder > CircuitTypes
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
Entry point for Barretenberg command-line interface.
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
#define STANDARD_TESTING_TAGS
static constexpr field one()
static constexpr field zero()