Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
origin_tag.test.cpp
Go to the documentation of this file.
1
14#include <gtest/gtest.h>
15
16using namespace bb;
17
18#ifndef AZTEC_NO_ORIGIN_TAGS
19
20// Test that combining values from different rounds without proper challenge coverage throws
21TEST(OriginTag, RejectsCrossRoundMixingWithoutChallenge)
22{
23 const size_t transcript_id = 0;
24
25 // Round 0: prover submits c0, verifier responds with challenge α
26 // Round 1: prover submits c1
27 auto c0_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/true);
28 auto alpha_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/false);
29 auto c1_tag = OriginTag(transcript_id, /*round=*/1, /*is_submitted=*/true);
30
31 // OK: c0 * alpha - round 0 data bound by round 0 challenge
32 OriginTag c0_times_alpha;
33 EXPECT_NO_THROW(c0_times_alpha = OriginTag(c0_tag, alpha_tag));
34
35 // REJECT: (c0 * alpha) + c1
36 // c1 (round 1 submitted) has no challenge binding it
37 // max_submitted_round (1) > max_challenge_round (0)
38 EXPECT_THROW(OriginTag(c0_times_alpha, c1_tag), std::runtime_error);
39
40 // REJECT: c0 + c1 directly (different rounds, no challenges)
41 EXPECT_THROW(OriginTag(c0_tag, c1_tag), std::runtime_error);
42}
43
44// Test that stale challenges don't provide coverage for later rounds
45TEST(OriginTag, RejectsStaleChallengeCoverage)
46{
47 const size_t transcript_id = 0;
48
49 // Timeline:
50 // Round 0: verifier generates alpha
51 // Round 1: prover submits c1 (after seeing alpha)
52 // Round 2: prover submits c2 (after seeing alpha)
53 //
54 // Neither c1 nor c2 is bound by alpha since they were chosen after seeing it
55
56 auto alpha_tag = OriginTag(transcript_id, /*round_number=*/0, /*is_submitted=*/false);
57 auto c1_tag = OriginTag(transcript_id, /*round_number=*/1, /*is_submitted=*/true);
58 auto c2_tag = OriginTag(transcript_id, /*round=*/2, /*is_submitted=*/true);
59
60 // OK: c1 * alpha - alpha has no submitted data, so the cross-round check doesn't apply.
61 // The check only triggers when mixing PROVER VALUES from different rounds.
62 OriginTag c1_times_alpha;
63 EXPECT_NO_THROW(c1_times_alpha = OriginTag(c1_tag, alpha_tag));
64
65 // REJECT: (c1 * alpha) + c2
66 // Now we're mixing prover values from rounds 1 and 2.
67 // max_submitted = 2, max_challenge = 0 -> insufficient coverage
68 EXPECT_THROW(OriginTag(c1_times_alpha, c2_tag), std::runtime_error);
69}
70
71// Test that proper challenge coverage allows cross-round combinations
72TEST(OriginTag, AllowsProperlyBoundCrossRoundCombinations)
73{
74 const size_t transcript_id = 0;
75
76 auto c0_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/true);
77 auto alpha_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/false);
78 auto c1_tag = OriginTag(transcript_id, /*round=*/1, /*is_submitted=*/true);
79 auto beta_tag = OriginTag(transcript_id, /*round=*/1, /*is_submitted=*/false);
80
81 // OK: c0 * alpha + c1 * beta - all data properly bound by challenges
82 auto c0_times_alpha = OriginTag(c0_tag, alpha_tag);
83 auto c1_times_beta = OriginTag(c1_tag, beta_tag);
84
85 OriginTag result;
86 EXPECT_NO_THROW(result = OriginTag(c0_times_alpha, c1_times_beta));
87
88 // OK: c0 + c1 * beta - β (round 1 challenge) covers both c0 and c1
89 // because β is derived from a hash chain that includes c0
90 EXPECT_NO_THROW(result = OriginTag(c0_tag, c1_times_beta));
91}
92
93// Test that same-round combinations are allowed before any challenge
94TEST(OriginTag, AllowsSameRoundCombinationsWithoutChallenge)
95{
96 const size_t transcript_id = 0;
97
98 auto a_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/true);
99 auto b_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/true);
100
101 // OK: combining values from the same round doesn't require a challenge
102 OriginTag result;
103 EXPECT_NO_THROW(result = OriginTag(a_tag, b_tag));
104}
105
106// Test constant tags don't affect provenance
107TEST(OriginTag, ConstantTagsAreNeutral)
108{
109 const size_t transcript_id = 0;
110
111 auto submitted_tag = OriginTag(transcript_id, /*round=*/0, /*is_submitted=*/true);
112 auto constant_tag = OriginTag::constant();
113
114 // Combining with constant preserves the non-constant tag
115 OriginTag result;
116 EXPECT_NO_THROW(result = OriginTag(submitted_tag, constant_tag));
117 EXPECT_EQ(result, submitted_tag);
118
119 EXPECT_NO_THROW(result = OriginTag(constant_tag, submitted_tag));
120 EXPECT_EQ(result, submitted_tag);
121}
122
123// Test instant death tags throw on any operation
124TEST(OriginTag, InstantDeathTagsThrow)
125{
126 auto normal_tag = OriginTag::constant();
127 auto death_tag = OriginTag::poisoned();
128
129 EXPECT_THROW(OriginTag(normal_tag, death_tag), std::runtime_error);
130 EXPECT_THROW(OriginTag(death_tag, normal_tag), std::runtime_error);
131 EXPECT_THROW(OriginTag(death_tag, death_tag), std::runtime_error);
132}
133
134// Test different transcript indices cannot be combined
135TEST(OriginTag, RejectsCrossTranscriptCombinations)
136{
137 auto tag_transcript_0 = OriginTag(/*transcript_id=*/0, /*round=*/0, /*is_submitted=*/true);
138 auto tag_transcript_1 = OriginTag(/*transcript_id=*/1, /*round=*/0, /*is_submitted=*/true);
139
140 EXPECT_THROW(OriginTag(tag_transcript_0, tag_transcript_1), std::runtime_error);
141}
142
143// Integration test using actual transcript and circuit types
144TEST(OriginTag, TranscriptIntegration)
145{
149
151
152 // Create a transcript (simulating recursive verifier)
153 auto transcript = std::make_shared<Transcript>();
154
155 // Simulate a proof being loaded
156 std::vector<FF> fake_proof;
157 for (size_t i = 0; i < 10; i++) {
158 fake_proof.push_back(FF::from_witness(&builder, fr::random_element()));
159 }
160 transcript->load_proof(fake_proof);
161
162 // Round 0: Verifier receives c0 from proof
163 auto c0 = transcript->template receive_from_prover<FF>("c0");
164
165 // Round 0: Verifier gets challenge alpha
166 auto alpha = transcript->template get_challenge<FF>("alpha");
167
168 // Round 1: Verifier receives c1 from proof
169 auto c1 = transcript->template receive_from_prover<FF>("c1");
170
171 // OK: c0 * alpha (round 0 data bound by round 0 challenge)
172 FF c0_times_alpha = c0 * alpha;
173
174 // REJECT: (c0 * alpha) + c1 - c1 has no challenge binding it
175 EXPECT_THROW([[maybe_unused]] auto bad = c0_times_alpha + c1, std::runtime_error);
176
177 // REJECT: c0 + c1 directly (different rounds, no challenges)
178 EXPECT_THROW([[maybe_unused]] auto bad = c0 + c1, std::runtime_error);
179
180 // Round 1: Get challenge beta
181 auto beta = transcript->template get_challenge<FF>("beta");
182
183 // OK: c0 * alpha + c1 * beta - properly bound
184 FF c1_times_beta = c1 * beta;
185 EXPECT_NO_THROW([[maybe_unused]] auto good = c0_times_alpha + c1_times_beta);
186}
187
188// Test that free witness tags cannot interact with transcript-tagged values
189TEST(OriginTag, RejectsFreeWitnessInteraction)
190{
191 const size_t transcript_id = 0;
192
193 auto transcript_tag = OriginTag(transcript_id, /*round_number=*/0, /*is_submitted=*/true);
194 auto free_witness_tag = OriginTag::free_witness();
195
196 // REJECT: free witness interacting with transcript-tagged value
197 // Free witnesses are untracked values that shouldn't mix with protocol values
198 EXPECT_THROW(OriginTag(transcript_tag, free_witness_tag), std::runtime_error);
199 EXPECT_THROW(OriginTag(free_witness_tag, transcript_tag), std::runtime_error);
200
201 // OK: free witnesses can combine with each other
202 auto another_free_witness = OriginTag::free_witness();
203 EXPECT_NO_THROW(OriginTag(free_witness_tag, another_free_witness));
204
205 // OK: free witnesses can combine with constants
206 auto constant_tag = OriginTag::constant();
207 EXPECT_NO_THROW(OriginTag(free_witness_tag, constant_tag));
208}
209
210// Test demonstrating use of origin tags to "override" common false positive pattern in provenance checking.
211TEST(OriginTag, RetaggingReflectsProtocolConstraints)
212{
213 const size_t transcript_id = 0;
214
215 // Scenario: In a PCS protocol:
216 // Round 0: Prover sends commitment C
217 // Round 1: Verifier derives evaluation challenge z
218 // Round 2: Prover sends evaluation v = f(z) and opening proof
219 // Round 3: Verifier derives batching challenge for final check
220 //
221 // Naively, a combination like C * z + v would be rejected since the round provenance of z is less than the highest
222 // summitted round (1). However, this is a false positive since v is actually bound to C via the PCS opening. The
223 // pattern for signaling this to the tooling is to re-tag v with the challenge z that binds it after PCS
224 // verification.
225
226 auto commitment_tag = OriginTag(transcript_id, /*round_number=*/0, /*is_submitted=*/true);
227 auto eval_challenge_tag = OriginTag(transcript_id, /*round_number=*/0, /*is_submitted=*/false);
228 auto eval_tag = OriginTag(transcript_id, /*round_number=*/1, /*is_submitted=*/true);
229
230 // False positive: Mixing commitment (round 0) with evaluation (round 1) using only round 0 challenge
231 // The evaluation appears unbound from the verifier's perspective
232 auto comm_times_challenge = OriginTag(commitment_tag, eval_challenge_tag);
233 EXPECT_THROW(OriginTag(comm_times_challenge, eval_tag), std::runtime_error);
234
235 // In reality, the evaluation is constrained by the PCS opening.
236 // Re-tag the evaluation with the challenge that binds it (the one derived after
237 // the commitment was fixed but before the evaluation was sent).
238 // This reflects the protocol-level constraint: v must equal f(z) for committed f.
239 const auto& eval_retagged = eval_challenge_tag;
240
241 // OK: Now the "evaluation" is tagged as bound by the eval challenge
242 EXPECT_NO_THROW(OriginTag(comm_times_challenge, eval_retagged));
243}
244
245// Test that unique per-object IDs are assigned
246TEST(OriginTag, UniqueTagIds)
247{
248 auto tag_a = OriginTag(0, 0, true);
249 auto tag_b = OriginTag(0, 0, true);
250
251 // Each constructed tag gets a unique ID
252 EXPECT_NE(tag_a.tag_id, tag_b.tag_id);
253
254 // Copy preserves the ID
255 auto tag_a_copy = tag_a;
256 EXPECT_EQ(tag_a_copy.tag_id, tag_a.tag_id);
257
258 // Merge produces a new ID
259 auto merged = OriginTag(tag_a, tag_b);
260 EXPECT_NE(merged.tag_id, tag_a.tag_id);
261 EXPECT_NE(merged.tag_id, tag_b.tag_id);
262}
263
264#endif // AZTEC_NO_ORIGIN_TAGS
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
AluTraceBuilder builder
Definition alu.test.cpp:124
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
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...
static OriginTag constant()
static OriginTag poisoned()
static OriginTag free_witness()
static field random_element(numeric::RNG *engine=nullptr) noexcept