Skip to content

Commit 4f22497

Browse files
defkitaakoshh
andauthored
feat: acir_formal_proofs (#8140)
Co-authored-by: Akosh Farkash <[email protected]>
1 parent d992ad5 commit 4f22497

5 files changed

Lines changed: 520 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ members = [
4343

4444
"tooling/ssa_fuzzer",
4545
"tooling/ssa_fuzzer/fuzzer",
46+
"tooling/ssa_verification",
4647
"tooling/ssa_executor",
4748
"utils/protobuf",
4849
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
[package]
3+
name = "ssa_verification"
4+
description = "CLI tool to generate input for SSA formal verification"
5+
version.workspace = true
6+
authors.workspace = true
7+
edition.workspace = true
8+
rust-version.workspace = true
9+
license.workspace = true
10+
11+
[dependencies]
12+
noirc_evaluator.workspace = true
13+
acvm.workspace = true
14+
serde.workspace = true
15+
serde_json.workspace = true
16+
clap.workspace = true
17+
flate2.workspace = true
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
use acvm::{
2+
FieldElement,
3+
acir::{
4+
circuit::{Circuit, ExpressionWidth, Program as AcirProgram},
5+
native_types::Witness,
6+
},
7+
};
8+
use std::collections::BTreeSet;
9+
10+
use noirc_evaluator::ssa::ssa_gen::Ssa;
11+
use noirc_evaluator::ssa::{
12+
SsaEvaluatorOptions, ir::map::Id, optimize_ssa_builder_into_acir, primary_passes,
13+
secondary_passes,
14+
};
15+
use noirc_evaluator::ssa::{SsaLogging, ir::function::Function};
16+
use std::collections::HashMap;
17+
18+
use noirc_evaluator::brillig::BrilligOptions;
19+
use noirc_evaluator::ssa::{
20+
SsaBuilder,
21+
function_builder::FunctionBuilder,
22+
ir::{instruction::BinaryOp, types::Type},
23+
};
24+
use serde::{Deserialize, Serialize};
25+
26+
/// Represents artifacts generated from compiling an instruction
27+
#[derive(Serialize, Deserialize)]
28+
pub(crate) struct InstructionArtifacts {
29+
/// Name of the instruction
30+
pub(crate) instruction_name: String,
31+
32+
/// SSA representation formatted as "acir(inline) {...}"
33+
pub(crate) formatted_ssa: String,
34+
35+
/// JSON serialized SSA
36+
pub(crate) serialized_ssa: String,
37+
38+
/// Gzipped bytes of ACIR program
39+
pub(crate) serialized_acir: Vec<u8>,
40+
}
41+
42+
/// Represents the type of a variable in the instruction
43+
#[derive(Debug)]
44+
pub(crate) enum VariableType {
45+
/// Field element type
46+
Field,
47+
/// Unsigned integer type
48+
Unsigned,
49+
/// Signed integer type
50+
Signed,
51+
}
52+
53+
/// Represents a variable with its type and size information
54+
pub(crate) struct Variable {
55+
/// Type of the variable (Field, Unsigned, or Signed)
56+
pub(crate) variable_type: VariableType,
57+
/// Bit size of the variable (ignored for Field type)
58+
pub(crate) variable_size: u32,
59+
}
60+
61+
impl Variable {
62+
/// Gets a string representation of the variable's type and size
63+
pub(crate) fn get_name(&self) -> String {
64+
format!("{:?}_{}", self.variable_type, self.variable_size)
65+
}
66+
}
67+
68+
impl InstructionArtifacts {
69+
/// Converts a Variable into its corresponding SSA Type
70+
fn get_type(variable: &Variable) -> Type {
71+
match variable.variable_type {
72+
VariableType::Field => Type::field(),
73+
VariableType::Signed => Type::signed(variable.variable_size),
74+
VariableType::Unsigned => Type::unsigned(variable.variable_size),
75+
}
76+
}
77+
78+
/// Creates a new binary operation instruction artifact
79+
fn new_binary(
80+
op: BinaryOp,
81+
instruction_name: String,
82+
first_variable: &Variable,
83+
second_variable: &Variable,
84+
) -> Self {
85+
let first_variable_type = Self::get_type(first_variable);
86+
let second_variable_type = Self::get_type(second_variable);
87+
let ssa = binary_function(op, first_variable_type, second_variable_type);
88+
let serialized_ssa = &serde_json::to_string(&ssa).unwrap();
89+
let formatted_ssa = format!("{}", ssa);
90+
91+
let program = ssa_to_acir_program(ssa);
92+
let serialized_program = AcirProgram::serialize_program(&program);
93+
let name = format!(
94+
"{}_{}_{}",
95+
instruction_name,
96+
first_variable.get_name(),
97+
second_variable.get_name()
98+
);
99+
100+
Self {
101+
instruction_name: name,
102+
formatted_ssa,
103+
serialized_ssa: serialized_ssa.to_string(),
104+
serialized_acir: serialized_program,
105+
}
106+
}
107+
108+
/// Creates a new instruction artifact using a provided SSA generation function
109+
fn new_by_func(
110+
ssa_generate_function: fn(Type) -> Ssa,
111+
instruction_name: String,
112+
variable: &Variable,
113+
) -> Self {
114+
let variable_type = Self::get_type(variable);
115+
let ssa = ssa_generate_function(variable_type);
116+
Self::new_by_ssa(ssa, instruction_name, variable)
117+
}
118+
119+
fn new_by_ssa(ssa: Ssa, instruction_name: String, variable: &Variable) -> Self {
120+
let serialized_ssa = &serde_json::to_string(&ssa).unwrap();
121+
let formatted_ssa = format!("{}", ssa);
122+
123+
let program = ssa_to_acir_program(ssa);
124+
let serialized_program = AcirProgram::serialize_program(&program);
125+
let name = format!("{}_{}", instruction_name, variable.get_name());
126+
127+
Self {
128+
instruction_name: name,
129+
formatted_ssa,
130+
serialized_ssa: serialized_ssa.to_string(),
131+
serialized_acir: serialized_program,
132+
}
133+
}
134+
135+
/// Creates a new constrain instruction artifact
136+
pub(crate) fn new_constrain(variable: &Variable) -> Self {
137+
Self::new_by_func(constrain_function, "Constrain".into(), variable)
138+
}
139+
140+
/// Creates a new NOT operation instruction artifact
141+
pub(crate) fn new_not(variable: &Variable) -> Self {
142+
Self::new_by_func(not_function, "Not".into(), variable)
143+
}
144+
145+
/// Creates a new range check instruction artifact
146+
pub(crate) fn new_range_check(variable: &Variable, bit_size: u32) -> Self {
147+
let ssa = range_check_function(Self::get_type(variable), bit_size);
148+
Self::new_by_ssa(ssa, "RangeCheck".into(), variable)
149+
}
150+
151+
/// Creates a new truncate instruction artifact
152+
pub(crate) fn new_truncate(variable: &Variable, bit_size: u32, max_bit_size: u32) -> Self {
153+
let ssa = truncate_function(Self::get_type(variable), bit_size, max_bit_size);
154+
Self::new_by_ssa(ssa, "Truncate".into(), variable)
155+
}
156+
157+
/// Creates a new ADD operation instruction artifact
158+
pub(crate) fn new_add(first_variable: &Variable, second_variable: &Variable) -> Self {
159+
Self::new_binary(
160+
BinaryOp::Add { unchecked: false },
161+
"Binary::Add".into(),
162+
first_variable,
163+
second_variable,
164+
)
165+
}
166+
167+
/// Creates a new SUB operation instruction artifact
168+
pub(crate) fn new_sub(first_variable: &Variable, second_variable: &Variable) -> Self {
169+
Self::new_binary(
170+
BinaryOp::Sub { unchecked: false },
171+
"Binary::Sub".into(),
172+
first_variable,
173+
second_variable,
174+
)
175+
}
176+
177+
/// Creates a new XOR operation instruction artifact
178+
pub(crate) fn new_xor(first_variable: &Variable, second_variable: &Variable) -> Self {
179+
Self::new_binary(BinaryOp::Xor, "Binary::Xor".into(), first_variable, second_variable)
180+
}
181+
182+
/// Creates a new AND operation instruction artifact
183+
pub(crate) fn new_and(first_variable: &Variable, second_variable: &Variable) -> Self {
184+
Self::new_binary(BinaryOp::And, "Binary::And".into(), first_variable, second_variable)
185+
}
186+
187+
/// Creates a new OR operation instruction artifact
188+
pub(crate) fn new_or(first_variable: &Variable, second_variable: &Variable) -> Self {
189+
Self::new_binary(BinaryOp::Or, "Binary::Or".into(), first_variable, second_variable)
190+
}
191+
192+
/// Creates a new less than operation instruction artifact
193+
pub(crate) fn new_lt(first_variable: &Variable, second_variable: &Variable) -> Self {
194+
Self::new_binary(BinaryOp::Lt, "Binary::Lt".into(), first_variable, second_variable)
195+
}
196+
197+
/// Creates a new equals operation instruction artifact
198+
pub(crate) fn new_eq(first_variable: &Variable, second_variable: &Variable) -> Self {
199+
Self::new_binary(BinaryOp::Eq, "Binary::Eq".into(), first_variable, second_variable)
200+
}
201+
202+
/// Creates a new modulo operation instruction artifact
203+
pub(crate) fn new_mod(first_variable: &Variable, second_variable: &Variable) -> Self {
204+
Self::new_binary(BinaryOp::Mod, "Binary::Mod".into(), first_variable, second_variable)
205+
}
206+
207+
/// Creates a new multiply operation instruction artifact
208+
pub(crate) fn new_mul(first_variable: &Variable, second_variable: &Variable) -> Self {
209+
Self::new_binary(
210+
BinaryOp::Mul { unchecked: false },
211+
"Binary::Mul".into(),
212+
first_variable,
213+
second_variable,
214+
)
215+
}
216+
217+
/// Creates a new divide operation instruction artifact
218+
pub(crate) fn new_div(first_variable: &Variable, second_variable: &Variable) -> Self {
219+
Self::new_binary(BinaryOp::Div, "Binary::Div".into(), first_variable, second_variable)
220+
}
221+
222+
/// Creates a new shift left operation instruction artifact
223+
pub(crate) fn new_shl(first_variable: &Variable, second_variable: &Variable) -> Self {
224+
Self::new_binary(BinaryOp::Shl, "Binary::Shl".into(), first_variable, second_variable)
225+
}
226+
227+
/// Creates a new shift right operation instruction artifact
228+
pub(crate) fn new_shr(first_variable: &Variable, second_variable: &Variable) -> Self {
229+
Self::new_binary(BinaryOp::Shr, "Binary::Shr".into(), first_variable, second_variable)
230+
}
231+
}
232+
233+
/// Converts SSA to ACIR program
234+
fn ssa_to_acir_program(ssa: Ssa) -> AcirProgram<FieldElement> {
235+
// third brillig names, fourth errors
236+
let builder = SsaBuilder {
237+
ssa,
238+
ssa_logging: SsaLogging::None,
239+
print_codegen_timings: false,
240+
passed: HashMap::default(),
241+
skip_passes: vec![],
242+
};
243+
let ssa_evaluator_options = SsaEvaluatorOptions {
244+
ssa_logging: SsaLogging::None,
245+
print_codegen_timings: false,
246+
expression_width: ExpressionWidth::default(),
247+
emit_ssa: { None },
248+
skip_underconstrained_check: true,
249+
skip_brillig_constraints_check: true,
250+
inliner_aggressiveness: 0,
251+
max_bytecode_increase_percent: None,
252+
brillig_options: BrilligOptions::default(),
253+
enable_brillig_constraints_check_lookback: false,
254+
skip_passes: vec![],
255+
};
256+
let (acir_functions, brillig, _, _) = match optimize_ssa_builder_into_acir(
257+
builder,
258+
&ssa_evaluator_options,
259+
&primary_passes(&ssa_evaluator_options),
260+
secondary_passes,
261+
) {
262+
Ok(artifacts_and_warnings) => artifacts_and_warnings.0,
263+
Err(_) => panic!("Should compile manually generated SSA into acir"),
264+
};
265+
266+
let mut functions: Vec<Circuit<FieldElement>> = Vec::new();
267+
268+
for acir_func in acir_functions.iter() {
269+
let mut private_params: BTreeSet<Witness> =
270+
acir_func.input_witnesses.clone().into_iter().collect();
271+
let ret_values: BTreeSet<Witness> =
272+
acir_func.return_witnesses.clone().into_iter().collect();
273+
274+
private_params.extend(ret_values.iter().cloned());
275+
let circuit: Circuit<FieldElement> = Circuit {
276+
current_witness_index: acir_func.current_witness_index().witness_index(),
277+
opcodes: acir_func.opcodes.clone(),
278+
private_parameters: private_params.clone(),
279+
..Circuit::<FieldElement>::default()
280+
};
281+
functions.push(circuit);
282+
}
283+
AcirProgram { functions, unconstrained_functions: brillig }
284+
}
285+
286+
/// Creates an SSA function for binary operations
287+
fn binary_function(op: BinaryOp, first_variable_type: Type, second_variable_type: Type) -> Ssa {
288+
// returns v0 op v1
289+
let main_id: Id<Function> = Id::new(0);
290+
let mut builder = FunctionBuilder::new("main".into(), main_id);
291+
let v0 = builder.add_parameter(first_variable_type);
292+
let v1 = builder.add_parameter(second_variable_type);
293+
let v2 = builder.insert_binary(v0, op, v1);
294+
builder.terminate_with_return(vec![v2]);
295+
296+
builder.finish()
297+
}
298+
299+
/// Creates an SSA function for constraint operations
300+
fn constrain_function(variable_type: Type) -> Ssa {
301+
// constrains v0 == v1, returns v1
302+
let main_id: Id<Function> = Id::new(0);
303+
let mut builder = FunctionBuilder::new("main".into(), main_id);
304+
305+
let v0 = builder.add_parameter(variable_type.clone());
306+
let v1 = builder.add_parameter(variable_type);
307+
builder.insert_constrain(v0, v1, None);
308+
builder.terminate_with_return(vec![v1]);
309+
310+
builder.finish()
311+
}
312+
313+
/// Creates an SSA function for range check operations
314+
fn range_check_function(variable_type: Type, bit_size: u32) -> Ssa {
315+
let main_id: Id<Function> = Id::new(0);
316+
let mut builder = FunctionBuilder::new("main".into(), main_id);
317+
318+
let v0 = builder.add_parameter(variable_type);
319+
builder.insert_range_check(v0, bit_size, Some("Range Check failed".to_string()));
320+
builder.terminate_with_return(vec![v0]);
321+
322+
builder.finish()
323+
}
324+
325+
/// Creates an SSA function for truncate operations
326+
fn truncate_function(variable_type: Type, bit_size: u32, max_bit_size: u32) -> Ssa {
327+
let main_id: Id<Function> = Id::new(0);
328+
let mut builder = FunctionBuilder::new("main".into(), main_id);
329+
330+
let v0 = builder.add_parameter(variable_type);
331+
let v1 = builder.insert_truncate(v0, bit_size, max_bit_size);
332+
builder.terminate_with_return(vec![v1]);
333+
334+
builder.finish()
335+
}
336+
337+
/// Creates an SSA function for NOT operations
338+
fn not_function(variable_type: Type) -> Ssa {
339+
// returns not v0
340+
let main_id: Id<Function> = Id::new(0);
341+
let mut builder = FunctionBuilder::new("main".into(), main_id);
342+
343+
let v0 = builder.add_parameter(variable_type);
344+
let v1 = builder.insert_not(v0);
345+
builder.terminate_with_return(vec![v1]);
346+
347+
builder.finish()
348+
}

0 commit comments

Comments
 (0)