diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp index a7bd04b59a6..52d510d8774 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.cpp @@ -1,5 +1,6 @@ #include "bool.hpp" #include "../circuit_builders/circuit_builders.hpp" +#include "barretenberg/transcript/origin_tag.hpp" using namespace bb; @@ -50,6 +51,7 @@ bool_t::bool_t(const bool_t& other) witness_index = other.witness_index; witness_bool = other.witness_bool; witness_inverted = other.witness_inverted; + tag = other.tag; } template @@ -59,6 +61,7 @@ bool_t::bool_t(bool_t&& other) witness_index = other.witness_index; witness_bool = other.witness_bool; witness_inverted = other.witness_inverted; + tag = other.tag; } template bool_t& bool_t::operator=(const bool other) @@ -76,6 +79,7 @@ template bool_t& bool_t::operator=(const bo witness_index = other.witness_index; witness_bool = other.witness_bool; witness_inverted = other.witness_inverted; + tag = other.tag; return *this; } @@ -85,6 +89,7 @@ template bool_t& bool_t::operator=(bool_t&& witness_index = other.witness_index; witness_bool = other.witness_bool; witness_inverted = other.witness_inverted; + tag = other.tag; return *this; } @@ -177,6 +182,7 @@ template bool_t bool_t::operator&(const boo result.witness_index = IS_CONSTANT; result.witness_inverted = false; } + result.tag = OriginTag(tag, other.tag); return result; } @@ -257,6 +263,7 @@ template bool_t bool_t::operator|(const boo result.witness_inverted = false; result.witness_index = IS_CONSTANT; } + result.tag = OriginTag(tag, other.tag); return result; } @@ -316,6 +323,7 @@ template bool_t bool_t::operator^(const boo result.witness_inverted = false; result.witness_index = IS_CONSTANT; } + result.tag = OriginTag(tag, other.tag); return result; } @@ -333,6 +341,7 @@ template bool_t bool_t::operator==(const bo bool_t result(context == nullptr ? other.context : context); result.witness_bool = (witness_bool ^ witness_inverted) == (other.witness_bool ^ other.witness_inverted); result.witness_index = IS_CONSTANT; + result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag())); return result; } else if ((witness_index != IS_CONSTANT) && (other.witness_index == IS_CONSTANT)) { if (other.witness_bool ^ other.witness_inverted) { @@ -376,6 +385,8 @@ template bool_t bool_t::operator==(const bo right_coefficient, bb::fr::neg_one(), constant_coefficient }); + + result.tag = OriginTag(tag, other.tag); return result; } } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp index cbd8607f7a3..686b9a6deb1 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.hpp @@ -1,6 +1,7 @@ #pragma once #include "../circuit_builders/circuit_builders_fwd.hpp" #include "../witness/witness.hpp" +#include "barretenberg/transcript/origin_tag.hpp" namespace bb::stdlib { @@ -64,10 +65,13 @@ template class bool_t { Builder* get_context() const { return context; } + void set_origin_tag(const OriginTag& new_tag) const { tag = new_tag; } + OriginTag get_origin_tag() const { return tag; } mutable Builder* context = nullptr; mutable bool witness_bool = false; mutable bool witness_inverted = false; mutable uint32_t witness_index = IS_CONSTANT; + mutable OriginTag tag{}; }; template inline std::ostream& operator<<(std::ostream& os, bool_t const& v) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp index 45f4b3fe942..4744892ee42 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/bool/bool.test.cpp @@ -2,7 +2,9 @@ #include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/stdlib/primitives/byte_array/byte_array.cpp" #include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/transcript/origin_tag.hpp" #include +#include #define STDLIB_TYPE_ALIASES \ using Builder = TypeParam; \ @@ -19,6 +21,16 @@ template class BoolTest : public ::testing::Test {}; using CircuitTypes = ::testing::Types; +// Tags reused in tests +const size_t parent_id = 0; +const auto submitted_value_origin_tag = OriginTag(parent_id, /*round_id=*/0, /*is_submitted=*/true); +const auto challenge_origin_tag = OriginTag(parent_id, /*round_id=*/0, /*is_submitted=*/false); +const auto next_challenge_tag = OriginTag(parent_id, /*round_id=*/1, /*is_submitted=*/false); + +const auto first_two_merged_tag = OriginTag(submitted_value_origin_tag, challenge_origin_tag); +const auto first_and_third_merged_tag = OriginTag(submitted_value_origin_tag, next_challenge_tag); +const auto all_merged_tag = OriginTag(first_two_merged_tag, next_challenge_tag); + TYPED_TEST_SUITE(BoolTest, CircuitTypes); TYPED_TEST(BoolTest, TestBasicOperations) { @@ -30,21 +42,50 @@ TYPED_TEST(BoolTest, TestBasicOperations) bool_ct a = witness_ct(&builder, bb::fr::one()); bool_ct b = witness_ct(&builder, bb::fr::zero()); + + a.set_origin_tag(submitted_value_origin_tag); + b.set_origin_tag(challenge_origin_tag); + a = a ^ b; // a = 1 EXPECT_EQ(a.get_value(), 1); + + // Tags are merged on XOR + EXPECT_EQ(a.get_origin_tag(), first_two_merged_tag); + b = !b; // b = 1 (witness 0) EXPECT_EQ(b.get_value(), 1); + + // Tag is preserved on NOT + EXPECT_EQ(b.get_origin_tag(), challenge_origin_tag); + + a.set_origin_tag(submitted_value_origin_tag); + bool_ct d = (a == b); // EXPECT_EQ(d.get_value(), 1); + + // Tags are merged on == + EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag); + d = false; // d = 0 + d.set_origin_tag(challenge_origin_tag); EXPECT_EQ(d.get_value(), 0); + bool_ct e = a | d; // e = 1 = a EXPECT_EQ(e.get_value(), 1); + + // Tags are merged on OR + EXPECT_EQ(e.get_origin_tag(), first_two_merged_tag); + bool_ct f = e ^ b; // f = 0 EXPECT_EQ(f.get_value(), 0); + + f.set_origin_tag(challenge_origin_tag); d = (!f) & a; // d = 1 EXPECT_EQ(d.get_value(), 1); + // Tags are merged on AND + EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag); + bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); @@ -137,7 +178,14 @@ TYPED_TEST(BoolTest, LogicalAnd) bool_ct a = witness_ct(&builder, 1); bool_ct b = witness_ct(&builder, 1); - (!a) && (!b); + + a.set_origin_tag(submitted_value_origin_tag); + b.set_origin_tag(challenge_origin_tag); + + auto c = (!a) && (!b); + + // Tags are merged on logical AND + EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); bool result = CircuitChecker::check(builder); EXPECT_EQ(result, true); @@ -289,8 +337,16 @@ TYPED_TEST(BoolTest, Implies) bool b_val = (bool)(i > 1 ? true : false); bool_ct a = lhs_constant ? bool_ct(a_val) : (witness_ct(&builder, a_val)); bool_ct b = rhs_constant ? bool_ct(b_val) : (witness_ct(&builder, b_val)); + if (!(lhs_constant || rhs_constant)) { + a.set_origin_tag(submitted_value_origin_tag); + b.set_origin_tag(challenge_origin_tag); + } bool_ct c = a.implies(b); EXPECT_EQ(c.get_value(), !a.get_value() || b.get_value()); + if (!(lhs_constant || rhs_constant)) { + // Tags are merged on implies + EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); + } } } @@ -312,8 +368,17 @@ TYPED_TEST(BoolTest, ImpliesBothWays) bool b_val = (bool)(i > 1 ? true : false); bool_ct a = lhs_constant ? bool_ct(a_val) : (witness_ct(&builder, a_val)); bool_ct b = rhs_constant ? bool_ct(b_val) : (witness_ct(&builder, b_val)); + if (!(lhs_constant || rhs_constant)) { + a.set_origin_tag(submitted_value_origin_tag); + b.set_origin_tag(challenge_origin_tag); + } bool_ct c = a.implies_both_ways(b); EXPECT_EQ(c.get_value(), !(a.get_value() ^ b.get_value())); + + if (!(lhs_constant || rhs_constant)) { + // Tags are merged on implies both ways + EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); + } } } @@ -465,8 +530,18 @@ TYPED_TEST(BoolTest, ConditionalAssign) bool_ct r_ct = rhs_constant ? bool_ct(right) : (witness_ct(&builder, right)); bool_ct cond = (witness_ct(&builder, condition)); + if (!(lhs_constant | rhs_constant)) { + cond.set_origin_tag(submitted_value_origin_tag); + l_ct.set_origin_tag(challenge_origin_tag); + r_ct.set_origin_tag(next_challenge_tag); + } auto result = bool_ct::conditional_assign(cond, l_ct, r_ct); + if (!(lhs_constant | rhs_constant)) { + // Tags are merged on conditional assign + EXPECT_EQ(result.get_origin_tag(), all_merged_tag); + } + EXPECT_EQ(result.get_value(), condition ? left : right); } info("num gates = ", builder.get_num_gates()); @@ -514,8 +589,14 @@ TYPED_TEST(BoolTest, Normalize) auto generate_constraints = [&builder](bool value, bool is_constant, bool is_inverted) { bool_ct a = is_constant ? bool_ct(&builder, value) : witness_ct(&builder, value); bool_ct b = is_inverted ? !a : a; + if (!is_constant) { + b.set_origin_tag(submitted_value_origin_tag); + } bool_ct c = b.normalize(); EXPECT_EQ(c.get_value(), value ^ is_inverted); + if (!is_constant) { + EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag); + } }; generate_constraints(false, false, false); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp index 26f08fa0862..1081be69d34 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.cpp @@ -52,6 +52,7 @@ template field_t::field_t(const bool_t& oth additive_constant = other.witness_inverted ? bb::fr::one() : bb::fr::zero(); multiplicative_constant = other.witness_inverted ? bb::fr::neg_one() : bb::fr::one(); } + tag = other.tag; } template @@ -69,6 +70,7 @@ template field_t::operator bool_t() const result.witness_bool = (additive_constant == bb::fr::one()); result.witness_inverted = false; result.witness_index = IS_CONSTANT; + result.set_origin_tag(tag); return result; } bool add_constant_check = (additive_constant == bb::fr::zero()); @@ -85,6 +87,7 @@ template field_t::operator bool_t() const result.witness_inverted = inverted_check; result.witness_index = witness_index; context->create_bool_gate(witness_index); + result.set_origin_tag(tag); return result; } @@ -130,6 +133,7 @@ template field_t field_t::operator+(const f bb::fr::neg_one(), (additive_constant + other.additive_constant) }); } + result.tag = OriginTag(tag, other.tag); return result; } @@ -240,6 +244,7 @@ template field_t field_t::operator*(const f .q_o = bb::fr::neg_one(), .q_c = q_c }); } + result.tag = OriginTag(tag, other.tag); return result; } @@ -338,6 +343,7 @@ template field_t field_t::divide_no_zero_ch .q_o = q_o, .q_c = q_c }); } + result.tag = OriginTag(tag, other.tag); return result; } /** @@ -356,7 +362,9 @@ template field_t field_t::pow(const field_t ctx->failure("field_t::pow exponent accumulator incorrect"); } constexpr uint256_t MASK_32_BITS = 0xffff'ffff; - return get_value().pow(exponent_value & MASK_32_BITS); + auto result = field_t(get_value().pow(exponent_value & MASK_32_BITS)); + result.set_origin_tag(OriginTag(get_origin_tag(), exponent.get_origin_tag())); + return result; } bool exponent_constant = exponent.is_constant(); @@ -385,6 +393,7 @@ template field_t field_t::pow(const field_t accumulator *= (mul_coefficient * bit + 1); } accumulator = accumulator.normalize(); + accumulator.tag = OriginTag(tag, exponent.tag); return accumulator; } @@ -458,6 +467,7 @@ template field_t field_t::madd(const field_ .d_scaling = -bb::fr(1), .const_scaling = q_c, }); + result.tag = OriginTag(tag, to_mul.tag, to_add.tag); return result; } @@ -495,6 +505,7 @@ template field_t field_t::add_two(const fie .d_scaling = -bb::fr(1), .const_scaling = q_c, }); + result.tag = OriginTag(tag, add_a.tag, add_b.tag); return result; } @@ -531,6 +542,7 @@ template field_t field_t::normalize() const .b_scaling = 0, .c_scaling = bb::fr::neg_one(), .const_scaling = additive_constant }); + result.tag = tag; return result; } @@ -614,7 +626,9 @@ template void field_t::assert_is_not_zero(std::strin template bool_t field_t::is_zero() const { if (witness_index == IS_CONSTANT) { - return bool_t(context, (get_value() == bb::fr::zero())); + auto result = bool_t(context, (get_value() == bb::fr::zero())); + result.set_origin_tag(get_origin_tag()); + return result; } // To check whether a field element, k, is zero, we use the fact that, if k > 0, @@ -668,6 +682,7 @@ template bool_t field_t::is_zero() const .q_r = q_r, .q_o = q_o, .q_c = q_c }); + is_zero.set_origin_tag(tag); return is_zero; } @@ -686,7 +701,9 @@ template bool_t field_t::operator==(const f { Builder* ctx = (context == nullptr) ? other.context : context; if (is_constant() && other.is_constant()) { - return (get_value() == other.get_value()); + auto result = bool_t((get_value() == other.get_value())); + result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag())); + return result; } bb::fr fa = get_value(); @@ -707,6 +724,7 @@ template bool_t field_t::operator==(const f field_t::evaluate_polynomial_identity(diff, x, result, -field_t(bb::fr::one())); field_t::evaluate_polynomial_identity(diff, result, field_t(bb::fr::zero()), field_t(bb::fr::zero())); + result.set_origin_tag(OriginTag(tag, other.tag)); return result; } @@ -719,7 +737,9 @@ template field_t field_t::conditional_negate(const bool_t& predicate) const { if (predicate.is_constant()) { - return predicate.get_value() ? -(*this) : *this; + auto result = field_t(predicate.get_value() ? -(*this) : *this); + result.set_origin_tag(OriginTag(get_origin_tag(), predicate.get_origin_tag())); + return result; } field_t predicate_field(predicate); field_t multiplicand = -(predicate_field + predicate_field); @@ -733,7 +753,9 @@ field_t field_t::conditional_assign(const bool_t& pre const field_t& rhs) { if (predicate.is_constant()) { - return predicate.get_value() ? lhs : rhs; + auto result = field_t(predicate.get_value() ? lhs : rhs); + result.set_origin_tag(OriginTag(predicate.get_origin_tag(), lhs.get_origin_tag(), rhs.get_origin_tag())); + return result; } // if lhs and rhs are the same witness, just return it! if (lhs.get_witness_index() == rhs.get_witness_index() && (lhs.additive_constant == rhs.additive_constant) && @@ -1051,6 +1073,11 @@ template field_t field_t::accumulate(const accumulator[3 * i + 1].get_value() - accumulator[3 * i + 2].get_value(); accumulating_total = witness_t(ctx, new_total); } + OriginTag new_tag{}; + for (const auto& single_input : input) { + new_tag = OriginTag(new_tag, single_input.tag); + } + total.tag = new_tag; return total.normalize(); } field_t total; @@ -1091,6 +1118,9 @@ std::array, 3> field_t::slice(const uint8_t msb, const ((hi_wit * field_t(uint256_t(1) << msb_plus_one)) + lo_wit + (slice_wit * field_t(uint256_t(1) << lsb)))); std::array result = { lo_wit, slice_wit, hi_wit }; + for (size_t i = 0; i < 3; i++) { + result[i].tag = tag; + } return result; } @@ -1131,6 +1161,7 @@ std::vector> field_t::decompose_into_bits( // Extract bit vector and show that it has the same value as `this`. for (size_t i = 0; i < num_bits; ++i) { bool_t bit = get_bit(context, num_bits - 1 - i, val_u256); + bit.set_origin_tag(tag); result[num_bits - 1 - i] = bit; bb::fr scaling_factor_value = fr(2).pow(static_cast(num_bits - 1 - i)); field_t scaling_factor(context, scaling_factor_value); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp index 2bbfe93989b..479c274ec57 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.hpp @@ -2,6 +2,7 @@ #include "../circuit_builders/circuit_builders_fwd.hpp" #include "../witness/witness.hpp" #include "barretenberg/common/assert.hpp" +#include "barretenberg/transcript/origin_tag.hpp" #include namespace bb::stdlib { @@ -70,6 +71,7 @@ template class field_t { , additive_constant(other.additive_constant) , multiplicative_constant(other.multiplicative_constant) , witness_index(other.witness_index) + , tag(other.tag) {} field_t(field_t&& other) noexcept @@ -77,6 +79,7 @@ template class field_t { , additive_constant(other.additive_constant) , multiplicative_constant(other.multiplicative_constant) , witness_index(other.witness_index) + , tag(other.tag) {} field_t(const bool_t& other); @@ -99,6 +102,7 @@ template class field_t { multiplicative_constant = other.multiplicative_constant; witness_index = other.witness_index; context = (other.context == nullptr ? nullptr : other.context); + tag = other.tag; return *this; } @@ -108,6 +112,7 @@ template class field_t { multiplicative_constant = other.multiplicative_constant; witness_index = other.witness_index; context = (other.context == nullptr ? nullptr : other.context); + tag = other.tag; return *this; } @@ -115,6 +120,7 @@ template class field_t { { auto result = field_t(witness_t(&context, other.get_value())); result.assert_equal(other, "field_t::copy_as_new_witness, assert_equal"); + result.tag = other.tag; return result; } @@ -187,6 +193,9 @@ template class field_t { return result; } + void set_origin_tag(const OriginTag& new_tag) const { tag = new_tag; } + OriginTag get_origin_tag() const { return tag; }; + field_t conditional_negate(const bool_t& predicate) const; void assert_equal(const field_t& rhs, std::string const& msg = "field_t::assert_equal") const; @@ -430,6 +439,8 @@ template class field_t { * TLDR: witness_index is a pseudo pointer to a circuit witness **/ mutable uint32_t witness_index = IS_CONSTANT; + + mutable OriginTag tag{}; }; template inline std::ostream& operator<<(std::ostream& os, field_t const& v) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp index e8aec8a4745..258b5403bad 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/field/field.test.cpp @@ -946,6 +946,140 @@ template class stdlib_field : public testing::Test { bool circuit_checks = composer.check_circuit(); EXPECT_TRUE(circuit_checks); } + static void test_origin_tag_consistency() + { + Builder builder = Builder(); + auto a = field_ct(witness_ct(&builder, bb::fr::random_element())); + auto b = field_ct(witness_ct(&builder, bb::fr::random_element())); + EXPECT_TRUE(a.get_origin_tag().is_empty()); + EXPECT_TRUE(b.get_origin_tag().is_empty()); + const size_t parent_id = 0; + + const auto submitted_value_origin_tag = OriginTag(parent_id, /*round_id=*/0, /*is_submitted=*/true); + const auto challenge_origin_tag = OriginTag(parent_id, /*round_id=*/0, /*is_submitted=*/false); + const auto next_challenge_tag = OriginTag(parent_id, /*round_id=*/1, /*submitted=*/false); + + const auto first_two_merged_tag = OriginTag(submitted_value_origin_tag, challenge_origin_tag); + const auto first_and_third_merged_tag = OriginTag(submitted_value_origin_tag, next_challenge_tag); + const auto all_merged_tag = OriginTag(first_two_merged_tag, next_challenge_tag); + + a.set_origin_tag(submitted_value_origin_tag); + b.set_origin_tag(challenge_origin_tag); + + EXPECT_EQ(a.get_origin_tag(), submitted_value_origin_tag); + EXPECT_EQ(b.get_origin_tag(), challenge_origin_tag); + + // Basic additon merges tags + auto c = a + b; + EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag); + + // Basic multiplication merges tags + auto d = a * b; + EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag); + + // Basic subtraction merges tags + auto e = a - b; + EXPECT_EQ(e.get_origin_tag(), first_two_merged_tag); + + // Division merges tags + + auto f = a / b; + EXPECT_EQ(f.get_origin_tag(), first_two_merged_tag); + + // Exponentiation merges tags + + auto exponent = field_ct(witness_ct(&builder, 10)); + exponent.set_origin_tag(challenge_origin_tag); + auto g = a.pow(exponent); + EXPECT_EQ(g.get_origin_tag(), first_two_merged_tag); + + // Madd merges tags + auto h = field_ct(witness_ct(&builder, bb::fr::random_element())); + h.set_origin_tag(next_challenge_tag); + auto i = a.madd(b, h); + EXPECT_EQ(i.get_origin_tag(), all_merged_tag); + + // add_two merges tags + auto j = a.add_two(b, h); + EXPECT_EQ(j.get_origin_tag(), all_merged_tag); + + // Normalize preserves tag + + EXPECT_EQ(j.normalize().get_origin_tag(), j.get_origin_tag()); + + // is_zero preserves tag + + EXPECT_EQ(a.is_zero().get_origin_tag(), a.get_origin_tag()); + + // equals/not equals operator merges tags + + EXPECT_EQ((a == b).get_origin_tag(), first_two_merged_tag); + EXPECT_EQ((a != b).get_origin_tag(), first_two_merged_tag); + + // Conditionals merge tags + + auto k = bool_ct(witness_ct(&builder, 1)); + k.set_origin_tag(next_challenge_tag); + auto l = a.conditional_negate(k); + EXPECT_EQ(l.get_origin_tag(), first_and_third_merged_tag); + + auto m = field_ct::conditional_assign(k, a, b); + EXPECT_EQ(m.get_origin_tag(), all_merged_tag); + + // Accumulate merges tags + const size_t MAX_ACCUMULATED_ELEMENTS = 16; + std::vector elements; + std::vector accumulated_tags; + for (size_t index = 0; index < MAX_ACCUMULATED_ELEMENTS; index++) { + const auto current_tag = OriginTag(parent_id, index >> 1, !(index & 1)); + if (index == 0) { + accumulated_tags.push_back(current_tag); + } else { + accumulated_tags.emplace_back(accumulated_tags[index - 1], current_tag); + } + auto element = field_ct(witness_ct(&builder, bb::fr::random_element())); + element.set_origin_tag(current_tag); + elements.emplace_back(element); + } + + for (size_t index = MAX_ACCUMULATED_ELEMENTS - 1; index > 0; index--) { + EXPECT_EQ(field_ct::accumulate(elements).get_origin_tag(), accumulated_tags[index]); + elements.pop_back(); + } + + // Slice preserves tags + auto n = a.slice(1, 0); + for (const auto& element : n) { + EXPECT_EQ(element.get_origin_tag(), submitted_value_origin_tag); + } + + // Decomposition preserves tags + + auto decomposed_bits = a.decompose_into_bits(256); + for (const auto& bit : decomposed_bits) { + EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag); + } + + // Conversions + + auto o = field_ct(witness_ct(&builder, 1)); + o.set_origin_tag(submitted_value_origin_tag); + auto p = bool_ct(o); + EXPECT_EQ(p.get_origin_tag(), submitted_value_origin_tag); + + o.set_origin_tag(challenge_origin_tag); + o = field_ct(p); + + EXPECT_EQ(o.get_origin_tag(), submitted_value_origin_tag); + + auto q = field_ct(witness_ct(&builder, fr::random_element())); + auto poisoned_tag = challenge_origin_tag; + poisoned_tag.poison(); + q.set_origin_tag(poisoned_tag); +#ifndef NDEBUG + EXPECT_THROW(q + q, std::runtime_error); +#endif + } }; using CircuitTypes = testing::Types; @@ -1077,3 +1211,8 @@ TYPED_TEST(stdlib_field, test_ranged_less_than) { TestFixture::test_ranged_less_than(); } + +TYPED_TEST(stdlib_field, test_origin_tag_consistency) +{ + TestFixture::test_origin_tag_consistency(); +} diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp new file mode 100644 index 00000000000..6ed00361c11 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp @@ -0,0 +1,42 @@ +#include "barretenberg/transcript/origin_tag.hpp" +#include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" + +namespace bb { +using namespace numeric; +#ifndef NDEBUG + +/** + * @brief Detect if two elements from the same transcript are performing a suspicious interaction + * + * @details For now this detects that 2 elements from 2 different round can't mingle without a challenge in between + * + * @param tag_a + * @param tag_b + */ +void check_child_tags(const uint256_t& tag_a, const uint256_t& tag_b) +{ + const uint128_t* challenges_a = (const uint128_t*)(&tag_a.data[2]); + const uint128_t* challenges_b = (const uint128_t*)(&tag_b.data[2]); + + const uint128_t* submitted_a = (const uint128_t*)(&tag_a.data[0]); + const uint128_t* submitted_b = (const uint128_t*)(&tag_b.data[0]); + + if (*challenges_a == 0 && *challenges_b == 0 && *submitted_a != 0 && *submitted_b != 0 && + *submitted_a != *submitted_b) { + throw_or_abort("Submitted values from 2 different rounds are mixing without challenges"); + } +} + +bool OriginTag::operator==(const OriginTag& other) const +{ + return this->parent_tag == other.parent_tag && this->child_tag == other.child_tag && + this->instant_death == other.instant_death; +} +#else +bool OriginTag::operator==(const OriginTag&) const +{ + return true; +} +#endif +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp new file mode 100644 index 00000000000..8cd008831ae --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp @@ -0,0 +1,149 @@ +#pragma once +/** + * @file origin_tag.hpp + * @author Rumata888 + * @brief This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit primitives + * through tainting (common term meaning adding information that allows to track value origins) them. It then allows us + * to detect dangerous behaviours in-circuit. The mechanism is only enabled in DEBUG builds + * + */ +#include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include +#include + +namespace bb { + +void check_child_tags(const uint256_t& tag_a, const uint256_t& tag_b); + +#ifndef NDEBUG +struct OriginTag { + static constexpr size_t CONSTANT = 0; + // Parent tag is supposed to represent the index of a unique trancript object that generated the value. It uses a + // concrete index, not bits for now, since we never expect two different indices to be used in the same computation + // apart from equality assertion + size_t parent_tag; + + // Child tag specifies which submitted values and challenges have been used to generate this element + // The lower 128 bits represent using a submitted value from a corresponding round (the shift represents the round) + // The higher 128 bits represent using a challenge value from an corresponding round (the shift represents the + // round) + numeric::uint256_t child_tag; + + // Instant death is used for poisoning values we should never use in arithmetic + bool instant_death = false; + // Default Origin Tag has everything set to zero and can't cause any issues + OriginTag() = default; + OriginTag(const OriginTag& other) = default; + OriginTag(OriginTag&& other) = default; + OriginTag& operator=(const OriginTag& other) = default; + OriginTag& operator=(OriginTag&& other) noexcept + { + + parent_tag = other.parent_tag; + child_tag = other.child_tag; + instant_death = other.instant_death; + return *this; + } + /** + * @brief Construct a new Origin Tag object + * + * @param parent_index The index of the transcript object + * @param child_index The round in which we generate/receive the value + * @param is_submitted If the value is submitted by the prover (not a challenge) + */ + OriginTag(size_t parent_index, size_t child_index, bool is_submitted = true) + : parent_tag(parent_index) + , child_tag((static_cast(1) << (child_index + (is_submitted ? 0 : 128)))) + { + ASSERT(child_index < 128); + } + + /** + * @brief Construct a new Origin Tag by merging two other Origin Tags + * + * @details The function checks for 3 things: 1) The no tag has instant death set, 2) That tags are from the same + * transcript (same parent tag) or are empty, 3) A complex check for the child tags. After that the child tags are + * merged and we create a new Origin Tag + * @param tag_a + * @param tag_b + */ + OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) + { + if (tag_a.instant_death || tag_b.instant_death) { + throw_or_abort("Touched an element that should not have been touched"); + } + if (tag_a.parent_tag != tag_b.parent_tag && (tag_a.parent_tag != 0U) && (tag_b.parent_tag != 0U)) { + throw_or_abort("Tags from different transcripts were involved in the same computation"); + } + check_child_tags(tag_a.child_tag, tag_b.child_tag); + parent_tag = tag_a.parent_tag; + child_tag = tag_a.child_tag | tag_b.child_tag; + } + + /** + * @brief Construct a new Origin Tag from merging several origin tags + * + * @details Basically performs the same actions as the constructor from 2 Origin Tags, but iteratively + * + * @tparam T + * @param tag + * @param rest + */ + template OriginTag(const OriginTag& tag, const T&... rest) + { + parent_tag = tag.parent_tag; + child_tag = tag.child_tag; + if (tag.instant_death) { + throw_or_abort("Touched an element that should not have been touched"); + } + for (const auto& next_tag : { rest... }) { + if (next_tag.instant_death) { + throw_or_abort("Touched an element that should not have been touched"); + } + if (parent_tag != next_tag.parent_tag && (parent_tag != 0U) && (next_tag.parent_tag != 0U)) { + throw_or_abort("Tags from different transcripts were involved in the same computation"); + } + check_child_tags(child_tag, next_tag.child_tag); + child_tag |= next_tag.child_tag; + } + } + ~OriginTag() = default; + bool operator==(const OriginTag& other) const; + void poison() { instant_death = true; } + void unpoison() { instant_death = false; } + bool is_poisoned() const { return instant_death; } + bool is_empty() const { return !instant_death && parent_tag == 0 && child_tag == uint256_t(0); }; +}; +inline std::ostream& operator<<(std::ostream& os, OriginTag const& v) +{ + return os << "{ p_t: " << v.parent_tag << ", ch_t" << v.child_tag << ", instadeath: " << v.instant_death << " }"; +} + +#else + +struct OriginTag { + OriginTag() = default; + OriginTag(const OriginTag& other) = default; + OriginTag(OriginTag&& other) = default; + OriginTag& operator=(const OriginTag& other) = default; + OriginTag& operator=(OriginTag&& other) = default; + + OriginTag(size_t, size_t, bool is_submitted [[maybe_unused]] = true) {} + + OriginTag(const OriginTag&, const OriginTag&) {} + template OriginTag(const OriginTag&, const T&...) {} + bool operator==(const OriginTag& other) const; + void poison() {} + void unpoison() {} + bool is_poisoned() const { return false; } + bool is_empty() const { return true; }; +}; +inline std::ostream& operator<<(std::ostream& os, OriginTag const&) +{ + return os << "{ Origin Tag tracking is disabled in release builds }"; +} +#endif +} // namespace bb +template +concept usesTag = requires(T x, const bb::OriginTag& tag) { x.set_origin_tag(tag); }; \ No newline at end of file