Creusot Verification Skill
Creusot is a deductive verification tool for safe Rust. It translates Rust + Pearlite specifications to Why3/Coma and uses SMT solvers to prove correctness.
Quick Reference
Imports
use creusot_contracts::prelude::*; // Current (creusot_std in newer versions)
Core Attributes
| Attribute | Purpose |
|-----------|---------|
| #[requires(P)] | Precondition - must hold when function called |
| #[ensures(P)] | Postcondition - guaranteed when function returns |
| #[invariant(P)] | Loop invariant - true at every iteration |
| #[variant(E)] | Termination measure - must decrease each iteration |
| #[logic] | Pure logical function (not callable from Rust) |
| #[predicate] | Logical function returning bool |
| #[trusted] | Skip verification (assume contract holds) |
Key Operators
| Operator | Meaning |
|----------|---------|
| x@ | View/model operator - converts Rust value to logical type (e.g., i64 → Int) |
| ^x | Final value of mutable borrow (prophecy) |
| *x | Current value of borrow |
| ==> | Logical implication (in pearlite!{}) |
| forall<x: T> | Universal quantifier (in pearlite!{}) |
| exists<x: T> | Existential quantifier (in pearlite!{}) |
Logical Types
Int- Unbounded mathematical integers (no overflow)Seq<T>- Mathematical sequencesSet<T>,FSet<T>- Sets (infinite/finite)Map<K, V>- Mathematical functionsGhost<T>- Ghost values (exist only in proofs)Snapshot<T>- Immutable snapshot of a value
Workflow
Project Setup
cargo creusot new project-name
cd project-name
Verification Commands
cargo creusot # Compile to Coma only
cargo creusot prove # Compile and prove
cargo creusot prove -i # Open Why3 IDE on failure
cargo creusot prove --ide-always # Always open IDE
Writing Specifications
Basic Contract
#[requires(x@ < i64::MAX@)] // Precondition
#[ensures(result@ == x@ + 1)] // Postcondition
pub fn add_one(x: i64) -> i64 {
x + 1
}
Loop Invariants
Loops MUST have invariants to be verified. The invariant must:
- Hold on loop entry
- Be preserved by each iteration
- Combined with negated condition, imply postcondition
#[requires(n@ * (n@ + 1) / 2 <= u64::MAX@)]
#[ensures(result@ == n@ * (n@ + 1) / 2)]
pub fn sum_up_to(n: u64) -> u64 {
let mut sum = 0;
let mut i = 0;
#[invariant(i@ <= n@)]
#[invariant(sum@ == i@ * (i@ + 1) / 2)]
while i < n {
i += 1;
sum += i;
}
sum
}
For Loop Pattern
For loops use produced variable (sequence of yielded elements):
#[invariant(sum@ * 2 == produced.len() * (produced.len() + 1))]
for i in 1..=n {
sum += i;
}
Logic Functions and Predicates
#[predicate]
fn sorted<T: Ord>(s: Seq<T>) -> bool {
pearlite! {
forall<i: Int, j: Int> 0 <= i && i < j && j < s.len()
==> s[i] <= s[j]
}
}
#[logic]
fn sum_seq(s: Seq<Int>) -> Int {
if s.len() == 0 { 0 }
else { s[0] + sum_seq(s.tail()) }
}
Mutable References with Prophecies
Use ^ (final) to specify the value at end of borrow lifetime:
#[ensures(^x == *x + 1)] // Final value equals current + 1
pub fn increment(x: &mut i32) {
*x += 1;
}
Ghost Code
Ghost code exists only during verification:
let old_v = ghost!(v); // Snapshot for invariant
#[invariant(v@.permutation_of(old_v@))]
Type Invariants
pub struct OPair(pub u64, pub u64);
impl Invariant for OPair {
#[logic]
fn invariant(self) -> bool {
pearlite! { self.0 <= self.1 }
}
}
Common Patterns
Overflow Prevention
Always specify bounds to prevent overflow verification failures:
#[requires(x@ + y@ <= i64::MAX@)]
#[requires(x@ + y@ >= i64::MIN@)]
Vec Operations
#[requires(i@ < v@.len())] // Bounds check
#[ensures(result@ == v@[i@])] // Element access
#[ensures((^v)@.len() == v@.len())] // Length preserved
Termination Variants
#[logic]
#[variant(x)] // x must decrease (implement WellFounded)
#[requires(x >= 0)]
fn factorial(x: Int) -> Int {
if x == 0 { 1 } else { x * factorial(x - 1) }
}
Debugging Failed Proofs
- Run with IDE:
cargo creusot prove -i verif/[FILE].coma - Check unproved goals: Yellow = hypothesis, Green = proved
- Common issues:
- Missing loop invariant clause
- Invariant too weak (doesn't imply postcondition)
- Missing overflow bounds
- SMT solver timeout (try simplifying formulas)
- Avoid division in invariants - SMT solvers struggle; multiply both sides instead
File Locations
- Coma output:
verif/[crate]_rlib/[module]/[function].coma - Config:
why3find.json,Cargo.toml
Further Reference
For detailed Pearlite syntax, common specification patterns, and advanced features, see references/pearlite-syntax.md.
Scan to join WeChat group