Verified linear algebra

with the Rust programming language

CHOOSE

Index

Project goal

\mathbb{C}^{n \times n}

Formally verify vector 2-norm

  • Write code in Rust
    • Define complex numbers
    • Define vectors in
  • Translate code to Coq
  • Verify norm properties
    • Positive definiteness
    • Nonnegative homogeneity
    • Triangle inequality
\Vert x \Vert = 0 \rightarrow x = 0
\Vert \lambda x \Vert = |\lambda| \Vert x \Vert
\Vert x + y \Vert \le \Vert x \Vert + \Vert y \Vert

2

Verus Installation was relatively straight forward. The Rust compiler could not find the Verus package. This did not affect Verus verifiying code, but rendered issues within the Rust toolchain. Ultimately, Verus proved to lack support of floats (decimals)

3

Aeneas Simple to install (like verus), but with an extra dependency on a related project. Lacks support of floats (decimals)

4

coq-of-rust Easy installation using Rust cargo tool. Same issue as Aeneas (and Verus)

6

Property-based Testing / Model-checking Not as rigorous as a formal proof, but far more robust than nothing. Kani is a viable library.

(in progress)

5

Creusot Straight-forward installation. Provides annotations that appear to support floats and translates Rust to WhyML.

(in progress)

1

Environment Develop container-based environment work writing Rust code that has Why3 with provers, Coq, and Lean installed

Project timeline

...the use of tools that mathematically analyze the space of possible behaviors of a design, rather than computing results for particular values.

 

...a powerful technique used to mathematically prove that an appropriately scaled model of a system does or does not exhibit desirable properties. [1]

Formal verification

Testing is a poor substitute for proof.

Category Theory for Programmers

[A] proof is rigorous when it is (or could be) written out in the first-order predicate language… as a sequence of inferences from the axioms ZFC, each inference made according to one of the stated rules…

Formally Verified Mathematics [2]

I verified some code

I don't need to understand linear algebra, OCaml, Coq, Why3, Rust, and the de Bruijn Criterion to understand your project, right?

I don't have to, right?

mind the gap

"formality gap"

Model

Implementation

"Math" world

"Real" world

\sqrt{x}

< />

Real & math

Removes formality gap

Single code base

Well known language

Better than nothing

real = math

Removes formality gap

Single code base

Well known language

Better than nothing

crossing the gap

real     math

Removes formality gap

Single code base

Well known language

Better than nothing

code translation

Real & math

"Math" world

"Real" world

Real = math

"Math" world

"Real" world

code translation

Intermediate

Representation

Why3

"Real" world

"Math" world

Code translation options

& coq-of-rust

verus

Formally verify Rust

Supports float type

Popular backends

Rigorous verification

creusot

Formally verify Rust

Supports float type

Popular backends

Rigorous verification

aeneas

Formally verify Rust

Supports float type

Popular backends

Rigorous verification

GitHub repository

 

Summary: Verification toolchain for Rust programs. Built with OCaml and has F*, Lean, Coq, and HOL backends...relies on a translation from Rust's mid-level intermediate representation (MIR) internal language to a pure lambda calculus

 

Issues: Only supports integers and natural numbers

aeneas

creusot

GitHub repository

 

Summary: Tool for deductive verification of Rust code. It allows you to annotate your code with specifications, invariants and assertions and then verify them formally and automatically, returning a proof that your code satisfies the specs.

 

Issues: NSTR (in progress)

verus

GitHub repository

 

Summary: Tool for verifying the correctness of code written in Rust. Developers write specifications of what their code should do, and Verus statically checks that the executable Rust code will always satisfy the specifications for all possible executions of the code

 

Issues: Only supports integers and natural numbers

GitHub repository

  • Containerized solution using Docker
  • Leverages multi-image build structure to minimize image size and maximize reuse
  • "Formal verification" image provides
    • OCaml and Coq installed
    • Why3 installed
    • Lean installed
    • Scripts for installing Aeneas, Creusot, Verus, and WebAssembly run-times

Environment setup and usage

creATE container

docker run -it \
	--privileged \
    --name notebook \
    --hostname $(hostname) \
    -p 1337:1337 \
    ghcr.io/jhwohlgemuth/lambda

verify environment

# Verify Coq opam packages are installed
opam list | grep coq

# Verify Coq version (installed with opam)
coqc --version

# Read Lean help
lean --help

# View installed Why3 provers
why3 config list-provers

install technologies

# https://github.com/AeneasVerif/aeneas
install_aeneas
aeneas --help

# https://github.com/xldenis/creusot/tree/master
install_creusot
cargo --list | grep creusot

# https://github.com/verus-lang/verus
install_verus
verus --help

open vscode in web browser

https://localhost:1337

status and way-ahead

\mathbb{C}^{4}

GitHub repository (jhwohlgemuth/verified-linear-algebra)

  • Implemented complex numbers
  • Implemented complex vectors (using Rust vec)
  • Implemented vector 2-norm in
  • Use property-based testing (model checking with Kani) to "verify" properties of 2-norm
    • Issue Kani does not support f64 square root
    • Note Perform model checking on
  • Future goal is to build up to        vectors and address lack of support for floats (f64) directly. Try using Creusot.
\Vert x \Vert^{2}_{2}
\mathbb{C}^{n}

model-checking with kani

GitHub repository

 

Summary: Bit-precise model checker for Rust. Verifies memory safety, the absence of some types of unexpected behavior (e.g., arithmetic overflows), user inserted assert!, and more. Somewhere in between property-based testing and formal verification.

 

Issues: Not rigorous verification

install kani

# Install
cargo install --locked kani-verifier

# Configure
cargo kani setup

# Use
cargo kani

# Visualize (useful after failures)
cargo kani --visualize --enable-unstable
type Number = f64;
type Vector = Vec<Complex>;

#[derive(Debug, PartialEq, Copy, Clone)]
struct Complex {
    re: Number,
    im: Number,
}
#[cfg(kani)]
#[kani::proof]
fn complex_numbers_identity() -> bool {
    let a: f64 = kani::any();
    let b: f64 = kani::any();
    let x: Complex = Complex::new(a, b);
    let additive_identity = Complex::new(0.0, 0.0);
    x.plus(&additive_identity) == x
}

complex numbers additive identity

#[cfg(kani)]
#[kani::proof]
fn two_norm_squared_positive_definiteness() -> () {
    let limit = 4000.0;
    let a: f64 = kani::any();
    let b: f64 = kani::any();
    let c: f64 = kani::any();
    let d: f64 = kani::any();
    let e: f64 = kani::any();
    let f: f64 = kani::any();
    let g: f64 = kani::any();
    let h: f64 = kani::any();
    for i in vec![a, b, c, d, e, f, g, h] {
    	kani::assume(-limit < i && i < limit);
    }
    let zero: Complex = Complex::new(0.0, 0.0);
    let x = vec![
        Complex::new(a, b),
        Complex::new(c, d),
        Complex::new(e, f),
        Complex::new(g, h),
    ];
    if two_norm_squared(x.clone()) != 0.0 {
    	assert!(x != vec![zero, zero, zero, zero]);
    }
}

vector 2-norm positive definiteness

#[cfg(kani)]
#[kani::proof]
fn two_norm_squared_nonnegative_homogeneity() -> bool {
    let limit = 4000.0;
    let value: f64 = kani::any();
    let a: f64 = kani::any();
    let b: f64 = kani::any();
    let c: f64 = kani::any();
    let d: f64 = kani::any();
    let e: f64 = kani::any();
    let f: f64 = kani::any();
    let g: f64 = kani::any();
    let h: f64 = kani::any();
    kani::assume(-limit < value && value < limit);
    for i in vec![a, b, c, d, e, f, g, h] {
    	kani::assume(-limit < i && i < limit);
    }
    let x = vec![
        Complex::new(a, b),
        Complex::new(c, d),
        Complex::new(e, f),
        Complex::new(g, h),
    ];
    let product: Vector = x.iter()
    	.map(|i| i.mult_scalar(value))
        .collect();
    two_norm_squared(product) == value.abs() * two_norm_squared(x)
}

vector 2-norm non-negative homogeneity

#[cfg(kani)]
#[kani::proof]
fn two_norm_squared_triangle_inequality() -> bool {
    let limit = 4000.0;
    let value: f64 = kani::any();
    let a: f64 = kani::any();
    let b: f64 = kani::any();
    let c: f64 = kani::any();
    let d: f64 = kani::any();
    let e: f64 = kani::any();
    let f: f64 = kani::any();
    let g: f64 = kani::any();
    let h: f64 = kani::any();
    kani::assume(-limit < value && value < limit);
    for i in vec![a, b, c, d, e, f, g, h] {
    	kani::assume(-limit < i && i < limit);
    }
    let x = vec![
        Complex::new(a, b),
        Complex::new(c, d),
        Complex::new(e, f),
        Complex::new(g, h),
    ];
    let sum: Vector = x.iter().enumerate()
    	.map(|(index, element)| element.plus(&x[index]))
        .collect();
    two_norm_squared(sum) <= two_norm_squared(x) + two_norm_squared(x)
}

vector 2-norm triangle inequality

reproduce results

# Build Docker container
docker run -it --name notebook -p 1337:1337 ghcr.io/jhwohlgemuth/lambda

# Clone GitHub project
git clone https://github.com/jhwohlgemuth/verified-linear-algebra
cd verified-linear-algebra

# Install Kani
cargo install --locked kani-verifier
cargo kani setup

# Execute proofs
cargo kani

“Reverse” code translation

"Math" world

"Real" world