use std::collections::BTreeMap;
use crate::field::goldilocks::MODULUS as GOLDILOCKS_P;
use crate::sym::{Constraint, ConstraintSystem, SymValue};
mod eval;
mod solver;
#[cfg(test)]
mod tests;
pub(crate) use eval::*;
pub use solver::*;
#[derive(Clone, Debug)]
pub struct Counterexample {
pub constraint_index: usize,
pub constraint_desc: String,
pub assignments: BTreeMap<String, u64>,
}
impl Counterexample {
pub fn format(&self) -> String {
let mut s = format!(
" Constraint #{}: {}\n",
self.constraint_index, self.constraint_desc
);
s.push_str(" Counterexample:\n");
let mut vars: Vec<_> = self.assignments.iter().collect();
vars.sort_by_key(|(k, _)| (*k).clone());
for (name, value) in &vars {
if !name.starts_with("__") {
s.push_str(&format!(" {} = {}\n", name, value));
}
}
s
}
}
#[derive(Clone, Debug)]
pub struct SolverResult {
pub constraints_checked: usize,
pub rounds: usize,
pub counterexamples: Vec<Counterexample>,
pub always_satisfied: Vec<usize>,
pub unevaluable: Vec<usize>,
pub all_passed: bool,
}
impl SolverResult {
pub fn format_report(&self) -> String {
let mut report = String::new();
report.push_str(&format!(
"Solver: {} constraints, {} rounds\n",
self.constraints_checked, self.rounds
));
if self.counterexamples.is_empty() {
report.push_str(" Result: ALL PASSED\n");
} else {
report.push_str(&format!(
" Result: {} VIOLATION(S) FOUND\n",
self.counterexamples.len()
));
for ce in &self.counterexamples {
report.push_str(&ce.format());
}
}
if !self.always_satisfied.is_empty() {
report.push_str(&format!(
" Redundant assertions (always true): {}\n",
self.always_satisfied.len()
));
}
if !self.unevaluable.is_empty() {
report.push_str(&format!(
" Unevaluable constraints: {}\n",
self.unevaluable.len()
));
}
report
}
}
#[derive(Clone, Debug)]
pub struct VerificationReport {
pub static_violations: Vec<String>,
pub random_result: SolverResult,
pub bmc_result: SolverResult,
pub redundant_assertions: Vec<usize>,
pub witness_required: usize,
pub verdict: Verdict,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Verdict {
Safe,
StaticViolation,
RandomViolation,
BmcViolation,
}
impl VerificationReport {
pub fn is_safe(&self) -> bool {
self.verdict == Verdict::Safe
}
pub fn format_report(&self) -> String {
let mut report = String::new();
report.push_str("โโโ Verification Report โโโ\n\n");
if self.static_violations.is_empty() {
report.push_str("Static analysis: PASS (no trivially violated assertions)\n");
} else {
report.push_str(&format!(
"Static analysis: FAIL ({} trivially violated assertion(s))\n",
self.static_violations.len()
));
for v in &self.static_violations {
report.push_str(&format!(" - {}\n", v));
}
}
report.push('\n');
report.push_str("Random testing (Schwartz-Zippel):\n");
report.push_str(&self.random_result.format_report());
report.push('\n');
report.push_str("Bounded model checking:\n");
report.push_str(&self.bmc_result.format_report());
report.push('\n');
if self.witness_required > 0 {
report.push_str(&format!(
"Witness-required: {} constraint(s) depend on hash outputs\n",
self.witness_required
));
report.push_str(" These require valid witnesses and cannot be tested randomly.\n");
report.push('\n');
}
if !self.redundant_assertions.is_empty() {
report.push_str(&format!(
"Optimization: {} assertion(s) appear redundant (always true)\n",
self.redundant_assertions.len()
));
report.push_str(" These could be removed to reduce proving cost.\n");
}
report.push('\n');
let verdict_str = match &self.verdict {
Verdict::Safe => "SAFE โ no violations found",
Verdict::StaticViolation => "UNSAFE โ static analysis found definite violations",
Verdict::RandomViolation => {
"UNSAFE โ random testing found violations (high confidence)"
}
Verdict::BmcViolation => "UNSAFE โ bounded model checking found violations",
};
report.push_str(&format!("Verdict: {}\n", verdict_str));
report
}
}
pub fn verify(system: &ConstraintSystem) -> VerificationReport {
let static_violations: Vec<String> = system
.violated_constraints()
.iter()
.map(|c| format_constraint(c))
.collect();
let witness_required = system
.constraints
.iter()
.filter(|c| c.is_hash_dependent())
.count();
let random_result = solve(system, &SolverConfig::default());
let bmc_result = bounded_check(system, &BmcConfig::default());
let mut redundant: Vec<usize> = random_result.always_satisfied.clone();
for idx in &bmc_result.always_satisfied {
if !redundant.contains(idx) {
redundant.push(*idx);
}
}
redundant.retain(|idx| {
random_result.always_satisfied.contains(idx) && bmc_result.always_satisfied.contains(idx)
});
redundant.sort();
let verdict = if !static_violations.is_empty() {
Verdict::StaticViolation
} else if !random_result.all_passed {
Verdict::RandomViolation
} else if !bmc_result.all_passed {
Verdict::BmcViolation
} else {
Verdict::Safe
};
VerificationReport {
static_violations,
random_result,
bmc_result,
redundant_assertions: redundant,
witness_required,
verdict,
}
}
fn collect_variables(system: &ConstraintSystem) -> Vec<String> {
let mut names = Vec::new();
for (name, max_version) in &system.variables {
for v in 0..=*max_version {
let key = if v == 0 {
name.clone()
} else {
format!("{}_{}", name, v)
};
if !names.contains(&key) {
names.push(key);
}
}
}
for pi in &system.pub_inputs {
let key = pi.to_string();
if !names.contains(&key) {
names.push(key);
}
}
for di in &system.divine_inputs {
let key = di.to_string();
if !names.contains(&key) {
names.push(key);
}
}
names
}
fn interesting_field_values(count: usize) -> Vec<u64> {
let mut values = vec![
0, 1, GOLDILOCKS_P - 1, 2, GOLDILOCKS_P - 2, 42, u32::MAX as u64, u32::MAX as u64 + 1, ];
let primes = [3, 5, 7, 11, 13, 17, 19, 23, 29, 31];
for &p in &primes {
if values.len() < count {
values.push(p);
}
}
let mut pow2 = 1u64;
for _ in 0..63 {
pow2 = pow2.wrapping_mul(2);
if pow2 < GOLDILOCKS_P && values.len() < count {
values.push(pow2);
}
}
values.truncate(count);
values
}
fn add_special_values(assignments: &mut BTreeMap<String, u64>, var_names: &[String], round: usize) {
let special = [0, 1, GOLDILOCKS_P - 1, 2, u32::MAX as u64];
if round < special.len() {
let val = special[round];
for name in var_names {
assignments.insert(name.clone(), val);
}
}
}
fn generate_combinations(
var_names: &[String],
values: &[u64],
max_combos: usize,
) -> Vec<BTreeMap<String, u64>> {
let num_vars = var_names.len();
let num_values = values.len();
let total: u128 = (num_values as u128)
.checked_pow(num_vars as u32)
.unwrap_or(u128::MAX);
if total <= max_combos as u128 {
let mut combos = Vec::new();
let mut indices = vec![0usize; num_vars];
loop {
let mut assignment = BTreeMap::new();
for (i, name) in var_names.iter().enumerate() {
assignment.insert(name.clone(), values[indices[i]]);
}
combos.push(assignment);
let mut carry = true;
for i in (0..num_vars).rev() {
if carry {
indices[i] += 1;
if indices[i] >= num_values {
indices[i] = 0;
} else {
carry = false;
}
}
}
if carry {
break; }
}
combos
} else {
let mut rng = Rng::new(0xBEEF_CAFE);
let mut combos = Vec::with_capacity(max_combos);
for _ in 0..max_combos {
let mut assignment = BTreeMap::new();
for name in var_names {
let idx = (rng.next_u64() as usize) % num_values;
assignment.insert(name.clone(), values[idx]);
}
combos.push(assignment);
}
combos
}
}
pub fn format_constraint(c: &Constraint) -> String {
match c {
Constraint::Equal(a, b) => {
format!("{} == {}", format_sym_value(a), format_sym_value(b))
}
Constraint::AssertTrue(v) => {
format!("assert({})", format_sym_value(v))
}
Constraint::Conditional(cond, inner) => {
format!(
"if {} then {}",
format_sym_value(cond),
format_constraint(inner)
)
}
Constraint::RangeU32(v) => {
format!("{} โ U32", format_sym_value(v))
}
Constraint::DigestEqual(_, _) => {
format!("digest_eq([..], [..])")
}
}
}
pub fn format_sym_value(v: &SymValue) -> String {
match v {
SymValue::Const(c) => format!("{}", c),
SymValue::Var(var) => var.to_string(),
SymValue::Add(a, b) => format!("({} + {})", format_sym_value(a), format_sym_value(b)),
SymValue::Mul(a, b) => format!("({} * {})", format_sym_value(a), format_sym_value(b)),
SymValue::Sub(a, b) => format!("({} - {})", format_sym_value(a), format_sym_value(b)),
SymValue::Neg(a) => format!("(-{})", format_sym_value(a)),
SymValue::Inv(a) => format!("(1/{})", format_sym_value(a)),
SymValue::Eq(a, b) => format!("({} == {})", format_sym_value(a), format_sym_value(b)),
SymValue::Lt(a, b) => format!("({} < {})", format_sym_value(a), format_sym_value(b)),
SymValue::Hash(_, idx) => format!("hash[{}]", idx),
SymValue::Divine(idx) => format!("divine_{}", idx),
SymValue::PubInput(idx) => format!("pub_in_{}", idx),
SymValue::Ite(c, t, e) => format!(
"(if {} then {} else {})",
format_sym_value(c),
format_sym_value(t),
format_sym_value(e)
),
SymValue::FieldAccess(inner, field) => {
format!("{}.{}", format_sym_value(inner), field)
}
}
}