Skip to content

Commit

Permalink
Reason about one block of SHA512 (#174)
Browse files Browse the repository at this point in the history
### Description:

We attempt to prove the functional correctness of SHA512 when exactly
one input block has to be hashed. The goal is to stress-test `sym_n`,
`simp_mem`, and `bv_decide`, and to determine whether we need other
kinds of automation.

### Testing:

What tests have been run? Did `make all` succeed for your changes? Was
conformance testing successful on an Aarch64 machine?

Yes.

### License:

By submitting this pull request, I confirm that my contribution is
made under the terms of the Apache 2.0 license.
  • Loading branch information
shigoel authored Sep 20, 2024
1 parent 79a5cc1 commit 43c558c
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 231 deletions.
18 changes: 15 additions & 3 deletions Arm/BitVec.lean
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ attribute [bitvec_rules] BitVec.toFin_neg
attribute [bitvec_rules] BitVec.neg_zero
attribute [bitvec_rules] BitVec.toNat_mul
attribute [bitvec_rules] BitVec.toFin_mul
attribute [bitvec_rules] BitVec.mul_zero
attribute [bitvec_rules] BitVec.mul_one
attribute [bitvec_rules] BitVec.one_mul
attribute [bitvec_rules] BitVec.toInt_mul
Expand All @@ -161,6 +162,9 @@ attribute [bitvec_rules] BitVec.ofNat_eq_ofNat
attribute [bitvec_rules] BitVec.zero_eq
attribute [bitvec_rules] BitVec.truncate_eq_zeroExtend

attribute [bitvec_rules] BitVec.add_sub_cancel
attribute [bitvec_rules] BitVec.sub_add_cancel

-- BitVec Simproc rules:
-- See Lean/Meta/Tactic/Simp/BuiltinSimprocs for the built-in
-- simprocs.
Expand Down Expand Up @@ -1040,10 +1044,18 @@ theorem extractLsBytes_zero {w : Nat} (base : Nat) :
simp only [getLsbD_extractLsBytes, Fin.is_lt, decide_True, getLsbD_zero, Bool.and_false,
implies_true]

/-- Extracting out all the bytes is equal to the bitvector. -/
/-- Extracting out all the bytes is equal to the bitvector.
The constraint on the width being `n * 8` is written as
an arbitrary `m` with a hypothesis `hm : m = n * 8`.
This is known in some circles as `fording`. See [1]
[1] https://personal.cis.strath.ac.uk/conor.mcbride/levitation.pdf
-/
@[bitvec_rules]
theorem extractLsBytes_eq_self {n : Nat} (x : BitVec (n * 8)) :
x.extractLsBytes 0 n = x := by
theorem extractLsBytes_eq_self {n : Nat} {m : Nat}
(hm : m = n * 8 := by omega)
(x : BitVec (no_index m)) :
x.extractLsBytes 0 n = x.cast hm := by
apply BitVec.eq_of_getLsbD_eq
intros i
simp only [getLsbD_extractLsBytes, Nat.zero_mul, Nat.zero_add, Nat.sub_zero]
Expand Down
21 changes: 12 additions & 9 deletions Arm/Insts/Common.lean
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ def ConditionHolds (cond : BitVec 4) (s : ArmState) : Bool :=
let V := read_flag V s
let result :=
match (extractLsb 3 1 cond) with
| 0b000#3 => Z = 1#1
| 0b001#3 => C = 1#1
| 0b010#3 => N = 1#1
| 0b011#3 => V = 1#1
| 0b100#3 => C = 1#1 ∧ Z = 0#1
| 0b101#3 => N = V
| 0b110#3 => N = V ∧ Z = 0#1
| 0b111#3 => true
| 0b000#3 => Z = 1#1 -- EQ or NE
| 0b001#3 => C = 1#1 -- CS or CC
| 0b010#3 => N = 1#1 -- MI or PL
| 0b011#3 => V = 1#1 -- VS or VC
| 0b100#3 => C = 1#1 ∧ Z = 0#1 -- HI or LS
| 0b101#3 => N = V -- GE or LT
| 0b110#3 => N = V ∧ Z = 0#1 -- GT or LE
| 0b111#3 => true -- AL
-- Condition flag values in the set 111x indicate always true
-- Otherwise, invert condition if necessary.
if (lsb cond 0) = 1#1 ∧ cond ≠ 0b1111#4 then
not result
else
Expand Down Expand Up @@ -162,6 +164,7 @@ theorem zero_iff_z_eq_one (x : BitVec 64) :

/-- `Aligned x a` witnesses that the bitvector `x` is `a`-bit aligned. -/
def Aligned (x : BitVec n) (a : Nat) : Prop :=
-- (TODO @alex) Switch to using extractLsb' to unify the two cases.
match a with
| 0 => True
| a' + 1 => extractLsb a' 0 x = BitVec.zero _
Expand Down Expand Up @@ -565,7 +568,7 @@ def rev_elems (n esize : Nat) (x : BitVec n) (h₀ : esize ∣ n) (h₁ : 0 < es
BitVec.cast h3 (element ++ rest_ans)
termination_by n

example : rev_elems 4 4 0xA#4 (by decide) (by decide) = 0xA#4 := by
example : rev_elems 4 4 0xA#4 (by decide) (by decide) = 0xA#4 := by
native_decide
example : rev_elems 8 4 0xAB#8 (by decide) (by decide) = 0xBA#8 := by native_decide
example : rev_elems 8 4 (rev_elems 8 4 0xAB#8 (by decide) (by decide))
Expand Down
6 changes: 6 additions & 0 deletions Arm/Memory/Attr.lean
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author(s): Siddharth Bhat
-/

import Lean

open Lean
Expand Down
1 change: 0 additions & 1 deletion Proofs/SHA512/SHA512.lean
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import Proofs.SHA512.SHA512_block_armv8_rules
import Proofs.SHA512.SHA512_block_armv8
import Proofs.SHA512.SHA512Sym
110 changes: 110 additions & 0 deletions Proofs/SHA512/SHA512Loop.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author(s): Shilpi Goel
-/
import Proofs.SHA512.SHA512Prelude
open BitVec

/-! ## Reasoning about the SHA512 loop
We prove that at the end of an iteration of the SHA512 loop, `sha512_loop_post`
is satisfied.
-/

namespace SHA512

-- (TODO @shilpi) Write the loop invariant.

structure sha512_loop_post
(pc nblocks sp ctx_base input_base : BitVec 64)
(si : ArmState) : Prop where
h_program : si.program = program
h_pc : r .PC si = pc
h_err : r .ERR si = .None
h_sp_aligned : CheckSPAlignment si
-- No blocks must be left unhashed by the time the loop runs to completion.
h_num_blocks : num_blocks si = 0#64
h_sp : stack_ptr si = sp - 16#64
h_ctx_base : ctx_addr si = ctx_base
h_input_base : input_addr si = input_base
-- (TODO @shilpi) The ctx must contain the hash.
-- h_ctx : si[ctx_base, 64] = SHA2.h0_512.toBitVec
h_ktbl : si[ktbl_addr, (SHA2.k_512.length * 8)] = BitVec.flatten SHA2.k_512
h_mem_sep : Memory.Region.pairwiseSeparate
[(sp - 16#64, 16),
(ctx_base, 64),
(input_base, (nblocks.toNat * 128)),
(ktbl_addr, (SHA2.k_512.length * 8))]

theorem sha512_loop_post.iff_def :
(sha512_loop_post pc nblocks sp ctx_base input_base si) ↔
(si.program = program ∧
r .PC si = pc ∧
r .ERR si = .None ∧
CheckSPAlignment si ∧
num_blocks si = 0#64
stack_ptr si = sp - 16#64
ctx_addr si = ctx_base ∧
input_addr si = input_base ∧
-- si[ctx_base, 64] = SHA2.h0_512.toBitVec ∧
si[ktbl_addr, (SHA2.k_512.length * 8)] = BitVec.flatten SHA2.k_512 ∧
Memory.Region.pairwiseSeparate
[(sp - 16#64, 16),
(ctx_base, 64),
(input_base, (nblocks.toNat * 128)),
(ktbl_addr, (SHA2.k_512.length * 8))]) := by
constructor
· intro h
obtain ⟨⟩ := h
simp only [*, minimal_theory]
· intro h
constructor <;> simp only [*, minimal_theory]
done

/- TODO: Symbolically simulate (program.length - 16 - 3) = 485 instructions
here. We elide the 16 instructions before the loop and 3 instructions after it.
Note that this would involve automatically reasoning about the conditional
branch here:
-- (0x126c90#64 , 0xb5ffc382#32) -- cbnz x2, 126500 <sha512_block_armv8+0x40>
-/
set_option linter.unusedVariables false in
set_option debug.skipKernelTC true in
-- set_option trace.Tactic.sym.heartbeats true in
-- set_option profiler true in
-- set_option profiler.threshold 1 in
set_option maxHeartbeats 0 in
-- set_option maxRecDepth 0 in
theorem sha512_block_armv8_loop_1block (si sf : ArmState)
(h_N : N = 1#64)
(h_si_prelude : sha512_prelude 0x126500#64 N SP CtxBase InputBase si)
-- TODO: Ideally, nsteps ought to be 485 to be able to simulate the loop to
-- completion.
(h_steps : nsteps = 20)
(h_run : sf = run nsteps si) :
-- (FIXME) PC should be 0x126c94#64 i.e., we are poised to execute the first
-- instruction following the loop. For now, we stop early on to remain in sync.
-- with the number of steps we simulate.
sha512_loop_post (0x126500#64 + nsteps*4)
N SP CtxBase InputBase sf := by
-- Prelude
subst h_N h_steps
obtain ⟨h_si_program, h_si_pc, h_si_err, h_si_sp_aligned,
h_si_num_blocks, h_si_sp, h_si_ctx_base,
h_si_input_base, h_si_ctx, h_si_ktbl, h_si_separate⟩ := h_si_prelude
simp only [num_blocks, ctx_addr, stack_ptr, input_addr] at *
simp only [sha512_loop_post.iff_def]
-- Symbolic Simulation
sym_n 20
-- Epilogue
-- cse (config := { processHyps := .allHyps })
simp (config := {ground := true}) only
[fst_AddWithCarry_eq_sub_neg,
ConditionHolds,
state_simp_rules,
bitvec_rules, minimal_theory]
sym_aggregate
exact ⟨h_si_ktbl, h_si_separate⟩
done

end SHA512
Loading

0 comments on commit 43c558c

Please sign in to comment.