diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp index 63286b89f8f6..e9d60e3a82fe 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp @@ -92,7 +92,8 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) { // Elements with instant death should not be touched if (tag_a.instant_death || tag_b.instant_death) { - throw_or_abort("Touched an element that should not have been touched"); + throw_or_abort("Touched an element that should not have been touched. tag_a id: " + + std::to_string(tag_a.tag_id) + ", tag_b id: " + std::to_string(tag_b.tag_id)); } // If one of the tags is a constant, just use the other tag if (tag_a.transcript_index == CONSTANT) { @@ -107,7 +108,9 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) // A free witness element should not interact with an element that has an origin if (tag_a.is_free_witness()) { if (!tag_b.is_free_witness() && !tag_b.is_empty()) { - throw_or_abort("A free witness element should not interact with an element that has an origin"); + throw_or_abort( + "A free witness element (id: " + std::to_string(tag_a.tag_id) + + ") should not interact with an element that has an origin (id: " + std::to_string(tag_b.tag_id) + ")"); } else { // If both are free witnesses or one of them is empty, just use tag_a *this = tag_a; @@ -116,7 +119,9 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) } if (tag_b.is_free_witness()) { if (!tag_a.is_free_witness() && !tag_a.is_empty()) { - throw_or_abort("A free witness element should not interact with an element that has an origin"); + throw_or_abort( + "A free witness element (id: " + std::to_string(tag_b.tag_id) + + ") should not interact with an element that has an origin (id: " + std::to_string(tag_a.tag_id) + ")"); } else { // If both are free witnesses or one of them is empty, just use tag_b *this = tag_b; @@ -125,7 +130,10 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) } // Elements from different transcripts shouldn't interact if (tag_a.transcript_index != tag_b.transcript_index) { - throw_or_abort("Tags from different transcripts were involved in the same computation"); + throw_or_abort("Tags from different transcripts were involved in the same computation. tag_a: { id: " + + std::to_string(tag_a.tag_id) + ", transcript: " + std::to_string(tag_a.transcript_index) + + " } tag_b: { id: " + std::to_string(tag_b.tag_id) + + ", transcript: " + std::to_string(tag_b.transcript_index) + " }"); } // Check that submitted values from different rounds don't mix without challenges check_round_provenance(tag_a.round_provenance, tag_b.round_provenance); diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp index 614cb1e8c027..634dc8c3ce02 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp @@ -16,6 +16,7 @@ #include "barretenberg/common/assert.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" +#include #include #include #include @@ -68,6 +69,11 @@ void check_round_provenance(const uint256_t& provenance_a, const uint256_t& prov #ifndef AZTEC_NO_ORIGIN_TAGS struct OriginTag { + // Unique per-object ID for debugging: when a tag merge fails, the error message includes + // the IDs of both tags so you can trace which specific field elements were involved. + static inline std::atomic next_tag_id{ 0 }; + uint64_t tag_id = next_tag_id.fetch_add(1, std::memory_order_relaxed); + static constexpr size_t CONSTANT = static_cast(-1); static constexpr size_t FREE_WITNESS = static_cast(-2); // transcript_index represents the index of a unique transcript object that generated the value. It uses @@ -96,7 +102,7 @@ struct OriginTag { OriginTag& operator=(const OriginTag& other) = default; OriginTag& operator=(OriginTag&& other) noexcept { - + tag_id = other.tag_id; transcript_index = other.transcript_index; round_provenance = other.round_provenance; instant_death = other.instant_death; @@ -204,8 +210,8 @@ struct OriginTag { }; inline std::ostream& operator<<(std::ostream& os, OriginTag const& v) { - return os << "{ transcript_idx: " << v.transcript_index << ", round_prov: " << v.round_provenance - << ", instadeath: " << v.instant_death << " }"; + return os << "{ id: " << v.tag_id << ", transcript_idx: " << v.transcript_index + << ", round_prov: " << v.round_provenance << ", instadeath: " << v.instant_death << " }"; } #else diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp index f8d78911fb54..9b2a5df7952c 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.test.cpp @@ -242,4 +242,23 @@ TEST(OriginTag, RetaggingReflectsProtocolConstraints) EXPECT_NO_THROW(OriginTag(comm_times_challenge, eval_retagged)); } +// Test that unique per-object IDs are assigned +TEST(OriginTag, UniqueTagIds) +{ + auto tag_a = OriginTag(0, 0, true); + auto tag_b = OriginTag(0, 0, true); + + // Each constructed tag gets a unique ID + EXPECT_NE(tag_a.tag_id, tag_b.tag_id); + + // Copy preserves the ID + auto tag_a_copy = tag_a; + EXPECT_EQ(tag_a_copy.tag_id, tag_a.tag_id); + + // Merge produces a new ID + auto merged = OriginTag(tag_a, tag_b); + EXPECT_NE(merged.tag_id, tag_a.tag_id); + EXPECT_NE(merged.tag_id, tag_b.tag_id); +} + #endif // AZTEC_NO_ORIGIN_TAGS