Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
chonk_batch_verifier.test.cpp
Go to the documentation of this file.
1#ifndef __wasm__
6
7#include <algorithm>
8#include <atomic>
9#include <condition_variable>
10#include <mutex>
11#include <numeric>
12#include <random>
13#include <set>
14
15using namespace bb;
16
17static constexpr size_t SMALL_LOG_2_NUM_GATES = 5;
18
19class ChonkBatchVerifierTests : public ::testing::Test {
20 protected:
22
23 using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer;
24
26 size_t num_app_circuits = 1)
27 {
28 CircuitProducer circuit_producer(num_app_circuits);
29 const size_t num_circuits = circuit_producer.total_num_circuits;
30 Chonk ivc{ num_circuits };
31 TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES };
32 for (size_t j = 0; j < num_circuits; ++j) {
33 circuit_producer.construct_and_accumulate_next_circuit(ivc, settings);
34 }
35 return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() };
36 }
37
42 std::mutex mutex;
45 size_t expected = 0;
46
48 {
49 std::lock_guard lock(mutex);
50 results.push_back(std::move(r));
51 cv.notify_one();
52 }
53
54 void wait_for(size_t count, std::chrono::seconds timeout = std::chrono::seconds(120))
55 {
56 expected = count;
57 std::unique_lock lock(mutex);
58 ASSERT_TRUE(cv.wait_for(lock, timeout, [&] { return results.size() >= expected; }))
59 << "Timed out waiting for " << expected << " results, got " << results.size();
60 }
61 };
62};
63
64TEST_F(ChonkBatchVerifierTests, BatchOfTwoValidProofs)
65{
66 auto [proof1, vk1] = generate_chonk_proof();
67 auto [proof2, vk2] = generate_chonk_proof();
68
69 ResultCollector collector;
70 ChonkBatchVerifier verifier;
71
72 // Both proofs use VK index 0 (same VK for simplicity)
73 verifier.start(
74 { vk1 }, /*num_cores=*/2, /*batch_size=*/2, [&](VerifyResult r) { collector.on_result(std::move(r)); });
75
76 verifier.enqueue(VerifyRequest{ .request_id = 1, .vk_index = 0, .proof = std::move(proof1) });
77 verifier.enqueue(VerifyRequest{ .request_id = 2, .vk_index = 0, .proof = std::move(proof2) });
78
79 collector.wait_for(2);
80 verifier.stop();
81
82 ASSERT_EQ(collector.results.size(), 2);
83 for (auto& r : collector.results) {
84 EXPECT_TRUE(r.verified()) << "request_id=" << r.request_id << " error=" << r.error_message;
85 EXPECT_GT(r.time_in_verify_ms, 0);
86 }
87}
88
90{
91 // Enqueue 1 proof with batch_size=4, then stop. The proof should be flushed.
92 auto [proof, vk] = generate_chonk_proof();
93
94 ResultCollector collector;
95 ChonkBatchVerifier verifier;
96
97 verifier.start(
98 { vk }, /*num_cores=*/1, /*batch_size=*/4, [&](VerifyResult r) { collector.on_result(std::move(r)); });
99 verifier.enqueue(VerifyRequest{ .request_id = 42, .vk_index = 0, .proof = std::move(proof) });
100
101 // Stop triggers flush of remaining items
102 verifier.stop();
103
104 ASSERT_EQ(collector.results.size(), 1);
105 EXPECT_TRUE(collector.results[0].verified());
106 EXPECT_EQ(collector.results[0].request_id, 42);
107}
108
109TEST_F(ChonkBatchVerifierTests, TamperedProofBisected)
110{
112
113 auto [good_proof, vk1] = generate_chonk_proof();
114 auto [bad_proof, vk2] = generate_chonk_proof();
115
116 // Corrupt the IPA proof portion
117 ASSERT_FALSE(bad_proof.ipa_proof.empty());
118 bad_proof.ipa_proof[0] = bad_proof.ipa_proof[0] + bb::fr(1);
119
120 ResultCollector collector;
121 ChonkBatchVerifier verifier;
122
123 verifier.start(
124 { vk1 }, /*num_cores=*/2, /*batch_size=*/2, [&](VerifyResult r) { collector.on_result(std::move(r)); });
125
126 verifier.enqueue(VerifyRequest{ .request_id = 1, .vk_index = 0, .proof = std::move(good_proof) });
127 verifier.enqueue(VerifyRequest{ .request_id = 2, .vk_index = 0, .proof = std::move(bad_proof) });
128
129 collector.wait_for(2);
130 verifier.stop();
131
132 ASSERT_EQ(collector.results.size(), 2);
133
134 // Find good and bad results by request_id
135 const VerifyResult* good = nullptr;
136 const VerifyResult* bad = nullptr;
137 for (auto& r : collector.results) {
138 if (r.request_id == 1) {
139 good = &r;
140 }
141 if (r.request_id == 2) {
142 bad = &r;
143 }
144 }
145
146 ASSERT_NE(good, nullptr);
147 ASSERT_NE(bad, nullptr);
148 EXPECT_TRUE(good->verified()) << "good proof should verify, error=" << good->error_message;
149 EXPECT_FALSE(bad->verified()) << "bad proof should fail";
150 EXPECT_GT(bad->batch_failure_count, 0u) << "bisection should have occurred";
151}
152
159TEST_F(ChonkBatchVerifierTests, RandomMixedBatches)
160{
162 auto [good_proof_template, vk] = generate_chonk_proof();
163
164 // { seed, total, num_bad, batch_size, num_cores }
165 struct TestCase {
166 uint32_t seed;
167 size_t total;
168 size_t num_bad;
169 uint32_t batch_size;
170 uint32_t num_cores;
171 };
172 const std::vector<TestCase> cases = {
173 { 42, 16, 0, 16, 4 }, // all valid
174 { 100, 8, 8, 8, 4 }, // all invalid
175 { 8080, 30, 1, 30, 4 }, // needle in haystack
176 { 2025, 30, 10, 30, 4 }, // ~1/3 bad
177 { 6174, 30, 15, 30, 4 }, // half bad
178 { 9999, 30, 29, 30, 4 }, // inverted needle
179 { 1337, 30, 7, 8, 4 }, // failures across multiple batches
180 { 314, 12, 3, 12, 1 }, // single core
181 { 555, 8, 3, 1, 4 }, // degenerate batch_size=1
182 };
183
184 for (const auto& [seed, total, num_bad, batch_size, num_cores] : cases) {
185 SCOPED_TRACE("seed=" + std::to_string(seed) + " total=" + std::to_string(total) +
186 " num_bad=" + std::to_string(num_bad) + " batch_size=" + std::to_string(batch_size));
187
188 // Pick bad indices via seeded Fisher-Yates shuffle
189 std::vector<size_t> indices(total);
190 std::iota(indices.begin(), indices.end(), 0);
191 std::mt19937 rng(seed);
192 for (size_t i = total - 1; i > 0; --i) {
194 std::swap(indices[i], indices[dist(rng)]);
195 }
196 std::set<size_t> bad_indices(indices.begin(), indices.begin() + static_cast<ptrdiff_t>(num_bad));
197
198 // Build proofs, corrupting IPA for bad ones
200 proofs.reserve(total);
201 for (size_t i = 0; i < total; ++i) {
202 proofs.push_back(good_proof_template);
203 if (bad_indices.count(i)) {
204 proofs.back().ipa_proof[0] = proofs.back().ipa_proof[0] + bb::fr(1);
205 }
206 }
207
208 ResultCollector collector;
209 ChonkBatchVerifier verifier;
210 verifier.start({ vk }, num_cores, batch_size, [&](VerifyResult r) { collector.on_result(std::move(r)); });
211
212 for (size_t i = 0; i < total; ++i) {
213 verifier.enqueue(
214 VerifyRequest{ .request_id = static_cast<uint64_t>(i), .vk_index = 0, .proof = std::move(proofs[i]) });
215 }
216
217 collector.wait_for(total, std::chrono::seconds(300));
218 verifier.stop();
219
220 ASSERT_EQ(collector.results.size(), total);
221 std::sort(collector.results.begin(), collector.results.end(), [](auto& a, auto& b) {
222 return a.request_id < b.request_id;
223 });
224 for (size_t i = 0; i < total; ++i) {
225 EXPECT_EQ(collector.results[i].request_id, i);
226 if (bad_indices.count(i)) {
227 EXPECT_FALSE(collector.results[i].verified()) << "proof " << i << " should fail";
228 } else {
229 EXPECT_TRUE(collector.results[i].verified()) << "proof " << i << " should pass";
230 }
231 }
232 }
233}
234
236{
237 auto [proof, vk] = generate_chonk_proof();
238
239 ResultCollector collector;
240 ChonkBatchVerifier verifier;
241
242 verifier.start(
243 { vk }, /*num_cores=*/1, /*batch_size=*/1, [&](VerifyResult r) { collector.on_result(std::move(r)); });
244
245 // vk_index=99 is out of range
246 verifier.enqueue(VerifyRequest{ .request_id = 7, .vk_index = 99, .proof = std::move(proof) });
247
248 collector.wait_for(1);
249 verifier.stop();
250
251 ASSERT_EQ(collector.results.size(), 1);
252 EXPECT_FALSE(collector.results[0].verified());
253 EXPECT_EQ(collector.results[0].request_id, 7);
254 EXPECT_NE(collector.results[0].error_message.find("invalid vk_index"), std::string::npos);
255}
256
257#endif // __wasm__
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
TEST_F(ChonkBatchVerifierTests, BatchOfTwoValidProofs)
PrivateFunctionExecutionMockCircuitProducer CircuitProducer
static std::pair< ChonkProof, std::shared_ptr< MegaZKFlavor::VKAndHash > > generate_chonk_proof(size_t num_app_circuits=1)
Asynchronous batch verifier for Chonk IVC proofs.
void enqueue(VerifyRequest request)
Enqueue a proof for verification.
void stop()
Stop the processor, flushing remaining proofs.
void start(std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks, uint32_t num_cores, uint32_t batch_size, ResultCallback on_result)
Start the coordinator thread.
The IVC scheme used by the aztec client for private function execution.
Definition chonk.hpp:39
FF a
FF b
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
::testing::Types< BN254Settings, GrumpkinSettings > TestSettings
field< Bn254FrParams > fr
Definition fr.hpp:155
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
Helper: collect results from the processor via callback.
void wait_for(size_t count, std::chrono::seconds timeout=std::chrono::seconds(120))
A request to verify a single Chonk proof.
Result of verifying a single proof within a batch.