Summer School on
Computer Aided Analysis of Cryptographic Protocols
Bucharest, Tuesday, 13 September 2016
Computing by evaluating expressions and declarations
let foo x y = (let z = y + 1 in z - y)
Recursive functions
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Immutable data structures
let rec map f xs = match xs with
| Nil -> Nil
| Cons hd tl -> Cons (f hd) (map f tl)
Unnamed functions (lambdas expressions, first-class values)
map (fun x -> x + 42) (Cons 1 (Cons 2 (Cons 3 Nil)))
avoid mutable state and other “side-effects” when possible
fold_right (+) (map incr xs) 0
this pure point-free style considered better than:
let sum = alloc 0 in
foreach xs (fun x ->
sum := !sum + x + 1
)
but of course, can't always avoid side-effects
module Hello
open FStar.IO
let main = print_string "Hello F*\n"
$ fstar.exe --no_extract ... --odir out --codegen OCaml hello.fst
Extracting module Hello
...
ocamlfind opt ... out/Hello.ml -o hello.exe
./hello.exe
Hello F*!
$ocaml
...
# 42;;
- : int = 42
# let x = 2 in x + 1;;
- : int = 3
# "Hello";;
- : string = "Hello"
# let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)));;
val factorial : int -> int = <fun>
# List.map (fun x -> x + 42) [1;2;3];;
- : int list = [43; 44; 45]
# List.map (fun x -> x + 42) ["one";"two";"three"];;
^^^
Error: This expression has type string but an expression was expected of type int
stack.fsti
module Stack
val stack : Type0
val empty : stack
val push : int -> stack -> stack
val is_empty : stack -> bool
val pop : stack -> option stack
val top : stack -> option int
stackclient.fst
module StackClient
let main =
let s0 = Stack.empty in
let s1 = Stack.push 3 s0 in
let s2 = Stack.push 4 s1 in
Stack.top s2
stack.fst
module Stack
let stack = list int
let empty = []
let push x xs = x :: xs
let is_empty xs = match xs with
| [] -> true
| x::xs' -> false
let pop xs = match xs with
| [] -> None
| x::xs' -> Some xs'
let top xs = match xs with
| [] -> None
| x::xs' -> Some x
stackclientbad.fst
module StackClientBad
let main =
let s0 = Stack.empty in
let s1 = Stack.push 3 s0 in
2 :: s1
[hritcu@detained code]$ fstar.exe stackclientbad.fst
./stackclientbad.fst(6,9-6,11) : Error
Expected expression of type "list (?32566 s0 s1)";
got expression "s1" of type "Stack.stack"
Refinement types
type nat = x:int{x>=0}
Function types (together with refinements)
val factorial : nat -> nat
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Dependent function types
val incr : x:int -> y:int{x < y}
let incr x = x + 1
Quiz: can you find other types for incr
?
The F* functions we saw so far were all pure/total functions
Tot
marker = no side-effects, terminates on all inputs
val incr : x:int -> Tot (y:int{x < y})
let incr x = x + 1
val factorial : nat -> Tot nat
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Quiz: what about giving this weak type to factorial?
val factorial : int -> Tot int
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
^^^^^
Subtyping check failed; expected type (x:int{(x << n)}); got type int
factorial (-1)
loops! (int
type in F* is infinite)
<
(negative ones unrelated)
decreases e
)
val ackermann: m:nat -> n:nat -> Tot nat (decreases %[m;n])
let rec ackermann n m =
if m=0 then n + 1
else if n = 0 then ackermann 1 (m - 1)
else ackermann (ackermann (n - 1) m) (m - 1)
val ackermann: m:nat -> n:nat -> Tot nat
We might not want to prove all code terminating
val factorial : int -> Dv int
Some useful code really is not always terminating
val eval : exp -> Dv exp
let rec eval e =
match e with
| App (Lam x e1) e2 -> eval (subst x e2 e1)
| App e1 e2 -> App (eval e1) (eval e2)
| Lam x e1 -> Lam x (eval e1)
| _ -> e
let main = eval (App (Lam 0 (App (Var 0) (Var 0)))
(Lam 0 (App (Var 0) (Var 0))))
./divergence.exe
F* encapsulates effects
val factorial : int -> Dv int
type tau = x:int{x = factorial (-1)}
^^^^^^^^^^^^^^^^^^
Expected a pure expression; got an expression ... with effect "DIV"
ML effects are implemented primitively:
ML
effect: non-termination, state, exceptions, input-output
F* automatically lifts as needed (Tot<Dv<ML
)
(1+2) + factorial (-1) : Dv int
val swap_add_sub : r1:ref int -> r2:ref int -> St unit
let swap_add_sub r1 r2 =
r1 := !r1 + !r2;
r2 := !r1 - !r2;
r1 := !r1 - !r2
let main =
let r1 = alloc 1 in
let r2 = alloc 2 in
swap_add_sub r1 r2;
print_string ("r1=" ^ string_of_int !r1 ^ "; " ^
"r2=" ^ string_of_int !r2 ^ "\n")
...
r1=2; r2=1
Exercise: write a more natural implementation for swap
val factorial : nat -> Tot nat
val factorial : x:int -> Pure int (requires (x >= 0))
(ensures (fun y -> y >= 0))
Tot
is actually just an abbreviation
Tot t = Pure t (requires True) (ensures (fun _ -> True))
Pure
, ST
)
int
)
val factorial : x:int -> Pure int (requires (x >= 0))
(ensures (fun y -> y >= 0))
Pure
, more complex for ST
and ML
val swap_add_sub : r1:ref int -> r2:ref int -> ST unit
(requires (fun h' -> r1<>r2))
(ensures (fun h' _ h ->
let x1 = sel h' r1 in
let x2 = sel h' r2 in
equal h (upd (upd h' r1 x2) r2 x1)))
let swap_add_sub r1 r2 =
r1 := !r1 + !r2;
r2 := !r1 - !r2;
r1 := !r1 - !r2
St
is actually just an abbreviation
St t = ST t (requires True) (ensures (fun _ _ _ -> True))
module FStar.Heap
val heap : Type0
val sel : #a:Type -> heap -> ref a -> Tot a
val upd : #a:Type -> heap -> ref a -> a -> Tot heap
val equal : heap -> heap -> Tot bool
assume SelUpd1 : sel (upd h r v) r == v
assume SelUpd2 : r<>r' ==> sel (upd h r v) r' == sel h r'
module FStar.ST
val (!): #a:Type -> r:ref a -> ST a
(requires (fun h -> True))
(ensures (fun h0 x h1 -> equal h0 h1 /\ x==sel h0 r))
val (:=): #a:Type -> r:ref a -> v:a -> ST unit
(requires (fun h -> True))
(ensures (fun h0 x h1 -> equal h1 (upd h0 r v)))
val factorial_tot : nat -> Tot nat
let rec factorial_tot x = if x = 0 then 1 else x * factorial_tot (x - 1)
(* TODO: write a stronger ensures clause for factorial that proves
it does the same thing as factorial_tot *)
val factorial : r1:ref nat -> r2:ref nat -> ST unit
(requires (fun h' -> True))
(ensures (fun h' a h -> True))
let rec factorial r1 r2 =
let x1 = !r1 in
if x1 = 0
then r2 := 1
else
(r1 := x1 - 1;
factorial r1 r2;
r2 := !r2 * x1)
(* global log of integers *)
val log : ref (list int)
let log = alloc []
val add_to_log : e:int -> ST unit
(requires (fun h0 -> True))
(ensures (fun h0 _ h1 ->
mem e (sel h1 log) (* p1: new element added *)
/\ (forall x. mem x (sel h0 log) ==> mem x (sel h1 log))
(* p2: all old elements preserved *)
))
let add_to_log e =
log := (e :: !log)
let main =
add_to_log 1;
let log1 = !log in
assert (mem 1 log1); (* proved by p1 *)
add_to_log 2;
let log2 = !log in
assert (mem 2 log2 (* proved by p1 *)
/\ mem 1 log2) (* proved by p2! *)
General-Purpose Functional Programming
SMT-based semi-automated verification tool
Checker of manual proofs
Reasoning about user-defined effects
Relational verification
Compiling subset of F* to C (Kremlin)
Securely compiling F* (full abstraction)
Markulf: verifying crypto implementations in F*
Want to learn functional programming?
Want to learn more about F*?
Taking this to the next level
F* team: Nikhil Swamy, Danel Ahman, Catalin Hritcu, Guido Martinez, Gordon Plotkin, Jonathan Protzenko, Aseem Rastogi, Chantal Keller, Antoine Delignat-Lavaud, Simon Forest, Karthikeyan Bhargavan, Cédric Fournet, Pierre-Yves Strub, Markulf Kohlweiss, Jean-Karim Zinzindohoue, Santiago Zanella-Béguelin, Daan Leijen, Nataliya Guts, Michael Hicks, Gavin Bierman, Clement Pit-Claudel, Perry Wang, Alfredo Pironti, Matthew Green, Chris Brzuska, Ben Dowling, …