Skip to content

Commit 2de3bc5

Browse files
authored
feat: SMT Verificaiton Module: Data Structures (#13658)
This pr adds new Symbolic objects: Tuple, Array and Set # Data Structures - Added `STuple`, `SymArray`, `SymSet` classes to ease up lookup tables and ROM/RAM arrays symbolic translation - Reflected new symbolic objects in `UltraCircuit`, `STerm` and `Solver` - Added tests for all of the new structures - Added pretty print for these structures # Bool added tests for symbolic bool class # Solver - Added a few more default solver configurations to use. - Added `ff_bitsum` option to solver config. It allows solver to understand bitsums (namely constraints of the form `b0 + 2 * b1 + 4 * b2 + ... == X`) - Added few more debug solver options - Added few options to handle arrays and sets - Fixed a bug: `lookup_enabled` was not handled properly
1 parent 9a3bb46 commit 2de3bc5

File tree

13 files changed

+1235
-169
lines changed

13 files changed

+1235
-169
lines changed

barretenberg/cpp/src/barretenberg/smt_verification/README.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ To store it on the disk just do the following
134134

135135
4. Terms creation
136136

137+
### Arithmetic Variables
138+
137139
You can initialize symbolic variable via `STerm::Var(str name, &solver, TermType type)` or `STerm::Const(str val, &solver, TermType type, u32 base=16)`
138140

139141
But also you can use `FFVar(str name, &Solver)` or equivalently via `FFIVar` and `BVVar` so you don't have to mess with types.
@@ -162,10 +164,12 @@ To store it on the disk just do the following
162164
- `STerm::in(cvc5::Term table&)` - simple set inclusion.
163165
- `static STerm::in_table(std::vector<STerm> entry, cvc5::Term& table)` - lookup table inclusion.
164166

165-
---
167+
---
168+
169+
### Boolean Variables
166170

167171
There is a Bool type:
168-
- `Bool Bool(STerm t)` or `Bool Bool(bool b, Solver* s)`
172+
- `Bool Bool(STerm t)`, `Bool Bool(bool b, Solver* s)` or `Bool(str name, Solver* s)`
169173

170174
You can `|, &, ==, !=, !` these variables and also `batch_or`, `batch_and` them.
171175
To create a constraint you should call `Bool::assert_term()` method.
@@ -175,6 +179,84 @@ To store it on the disk just do the following
175179
**!Note that constraint like `(Bool(STerm a) == Bool(STerm b)).assert_term()`, where a has `FFTerm` type and b has `FFITerm` type, won't work, since their types differ.**
176180
**!Note `Bool(a == b)` won't work since `a==b` will create an equality constraint as I mentioned earlier and the return type of this operation is `void`.**
177181

182+
---
183+
184+
### Data Structures
185+
186+
There're three extra data structures:
187+
188+
#### STuple
189+
190+
Symbolic Tuple type.
191+
You can group several items in one term.
192+
**!Note Only compatible with `STerm` class**
193+
194+
`STuple STuple(vec<STerm>, Solver* slv)`
195+
196+
**!Note that you can not access the element of the tuple by its index after creation**
197+
198+
#### SymArray
199+
200+
Symbolic Array type.
201+
You can store symbolic values. And access them by symbolic index.
202+
203+
Both index and entry can be any of the symbolic types: `STerm`, `Bool`, `STuple`, `SymArray`, `SymSet`
204+
205+
Create an empty array:
206+
`SymArray SymArray<sym_index, sym_entry>(cvc5:Sort idx_sort, TermType idx_type, cvc5::Sort entry_sort, TermType entry_type, Solver* s, str name = "")`
207+
208+
***!Note passing cvc5 native types directly is a little bit advanced compared to the ordinary usage of this module. See the tests***
209+
210+
Create an array from indicies and entrys:
211+
`SymArray SymArray<sym_index, sym_entry>(vector<sym_index> indicies, vec<sym_entry> entries, str name = "")`
212+
213+
Create an integer indexed array from entries:
214+
`SymArray SymArray<sym_index, sym_entry>(vec<sym_entry> entries, STerm index_base, str name = "")`
215+
216+
**!Note you need to provide an example for the integer like index entry. Most of the time you'll be fine using: `index_base` = `FFConst(1, &slv)`| `FFIConst(1, &slv)`| `IConst(1, &slv)`| `BVConst(1, &slv)`**
217+
218+
After you've created an array you can put/overwrite elements in it by:
219+
220+
`arr.put(sym_idx, sym_entry)`
221+
222+
And access them:
223+
224+
`arr.get(sym_idx)`
225+
`arr[sym_idx]`
226+
227+
228+
For debugging purposes there's a `print_trace` method, that will print all the `put` operations
229+
230+
#### SymSet
231+
232+
Symbolic Set type.
233+
You can store symbolic values. You can check wheter an element belong to the set or not.
234+
235+
Entries can be any of the symbolic types: `STerm`, `Bool`, `STuple`, `SymArray`, `SymSet`
236+
237+
Create an empty set:
238+
`SymSet SymSet<sym_entry>(cvc5::Sort entry_sort, TermType entry_type, Solver* s, str name = "")`
239+
240+
***!Note passing cvc5 native types directly is a little bit advanced compared to the ordinary usage of this module. See the tests***
241+
242+
Create a set from vector of values:
243+
`SymSet SymSet<sym_entry>(vec<sym_entry> entries, str name = "")`
244+
245+
After you've created a set you can put elements in it by:
246+
247+
`set.insert(sym_entry)`
248+
249+
And add inclusion constraints:
250+
251+
`set.contains(entry)`
252+
`set.not_contains(entry)`
253+
254+
For debugging purposes there's a `print_trace` method, that will print all the `insert` operations
255+
256+
---
257+
258+
All the types support printing their name in variable case and value in constant case
259+
178260
## 3. Post model checking
179261
After generating all the constrains you should call `bool res = solver.check()` and depending on your goal it could be `true` or `false`.
180262

@@ -263,6 +345,10 @@ Avalaible test suits in `smt_verification_tests`:
263345
- `FFITerm*`
264346
- `ITerm*`
265347
- `BVTerm*`
348+
- `SymbolicBool*`
349+
- `SymbolicTuple*`
350+
- `SymbolicArray*`
351+
- `SymbolicSet*`
266352
---
267353
268354
- `Subcircuits*`
@@ -402,6 +488,8 @@ void model_variables(SymCircuit& c, Solver* s, FFTerm& evaluation)
402488
More examples can be found in
403489
404490
- [terms/ffterm.test.cpp](terms/ffterm.test.cpp), [terms/ffiterm.test.cpp](terms/ffiterm.test.cpp), [terms/bvterm.test.cpp](terms/bvterm.test.cpp), [terms/iterm.test.cpp](terms/iterm.test.cpp)
491+
- [terms/bool.test.cpp](terms/bool.test.cpp)
492+
- [terms/data_types.test.cpp]
405493
- [circuit/standard_circuit.test.cpp](circuit/standard_circuit.test.cpp), [circuit/ultra_circuit](circuit/ultra_circuit.test.cpp)
406494
- [smt_polynomials.test.cpp](smt_polynomials.test.cpp), [smt_examples.test.cpp](smt_examples.test.cpp)
407495
- [bb_tests](bb_tests)

barretenberg/cpp/src/barretenberg/smt_verification/circuit/circuit_base.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <unordered_map>
77

88
#include "barretenberg/smt_verification/terms/bool.hpp"
9+
#include "barretenberg/smt_verification/terms/data_structures.hpp"
910
#include "barretenberg/smt_verification/terms/term.hpp"
1011

1112
#include "subcircuits.hpp"

barretenberg/cpp/src/barretenberg/smt_verification/circuit/ultra_circuit.cpp

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,34 +140,38 @@ size_t UltraCircuit::handle_arithmetic_relation(size_t cursor, size_t idx)
140140

141141
void UltraCircuit::process_new_table(uint32_t table_idx)
142142
{
143-
std::vector<std::vector<cvc5::Term>> new_table;
143+
std::vector<STuple> new_table;
144144
bool is_xor = true;
145145
bool is_and = true;
146146

147147
for (auto table_entry : this->lookup_tables[table_idx]) {
148-
std::vector<cvc5::Term> tmp_entry = {
148+
STuple tmp_entry({
149149
STerm(table_entry[0], this->solver, this->type),
150150
STerm(table_entry[1], this->solver, this->type),
151151
STerm(table_entry[2], this->solver, this->type),
152-
};
152+
});
153153
new_table.push_back(tmp_entry);
154154

155155
is_xor &= (static_cast<uint256_t>(table_entry[0]) ^ static_cast<uint256_t>(table_entry[1])) ==
156156
static_cast<uint256_t>(table_entry[2]);
157157
is_and &= (static_cast<uint256_t>(table_entry[0]) & static_cast<uint256_t>(table_entry[1])) ==
158158
static_cast<uint256_t>(table_entry[2]);
159159
}
160-
this->cached_symbolic_tables.insert({ table_idx, this->solver->create_lookup_table(new_table) });
160+
info("Creating lookup table №", this->cached_symbolic_tables.size());
161+
std::string table_name;
161162
if (is_xor) {
163+
table_name = "XOR_TABLE_" + std::to_string(new_table.size());
162164
this->tables_types.insert({ table_idx, TableType::XOR });
163-
info("Encountered a XOR table");
164165
} else if (is_and) {
166+
table_name = "AND_TABLE_" + std::to_string(new_table.size());
165167
this->tables_types.insert({ table_idx, TableType::AND });
166-
info("Encountered an AND table");
167168
} else {
169+
table_name = "UNK_TABLE_" + std::to_string(new_table.size());
168170
this->tables_types.insert({ table_idx, TableType::UNKNOWN });
169-
info("Encountered an UNKNOWN table");
170171
}
172+
info(table_name);
173+
SymSet<STuple> new_stable(new_table, this->tag + table_name);
174+
this->cached_symbolic_tables.insert({ table_idx, new_stable });
171175
}
172176

173177
/**
@@ -213,7 +217,6 @@ size_t UltraCircuit::handle_lookup_relation(size_t cursor, size_t idx)
213217
STerm first_entry = this->symbolic_vars[w_l_idx] + q_r * this->symbolic_vars[w_l_shift_idx];
214218
STerm second_entry = this->symbolic_vars[w_r_idx] + q_m * this->symbolic_vars[w_r_shift_idx];
215219
STerm third_entry = this->symbolic_vars[w_o_idx] + q_c * this->symbolic_vars[w_o_shift_idx];
216-
std::vector<STerm> entries = { first_entry, second_entry, third_entry };
217220

218221
if (this->type == TermType::BVTerm && this->enable_optimizations) {
219222
// Sort of an optimization.
@@ -237,7 +240,8 @@ size_t UltraCircuit::handle_lookup_relation(size_t cursor, size_t idx)
237240
}
238241
}
239242
info("Unknown Table");
240-
STerm::in_table(entries, this->cached_symbolic_tables[table_idx]);
243+
STuple entries({ first_entry, second_entry, third_entry });
244+
this->cached_symbolic_tables[table_idx].contains(entries);
241245
return cursor + 1;
242246
}
243247

@@ -380,14 +384,16 @@ void UltraCircuit::handle_range_constraints()
380384
uint64_t range = this->range_tags[tag];
381385
if (this->type == TermType::FFTerm || !this->enable_optimizations) {
382386
if (!this->cached_range_tables.contains(range)) {
383-
std::vector<cvc5::Term> new_range_table;
387+
std::vector<STerm> new_range_table;
384388
for (size_t entry = 0; entry < range; entry++) {
385389
new_range_table.push_back(STerm(entry, this->solver, this->type));
386390
}
387-
this->cached_range_tables.insert({ range, this->solver->create_table(new_range_table) });
391+
std::string table_name = this->tag + "RANGE_" + std::to_string(range);
392+
SymSet<STerm> new_range_stable(new_range_table, table_name);
393+
info("Initialized new range: ", table_name);
394+
this->cached_range_tables.insert({ range, new_range_stable });
388395
}
389-
390-
this->symbolic_vars[i].in(this->cached_range_tables[range]);
396+
this->cached_range_tables[range].contains(this->symbolic_vars[i]);
391397
} else {
392398
this->symbolic_vars[i] <= range;
393399
}

barretenberg/cpp/src/barretenberg/smt_verification/circuit/ultra_circuit.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class UltraCircuit : public CircuitBase {
2323
std::vector<std::vector<std::vector<uint32_t>>> wires_idxs; // values of the gates' wires idxs
2424

2525
std::vector<std::vector<std::vector<bb::fr>>> lookup_tables;
26-
std::unordered_map<uint32_t, cvc5::Term> cached_symbolic_tables;
26+
std::unordered_map<uint32_t, SymSet<STuple>> cached_symbolic_tables;
2727
std::unordered_map<uint32_t, TableType> tables_types;
28-
std::unordered_map<uint64_t, cvc5::Term> cached_range_tables;
28+
std::unordered_map<uint64_t, SymSet<STerm>> cached_range_tables;
2929

3030
explicit UltraCircuit(CircuitSchema& circuit_info,
3131
Solver* solver,

0 commit comments

Comments
 (0)