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.witness_inverted && !a_false.witness_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
115 bool_ct a_true = witness_ct(&builder, 1);
116 bool_ct a_false = witness_ct(&builder, 0);
117 EXPECT_TRUE(a_true.get_value());
118 EXPECT_FALSE(a_false.get_value());
119 EXPECT_TRUE(!a_true.is_constant() && !a_false.is_constant());
120 EXPECT_TRUE(!a_true.witness_inverted && !a_false.witness_inverted);
121 // Each witness bool must be constrained => expect 2 gates being added
122 EXPECT_TRUE(builder.get_estimated_num_finalized_gates() - num_gates_start == 2);
123 EXPECT_TRUE(CircuitChecker::check(builder));
124
125 // Test failure
126 bool_ct a_incorrect;
127 uint256_t random_value(engine.get_random_uint256());
128
129 if (random_value * random_value - random_value != 0) {
130 EXPECT_THROW_OR_ABORT(a_incorrect = witness_ct(&builder, random_value),
131 "((other.witness == bb::fr::one()) || (other.witness == bb::fr::zero()))");
132 };
133 }
134 void test_AND()
135 {
137 "AND", [](const bool_ct& a, const bool_ct& b) { return a & b; }, [](bool a, bool b) { return a && b; });
138 }
139
140 void test_xor()
141 {
143 "XOR", [](const bool_ct& a, const bool_ct& b) { return a ^ b; }, [](bool a, bool b) { return a ^ b; });
144 }
145
146 void test_OR()
147 {
149 "OR", [](const bool_ct& a, const bool_ct& b) { return a || b; }, [](bool a, bool b) { return a || b; });
150 }
151
152 void test_EQ()
153 {
155 "==", [](const bool_ct& a, const bool_ct& b) { return a == b; }, [](bool a, bool b) { return a == b; });
156 }
157
158 void test_NEQ()
159 {
161 "!=", [](const bool_ct& a, const bool_ct& b) { return a != b; }, [](bool a, bool b) { return a != b; });
162 }
163
165 {
167 "=>",
168 [](const bool_ct& a, const bool_ct& b) { return a.implies(b); },
169 [](bool a, bool b) { return !a || b; });
170 }
171
173 {
175 "<=>",
176 [](const bool_ct& a, const bool_ct& b) { return a.implies_both_ways(b); },
177 [](bool a, bool b) { return !(a ^ b); });
178 }
179
181 {
182
183 for (auto& lhs : all_inputs) {
184 for (auto& rhs : all_inputs) {
186
189
190 if (a.is_constant() && b.is_constant() && !(!a.get_value() || b.get_value())) {
191 EXPECT_THROW_OR_ABORT(a.must_imply(b), R"(\‍(lhs\.get_value\‍(\) == rhs\.get_value\‍(\)\))");
192 } else {
193 bool result_is_constant = (!a || b).is_constant();
194
195 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
196
197 if (!a.is_constant() && !b.is_constant()) {
198 a.set_origin_tag(submitted_value_origin_tag);
199 b.set_origin_tag(challenge_origin_tag);
200 }
201 a.must_imply(b);
202 // !a || b
203 // b = 1 =>
204 bool expected = !(lhs.value ^ lhs.is_inverted) || rhs.value ^ rhs.is_inverted;
205
206 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
207
208 if (!a.is_constant() && !b.is_constant()) {
209 EXPECT_EQ(diff, 2);
210 }
211 // Due to optimizations, the result of a => b can be a constant, in this case, the the assert_equal
212 // reduces to an out-of-circuit ASSERT
213 if (result_is_constant) {
214 EXPECT_EQ(diff, 0);
215 }
216
217 if (!result_is_constant && a.is_constant() && !b.is_constant()) {
218 // we only add gates if the value `true` is not flipped to `false` and we need to add a new
219 // constant == 1, which happens iff `b` is not inverted.
220 EXPECT_EQ(diff, static_cast<size_t>(!b.witness_inverted));
221 }
222
223 if (!result_is_constant && !a.is_constant() && b.is_constant()) {
224 // we only add gates if the value `true` is not flipped to `false` and we need to add a new
225 // constant == 1, which happens iff `a` is inverted.
226 EXPECT_EQ(diff, static_cast<size_t>(a.witness_inverted));
227 }
228 EXPECT_EQ(CircuitChecker::check(builder), expected);
229 }
230 }
231 }
232 }
233
235 {
236 for (auto lhs : all_inputs) {
237 for (auto rhs : all_inputs) {
238 for (auto predicate : all_inputs) {
240
243 bool_ct condition = create_bool_ct(predicate, &builder);
244
245 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
246 if (!a.is_constant() && !b.is_constant()) {
247 condition.set_origin_tag(submitted_value_origin_tag);
248 a.set_origin_tag(challenge_origin_tag);
249 b.set_origin_tag(next_challenge_tag);
250 }
251
252 bool_ct result = bool_ct::conditional_assign(condition, a, b);
253 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
254 if (!a.is_constant() && !b.is_constant()) {
255 EXPECT_EQ(result.get_origin_tag(), first_second_third_merged_tag);
256 }
257 bool expected = (condition.get_value()) ? a.get_value() : b.get_value();
258 EXPECT_EQ(result.get_value(), expected);
259
260 if (condition.is_constant()) {
261 EXPECT_EQ(diff, 0);
262 }
263
264 EXPECT_TRUE(CircuitChecker::check(builder));
265 }
266 }
267 }
268 }
269
271 {
272 for (auto a_raw : all_inputs) {
273 auto builder = Builder();
274
275 bool_ct a = create_bool_ct(a_raw, &builder);
276
277 size_t num_gates_start = builder.get_estimated_num_finalized_gates();
278 if (!a.is_constant()) {
279 a.set_origin_tag(submitted_value_origin_tag);
280 }
281 bool_ct c = a.normalize();
282 EXPECT_EQ(c.get_value(), a.get_value());
283 if (!a.is_constant()) {
284 EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag);
285 }
286 EXPECT_EQ(c.witness_inverted, false);
287 size_t diff = builder.get_estimated_num_finalized_gates() - num_gates_start;
288 // Note that although `normalize()` returns value, it flips the `witness_inverted` flag of `a` if it was
289 // `true`.
290 EXPECT_EQ(diff, static_cast<size_t>(!a.is_constant() && a_raw.is_inverted));
291 EXPECT_TRUE(CircuitChecker::check(builder));
292 }
293 }
294
296 {
297
298 for (auto lhs : all_inputs) {
299 for (auto rhs : all_inputs) {
300
302
305
306 bool failed = a.get_value() != b.get_value();
307
308 if (!a.is_constant() && !b.is_constant()) {
309 a.assert_equal(b);
310 // CircuitChecker is not verifying the permutation relation
311 EXPECT_EQ(builder.failed(), failed);
312 } else if (!a.is_constant() || !b.is_constant()) {
313 a.assert_equal(b);
314 EXPECT_EQ(CircuitChecker::check(builder), !failed);
315 } else {
316 if (failed) {
317 EXPECT_THROW_OR_ABORT(a.assert_equal(b), R"(\‍(lhs\.get_value\‍(\) == rhs\.get_value\‍(\)\))");
318 }
319 }
320 }
321 }
322 }
323
325 {
326 auto builder = Builder();
327
328 auto gates_before = builder.get_estimated_num_finalized_gates();
329
332
333 a.set_origin_tag(submitted_value_origin_tag);
334 b.set_origin_tag(challenge_origin_tag);
335
336 a = a ^ b; // a = 1
337 EXPECT_EQ(a.get_value(), 1);
338
339 // Tags are merged on XOR
340 EXPECT_EQ(a.get_origin_tag(), first_two_merged_tag);
341
342 b = !b; // b = 1 (witness 0)
343 EXPECT_EQ(b.get_value(), 1);
344
345 // Tag is preserved on NOT
346 EXPECT_EQ(b.get_origin_tag(), challenge_origin_tag);
347
348 a.set_origin_tag(submitted_value_origin_tag);
349
350 bool_ct d = (a == b); //
351 EXPECT_EQ(d.get_value(), 1);
352
353 // Tags are merged on ==
354 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
355
356 d = false; // d = 0
357 d.set_origin_tag(challenge_origin_tag);
358 EXPECT_EQ(d.get_value(), 0);
359
360 bool_ct e = a | d; // e = 1 = a
361 EXPECT_EQ(e.get_value(), 1);
362
363 // Tags are merged on OR
364 EXPECT_EQ(e.get_origin_tag(), first_two_merged_tag);
365
366 bool_ct f = e ^ b; // f = 0
367 EXPECT_EQ(f.get_value(), 0);
368
369 f.set_origin_tag(challenge_origin_tag);
370 d = (!f) & a; // d = 1
371 EXPECT_EQ(d.get_value(), 1);
372
373 // Tags are merged on AND
374 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
375
376 bool result = CircuitChecker::check(builder);
377 EXPECT_EQ(result, true);
378
379 auto gates_after = builder.get_estimated_num_finalized_gates();
380 EXPECT_EQ(gates_after - gates_before, 6UL);
381 }
382
383 // Check that (a && (b || c)) ^ (d => f) <=> ((a && b) || (a && c)) ^ (!d || f)) for all inputs.
385 {
386 for (const auto& a_input : all_inputs) {
387 for (const auto& b_input : all_inputs) {
388 for (const auto& c_input : all_inputs) {
389 for (const auto& d_input : all_inputs) {
390 for (const auto& f_input : all_inputs) {
392
393 // Construct bool_cts from inputs
394 bool_ct a = create_bool_ct(a_input, &builder);
395 bool_ct b = create_bool_ct(b_input, &builder);
396 bool_ct c = create_bool_ct(c_input, &builder);
397 bool_ct d = create_bool_ct(d_input, &builder);
398 bool_ct f = create_bool_ct(f_input, &builder);
399
400 // === Formula 1 ===
401 bool_ct lhs = (a && (b || c)) ^ (d.implies(f));
402 bool_ct rhs = ((a && b) || (a && c)) ^ (!d || f);
403
404 // Equivalence check
405 bool_ct equivalent = lhs.implies_both_ways(rhs);
406 if (!equivalent.get_value()) {
407 info("FAIL:");
408 info("a: ", a.get_value(), " b: ", b.get_value(), " c: ", c.get_value());
409 info("d: ", d.get_value(), " f: ", f.get_value());
410 }
411
412 EXPECT_EQ(equivalent.get_value(), true);
413 EXPECT_TRUE(CircuitChecker::check(builder));
414 }
415 }
416 }
417 }
418 }
419 }
420};
421
422using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder>;
423
425TYPED_TEST(BoolTest, ConstructFromConstBool)
426{
427 TestFixture::test_construct_from_const_bool();
428}
429
430TYPED_TEST(BoolTest, ConstructFromWitness)
431{
432 TestFixture::test_construct_from_witness();
433}
434
435TYPED_TEST(BoolTest, Normalization)
436{
437 TestFixture::test_normalize();
438}
440{
441 TestFixture::test_xor();
442}
443
445{
446 TestFixture::test_AND();
447}
448
450{
451 TestFixture::test_OR();
452}
453
455{
456 TestFixture::test_EQ();
457}
458
460{
461 TestFixture::test_NEQ();
462}
463
465{
466 TestFixture::test_implies();
467}
468
469TYPED_TEST(BoolTest, ImpliesBothWays)
470{
471 TestFixture::test_implies_both_ways();
472}
473
475{
476 TestFixture::test_must_imply();
477}
478
479TYPED_TEST(BoolTest, ConditionalAssign)
480{
481 TestFixture::test_conditional_assign();
482}
483
484TYPED_TEST(BoolTest, TestBasicOperationsTags)
485{
486 TestFixture::test_basic_operations_tags();
487}
488
489TYPED_TEST(BoolTest, TestSimpleProof)
490{
491 TestFixture::test_simple_proof();
492}
493TYPED_TEST(BoolTest, AssertEqual)
494{
495 TestFixture::test_assert_equal();
496}
#define EXPECT_THROW_OR_ABORT(statement, matcher)
Definition assert.hpp:150
void test_xor()
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()
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.
virtual uint256_t get_random_uint256()=0
Implements boolean logic in-circuit.
Definition bool.hpp:59
bool get_value() const
Definition bool.hpp:109
bool is_constant() const
Definition bool.hpp:111
void set_origin_tag(const OriginTag &new_tag) const
Definition bool.hpp:119
bool_t implies(const bool_t &other) const
Implements implication operator in circuit.
Definition bool.cpp:451
bool_t normalize() const
A bool_t element is normalized if witness_inverted == false. For a given *this, output its normalized...
Definition bool.cpp:478
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:429
bool witness_inverted
Definition bool.hpp:132
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:468
OriginTag get_origin_tag() const
Definition bool.hpp:120
void info(Args... args)
Definition log.hpp:70
AluTraceBuilder builder
Definition alu.test.cpp:123
FF a
FF b
numeric::RNG & engine
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
testing::Types< bb::UltraCircuitBuilder > CircuitTypes
static constexpr field one()
static constexpr field zero()