hraness
Theme
Appearance

kani: checking every possible number a function can see

bounded model checking for the arithmetic tests only sample

by hraness · drafted with ai assistance

the rest of this lesson is free: add your email to keep reading.

Kani checks every input a small Rust function can receive within a limit you declare, where a property test only samples some of them. Most integer bugs sit at the edges: the i64 that overflows at 2^63, the subtraction that goes negative exactly once, the == that should have been <=. Random generation rarely hits those values. A bounded model checker finds all of them inside the bound.

a sample versus every input

Kani is a bounded model checker for Rust. You write a proof harness: a function that calls the code under test with kani::any() inputs, asserts a property, and optionally limits the input size. Kani then explores every input within that limit symbolically. If it reports no violation, the property holds for all of those inputs, which no number of test runs can show.

A test says “I looked at a thousand cases.” A bounded proof says “there is no counterexample inside this bound,” and nothing about inputs outside it. That makes the bound part of the claim, so it belongs in the code next to the harness.

which functions get a proof

Kani pays off on arithmetic and memory-safety properties, where a wrong value corrupts data silently instead of raising an error: counter arithmetic, saturating operations, index math, capacity checks, and quorum counting. These are small functions whose inputs can be enumerated.

vhalla’s spent set is the clearest example. It tracks which sequence numbers have been consumed. Its correctness is a small arithmetic property (a number is in the set if and only if it was inserted, and ranges never overlap), and it also protects security, because a replayed sequence number means a replayed message. A CI proof that the arithmetic is right for every value inside the bound is the evidence a sampled test can only approximate.

Gobstopper’s admission proof has the same shape. Gobstopper decides whether to accept a request by comparing counters with fixed limits, and a Kani harness proves the comparison accepts exactly the region it claims. The proof runs in CI like a test, so the arithmetic cannot drift unnoticed when the surrounding code changes.

how the proofs run

Each proof sits beside the code it constrains as a #[kani::proof] harness in the crate, runs in a dedicated CI job, and shows its bound in the harness. Proofs are written only for functions that are deterministic, make no environment calls, and are worth checking for every input: the arithmetic kernels where a wrong answer is silent.

Kani checks named functions inside a declared bound, never the whole system. Keeping the proofs small keeps them fast, and because the bound and the property are code, a reviewer can read exactly what was proved.

the bound is part of the claim

A proof over u8 inputs covers 256 cases. It says nothing about the u64 a production path might see unless the code’s own types restrict the input to the proved range. The reliable pattern is to make the proved bound and the runtime bound the same type, so an out-of-scope value cannot be constructed at all.

kani::any() covers only what the harness declares. A proof about insert(x) does not cover remove(x), and a proof over one operation does not cover sequences of operations, which the Hegel lesson addresses.

Proofs are slow compared with tests. Kani explores paths the way a solver does, and a harness that models too much (heap structures, strings, long loops) does not finish. Each proof stays a small claim about a small, named function, and its CI job has a time limit like any other.

For the kernels Kani covers, random testing is weakest, because the failure is one specific value rather than a pattern across many. One CI job turns “we tested a lot of numbers” into “every number in the bound was checked.” For a replay-protection set or a billing counter, that is the property you need.

keep reading: free for subscribers

the rest of this lesson is free. enter your email to subscribe, and every subscriber lesson unlocks in this browser.

already subscribed? enter the same email to unlock.