Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
mock_circuit_producer.hpp
Go to the documentation of this file.
1#pragma once
2
8
9using namespace bb;
10
11namespace {
12
19class MockDatabusProducer {
20 private:
21 using ClientCircuit = Chonk::ClientCircuit;
22 using Flavor = MegaFlavor;
23 using FF = Flavor::FF;
24 using BusDataArray = std::vector<FF>;
25
26 static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus inputs
28 BusDataArray kernel_return_data;
29
30 uint64_t next_bus_value = 1; // use simple deterministic values for easier test debugging
31
32 BusDataArray generate_mock_bus_array()
33 {
34 BusDataArray result;
35 for (size_t i = 0; i < BUS_ARRAY_SIZE; ++i) {
36 result.emplace_back(FF(next_bus_value++));
37 }
38 return result;
39 }
40
41 static BusDataArray generate_default_commitment_bus_array(const BusId bus_idx)
42 {
43 // All-zero entries preserve the default commitment while giving the mock lookup relation a non-empty,
44 // column-specific table shape.
45 return BusDataArray(static_cast<size_t>(bus_idx) + 1, FF(0));
46 }
47
48 static void append_calldata(ClientCircuit& circuit, const BusId bus_idx, const BusDataArray& data)
49 {
50 for (const auto& val : data) {
51 circuit.add_public_calldata(bus_idx, circuit.add_variable(val));
52 }
53 }
54
55 static void append_return_data(ClientCircuit& circuit, const BusDataArray& data)
56 {
57 for (const auto& val : data) {
58 circuit.add_public_return_data(circuit.add_variable(val));
59 }
60 }
61
62 static void exercise_calldata_lookup(ClientCircuit& circuit, const BusId bus_idx, const size_t bus_size)
63 {
64 BB_ASSERT_GT(bus_size, 0UL);
65 const size_t read_idx = static_cast<size_t>(bus_idx) % bus_size;
66 circuit.read_calldata(bus_idx, circuit.add_variable(FF(read_idx)));
67 }
68
69 static void exercise_return_data_lookup(ClientCircuit& circuit, const size_t bus_size)
70 {
71 BB_ASSERT_GT(bus_size, 0UL);
72 const size_t read_idx = static_cast<size_t>(BusId::RETURNDATA) % bus_size;
73 circuit.read_return_data(circuit.add_variable(FF(read_idx)));
74 }
75
76 public:
80 void populate_app_databus(ClientCircuit& circuit)
81 {
82 for (auto& app_data : app_return_data) {
83 if (app_data.empty()) {
84 app_data = generate_mock_bus_array();
85 append_return_data(circuit, app_data);
86 exercise_return_data_lookup(circuit, app_data.size());
87 return;
88 }
89 }
90 };
91
96 void populate_kernel_databus(ClientCircuit& circuit)
97 {
98 // Populate kernel calldata from previous kernel return data (if it exists)
99 const BusDataArray& kernel_calldata = kernel_return_data.empty()
100 ? generate_default_commitment_bus_array(BusId::KERNEL_CALLDATA)
101 : kernel_return_data;
102 append_calldata(circuit, BusId::KERNEL_CALLDATA, kernel_calldata);
103 exercise_calldata_lookup(circuit, BusId::KERNEL_CALLDATA, kernel_calldata.size());
104
105 // Populate app calldata from app return data (if it exists), then clear the app return data
106 for (size_t idx = 0; idx < app_return_data.size(); ++idx) {
107 const auto bus_idx = static_cast<BusId>(idx + static_cast<size_t>(BusId::APP_CALLDATA));
108 const BusDataArray& app_calldata =
109 app_return_data[idx].empty() ? generate_default_commitment_bus_array(bus_idx) : app_return_data[idx];
110 append_calldata(circuit, bus_idx, app_calldata);
111 exercise_calldata_lookup(circuit, bus_idx, app_calldata.size());
112 app_return_data[idx].clear();
113 }
114
115 // Mock the return data for the present kernel circuit
116 kernel_return_data = generate_mock_bus_array();
117 append_return_data(circuit, kernel_return_data);
118 exercise_return_data_lookup(circuit, kernel_return_data.size());
119 };
120};
121
126struct TestSettings {
127 // number of public inputs to manually add to circuits, by default this would be 0 because we use the
128 // MockDatabusProducer to test public inputs handling
129 size_t num_public_inputs = 0;
130 // by default we will create more complex apps and kernel with various types of gates but in case we want to
131 // specifically test overflow behaviour or unstructured circuits we can manually construct simple circuits with a
132 // specified number of gates
133 size_t log2_num_gates = 0;
134};
135
144class PrivateFunctionExecutionMockCircuitProducer {
145 using ClientCircuit = Chonk::ClientCircuit;
146 using Flavor = MegaFlavor;
148
149 size_t circuit_counter = 0;
150 std::vector<bool> is_kernel_flags;
151
152 MockDatabusProducer mock_databus;
153 bool large_first_app = true;
154 constexpr static size_t NUM_TRAILING_KERNELS = bb::NUM_TRAILING_KERNELS; // reset-tail, hiding
155
156 public:
157 size_t total_num_circuits = 0;
158
162 std::vector<Chonk::CircuitKind> circuit_kinds() const
163 {
165 kinds.reserve(total_num_circuits);
166 for (size_t idx = 0; idx < total_num_circuits; ++idx) {
167 if (!is_kernel_flags[idx]) {
168 kinds.push_back(Chonk::CircuitKind::App);
169 } else {
170 kinds.push_back(idx + 1 == total_num_circuits ? Chonk::CircuitKind::HidingKernel
172 }
173 }
174 return kinds;
175 }
176
177 PrivateFunctionExecutionMockCircuitProducer(size_t num_app_circuits, bool large_first_app = true)
178 : large_first_app(large_first_app)
179 {
180 for (size_t i = 0; i < num_app_circuits / MAX_APPS_PER_KERNEL; ++i) {
181 for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) {
182 is_kernel_flags.emplace_back(false);
183 }
184 is_kernel_flags.emplace_back(true);
185 }
186 if (num_app_circuits % MAX_APPS_PER_KERNEL != 0) {
187 for (size_t idx = 0; idx < num_app_circuits % MAX_APPS_PER_KERNEL; ++idx) {
188 is_kernel_flags.emplace_back(false);
189 }
190 is_kernel_flags.emplace_back(true);
191 }
192 for (size_t i = 0; i < NUM_TRAILING_KERNELS; ++i) {
193 is_kernel_flags.emplace_back(true);
194 }
195 total_num_circuits = is_kernel_flags.size();
196 }
197
198 PrivateFunctionExecutionMockCircuitProducer(std::vector<bool> leading_is_kernel_flags, bool large_first_app = false)
199 : is_kernel_flags(std::move(leading_is_kernel_flags))
200 , large_first_app(large_first_app)
201 {
202 BB_ASSERT(!is_kernel_flags.empty(), "Mock circuit layout must contain at least one leading circuit");
203 BB_ASSERT_EQ(is_kernel_flags[0], false, "Mock circuit layout must start with an app circuit");
204 for (size_t i = 0; i < NUM_TRAILING_KERNELS; ++i) {
205 is_kernel_flags.emplace_back(true);
206 }
207 total_num_circuits = is_kernel_flags.size();
208 }
209
216 ClientCircuit create_next_circuit(Chonk& ivc,
217 size_t log2_num_gates = 0,
218 size_t num_public_inputs = 0,
219 bool check_circuit_sizes = false)
220 {
221 const bool is_kernel = is_kernel_flags[circuit_counter++];
222 const bool use_large_circuit = large_first_app && (circuit_counter == 1); // first circuit is size 2^19
223 // Check if this is one of the trailing kernels (reset, tail, hiding)
224 const bool is_trailing_kernel =
225 (ivc.get_num_circuits_accumulated() >= ivc.get_num_circuits() - NUM_TRAILING_KERNELS);
226
227 ClientCircuit circuit{ ivc.goblin.op_queue };
228 // if the number of gates is specified we just add a number of arithmetic gates
229 if (log2_num_gates != 0) {
230 MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates, /* include_public_inputs= */ false);
231 // Add some public inputs
232 for (size_t i = 0; i < num_public_inputs; ++i) {
233 circuit.add_public_variable(typename Flavor::FF(13634816 + i)); // arbitrary number
234 }
235 } else {
236 // If the number of gates is not specified we create a structured mock circuit
237 if (is_kernel) {
238 // For trailing kernels (reset, tail, hiding), skip the expensive mock kernel logic to match real Noir
239 // flows. These kernels are simpler and mainly contain the completion logic added by Chonk.
240 if (!is_trailing_kernel) {
241 GoblinMockCircuits::construct_mock_folding_kernel(circuit); // construct mock base logic
242 }
243 } else {
244 GoblinMockCircuits::construct_mock_app_circuit(circuit, use_large_circuit); // construct mock app
245 }
246 }
247
248 if (is_kernel) {
249 mock_databus.populate_kernel_databus(circuit); // populate databus inputs/outputs
250 } else {
251 mock_databus.populate_app_databus(circuit); // populate databus outputs
252 }
253
254 if (is_kernel) {
256 } else {
258 }
259
260 if (check_circuit_sizes) {
261 // Size the circuit under its actual proving flavor — apps are MegaAppFlavor, kernels
262 // are MegaKernelFlavor (trailing kernels included: the hiding-kernel-sized
263 // MegaZKFlavor only differs in TRACE_OFFSET, not in dyadic size for these mocks).
264 const size_t log2_dyadic_size = is_kernel ? ProverInstance_<Chonk::KernelFlavor>(circuit).log_dyadic_size()
266 if (log2_num_gates != 0) {
267 if (is_kernel) {
268 // There are various possibilities here, so we provide a bound
270 log2_dyadic_size,
271 19UL,
272 "Log number of gates in a kernel with fixed number of arithmetic gates has exceeded bound.");
273 vinfo("Log number of gates in a kernel with fixed number of arithmetic gates is: ",
274 log2_dyadic_size);
275 } else {
276 // The offset is due to the fact that finalization adds a certain number of gates
277 size_t LOG2_OFFSET = 2;
278 BB_ASSERT_LTE(log2_dyadic_size,
279 log2_num_gates + LOG2_OFFSET,
280 "Log number of arithemtic gates produced is different from the one requested.");
281 }
282 } else {
283 if (is_kernel) {
284 // Trailing kernels (reset, tail, hiding) are simpler than regular kernels
285 if (is_trailing_kernel) {
286 // Trailing kernels should be significantly smaller, with hiding kernel < 2^16
287 BB_ASSERT_LTE(log2_dyadic_size,
288 17UL,
289 "Trailing kernel circuit size has exceeded expected bound (should be <= 2^16).");
290 vinfo("Log number of gates in a trailing kernel circuit is: ", log2_dyadic_size);
291 } else {
292 const bool is_init_kernel = circuit_counter == 2;
293 const size_t expected_log2_dyadic_size = is_init_kernel ? 17UL : 18UL;
294 BB_ASSERT_EQ(log2_dyadic_size,
295 expected_log2_dyadic_size,
296 "There has been a change in the number of gates of a mock kernel circuit.");
297 }
298 } else {
299 BB_ASSERT_EQ(log2_dyadic_size,
300 use_large_circuit ? 19UL : 17UL,
301 "There has been a change in the of gates generated for a mock app circuit.");
302 }
303 }
304 }
305 return circuit;
306 }
307
308 void construct_and_accumulate_next_circuit(Chonk& ivc, TestSettings settings = {}, bool check_circuit_sizes = false)
309 {
310 // If this is a mock hiding kernel, remove the settings and use a default (non-structured) trace
311 const bool is_hiding_kernel = ivc.get_num_circuits_accumulated() == ivc.get_num_circuits() - 1;
312 if (is_hiding_kernel) {
313 settings = TestSettings{};
314 }
315 auto circuit =
316 create_next_circuit(ivc, settings.log2_num_gates, settings.num_public_inputs, check_circuit_sizes);
317 ivc.accumulate(circuit, make_circuit_verification_key(ivc.current_kind(), circuit));
318 }
319
320 public:
321 // Build the per-kind verification key for the current circuit. Uses `dispatch_kind` to map
322 // the runtime `CircuitKind` to the matching flavor and derive its precomputed VK.
323 static Chonk::CircuitVerificationKey make_circuit_verification_key(Chonk::CircuitKind kind,
324 ClientCircuit& builder_in)
325 {
327 using FlavorT = flavor_for<K>;
328 using VK = typename FlavorT::VerificationKey;
331 auto prover_instance = std::make_shared<ProverInstance_<FlavorT>>(builder);
332 return Chonk::CircuitVerificationKey{ std::make_shared<VK>(prover_instance->get_precomputed()) };
333 });
334 }
335};
336
337} // namespace
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
The IVC scheme used by the aztec client for private function execution.
Definition chonk.hpp:42
void complete_kernel_circuit_logic(ClientCircuit &circuit)
Append logic to complete a kernel circuit.
Definition chonk.cpp:355
CircuitKind current_kind() const
Kind of the circuit currently being accumulated (or, between accumulate calls, the next one expected)...
Definition chonk.cpp:610
void accumulate(ClientCircuit &circuit, const CircuitVerificationKey &vk)
Accumulate a circuit into the running IVC.
Definition chonk.cpp:635
bb::CircuitVerificationKey CircuitVerificationKey
Definition chonk.hpp:101
size_t get_num_circuits() const
Definition chonk.hpp:203
size_t get_num_circuits_accumulated() const
Get the number of circuits accumulated by the IVC.
Definition chonk.hpp:274
MegaCircuitBuilder ClientCircuit
Definition chonk.hpp:58
Goblin goblin
Definition chonk.hpp:188
typename Curve::ScalarField FF
std::shared_ptr< OpQueue > op_queue
Definition goblin.hpp:59
static void construct_mock_app_circuit(MegaBuilder &builder, bool large=false)
Populate a builder with some arbitrary but nontrivial constraints.
static void construct_mock_folding_kernel(MegaBuilder &builder)
Construct a mock kernel circuit.
std::shared_ptr< ECCOpQueue > op_queue
Curve::ScalarField FF
NativeVerificationKey_< PrecomputedEntities< Commitment >, Codec, HashFunction, CommitmentKey > VerificationKey
The verification key stores commitments to the precomputed (non-witness) polynomials used by the veri...
static void construct_arithmetic_circuit(Builder &builder, const size_t target_log2_dyadic_size=4, bool include_public_inputs=true)
Populate a builder with a specified number of arithmetic gates; includes a PI.
Base Native verification key class.
Definition flavor.hpp:138
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
size_t log_dyadic_size() const
static void add_default(Builder &builder)
Add default public inputs when they are not present.
#define vinfo(...)
Definition log.hpp:94
AluTraceBuilder builder
Definition alu.test.cpp:124
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
BusId
Definition databus.hpp:75
typename flavor_for_impl< K >::type flavor_for
::testing::Types< BN254Settings > TestSettings
constexpr decltype(auto) dispatch_kind(CircuitKind kind, F &&f)
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::byte * data
VectorField result