Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
components_check.cpp
Go to the documentation of this file.
1
7
8namespace acir_components_check {
9
12static constexpr size_t NO_CIRCUIT_CC = SIZE_MAX;
13
15{
16 build_acir_component_map();
17 build_circuit_component_map();
18 return compare_components();
19}
20
22{
23 AcirGraph acir_graph;
24 acir_graph.process_acir_circuit(acir_circuit_);
25 acir_witness_map_ = acir_graph.get_witness_component_map();
26}
27
29{
30 // Real CCs: variables that share arithmetic gates form connected components.
32 auto circuit_cc = analyzer.find_connected_components();
33
34 // Map each circuit CC variable to its CC index
35 for (size_t cc_id = 0; cc_id < circuit_cc.size(); cc_id++) {
36 for (auto v : circuit_cc[cc_id].vars()) {
37 circuit_var_to_cc_[v] = cc_id;
38 }
39 }
40
41 // Collect gate counts
42 gate_counts_ = analyzer.get_variables_gate_counts();
43
44 // Collect range_list variables
45 for (const auto& [_, range_list] : builder_.range_lists) {
46 for (auto var_idx : range_list.variable_indices) {
47 range_list_vars_.insert(builder_.real_variable_index[var_idx]);
48 }
49 }
50
51 // Collect constant variable indices
52 for (const auto& [_, var_idx] : builder_.constant_variable_indices) {
53 constant_var_set_.insert(var_idx);
54 }
55
56 // Virtual CC ids start after real analyzer CC ids: one id per distinct constant or
57 // singleton/range-list real variable, so ACIR witnesses that only touch those paths do not
58 // falsely SPLIT a component that is "together" in ACIR but not linked by arithmetic CCs.
59 size_t next_virtual_id = circuit_cc.size();
60 std::unordered_map<uint32_t, size_t> virtual_cc_ids;
61
62 for (const auto& [witness, _] : acir_witness_map_) {
63 if (witness >= builder_.real_variable_index.size()) {
64 circuit_witness_map_[witness] = NO_CIRCUIT_CC;
65 continue;
66 }
67
68 uint32_t real_idx = builder_.real_variable_index[witness];
69
70 // In a real CC?
71 if (auto it = circuit_var_to_cc_.find(real_idx); it != circuit_var_to_cc_.end()) {
72 circuit_witness_map_[witness] = it->second;
73 continue;
74 }
75
76 // Mapped to a constant variable? (e.g., via assert_equal to zero_idx)
77 if (constant_var_set_.contains(real_idx)) {
78 if (!virtual_cc_ids.contains(real_idx)) {
79 virtual_cc_ids[real_idx] = next_virtual_id++;
80 }
81 circuit_witness_map_[witness] = virtual_cc_ids[real_idx];
82 continue;
83 }
84
85 // Singleton: in a gate or range_list but not in any CC (degree-0)
86 bool in_gate = gate_counts_.contains(real_idx) && gate_counts_.at(real_idx) > 0;
87 bool in_range_list = range_list_vars_.contains(real_idx);
88 if (in_gate || in_range_list) {
89 if (!virtual_cc_ids.contains(real_idx)) {
90 virtual_cc_ids[real_idx] = next_virtual_id++;
91 }
92 circuit_witness_map_[witness] = virtual_cc_ids[real_idx];
93 continue;
94 }
95
96 circuit_witness_map_[witness] = NO_CIRCUIT_CC;
97 }
98}
99
101{
102 std::vector<Error> errors;
103
104 // Group ACIR witnesses by their ACIR component
106 for (const auto& [witness, acir_comp] : acir_witness_map_) {
107 acir_comp_witnesses[acir_comp].push_back(witness);
108 }
109
110 for (const auto& [acir_comp, witnesses] : acir_comp_witnesses) {
111 std::unordered_set<size_t> circuit_ccs_seen;
112 std::vector<uint32_t> unconstrained;
113
114 for (auto w : witnesses) {
115 auto it = circuit_witness_map_.find(w);
116 if (it == circuit_witness_map_.end() || it->second == NO_CIRCUIT_CC) {
117 unconstrained.push_back(w);
118 } else {
119 circuit_ccs_seen.insert(it->second);
120 }
121 }
122
123 if (circuit_ccs_seen.size() > 1) {
124 std::string msg = "ACIR component " + std::to_string(acir_comp) + " is split across " +
125 std::to_string(circuit_ccs_seen.size()) + " circuit components. Witnesses: ";
126 for (auto w : witnesses) {
127 msg += "w" + std::to_string(w) + "(cc=";
128 auto cit = circuit_witness_map_.find(w);
129 if (cit != circuit_witness_map_.end() && cit->second != NO_CIRCUIT_CC) {
130 msg += std::to_string(cit->second);
131 } else {
132 msg += "none";
133 }
134 msg += ") ";
135 }
136 errors.push_back({ Error::Type::SPLIT, acir_comp, msg });
137 }
138
139 if (!unconstrained.empty()) {
140 std::string msg = "ACIR component " + std::to_string(acir_comp) + " has " +
141 std::to_string(unconstrained.size()) + " witness(es) missing from circuit: ";
142 for (auto w : unconstrained) {
143 msg += format_witness_debug(w) + " ";
144 }
145 errors.push_back({ Error::Type::UNCONSTRAINED, acir_comp, msg });
146 }
147 }
148
149 return errors;
150}
151
152template <typename Builder> std::string ComponentsChecker_<Builder>::format_witness_debug(uint32_t w) const
153{
154 if (w >= builder_.real_variable_index.size()) {
155 return "w" + std::to_string(w) + "(real=missing,const=false,cc=false,gates=false,rl=false)";
156 }
157
158 uint32_t real_idx = builder_.real_variable_index[w];
159 bool is_const = constant_var_set_.contains(real_idx);
160 bool in_cc = circuit_var_to_cc_.contains(real_idx);
161 bool has_gates = gate_counts_.contains(real_idx) && gate_counts_.at(real_idx) > 0;
162 bool in_rl = range_list_vars_.contains(real_idx);
163 auto b = [](bool v) { return v ? "true" : "false"; };
164 return "w" + std::to_string(w) + "(real=" + std::to_string(real_idx) + ",const=" + b(is_const) + ",cc=" + b(in_cc) +
165 ",gates=" + b(has_gates) + ",rl=" + b(in_rl) + ")";
166}
167
168// Explicit instantiations. Both UltraCircuitBuilder and MegaCircuitBuilder satisfy the field
169// requirements (MegaCircuitBuilder_ inherits from UltraCircuitBuilder_).
172
173} // namespace acir_components_check
Undirected graph on ACIR witness indices; connected components = "ACIR components".
std::unordered_map< uint32_t, size_t > get_witness_component_map() const
Map each witness that appears in at least one edge to a component id.
void process_acir_circuit(const Acir::Circuit &circuit)
Walk circuit.opcodes, populate adjacency, then merge per-block memory witnesses.
Structural comparison between ACIR-level and circuit-level connected components.
void build_circuit_component_map()
Build circuit-level witness → component mapping. Runs the static analyzer, then classifies each ACIR ...
std::vector< Error > check()
Run the full check. Returns list of errors (empty = pass).
void build_acir_component_map()
Build ACIR-level witness → component mapping.
std::vector< Error > compare_components() const
Compare the two maps structurally.
std::string format_witness_debug(uint32_t witness_idx) const
Format details about an unconstrained witness for error reporting.
std::vector< ConnectedComponent > find_connected_components()
this methond finds all connected components in the graph described by adjacency lists and marks some ...
Definition graph.cpp:523
std::unordered_map< uint32_t, size_t > get_variables_gate_counts() const
Definition graph.hpp:108
FF b
Validates that ACIR witness connectivity (from the Noir circuit) matches circuit variable connectivit...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)