diff --git a/README.md b/README.md
index 61d80b973..4595db3af 100644
--- a/README.md
+++ b/README.md
@@ -129,13 +129,13 @@ yours to keep:
✅ |
-❌ |
+✅ SHA extensions, AVX2, BMI1, BMI2 |
-❌ |
+✅ SHA extensions |
-❌ |
+✅ |
-❌ |
+✅ |
diff --git a/bench/benches/primitives/main.rs b/bench/benches/primitives/main.rs
index 787c57cc9..1955098e5 100644
--- a/bench/benches/primitives/main.rs
+++ b/bench/benches/primitives/main.rs
@@ -40,6 +40,7 @@ mod poly1305;
mod rc2_cbc;
mod scrypt;
mod sha1;
+mod sha224;
mod sha256;
mod sha3;
mod sha512;
@@ -211,6 +212,7 @@ const BENCHES: &[Bench] = &[
(rc2_cbc::USES, rc2_cbc::bench),
(scrypt::USES, scrypt::bench),
(sha1::USES, sha1::bench),
+ (sha224::USES, sha224::bench),
(sha256::USES, sha256::bench),
(sha3::USES, sha3::bench),
(sha512::USES, sha512::bench),
diff --git a/bench/benches/primitives/sha224.rs b/bench/benches/primitives/sha224.rs
new file mode 100644
index 000000000..67000f877
--- /dev/null
+++ b/bench/benches/primitives/sha224.rs
@@ -0,0 +1,15 @@
+//! SHA-224.
+
+use criterion::Criterion;
+use openssl::hash::MessageDigest;
+use verified_garbage::hashes::sha224::Sha224;
+
+use crate::hash_group;
+
+/// The library modules whose code these benchmarks run (see
+/// `ci/bench_arches.py`).
+pub const USES: &[&str] = &["sha224", "sha256"];
+
+pub fn bench(c: &mut Criterion) {
+ hash_group(c, "sha224", Sha224::digest, MessageDigest::sha224());
+}
diff --git a/lean/VerifiedGarbage/Artifacts/Sha224/AArch64.lean b/lean/VerifiedGarbage/Artifacts/Sha224/AArch64.lean
new file mode 100644
index 000000000..7ea87cf7b
--- /dev/null
+++ b/lean/VerifiedGarbage/Artifacts/Sha224/AArch64.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.TCB.AArch64.Target
+import VerifiedGarbage.Proof.Sha256.AArch64.Shared
+
+/-!
+# SHA-224 (FIPS 180-4) on AArch64
+
+A registration file (see `TCB/Emit.lean`): the artifacts it lists are
+emitted. **Review note**: `sig` and `doc` are trusted, as they tie the Rust
+caller to the contract; check them against the contract's `pre`/`post`. An
+artifact made from a function's `Api` (in `Spec/`, reviewed with the
+contract) takes them from there, and this file adds only notes on the
+implementation. The emitter adds the `# Safety` items that depend on the
+target (`Sig.layoutDoc`), from `stack` and `writeArgs`, which `ofSig` checks
+against the contract.
+
+SHA-224 is SHA-256 from another initial hash value: only its `init` is its
+own, and it continues with SHA-256's `update` and `finalize`
+(`Artifacts/Sha256/`).
+-/
+
+namespace VG.Artifacts.Sha224.AArch64
+
+def artifacts : List Artifact := [
+ { Spec.Sha256.init224Api with
+ target := AArch64.target
+ doc := Spec.Sha256.init224Api.doc
+ code := Impl.Sha256.AArch64.Stream.init224
+ contract := Spec.Sha256.init224Contract AArch64.abi
+ verified := Proof.Sha256.AArch64.Shared.init224
+ spSafe := Code.all_of_forall (fun _ => rfl) _ }]
+
+end VG.Artifacts.Sha224.AArch64
diff --git a/lean/VerifiedGarbage/Artifacts/Sha224/Arm.lean b/lean/VerifiedGarbage/Artifacts/Sha224/Arm.lean
new file mode 100644
index 000000000..7d0bc7d3c
--- /dev/null
+++ b/lean/VerifiedGarbage/Artifacts/Sha224/Arm.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.TCB.Arm.Target
+import VerifiedGarbage.Proof.Sha256.Arm.Shared
+
+/-!
+# SHA-224 (FIPS 180-4) on ARMv7
+
+A registration file (see `TCB/Emit.lean`): the artifacts it lists are
+emitted. **Review note**: `sig` and `doc` are trusted, as they tie the Rust
+caller to the contract; check them against the contract's `pre`/`post`. An
+artifact made from a function's `Api` (in `Spec/`, reviewed with the
+contract) takes them from there, and this file adds only notes on the
+implementation. The emitter adds the `# Safety` items that depend on the
+target (`Sig.layoutDoc`), from `stack` and `writeArgs`, which `ofSig` checks
+against the contract.
+
+SHA-224 is SHA-256 from another initial hash value: only its `init` is its
+own, and it continues with SHA-256's `update` and `finalize`
+(`Artifacts/Sha256/`).
+-/
+
+namespace VG.Artifacts.Sha224.Arm
+
+def artifacts : List Artifact := [
+ { Spec.Sha256.init224Api with
+ target := Arm.target
+ doc := Spec.Sha256.init224Api.doc
+ code := Impl.Sha256.Arm.Stream.init224
+ contract := Spec.Sha256.init224Contract Arm.abi
+ verified := Proof.Sha256.Arm.Shared.init224
+ spSafe := Code.all_of_forall (fun _ => rfl) _ }]
+
+end VG.Artifacts.Sha224.Arm
diff --git a/lean/VerifiedGarbage/Artifacts/Sha224/X86.lean b/lean/VerifiedGarbage/Artifacts/Sha224/X86.lean
new file mode 100644
index 000000000..f76753663
--- /dev/null
+++ b/lean/VerifiedGarbage/Artifacts/Sha224/X86.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.TCB.X86.Target
+import VerifiedGarbage.Proof.Sha256.X86.Shared
+
+/-!
+# SHA-224 (FIPS 180-4) on x86 (32-bit)
+
+A registration file (see `TCB/Emit.lean`): the artifacts it lists are
+emitted. **Review note**: `sig` and `doc` are trusted, as they tie the Rust
+caller to the contract; check them against the contract's `pre`/`post`. An
+artifact made from a function's `Api` (in `Spec/`, reviewed with the
+contract) takes them from there, and this file adds only notes on the
+implementation. The emitter adds the `# Safety` items that depend on the
+target (`Sig.layoutDoc`), from `stack` and `writeArgs`, which `ofSig` checks
+against the contract.
+
+SHA-224 is SHA-256 from another initial hash value: only its `init` is its
+own, and it continues with SHA-256's `update` and `finalize`
+(`Artifacts/Sha256/`).
+-/
+
+namespace VG.Artifacts.Sha224.X86
+
+def artifacts : List Artifact := [
+ { Spec.Sha256.init224Api with
+ target := X86.target
+ doc := Spec.Sha256.init224Api.doc
+ code := Impl.Sha256.X86.Stream.init224
+ contract := Spec.Sha256.init224Contract X86.abi
+ verified := Proof.Sha256.X86.Shared.init224
+ spSafe := Code.all_of_allInstrs (by lit_decide) }]
+
+end VG.Artifacts.Sha224.X86
diff --git a/lean/VerifiedGarbage/Artifacts/Sha224/X86_64.lean b/lean/VerifiedGarbage/Artifacts/Sha224/X86_64.lean
new file mode 100644
index 000000000..a81acd919
--- /dev/null
+++ b/lean/VerifiedGarbage/Artifacts/Sha224/X86_64.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.TCB.X86_64.Target
+import VerifiedGarbage.Proof.Sha256.X86_64.Shared
+
+/-!
+# SHA-224 (FIPS 180-4) on x86-64
+
+A registration file (see `TCB/Emit.lean`): the artifacts it lists are
+emitted. **Review note**: `sig` and `doc` are trusted, as they tie the Rust
+caller to the contract; check them against the contract's `pre`/`post`. An
+artifact made from a function's `Api` (in `Spec/`, reviewed with the
+contract) takes them from there, and this file adds only notes on the
+implementation. The emitter adds the `# Safety` items that depend on the
+target (`Sig.layoutDoc`), from `stack` and `writeArgs`, which `ofSig` checks
+against the contract.
+
+SHA-224 is SHA-256 from another initial hash value: only its `init` is its
+own, and it continues with SHA-256's `update` and `finalize`
+(`Artifacts/Sha256/`).
+-/
+
+namespace VG.Artifacts.Sha224.X86_64
+
+def artifacts : List Artifact := [
+ { Spec.Sha256.init224Api with
+ target := X86_64.target
+ doc := Spec.Sha256.init224Api.doc
+ code := Impl.Sha256.X86_64.Stream.init224
+ contract := Spec.Sha256.init224Contract X86_64.abi
+ verified := Proof.Sha256.X86_64.Shared.init224
+ spSafe := Code.all_of_allInstrs (by lit_decide) }]
+
+end VG.Artifacts.Sha224.X86_64
diff --git a/lean/VerifiedGarbage/Impl/Sha256/AArch64/Stream.lean b/lean/VerifiedGarbage/Impl/Sha256/AArch64/Stream.lean
index 7f0b4de97..faaa88c17 100644
--- a/lean/VerifiedGarbage/Impl/Sha256/AArch64/Stream.lean
+++ b/lean/VerifiedGarbage/Impl/Sha256/AArch64/Stream.lean
@@ -7,7 +7,7 @@ import VerifiedGarbage.Impl.MdStream.AArch64
The streaming state (96 bytes at `state`) is the hash value followed by a
64-byte buffer (see `VG.Spec.Sha256.Repr`).
-* `init(state = x0)` stores `H⁽⁰⁾`.
+* `init(state = x0)` stores `H⁽⁰⁾` (`init224`, SHA-224's).
* `update` and `finalize` are the generic streaming code
(`Impl/MdStream/AArch64.lean`), calling the compression function
(`vg_sha256_compress`) with `scratch[0..112)` as its scratch space, and
@@ -25,12 +25,19 @@ open VG.Impl.Sha256.AArch64 (compress)
/-- `mov d, n` (as `add d, n, #0`). -/
def mov (d n : Reg) : Instr := .addImm .x d n 0
-def init : Prog isa :=
+/-- Stores the initial hash value `iv`. -/
+def initWith (iv : Spec.Sha256.HashValue) : Prog isa :=
.block ((List.range 8).flatMap fun k =>
- [.movz .w .x9 (Spec.Sha256.H0[k]!.extractLsb' 0 16) 0,
- .movk .w .x9 (Spec.Sha256.H0[k]!.extractLsb' 16 16) 1,
+ [.movz .w .x9 (iv[k]!.extractLsb' 0 16) 0,
+ .movk .w .x9 (iv[k]!.extractLsb' 16 16) 1,
.str .w .x9 .x0 (4 * k)])
+/-- `vg_sha256_init`. -/
+def init : Prog isa := initWith Spec.Sha256.H0
+
+/-- `vg_sha224_init`. -/
+def init224 : Prog isa := initWith Spec.Sha256.H0_224
+
/-- The callee-saved registers we use, and where they are saved in `scratch`. -/
def saved : List (Reg × Nat) :=
[(.x19, 112), (.x20, 120), (.x21, 128), (.x22, 136), (.x23, 144), (.x24, 152)]
diff --git a/lean/VerifiedGarbage/Impl/Sha256/Arm/Stream.lean b/lean/VerifiedGarbage/Impl/Sha256/Arm/Stream.lean
index d09d79ccd..43c515dd3 100644
--- a/lean/VerifiedGarbage/Impl/Sha256/Arm/Stream.lean
+++ b/lean/VerifiedGarbage/Impl/Sha256/Arm/Stream.lean
@@ -7,7 +7,7 @@ import VerifiedGarbage.Impl.MdStream.Arm
The streaming state (96 bytes at `state`) is the hash value followed by a
64-byte buffer (see `VG.Spec.Sha256.Repr`).
-* `init(state = r0)` stores the initial hash value.
+* `init(state = r0)` stores the initial hash value (`init224`, SHA-224's).
* `update` and `finalize` are the generic streaming code
(`Impl/MdStream/Arm.lean`), calling the compression function
(`vg_sha256_compress`) with `scratch[0..112)` as its scratch space, and saving our
@@ -22,12 +22,19 @@ namespace VG.Impl.Sha256.Arm.Stream
open VG.Arm
open VG.Impl.Sha256.Arm (compress)
-def init : Prog isa :=
+/-- Stores the initial hash value `iv`. -/
+def initWith (iv : Spec.Sha256.HashValue) : Prog isa :=
.block ((List.range 8).flatMap fun k =>
- [.movw .r12 (Spec.Sha256.H0[k]!.extractLsb' 0 16),
- .movt .r12 (Spec.Sha256.H0[k]!.extractLsb' 16 16),
+ [.movw .r12 (iv[k]!.extractLsb' 0 16),
+ .movt .r12 (iv[k]!.extractLsb' 16 16),
.str .r12 .r0 (4 * k)])
+/-- `vg_sha256_init`. -/
+def init : Prog isa := initWith Spec.Sha256.H0
+
+/-- `vg_sha224_init`. -/
+def init224 : Prog isa := initWith Spec.Sha256.H0_224
+
/-- The callee-saved registers we use (and `lr`), and where they are saved in `scratch`. -/
def saved : List (Reg × Nat) :=
[(.r4, 112), (.r5, 116), (.r6, 120), (.r7, 124), (.r8, 128), (.r9, 132), (.r10, 136), (.r11, 140),
diff --git a/lean/VerifiedGarbage/Impl/Sha256/X86/Stream.lean b/lean/VerifiedGarbage/Impl/Sha256/X86/Stream.lean
index 41e1c3f15..e4f755809 100644
--- a/lean/VerifiedGarbage/Impl/Sha256/X86/Stream.lean
+++ b/lean/VerifiedGarbage/Impl/Sha256/X86/Stream.lean
@@ -8,7 +8,7 @@ The streaming state (96 bytes at `state`) is the hash value followed by a
64-byte buffer (see `VG.Spec.Sha256.Repr`). Every argument is on the stack
(cdecl).
-* `init(state)` stores `H⁽⁰⁾`.
+* `init(state)` stores `H⁽⁰⁾` (`init224`, SHA-224's).
* `update(state, count, data, len, scratch)` is the generic streaming code
(`Impl/MdStream/X86.lean`): it compresses every whole block left in `data`
with one call if the buffer is empty, and otherwise copies bytes into the
@@ -33,10 +33,17 @@ namespace VG.Impl.Sha256.X86.Stream
open VG.X86
open VG.Impl.Sha256.X86 (at_ compress)
-def init : Prog isa :=
+/-- Stores the initial hash value `iv`. -/
+def initWith (iv : Spec.Sha256.HashValue) : Prog isa :=
.seq (.block [.mov .eax (.mem (at_ .esp 4))])
(.block ((List.range 8).flatMap fun k =>
- [.mov .ecx (.imm Spec.Sha256.H0[k]!), .store (at_ .eax (4 * k)) .ecx]))
+ [.mov .ecx (.imm iv[k]!), .store (at_ .eax (4 * k)) .ecx]))
+
+/-- `vg_sha256_init`. -/
+def init : Prog isa := initWith Spec.Sha256.H0
+
+/-- `vg_sha224_init`. -/
+def init224 : Prog isa := initWith Spec.Sha256.H0_224
/-- The callee-saved registers, and where they are saved in `scratch`. -/
def saved : List (Reg × Nat) := [(.ebx, 112), (.esi, 116), (.edi, 120), (.ebp, 124)]
diff --git a/lean/VerifiedGarbage/Impl/Sha256/X86_64/Stream.lean b/lean/VerifiedGarbage/Impl/Sha256/X86_64/Stream.lean
index cd982f3d8..ac17f576d 100644
--- a/lean/VerifiedGarbage/Impl/Sha256/X86_64/Stream.lean
+++ b/lean/VerifiedGarbage/Impl/Sha256/X86_64/Stream.lean
@@ -9,7 +9,7 @@ import VerifiedGarbage.Impl.MdStream.X86_64
The streaming state (96 bytes at `state`) is the hash value followed by a
64-byte buffer (see `VG.Spec.Sha256.Repr`).
-* `init(state = rdi)` stores `H⁽⁰⁾`.
+* `init(state = rdi)` stores `H⁽⁰⁾` (`init224`, SHA-224's).
* `update(state = rdi, count = rsi, data = rdx, len = rcx, scratch = r8)`
processes one block per iteration: straight from `data` while the buffer is
empty and a whole block remains, otherwise by copying bytes into the buffer,
@@ -42,9 +42,16 @@ def Callee.scalar : Callee := ⟨"vg_sha256_compress", compress⟩
def Callee.shani : Callee := ⟨"vg_sha256_compress_shani", ShaNi.compress⟩
def Callee.avx2 : Callee := ⟨"vg_sha256_compress_avx2", Avx2.compress⟩
-def init : Prog isa :=
+/-- Stores the initial hash value `iv`. -/
+def initWith (iv : Spec.Sha256.HashValue) : Prog isa :=
.block ((List.range 8).flatMap fun k =>
- [.mov32 .rax (.imm Spec.Sha256.H0[k]!), .store32 (at_ .rdi (4 * k)) .rax])
+ [.mov32 .rax (.imm iv[k]!), .store32 (at_ .rdi (4 * k)) .rax])
+
+/-- `vg_sha256_init`. -/
+def init : Prog isa := initWith Spec.Sha256.H0
+
+/-- `vg_sha224_init`. -/
+def init224 : Prog isa := initWith Spec.Sha256.H0_224
/-- The callee-saved registers, and where they are saved in `scratch`. -/
def saved : List (Reg × Nat) := [(.rbx, 560), (.rbp, 568), (.r12, 576), (.r13, 584), (.r14, 592), (.r15, 600)]
diff --git a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Contract.lean b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Contract.lean
index 8666c554a..63b8f8f35 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Contract.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Contract.lean
@@ -41,15 +41,16 @@ def compressAArch64 : Contract AArch64.isa where
s₁.gpr .x2 = s₂.gpr .x2 ∧ s₁.gpr .x3 = s₂.gpr .x3 ∧ s₁.sp = s₂.sp
open AArch64 in
-/-- AArch64 contract for `vg_sha256_init(state: *mut [u8; 96])`: makes the
-streaming state at `state` represent the empty message.
+/-- AArch64 contract for `vg_sha256_init(state: *mut [u8; 96])` and
+`vg_sha224_init`, which store the initial hash value `iv`: makes the
+streaming state at `state` represent the empty message, hashed from `iv`.
The code may write `state` (96 bytes). The pointer is public. -/
-def initAArch64 : Contract AArch64.isa where
+def initAArch64 (iv : HashValue) : Contract AArch64.isa where
pre s :=
let state : Region := ⟨s.gpr .x0, 96⟩
s.rd = [] ∧ s.wr = [state]
- post s s' := Repr s'.mem (s.gpr .x0) []
+ post s s' := ReprFrom iv s'.mem (s.gpr .x0) []
pub s₁ s₂ := s₁.gpr .x0 = s₂.gpr .x0 ∧ s₁.sp = s₂.sp
open AArch64 in
diff --git a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Shared.lean b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Shared.lean
index 8bcb452c4..24bbb714a 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Shared.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Shared.lean
@@ -117,6 +117,13 @@ theorem init :
AArch64.abi, AArch64.argRegs]
[Proof.Sha256.AArch64.Stream.initSat] using Proof.Sha256.AArch64.Stream.initSat)
+theorem init224 :
+ Verified AArch64.target Impl.Sha256.AArch64.Stream.init224 (Spec.Sha256.init224Contract AArch64.abi) :=
+ Proof.Sha256.AArch64.Stream.init224_verified.of_implies (by
+ contract_implies [Spec.Sha256.init224Contract, Spec.Sha256.initSig, Proof.Sha256.initAArch64,
+ AArch64.abi, AArch64.argRegs]
+ [Proof.Sha256.AArch64.Stream.initSat] using Proof.Sha256.AArch64.Stream.initSat)
+
theorem update_of {code : Prog isa} (hv : Verified AArch64.target code Proof.Sha256.updateAArch64) :
Verified AArch64.target code (Spec.Sha256.updateContract AArch64.abi 16) := by
have hi : updateWide.Implies (Spec.Sha256.updateContract AArch64.abi 16) := by
diff --git a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Stream/Init.lean b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Stream/Init.lean
index d13000f82..60a77b3bb 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/AArch64/Stream/Init.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/AArch64/Stream/Init.lean
@@ -18,8 +18,10 @@ open VG.Spec.Sha256 (stateAt H0)
def word (x : BitVec 32) (off : Nat) : List Instr :=
[.movz .w .x9 (x.extractLsb' 0 16) 0, .movk .w .x9 (x.extractLsb' 16 16) 1, .str .w .x9 .x0 off]
-theorem init_eq : init = .block (word H0[0] 0 ++ word H0[1] 4 ++ word H0[2] 8 ++ word H0[3] 12 ++
- word H0[4] 16 ++ word H0[5] 20 ++ word H0[6] 24 ++ word H0[7] 28) := rfl
+variable (iv : Spec.Sha256.HashValue)
+
+theorem initWith_eq : initWith iv = .block (word iv[0] 0 ++ word iv[1] 4 ++ word iv[2] 8 ++ word iv[3] 12 ++
+ word iv[4] 16 ++ word iv[5] 20 ++ word iv[6] 24 ++ word iv[7] 28) := rfl
/-- `movz` of the low half then `movk` of the high half, then a 32-bit store. -/
theorem movzk (x : BitVec 32) :
@@ -49,13 +51,13 @@ theorem word_ok {x : BitVec 32} {off : Nat} (ho : off % 4 = 0 ∧ off < 16384) {
congr 1
exact movzk x
-theorem init_correct {s₀ : State} (hp : Proof.Sha256.initAArch64.pre s₀) :
- WP isa init s₀ fun s' => abiPreserved s₀ s' ∧ Proof.Sha256.initAArch64.post s₀ s' := by
- apply WP.withPreservedV (hc := by decide +kernel)
+theorem init_correct {s₀ : State} (hp : (Proof.Sha256.initAArch64 iv).pre s₀) :
+ WP isa (initWith iv) s₀ fun s' => abiPreserved s₀ s' ∧ (Proof.Sha256.initAArch64 iv).post s₀ s' := by
+ apply WP.withPreservedV (hc := by rw [initWith_eq]; rfl)
obtain ⟨-, hwr⟩ := hp
have o : ∀ k, k < 8 → InRegions s₀.wr (s₀.gpr .x0 + BitVec.ofNat 64 (4 * k)) 4 :=
fun k hk => ⟨⟨s₀.gpr .x0, 96⟩, by simp [hwr], contains_offset (by omega) (by omega)⟩
- rw [init_eq, ← List.append_nil (_ ++ word H0[7] 28)]
+ rw [initWith_eq, ← List.append_nil (_ ++ word iv[7] 28)]
simp only [List.append_assoc]
refine word_ok (by decide) (o 0 (by omega)) fun s1 g1 _ wr1 sp1 m1 => ?_
refine word_ok (by decide) (by rw [wr1, g1 _ (by decide)]; exact o 1 (by omega))
@@ -80,7 +82,7 @@ theorem init_correct {s₀ : State} (hp : Proof.Sha256.initAArch64.pre s₀) :
WP.block_nil ?_
have k8 : ∀ r, r ≠ .x9 → s8.gpr r = s₀.gpr r := fun r h => by
rw [g8 r h, g7 r h, g6 r h, g5 r h, k4 r h]
- have hm : s8.mem = writeState s₀.mem (s₀.gpr .x0) H0 := by
+ have hm : s8.mem = writeState s₀.mem (s₀.gpr .x0) iv := by
rw [m8, m7, m6, m5, m4, m3, m2, m1]
simp only [g7 _ (show Reg.x0 ≠ .x9 by decide), g6 _ (show Reg.x0 ≠ .x9 by decide),
g5 _ (show Reg.x0 ≠ .x9 by decide), k4 _ (show Reg.x0 ≠ .x9 by decide),
@@ -90,9 +92,9 @@ theorem init_correct {s₀ : State} (hp : Proof.Sha256.initAArch64.pre s₀) :
refine ⟨⟨fun r hr => k8 r ?_, by rw [sp8, sp7, sp6, sp5, sp4, sp3, sp2, sp1]⟩, ?_⟩
· simp only [preserved, List.mem_cons, List.not_mem_nil, or_false] at hr
rcases hr with rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl <;> decide
- · show Spec.Sha256.Repr s8.mem (s₀.gpr .x0) []
+ · show Spec.Sha256.ReprFrom iv s8.mem (s₀.gpr .x0) []
rw [hm]
- exact Proof.Sha256.Stream.repr_nil (stateAt_writeState _ _ _)
+ exact Proof.Sha256.Stream.reprFrom_nil (stateAt_writeState _ _ _)
/-- A state satisfying the precondition. -/
def initSat : State where
@@ -103,14 +105,23 @@ def initSat : State where
rd := []
wr := [⟨0x1000, 96⟩]
-theorem init_verified : Verified AArch64.target init Proof.Sha256.initAArch64 := by
+/-- `initWith iv` is verified, given that the taint analysis, which the
+kernel can only run on a literal `iv`, accepts it. -/
+theorem initWith_verified {hc} (hct : (taint.check (Taint.ofRegs [.x0]) (initWith iv) hc).isSome = true) :
+ Verified AArch64.target (initWith iv) (Proof.Sha256.initAArch64 iv) := by
refine ⟨fun s hs => ?_, ?_, ⟨initSat, rfl, rfl⟩⟩
- · obtain ⟨t, s', he, h⟩ := init_correct hs
+ · obtain ⟨t, s', he, h⟩ := init_correct iv hs
exact ⟨t, s', he, h⟩
- · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.x0]) ?_ (by taint_decide)
+ · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.x0]) ?_ hct
intro s₁ s₂ _ _ h
refine ⟨h.2, fun r hr => ?_⟩
simp only [VG.AArch64.Taint.mem_ofRegs, List.mem_cons, List.not_mem_nil, or_false] at hr
subst hr; exact h.1
+theorem init_verified : Verified AArch64.target init (Proof.Sha256.initAArch64 H0) :=
+ initWith_verified _ (hct := by taint_decide)
+
+theorem init224_verified : Verified AArch64.target init224 (Proof.Sha256.initAArch64 Spec.Sha256.H0_224) :=
+ initWith_verified _ (hct := by taint_decide)
+
end VG.Proof.Sha256.AArch64.Stream
diff --git a/lean/VerifiedGarbage/Proof/Sha256/Arm/Contract.lean b/lean/VerifiedGarbage/Proof/Sha256/Arm/Contract.lean
index 883dc5d61..d6709668a 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/Arm/Contract.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/Arm/Contract.lean
@@ -49,16 +49,17 @@ low word in `r2`). -/
def countArm (s : Arm.State) : BitVec 64 := s.gpr .r3 ++ s.gpr .r2
open Arm in
-/-- 32-bit ARM contract for `vg_sha256_init(state: *mut [u8; 96])`: makes the
-streaming state at `state` represent the empty message.
+/-- 32-bit ARM contract for `vg_sha256_init(state: *mut [u8; 96])` and
+`vg_sha224_init`, which store the initial hash value `iv`: makes the
+streaming state at `state` represent the empty message, hashed from `iv`.
The code may write `state` (96 bytes), which may not wrap around the end of
the (32-bit) address space. The pointer is public. -/
-def initArm : Contract Arm.isa where
+def initArm (iv : HashValue) : Contract Arm.isa where
pre s :=
let state : Region := ⟨State.addr (s.gpr .r0), 96⟩
s.rd = [] ∧ s.wr = [state] ∧ (s.gpr .r0).toNat + 96 ≤ 2 ^ 32
- post s s' := Repr s'.mem (State.addr (s.gpr .r0)) []
+ post s s' := ReprFrom iv s'.mem (State.addr (s.gpr .r0)) []
pub s₁ s₂ := s₁.gpr .r0 = s₂.gpr .r0
open Arm in
diff --git a/lean/VerifiedGarbage/Proof/Sha256/Arm/Shared.lean b/lean/VerifiedGarbage/Proof/Sha256/Arm/Shared.lean
index 5fa4e06aa..d849b9a3f 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/Arm/Shared.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/Arm/Shared.lean
@@ -151,6 +151,13 @@ theorem init : Verified Arm.target Impl.Sha256.Arm.Stream.init (Spec.Sha256.init
[Proof.Sha256.Arm.Stream.initSat, Arm.stackArg, Arm.stackArgAddr, Mem.readW,
Mem.read] using Proof.Sha256.Arm.Stream.initSat)
+theorem init224 : Verified Arm.target Impl.Sha256.Arm.Stream.init224 (Spec.Sha256.init224Contract Arm.abi) :=
+ Proof.Sha256.Arm.Stream.init224_verified.of_implies (by
+ contract_implies [Spec.Sha256.init224Contract, Spec.Sha256.initSig, Proof.Sha256.initArm, Arm.abi,
+ Arm.argRegs, Arm.reduceClassify, Arm.Loc.val, Arm.State.addr]
+ [Proof.Sha256.Arm.Stream.initSat, Arm.stackArg, Arm.stackArgAddr, Mem.readW,
+ Mem.read] using Proof.Sha256.Arm.Stream.initSat)
+
theorem updateWide_implies : updateWide.Implies (Spec.Sha256.updateContract Arm.abi) := by
contract_implies [Spec.Sha256.updateContract, Spec.Sha256.updateSig, updateWide,
Proof.Sha256.updateArm, Proof.Sha256.countArm, Arm.abi, Arm.argRegs, Arm.reduceClassify,
diff --git a/lean/VerifiedGarbage/Proof/Sha256/Arm/Stream/Init.lean b/lean/VerifiedGarbage/Proof/Sha256/Arm/Stream/Init.lean
index 49dc003a3..4edfd7276 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/Arm/Stream/Init.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/Arm/Stream/Init.lean
@@ -19,8 +19,10 @@ open VG.Spec.Sha256 (stateAt H0)
def word (x : BitVec 32) (off : Nat) : List Instr :=
[.movw .r12 (x.extractLsb' 0 16), .movt .r12 (x.extractLsb' 16 16), .str .r12 .r0 off]
-theorem init_eq : init = .block (word H0[0] 0 ++ word H0[1] 4 ++ word H0[2] 8 ++ word H0[3] 12 ++
- word H0[4] 16 ++ word H0[5] 20 ++ word H0[6] 24 ++ word H0[7] 28) := rfl
+variable (iv : Spec.Sha256.HashValue)
+
+theorem initWith_eq : initWith iv = .block (word iv[0] 0 ++ word iv[1] 4 ++ word iv[2] 8 ++ word iv[3] 12 ++
+ word iv[4] 16 ++ word iv[5] 20 ++ word iv[6] 24 ++ word iv[7] 28) := rfl
theorem word_ok {x : BitVec 32} {off : Nat} (ho : off < 4096) {rest : List Instr}
{s : State} {Q : State → Prop} (hfit : (s.gpr .r0).toNat + off < 2 ^ 32)
@@ -35,12 +37,12 @@ theorem word_ok {x : BitVec 32} {off : Nat} (ho : off < 4096) {rest : List Instr
rw [u.mem]
simp only [State.setReg, ite_true, movw_movt]
-theorem init_correct {s₀ : State} (hp : Proof.Sha256.initArm.pre s₀) :
- WP isa init s₀ fun s' => abiPreserved s₀ s' ∧ Proof.Sha256.initArm.post s₀ s' := by
+theorem init_correct {s₀ : State} (hp : (Proof.Sha256.initArm iv).pre s₀) :
+ WP isa (initWith iv) s₀ fun s' => abiPreserved s₀ s' ∧ (Proof.Sha256.initArm iv).post s₀ s' := by
obtain ⟨-, hwr, hfit⟩ := hp
have o : ∀ k, k < 8 → InRegions s₀.wr (State.addr (s₀.gpr .r0) + BitVec.ofNat 64 (4 * k)) 4 :=
fun k hk => ⟨⟨State.addr (s₀.gpr .r0), 96⟩, by simp [hwr], contains_offset (by omega) (by omega)⟩
- rw [init_eq, ← List.append_nil (_ ++ word H0[7] 28)]
+ rw [initWith_eq, ← List.append_nil (_ ++ word iv[7] 28)]
simp only [List.append_assoc]
refine word_ok (by decide) (by omega) (o 0 (by omega)) fun s1 g1 _ wr1 sp1 m1 => ?_
have k1 : s1.gpr .r0 = s₀.gpr .r0 := g1 _ (by decide)
@@ -71,15 +73,15 @@ theorem init_correct {s₀ : State} (hp : Proof.Sha256.initArm.pre s₀) :
fun s8 g8 _ _ sp8 m8 => WP.block_nil ?_
have k8 : ∀ r, r ≠ .r12 → s8.gpr r = s₀.gpr r := fun r h => by
rw [g8 r h, g7 r h, g6 r h, g5 r h, g4 r h, g3 r h, g2 r h, g1 r h]
- have hm : s8.mem = Proof.Sha256.StateMem.writeState s₀.mem (State.addr (s₀.gpr .r0)) H0 := by
+ have hm : s8.mem = Proof.Sha256.StateMem.writeState s₀.mem (State.addr (s₀.gpr .r0)) iv := by
rw [m8, m7, m6, m5, m4, m3, m2, m1, k7, k6, k5, k4, k3, k2, k1]
rfl
refine ⟨⟨fun r hr => k8 r ?_, by rw [sp8, sp7, sp6, sp5, sp4, sp3, sp2, sp1]⟩, ?_⟩
· simp only [preserved, List.mem_cons, List.not_mem_nil, or_false] at hr
rcases hr with rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl <;> decide
- · show Spec.Sha256.Repr s8.mem (State.addr (s₀.gpr .r0)) []
+ · show Spec.Sha256.ReprFrom iv s8.mem (State.addr (s₀.gpr .r0)) []
rw [hm]
- exact Proof.Sha256.Stream.repr_nil (Proof.Sha256.StateMem.stateAt_writeState _ _ _)
+ exact Proof.Sha256.Stream.reprFrom_nil (Proof.Sha256.StateMem.stateAt_writeState _ _ _)
/-- A state satisfying the precondition. -/
def initSat : State where
@@ -94,11 +96,20 @@ def initSat : State where
rd := []
wr := [⟨0x1000, 96⟩]
-theorem init_verified : Verified Arm.target init Proof.Sha256.initArm := by
+/-- `initWith iv` is verified, given that the taint analysis, which the
+kernel can only run on a literal `iv`, accepts it. -/
+theorem initWith_verified {hc} (hct : (taint.check (Taint.ofRegs [.r0]) (initWith iv) hc).isSome = true) :
+ Verified Arm.target (initWith iv) (Proof.Sha256.initArm iv) := by
refine ⟨fun s hs => ?_, ?_, ⟨initSat, rfl, rfl, by decide⟩⟩
- · obtain ⟨t, s', he, h⟩ := init_correct hs
+ · obtain ⟨t, s', he, h⟩ := init_correct iv hs
exact ⟨t, s', he, h⟩
- · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.r0]) (fun _ _ _ _ hp => ?_) (by taint_decide)
+ · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.r0]) (fun _ _ _ _ hp => ?_) hct
exact Taint.agree_ofRegs fun r hr => by simp only [List.mem_singleton] at hr; subst hr; exact hp
+theorem init_verified : Verified Arm.target init (Proof.Sha256.initArm H0) :=
+ initWith_verified _ (hct := by taint_decide)
+
+theorem init224_verified : Verified Arm.target init224 (Proof.Sha256.initArm Spec.Sha256.H0_224) :=
+ initWith_verified _ (hct := by taint_decide)
+
end VG.Proof.Sha256.Arm.Stream
diff --git a/lean/VerifiedGarbage/Proof/Sha256/Stream.lean b/lean/VerifiedGarbage/Proof/Sha256/Stream.lean
index b1fa38fe3..e48b706b0 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/Stream.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/Stream.lean
@@ -113,8 +113,12 @@ theorem bytesAt_writeBytes (m : Mem) (p : Addr) (r : Nat) (xs : List Byte) (h :
/-! ## `Repr` -/
-theorem repr_nil {mem : Mem} {p : Addr} (h : stateAt mem p = H0) : Spec.Sha256.Repr mem p [] := by
- simp [Spec.Sha256.Repr, Spec.Sha256.ReprFrom, h, compressList_zero, bytesAt]
+theorem reprFrom_nil {iv : HashValue} {mem : Mem} {p : Addr} (h : stateAt mem p = iv) :
+ Spec.Sha256.ReprFrom iv mem p [] := by
+ simp [Spec.Sha256.ReprFrom, h, compressList_zero, bytesAt]
+
+theorem repr_nil {mem : Mem} {p : Addr} (h : stateAt mem p = H0) : Spec.Sha256.Repr mem p [] :=
+ reprFrom_nil h
/-- Appending bytes that stay within the buffer. -/
theorem repr_append_buf {mem mem' : Mem} {p : Addr} {m xs : List Byte} (hr : Spec.Sha256.Repr mem p m)
diff --git a/lean/VerifiedGarbage/Proof/Sha256/X86/Contract.lean b/lean/VerifiedGarbage/Proof/Sha256/X86/Contract.lean
index 9e9b7e509..b7f6a4c3c 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/X86/Contract.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/X86/Contract.lean
@@ -60,22 +60,23 @@ and 2 (cdecl: the low word first). -/
def countX86 (s : X86.State) : BitVec 64 := arg s 2 ++ arg s 1
open X86 in
-/-- x86 (32-bit) contract for `vg_sha256_init(state: *mut [u8; 96])`, whose
+/-- x86 (32-bit) contract for `vg_sha256_init(state: *mut [u8; 96])` and
+`vg_sha224_init`, which store the initial hash value `iv`, whose
argument is on the stack (cdecl): makes the streaming state at `state`
-represent the empty message.
+represent the empty message, hashed from `iv`.
The code may read the argument (4 bytes above the return address) and write
`state` (96 bytes), which may not overlap the argument or the return address;
nothing may wrap around the end of the (32-bit) address space. `esp` and the
pointer are public. -/
-def initX86 : Contract X86.isa where
+def initX86 (iv : HashValue) : Contract X86.isa where
pre s :=
let state : Region := ⟨(arg s 0).setWidth 64, 96⟩
let args : Region := ⟨argAddr s 0, 4⟩
let ret : Region := ⟨(s.gpr .esp).setWidth 64, 4⟩
s.rd = [args] ∧ s.wr = [state] ∧ args.Disjoint state ∧ ret.Disjoint state ∧
(arg s 0).toNat + 96 ≤ 2 ^ 32 ∧ (s.gpr .esp).toNat + 8 ≤ 2 ^ 32
- post s s' := Repr s'.mem ((arg s 0).setWidth 64) []
+ post s s' := ReprFrom iv s'.mem ((arg s 0).setWidth 64) []
pub s₁ s₂ := s₁.gpr .esp = s₂.gpr .esp ∧ arg s₁ 0 = arg s₂ 0
open X86 in
diff --git a/lean/VerifiedGarbage/Proof/Sha256/X86/Shared.lean b/lean/VerifiedGarbage/Proof/Sha256/X86/Shared.lean
index d15d84ac9..706316ad0 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/X86/Shared.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/X86/Shared.lean
@@ -27,9 +27,11 @@ open VG.Spec.Sha256 (stateAt H0)
/-- The two instructions storing the word `x` at `[eax + 4 * k]`. -/
def word (x : BitVec 32) (k : Nat) : List Instr := [.mov .ecx (.imm x), .store (at_ .eax (4 * k)) .ecx]
-theorem init_eq : init = .seq (.block [.mov .eax (.mem (at_ .esp 4))])
- (.block (word H0[0] 0 ++ word H0[1] 1 ++ word H0[2] 2 ++ word H0[3] 3 ++ word H0[4] 4 ++
- word H0[5] 5 ++ word H0[6] 6 ++ word H0[7] 7)) := rfl
+variable (iv : Spec.Sha256.HashValue)
+
+theorem initWith_eq : initWith iv = .seq (.block [.mov .eax (.mem (at_ .esp 4))])
+ (.block (word iv[0] 0 ++ word iv[1] 1 ++ word iv[2] 2 ++ word iv[3] 3 ++ word iv[4] 4 ++
+ word iv[5] 5 ++ word iv[6] 6 ++ word iv[7] 7)) := rfl
theorem word_ok {x : BitVec 32} {k : Nat} {rest : List Instr} {s : State} {Q : State → Prop}
{st : BitVec 32} (heax : s.gpr .eax = st) (hout : InRegions s.wr (addr st (4 * k)) 4)
@@ -43,18 +45,18 @@ theorem word_ok {x : BitVec 32} {k : Nat} {rest : List Instr} {s : State} {Q : S
(by rw [u₂.rd, u₁.rd]) (by rw [u₂.wr, u₁.wr]) ?_
rw [u₂.mem, u₁.gpr, u₁.mem]
-theorem init_correct {s₀ : State} (hp : Proof.Sha256.initX86.pre s₀) :
- WP isa init s₀ fun s' => abiPreserved s₀ s' ∧ Proof.Sha256.initX86.post s₀ s' := by
+theorem init_correct {s₀ : State} (hp : (Proof.Sha256.initX86 iv).pre s₀) :
+ WP isa (initWith iv) s₀ fun s' => abiPreserved s₀ s' ∧ (Proof.Sha256.initX86 iv).post s₀ s' := by
obtain ⟨hrd, hwr, hargs, hret, hfit, hsp⟩ := hp
set st := arg s₀ 0 with hst
have o : ∀ k, k < 8 → InRegions s₀.wr (addr st (4 * k)) 4 :=
fun k hk => ⟨⟨st.setWidth 64, 96⟩, by simp [hwr], contains_addr (by omega) (by omega) hfit⟩
- rw [init_eq]
+ rw [initWith_eq]
refine WP.seq (wp_movm (a := addr (s₀.gpr .esp) 4) (ea_at _ _ _)
⟨⟨argAddr s₀ 0, 4⟩, by simp [hrd], Region.contains_self _ _⟩ fun s₁ u₁ => WP.block_nil ?_)
have e1 : s₁.gpr .eax = st := u₁.gpr
have w1 : s₁.wr = s₀.wr := u₁.wr
- rw [← List.append_nil (_ ++ word H0[7] 7)]
+ rw [← List.append_nil (_ ++ word iv[7] 7)]
simp only [List.append_assoc]
refine word_ok e1 (by rw [w1]; exact o 0 (by omega)) fun s2 a2 g2 _ wr2 m2 => ?_
refine word_ok a2 (by rw [wr2, w1]; exact o 1 (by omega)) fun s3 a3 g3 _ wr3 m3 => ?_
@@ -71,7 +73,7 @@ theorem init_correct {s₀ : State} (hp : Proof.Sha256.initX86.pre s₀) :
rw [g9 r h, g8 r h, g7 r h, g6 r h, g5 r h, g4 r h, g3 r h, g2 r h, u₁.other r h']
have ha : ∀ k, k < 8 → addr st (4 * k) = st.setWidth 64 + BitVec.ofNat 64 (4 * k) :=
fun k hk => addr_eq (by omega)
- have hm : s9.mem = Proof.Sha256.StateMem.writeState s₀.mem (st.setWidth 64) H0 := by
+ have hm : s9.mem = Proof.Sha256.StateMem.writeState s₀.mem (st.setWidth 64) iv := by
rw [m9, m8, m7, m6, m5, m4, m3, m2, u₁.mem, ha 0 (by omega), ha 1 (by omega), ha 2 (by omega),
ha 3 (by omega), ha 4 (by omega), ha 5 (by omega), ha 6 (by omega), ha 7 (by omega)]
rfl
@@ -91,9 +93,9 @@ theorem init_correct {s₀ : State} (hp : Proof.Sha256.initX86.pre s₀) :
· simp only [calleeSaved, List.mem_cons, List.not_mem_nil, or_false] at hr
rcases hr with rfl | rfl | rfl | rfl | rfl <;> decide
· exact hf.readW (Region.contains_self _ _) (by simpa using hret) (by decide)
- · show Spec.Sha256.Repr s9.mem (st.setWidth 64) []
+ · show Spec.Sha256.ReprFrom iv s9.mem (st.setWidth 64) []
rw [hm]
- exact Proof.Sha256.Stream.repr_nil (Proof.Sha256.StateMem.stateAt_writeState _ _ _)
+ exact Proof.Sha256.Stream.reprFrom_nil (Proof.Sha256.StateMem.stateAt_writeState _ _ _)
/-- Memory holding the argument `0x1000` at `0x4004`. -/
def initSatMem : Mem := fun a => if a = 0x4005 then 0x10 else 0
@@ -110,7 +112,7 @@ def initSat : State where
rd := [⟨0x4004, 4⟩]
wr := [⟨0x1000, 96⟩]
-theorem initSat_pre : Proof.Sha256.initX86.pre initSat := by
+theorem initSat_pre : (Proof.Sha256.initX86 iv).pre initSat := by
have a0 : arg initSat 0 = 0x1000 := by decide
have e : argAddr initSat 0 = 0x4004 := by decide
simp only [Proof.Sha256.initX86, a0, e]
@@ -120,10 +122,11 @@ theorem initSat_pre : Proof.Sha256.initX86.pre initSat := by
is the base address of the writable region. -/
def initτ₀ : VG.X86.Taint.T := { regs := .ofList [.esp], flags := false, argLen := 8 }
-theorem init_agree₀ {s₁ s₂ : State} (h₁ : Proof.Sha256.initX86.pre s₁) (h₂ : Proof.Sha256.initX86.pre s₂)
- (hpub : Proof.Sha256.initX86.pub s₁ s₂) : VG.X86.Taint.Agree initτ₀ s₁ s₂ := by
+theorem init_agree₀ {s₁ s₂ : State} (h₁ : (Proof.Sha256.initX86 iv).pre s₁)
+ (h₂ : (Proof.Sha256.initX86 iv).pre s₂) (hpub : (Proof.Sha256.initX86 iv).pub s₁ s₂) :
+ VG.X86.Taint.Agree initτ₀ s₁ s₂ := by
obtain ⟨hesp, a0⟩ := hpub
- have wf : ∀ s, Proof.Sha256.initX86.pre s → VG.X86.Taint.Wf initτ₀ s := by
+ have wf : ∀ s, (Proof.Sha256.initX86 iv).pre s → VG.X86.Taint.Wf initτ₀ s := by
intro s hs
obtain ⟨-, hw, hd, hr, -, hsp⟩ := hs
refine VG.X86.Taint.Wf.entry rfl rfl ⟨fun h => absurd rfl h, fun _ h => (List.not_mem_nil h).elim,
@@ -143,12 +146,20 @@ theorem init_agree₀ {s₁ s₂ : State} (h₁ : Proof.Sha256.initX86.pre s₁)
show (k - 4) / 4 = 0 by omega]
exact congrArg _ a0
-theorem init_verified : Verified X86.target init Proof.Sha256.initX86 := by
- refine ⟨fun s hs => ?_, ?_, ⟨initSat, initSat_pre⟩⟩
- · obtain ⟨t, s', he, h⟩ := init_correct hs
+/-- `initWith iv` is verified, given that the taint analysis, which the
+kernel can only run on a literal `iv`, accepts it. -/
+theorem initWith_verified {hc} (hct : (taint.check initτ₀ (initWith iv) hc).isSome = true) :
+ Verified X86.target (initWith iv) (Proof.Sha256.initX86 iv) := by
+ refine ⟨fun s hs => ?_, ?_, ⟨initSat, initSat_pre iv⟩⟩
+ · obtain ⟨t, s', he, h⟩ := init_correct iv hs
exact ⟨t, s', he, h⟩
- · exact VG.Taint.constantTime (A := taint) initτ₀ (fun _ _ h₁ h₂ hp => init_agree₀ h₁ h₂ hp)
- (by taint_decide)
+ · exact VG.Taint.constantTime (A := taint) initτ₀ (fun _ _ h₁ h₂ hp => init_agree₀ iv h₁ h₂ hp) hct
+
+theorem init_verified : Verified X86.target init (Proof.Sha256.initX86 H0) :=
+ initWith_verified _ (hct := by taint_decide)
+
+theorem init224_verified : Verified X86.target init224 (Proof.Sha256.initX86 Spec.Sha256.H0_224) :=
+ initWith_verified _ (hct := by taint_decide)
end VG.Proof.Sha256.X86.Stream
@@ -419,6 +430,13 @@ theorem init :
X86.abi, X86.argSlots, X86.argVal, X86.argBytes]
[Proof.Sha256.X86.Stream.initSat, Proof.Sha256.X86.Stream.initSatMem, X86.arg, X86.argAddr, Mem.readW, Mem.read] using Proof.Sha256.X86.Stream.initSat)
+theorem init224 :
+ Verified X86.target Impl.Sha256.X86.Stream.init224 (Spec.Sha256.init224Contract X86.abi) :=
+ Proof.Sha256.X86.Stream.init224_verified.of_implies (by
+ contract_implies [Spec.Sha256.init224Contract, Spec.Sha256.initSig, Proof.Sha256.initX86,
+ X86.abi, X86.argSlots, X86.argVal, X86.argBytes]
+ [Proof.Sha256.X86.Stream.initSat, Proof.Sha256.X86.Stream.initSatMem, X86.arg, X86.argAddr, Mem.readW, Mem.read] using Proof.Sha256.X86.Stream.initSat)
+
theorem updateWide_implies : updateWide.Implies (Spec.Sha256.updateContract X86.abi 20) := by
contract_implies [Spec.Sha256.updateContract, Spec.Sha256.updateSig, updateWide,
Proof.Sha256.updateX86, Proof.Sha256.countX86, X86.abi, X86.argSlots, X86.argVal, X86.argBytes]
diff --git a/lean/VerifiedGarbage/Proof/Sha256/X86_64/Contract.lean b/lean/VerifiedGarbage/Proof/Sha256/X86_64/Contract.lean
index 36584fdad..e77d6b7b9 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/X86_64/Contract.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/X86_64/Contract.lean
@@ -39,17 +39,18 @@ def compressX86_64 : Contract X86_64.isa where
s₁.gpr .rdx = s₂.gpr .rdx ∧ s₁.gpr .rcx = s₂.gpr .rcx
open X86_64 in
-/-- x86-64 contract for `vg_sha256_init(state: *mut [u8; 96])`: makes the
-streaming state at `state` represent the empty message.
+/-- x86-64 contract for `vg_sha256_init(state: *mut [u8; 96])` and
+`vg_sha224_init`, which store the initial hash value `iv`: makes the
+streaming state at `state` represent the empty message, hashed from `iv`.
The code may write `state` (96 bytes), which may not overlap the return
address on the stack. The pointer is public. -/
-def initX86_64 : Contract X86_64.isa where
+def initX86_64 (iv : HashValue) : Contract X86_64.isa where
pre s :=
let state : Region := ⟨s.gpr .rdi, 96⟩
let ret : Region := ⟨s.gpr .rsp, 8⟩
s.rd = [] ∧ s.wr = [state] ∧ ret.Disjoint state
- post s s' := Repr s'.mem (s.gpr .rdi) []
+ post s s' := ReprFrom iv s'.mem (s.gpr .rdi) []
pub s₁ s₂ := s₁.gpr .rdi = s₂.gpr .rdi
open X86_64 in
diff --git a/lean/VerifiedGarbage/Proof/Sha256/X86_64/Shared.lean b/lean/VerifiedGarbage/Proof/Sha256/X86_64/Shared.lean
index 06aedb831..52fc0d22b 100644
--- a/lean/VerifiedGarbage/Proof/Sha256/X86_64/Shared.lean
+++ b/lean/VerifiedGarbage/Proof/Sha256/X86_64/Shared.lean
@@ -21,23 +21,25 @@ open VG.Proof.Sha256.X86_64 (ea_at contains_offset' writeState stateAt_writeStat
/-! ## `init` -/
-theorem init_eq : init = .block [
- .mov32 .rax (.imm 0x6a09e667), .store32 (at_ .rdi (4 * 0)) .rax,
- .mov32 .rax (.imm 0xbb67ae85), .store32 (at_ .rdi (4 * 1)) .rax,
- .mov32 .rax (.imm 0x3c6ef372), .store32 (at_ .rdi (4 * 2)) .rax,
- .mov32 .rax (.imm 0xa54ff53a), .store32 (at_ .rdi (4 * 3)) .rax,
- .mov32 .rax (.imm 0x510e527f), .store32 (at_ .rdi (4 * 4)) .rax,
- .mov32 .rax (.imm 0x9b05688c), .store32 (at_ .rdi (4 * 5)) .rax,
- .mov32 .rax (.imm 0x1f83d9ab), .store32 (at_ .rdi (4 * 6)) .rax,
- .mov32 .rax (.imm 0x5be0cd19), .store32 (at_ .rdi (4 * 7)) .rax] := rfl
+variable (iv : Spec.Sha256.HashValue)
+
+theorem initWith_eq : initWith iv = .block [
+ .mov32 .rax (.imm iv[0]), .store32 (at_ .rdi (4 * 0)) .rax,
+ .mov32 .rax (.imm iv[1]), .store32 (at_ .rdi (4 * 1)) .rax,
+ .mov32 .rax (.imm iv[2]), .store32 (at_ .rdi (4 * 2)) .rax,
+ .mov32 .rax (.imm iv[3]), .store32 (at_ .rdi (4 * 3)) .rax,
+ .mov32 .rax (.imm iv[4]), .store32 (at_ .rdi (4 * 4)) .rax,
+ .mov32 .rax (.imm iv[5]), .store32 (at_ .rdi (4 * 5)) .rax,
+ .mov32 .rax (.imm iv[6]), .store32 (at_ .rdi (4 * 6)) .rax,
+ .mov32 .rax (.imm iv[7]), .store32 (at_ .rdi (4 * 7)) .rax] := rfl
theorem init_post {s₀ : State}
(hret : Region.Disjoint ⟨s₀.gpr .rsp, 8⟩ ⟨s₀.gpr .rdi, 96⟩) (g : Reg → BitVec 64)
(hg : ∀ r, r ≠ .rax → g r = s₀.gpr r) :
- gprPreserved s₀ { s₀ with gpr := g, mem := writeState s₀.mem (s₀.gpr .rdi) Spec.Sha256.H0 } ∧
- Proof.Sha256.initX86_64.post s₀
- { s₀ with gpr := g, mem := writeState s₀.mem (s₀.gpr .rdi) Spec.Sha256.H0 } := by
- have hf : Frame [⟨s₀.gpr .rdi, 96⟩] s₀.mem (writeState s₀.mem (s₀.gpr .rdi) Spec.Sha256.H0) := by
+ gprPreserved s₀ { s₀ with gpr := g, mem := writeState s₀.mem (s₀.gpr .rdi) iv } ∧
+ (Proof.Sha256.initX86_64 iv).post s₀
+ { s₀ with gpr := g, mem := writeState s₀.mem (s₀.gpr .rdi) iv } := by
+ have hf : Frame [⟨s₀.gpr .rdi, 96⟩] s₀.mem (writeState s₀.mem (s₀.gpr .rdi) iv) := by
have c : ∀ k, k < 8 → (⟨s₀.gpr .rdi, 96⟩ : Region).Contains
(s₀.gpr .rdi + BitVec.ofInt 64 ((4 * k : Nat) : Int)) (32 / 8) :=
fun k hk => contains_offset' (by omega) (by omega)
@@ -45,27 +47,30 @@ theorem init_post {s₀ : State}
refine (((((((((Frame.refl _ _).writeW ?_ _ (c 0 ?_)).writeW ?_ _ (c 1 ?_)).writeW ?_ _
(c 2 ?_)).writeW ?_ _ (c 3 ?_)).writeW ?_ _ (c 4 ?_)).writeW ?_ _ (c 5 ?_)).writeW ?_ _
(c 6 ?_)).writeW ?_ _ (c 7 ?_)) <;> simp
- refine ⟨⟨fun r hr => hg r ?_, ?_⟩, Stream.repr_nil (stateAt_writeState _ _ _)⟩
+ refine ⟨⟨fun r hr => hg r ?_, ?_⟩, Stream.reprFrom_nil (stateAt_writeState _ _ _)⟩
· simp only [calleeSaved, List.mem_cons, List.not_mem_nil, or_false] at hr
rcases hr with rfl | rfl | rfl | rfl | rfl | rfl | rfl <;> decide
· exact hf.readW (Region.contains_self _ _) (by simpa using hret) (by decide)
+theorem setWidth_32_64 (x : BitVec 32) : (x.setWidth 64).setWidth 32 = x :=
+ BitVec.eq_of_toNat_eq (by simp)
+
set_option simprocs false in
-theorem init_correct {s₀ : State} (hp : Proof.Sha256.initX86_64.pre s₀) :
- WP isa init s₀ fun s' => gprPreserved s₀ s' ∧ Proof.Sha256.initX86_64.post s₀ s' := by
+theorem init_correct {s₀ : State} (hp : (Proof.Sha256.initX86_64 iv).pre s₀) :
+ WP isa (initWith iv) s₀ fun s' => gprPreserved s₀ s' ∧ (Proof.Sha256.initX86_64 iv).post s₀ s' := by
obtain ⟨hrd, hwr, hret⟩ := hp
have o : ∀ k, k < 8 → InRegions s₀.wr (s₀.gpr .rdi + BitVec.ofInt 64 ((4 * k : Nat) : Int)) 4 :=
fun k hk => ⟨⟨s₀.gpr .rdi, 96⟩, by simp [hwr], contains_offset' (by omega) (by omega)⟩
have o0 := o 0 (by omega); have o1 := o 1 (by omega); have o2 := o 2 (by omega)
have o3 := o 3 (by omega); have o4 := o 4 (by omega); have o5 := o 5 (by omega)
have o6 := o 6 (by omega); have o7 := o 7 (by omega)
- rw [init_eq]
+ rw [initWith_eq]
apply WP.of_runBlock
simp (config := {decide := true}) only [runBlock_cons, runStep_some,
runBlock_nil, exec, readSrc32, isa, ea_at, State.store32,
State.setReg32, State.setReg, o0, o1, o2, o3, o4, o5, o6, o7, ite_true, ite_false,
- Option.map_some, Option.some.injEq, exists_eq_left']
- exact init_post hret _ fun r hr => by simp [hr]
+ Option.map_some, Option.some.injEq, exists_eq_left', setWidth_32_64]
+ exact init_post iv hret _ fun r hr => by simp [hr]
/-- A state satisfying the precondition. -/
def initSat : State where
@@ -79,15 +84,26 @@ def initSat : State where
rd := []
wr := [⟨0x1000, 96⟩]
-theorem init_verified : Verified X86_64.target init Proof.Sha256.initX86_64 := by
+/-- `initWith iv` is verified, given the checks that evaluate its code
+(which the kernel can only run on a literal `iv`): it never loads MXCSR, and
+the taint analysis accepts it. -/
+theorem initWith_verified (hm : (initWith iv).allInstrs (fun i => !loadsMxcsr i) = true)
+ {hc} (hct : (taint.check (Taint.ofRegs [.rdi]) (initWith iv) hc).isSome = true) :
+ Verified X86_64.target (initWith iv) (Proof.Sha256.initX86_64 iv) := by
refine ⟨fun s hs => ?_, ?_, ⟨initSat, rfl, rfl, ?_⟩⟩
- · obtain ⟨t, s', he, h⟩ := init_correct hs
- exact ⟨t, s', he, abiPreserved_of_exec (by decide +kernel) he h.1, h.2⟩
- · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.rdi]) ?_ (by taint_decide)
+ · obtain ⟨t, s', he, h⟩ := init_correct iv hs
+ exact ⟨t, s', he, abiPreserved_of_exec hm he h.1, h.2⟩
+ · refine VG.Taint.constantTime (A := taint) (Taint.ofRegs [.rdi]) ?_ hct
intro s₁ s₂ _ _ h
exact Taint.agree_ofRegs fun r hr => by simp at hr; subst hr; exact h
· exact Region.Disjoint.symm (Offset.disjoint_of_le (by decide) (by decide))
+theorem init_verified : Verified X86_64.target init (Proof.Sha256.initX86_64 Spec.Sha256.H0) :=
+ initWith_verified _ (by decide +kernel) (hct := by taint_decide)
+
+theorem init224_verified : Verified X86_64.target init224 (Proof.Sha256.initX86_64 Spec.Sha256.H0_224) :=
+ initWith_verified _ (by decide +kernel) (hct := by taint_decide)
+
end VG.Proof.Sha256.X86_64.Stream
/-!
@@ -130,6 +146,13 @@ theorem init :
X86_64.abi, X86_64.argRegs]
[Proof.Sha256.X86_64.Stream.initSat] using Proof.Sha256.X86_64.Stream.initSat)
+theorem init224 :
+ Verified X86_64.target Impl.Sha256.X86_64.Stream.init224 (Spec.Sha256.init224Contract X86_64.abi) :=
+ Proof.Sha256.X86_64.Stream.init224_verified.of_implies (by
+ contract_implies [Spec.Sha256.init224Contract, Spec.Sha256.initSig, Proof.Sha256.initX86_64,
+ X86_64.abi, X86_64.argRegs]
+ [Proof.Sha256.X86_64.Stream.initSat] using Proof.Sha256.X86_64.Stream.initSat)
+
open VG.Impl.Sha256.X86_64.Stream (Callee) in
/-- `update`, for any compression function `f` (see `Variant.lean`). -/
theorem update {f : Callee} (hf : f.Ok)
diff --git a/lean/VerifiedGarbageTest/Sha224.lean b/lean/VerifiedGarbageTest/Sha224.lean
new file mode 100644
index 000000000..455bec9d8
--- /dev/null
+++ b/lean/VerifiedGarbageTest/Sha224.lean
@@ -0,0 +1,33 @@
+import VerifiedGarbageTest.Sha256
+
+/-!
+# Known-answer tests for the SHA-224 specification
+
+Two of the NIST CAVP SHA-224 vectors, read from the vendored response file
+`vectors/nist-cavp/sha224/SHA224ShortMsg.rsp` (see `vectors/sources/`)
+when this file is built and checked against `VG.Spec.Sha256.sha224`, so that
+a transcription error in its initial hash value fails the build. (As for
+SHA-256, the Rust tests run every CAVP vector against the implementation.)
+-/
+
+namespace VG.Test.Sha224
+
+open Lean Elab Command
+open VG.Test.Sha256 (vectors lengths)
+
+run_cmd do
+ -- This file is `lean/VerifiedGarbageTest/Sha224.lean`.
+ let file ← IO.FS.realPath (← getFileName)
+ let some root := file.parent >>= (·.parent) >>= (·.parent)
+ | throwError "no repository root above {file}"
+ let text ← IO.FS.readFile (root / "vectors" / "nist-cavp" / "sha224" / "SHA224ShortMsg.rsp")
+ let vs ← match vectors text with
+ | .ok vs => pure vs
+ | .error e => throwError "SHA224ShortMsg.rsp: {e}"
+ unless vs.length == 65 do throwError "expected 65 vectors, got {vs.length}"
+ for n in lengths do
+ let some (msg, md) := vs.find? (·.1.length == n)
+ | throwError "no {n}-byte vector"
+ unless Spec.Sha256.sha224 msg == md do throwError "SHA-224 of the {n}-byte CAVP vector is wrong"
+
+end VG.Test.Sha224
diff --git a/src/asm/aarch64/sha256.rs b/src/asm/aarch64/sha256.rs
index f632e6240..099344a30 100644
--- a/src/asm/aarch64/sha256.rs
+++ b/src/asm/aarch64/sha256.rs
@@ -2,6 +2,45 @@
//! Verified `sha256` functions for `aarch64`.
#![allow(dead_code)]
+/// Starts a SHA-224 computation: makes the SHA-256 streaming state `*state` represent the empty message, hashed from the initial hash value of SHA-224 (`VG.Spec.Sha256.H0_224`). Continue with `vg_sha256_update` and `vg_sha256_finalize`, and take the first 28 bytes of the final hash value as the digest.
+///
+/// Contract: `VG.Spec.Sha256.init224Contract`. The streaming state is the hash value followed by a buffered partial block (`VG.Spec.Sha256.ReprFrom`).
+///
+/// # Safety
+///
+/// * `state` must be valid for reads and writes of 96 bytes.
+/// * `state` must not wrap around the end of the address space (no Rust object does).
+#[unsafe(naked)]
+pub(crate) unsafe extern "C" fn vg_sha224_init(state: *mut [u8; 96]) {
+ core::arch::naked_asm!(
+ "movz w9, #40664, lsl #0",
+ "movk w9, #49413, lsl #16",
+ "str w9, [x0, #0]",
+ "movz w9, #54535, lsl #0",
+ "movk w9, #13948, lsl #16",
+ "str w9, [x0, #4]",
+ "movz w9, #56599, lsl #0",
+ "movk w9, #12400, lsl #16",
+ "str w9, [x0, #8]",
+ "movz w9, #22841, lsl #0",
+ "movk w9, #63246, lsl #16",
+ "str w9, [x0, #12]",
+ "movz w9, #2865, lsl #0",
+ "movk w9, #65472, lsl #16",
+ "str w9, [x0, #16]",
+ "movz w9, #5393, lsl #0",
+ "movk w9, #26712, lsl #16",
+ "str w9, [x0, #20]",
+ "movz w9, #36775, lsl #0",
+ "movk w9, #25849, lsl #16",
+ "str w9, [x0, #24]",
+ "movz w9, #20388, lsl #0",
+ "movk w9, #48890, lsl #16",
+ "str w9, [x0, #28]",
+ "ret",
+ )
+}
+
/// The SHA-256 compression function (FIPS 180-4 §6.2.2): updates the hash value `*state` with the `n` 64-byte blocks starting at `blocks`, in order.
///
/// Contract: `VG.Spec.Sha256.compressContract`. Constant time: only the pointers and `n` may affect timing, not the hash value or the blocks.
diff --git a/src/asm/arm/sha256.rs b/src/asm/arm/sha256.rs
index ce598e523..9de762bf7 100644
--- a/src/asm/arm/sha256.rs
+++ b/src/asm/arm/sha256.rs
@@ -2,6 +2,45 @@
//! Verified `sha256` functions for `arm`.
#![allow(dead_code)]
+/// Starts a SHA-224 computation: makes the SHA-256 streaming state `*state` represent the empty message, hashed from the initial hash value of SHA-224 (`VG.Spec.Sha256.H0_224`). Continue with `vg_sha256_update` and `vg_sha256_finalize`, and take the first 28 bytes of the final hash value as the digest.
+///
+/// Contract: `VG.Spec.Sha256.init224Contract`. The streaming state is the hash value followed by a buffered partial block (`VG.Spec.Sha256.ReprFrom`).
+///
+/// # Safety
+///
+/// * `state` must be valid for reads and writes of 96 bytes.
+/// * `state` must not wrap around the end of the address space (no Rust object does).
+#[unsafe(naked)]
+pub(crate) unsafe extern "C" fn vg_sha224_init(state: *mut [u8; 96]) {
+ core::arch::naked_asm!(
+ "movw r12, #40664",
+ "movt r12, #49413",
+ "str r12, [r0, #0]",
+ "movw r12, #54535",
+ "movt r12, #13948",
+ "str r12, [r0, #4]",
+ "movw r12, #56599",
+ "movt r12, #12400",
+ "str r12, [r0, #8]",
+ "movw r12, #22841",
+ "movt r12, #63246",
+ "str r12, [r0, #12]",
+ "movw r12, #2865",
+ "movt r12, #65472",
+ "str r12, [r0, #16]",
+ "movw r12, #5393",
+ "movt r12, #26712",
+ "str r12, [r0, #20]",
+ "movw r12, #36775",
+ "movt r12, #25849",
+ "str r12, [r0, #24]",
+ "movw r12, #20388",
+ "movt r12, #48890",
+ "str r12, [r0, #28]",
+ "bx lr",
+ )
+}
+
/// The SHA-256 compression function (FIPS 180-4 §6.2.2): updates the hash value `*state` with the `n` 64-byte blocks starting at `blocks`, in order.
///
/// Contract: `VG.Spec.Sha256.compressContract`. Constant time: only the pointers and `n` may affect timing, not the hash value or the blocks.
diff --git a/src/asm/x86/sha256.rs b/src/asm/x86/sha256.rs
index 73703777b..4b3fdb184 100644
--- a/src/asm/x86/sha256.rs
+++ b/src/asm/x86/sha256.rs
@@ -2,6 +2,39 @@
//! Verified `sha256` functions for `x86`.
#![allow(dead_code)]
+/// Starts a SHA-224 computation: makes the SHA-256 streaming state `*state` represent the empty message, hashed from the initial hash value of SHA-224 (`VG.Spec.Sha256.H0_224`). Continue with `vg_sha256_update` and `vg_sha256_finalize`, and take the first 28 bytes of the final hash value as the digest.
+///
+/// Contract: `VG.Spec.Sha256.init224Contract`. The streaming state is the hash value followed by a buffered partial block (`VG.Spec.Sha256.ReprFrom`).
+///
+/// # Safety
+///
+/// * `state` must be valid for reads and writes of 96 bytes.
+/// * `state` must not overlap the arguments on the stack (distinct Rust objects never do).
+/// * `state` must not overlap the return address on the stack, or wrap around the end of the address space (no Rust object does).
+#[unsafe(naked)]
+pub(crate) unsafe extern "C" fn vg_sha224_init(state: *mut [u8; 96]) {
+ core::arch::naked_asm!(
+ "mov eax, DWORD PTR [esp+4]",
+ "mov ecx, -1056596264",
+ "mov DWORD PTR [eax], ecx",
+ "mov ecx, 914150663",
+ "mov DWORD PTR [eax+4], ecx",
+ "mov ecx, 812702999",
+ "mov DWORD PTR [eax+8], ecx",
+ "mov ecx, -150054599",
+ "mov DWORD PTR [eax+12], ecx",
+ "mov ecx, -4191439",
+ "mov DWORD PTR [eax+16], ecx",
+ "mov ecx, 1750603025",
+ "mov DWORD PTR [eax+20], ecx",
+ "mov ecx, 1694076839",
+ "mov DWORD PTR [eax+24], ecx",
+ "mov ecx, -1090891868",
+ "mov DWORD PTR [eax+28], ecx",
+ "ret",
+ )
+}
+
/// Starts a SHA-256 computation: makes the streaming state `*state` represent the empty message.
///
/// Contract: `VG.Spec.Sha256.initContract`. The streaming state is the hash value followed by a buffered partial block (`VG.Spec.Sha256.Repr`).
diff --git a/src/asm/x86_64/sha256.rs b/src/asm/x86_64/sha256.rs
index 3a8af49b2..6ad068da0 100644
--- a/src/asm/x86_64/sha256.rs
+++ b/src/asm/x86_64/sha256.rs
@@ -2,6 +2,37 @@
//! Verified `sha256` functions for `x86_64`.
#![allow(dead_code)]
+/// Starts a SHA-224 computation: makes the SHA-256 streaming state `*state` represent the empty message, hashed from the initial hash value of SHA-224 (`VG.Spec.Sha256.H0_224`). Continue with `vg_sha256_update` and `vg_sha256_finalize`, and take the first 28 bytes of the final hash value as the digest.
+///
+/// Contract: `VG.Spec.Sha256.init224Contract`. The streaming state is the hash value followed by a buffered partial block (`VG.Spec.Sha256.ReprFrom`).
+///
+/// # Safety
+///
+/// * `state` must be valid for reads and writes of 96 bytes.
+/// * `state` must not overlap the return address on the stack, or wrap around the end of the address space (no Rust object does).
+#[unsafe(naked)]
+pub(crate) unsafe extern "sysv64" fn vg_sha224_init(state: *mut [u8; 96]) {
+ core::arch::naked_asm!(
+ "mov eax, -1056596264",
+ "mov DWORD PTR [rdi], eax",
+ "mov eax, 914150663",
+ "mov DWORD PTR [rdi+4], eax",
+ "mov eax, 812702999",
+ "mov DWORD PTR [rdi+8], eax",
+ "mov eax, -150054599",
+ "mov DWORD PTR [rdi+12], eax",
+ "mov eax, -4191439",
+ "mov DWORD PTR [rdi+16], eax",
+ "mov eax, 1750603025",
+ "mov DWORD PTR [rdi+20], eax",
+ "mov eax, 1694076839",
+ "mov DWORD PTR [rdi+24], eax",
+ "mov eax, -1090891868",
+ "mov DWORD PTR [rdi+28], eax",
+ "ret",
+ )
+}
+
/// The SHA-256 compression function (FIPS 180-4 §6.2.2): updates the hash value `*state` with the `n` 64-byte blocks starting at `blocks`, in order.
///
/// Contract: `VG.Spec.Sha256.compressContract`. Constant time: only the pointers and `n` may affect timing, not the hash value or the blocks.
diff --git a/src/hashes/mod.rs b/src/hashes/mod.rs
index eb112b194..ccb5ce3a6 100644
--- a/src/hashes/mod.rs
+++ b/src/hashes/mod.rs
@@ -21,6 +21,7 @@ pub mod blake2b;
pub mod blake2s;
pub mod md5;
pub mod sha1;
+pub mod sha224;
pub mod sha256;
pub mod sha3;
pub mod sha512;
diff --git a/src/hashes/sha224.rs b/src/hashes/sha224.rs
new file mode 100644
index 000000000..fae080103
--- /dev/null
+++ b/src/hashes/sha224.rs
@@ -0,0 +1,124 @@
+//! SHA-224 (FIPS 180-4).
+//!
+//! SHA-224 is SHA-256 from another initial hash value, with its digest the
+//! first 28 bytes of the final hash value. `vg_sha224_init` (contract
+//! `VG.Spec.Sha256.init224Contract`) makes a SHA-256 streaming state
+//! represent the empty message, hashed from SHA-224's initial hash value
+//! (`VG.Spec.Sha256.ReprFrom`); SHA-256's `vg_sha256_update` and
+//! `vg_sha256_finalize` (`updateContract` and `finalizeContract`, which hold
+//! for any initial hash value) then absorb the message and output the final
+//! hash value.
+//!
+//! The implementations of `update` and `finalize` are SHA-256's, chosen the
+//! same way (see `super::sha256`).
+
+#![cfg(any(
+ target_arch = "x86_64",
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "x86"
+))]
+
+#[cfg(target_arch = "x86_64")]
+use crate::arch::sha256::{
+ VG_SHA256_FINALIZE_AVX2_FEATURES, VG_SHA256_FINALIZE_SHANI_FEATURES,
+ VG_SHA256_UPDATE_AVX2_FEATURES, VG_SHA256_UPDATE_SHANI_FEATURES, vg_sha256_finalize_avx2,
+ vg_sha256_finalize_shani, vg_sha256_update_avx2, vg_sha256_update_shani,
+};
+#[cfg(target_arch = "aarch64")]
+use crate::arch::sha256::{
+ VG_SHA256_FINALIZE_SHA2_FEATURES, VG_SHA256_UPDATE_SHA2_FEATURES, vg_sha256_finalize_sha2,
+ vg_sha256_update_sha2,
+};
+use crate::arch::sha256::{vg_sha224_init, vg_sha256_finalize, vg_sha256_update};
+
+super::streaming_hash!(
+ /// An incremental SHA-224 computation (FIPS 180-4 §6.3).
+ Sha224 {
+ state: 96,
+ scratch: 76,
+ block: 64,
+ output: 28,
+ final_hash: 32,
+ init: vg_sha224_init,
+ backends: Sha224Backend {
+ Scalar => (vg_sha256_update, vg_sha256_finalize),
+ #[cfg(target_arch = "aarch64")]
+ Sha2 if [VG_SHA256_UPDATE_SHA2_FEATURES, VG_SHA256_FINALIZE_SHA2_FEATURES] =>
+ (vg_sha256_update_sha2, vg_sha256_finalize_sha2),
+ #[cfg(target_arch = "x86_64")]
+ ShaNi if [VG_SHA256_UPDATE_SHANI_FEATURES, VG_SHA256_FINALIZE_SHANI_FEATURES] =>
+ (vg_sha256_update_shani, vg_sha256_finalize_shani),
+ #[cfg(target_arch = "x86_64")]
+ Avx2 if [VG_SHA256_UPDATE_AVX2_FEATURES, VG_SHA256_FINALIZE_AVX2_FEATURES] =>
+ (vg_sha256_update_avx2, vg_sha256_finalize_avx2),
+ },
+ }
+);
+
+#[cfg(test)]
+mod tests {
+ use super::{Sha224, Sha224Backend};
+ use crate::cpu::{Features, detected};
+ use crate::hashes::sha256::Sha256Backend;
+
+ /// Every way of splitting a message into two updates gives the same
+ /// digest, for every length around the padding boundaries.
+ #[test]
+ fn incremental() {
+ let msg: [u8; 200] = core::array::from_fn(|i| (i * 7 + 3) as u8);
+ for len in 0..msg.len() {
+ let expected = Sha224::digest(&msg[..len]);
+ for split in 0..=len {
+ let mut h = Sha224::default();
+ h.update(&msg[..split]);
+ let copy = h.clone();
+ h.update(&msg[split..len]);
+ assert_eq!(h.finalize(), expected);
+ let mut h = copy;
+ for byte in &msg[split..len] {
+ h.update(core::slice::from_ref(byte));
+ }
+ assert_eq!(h.finalize(), expected);
+ }
+ }
+ }
+
+ /// The implementation chosen for each set of features is SHA-256's.
+ #[test]
+ fn select() {
+ for bits in 0..(1 << crate::cpu::NAMES.len()) {
+ let f = Features(bits);
+ let expected = match Sha256Backend::select(f) {
+ Sha256Backend::Scalar => Sha224Backend::Scalar,
+ #[cfg(target_arch = "aarch64")]
+ Sha256Backend::Sha2 => Sha224Backend::Sha2,
+ #[cfg(target_arch = "x86_64")]
+ Sha256Backend::ShaNi => Sha224Backend::ShaNi,
+ #[cfg(target_arch = "x86_64")]
+ Sha256Backend::Avx2 => Sha224Backend::Avx2,
+ };
+ assert_eq!(Sha224Backend::select(f), expected, "{bits:#b}");
+ }
+ assert_eq!(Sha224::new().backend, Sha224Backend::select(detected()));
+ }
+
+ /// The `HashFunction` implementation is the inherent functions.
+ #[test]
+ fn hash_function() {
+ use crate::hashes::HashFunction;
+ let msg = [0x5a; 300];
+ let mut h = ::new();
+ HashFunction::update(&mut h, &msg[..100]);
+ HashFunction::update(&mut h, &msg[100..]);
+ assert_eq!(HashFunction::finalize(h), Sha224::digest(&msg));
+ assert_eq!(::digest(&msg), Sha224::digest(&msg));
+ assert_eq!(
+ (
+ ::OUTPUT_SIZE,
+ ::BLOCK_SIZE
+ ),
+ (28, 64)
+ );
+ }
+}
diff --git a/tests/cavp/main.rs b/tests/cavp/main.rs
index 98a9c7226..59211fdce 100644
--- a/tests/cavp/main.rs
+++ b/tests/cavp/main.rs
@@ -16,6 +16,7 @@
mod aes_gcm;
mod rc2_cbc;
mod sha1;
+mod sha224;
mod sha256;
mod sha3;
mod sha512;
diff --git a/tests/cavp/sha224.rs b/tests/cavp/sha224.rs
new file mode 100644
index 000000000..8d201290e
--- /dev/null
+++ b/tests/cavp/sha224.rs
@@ -0,0 +1,33 @@
+//! SHA-224: every message length from 0 to 64 bytes, 64 long messages and the
+//! Monte Carlo test.
+
+use super::{check_messages, check_monte_carlo};
+use verified_garbage::hashes::sha224::Sha224;
+
+/// Every message length from 0 to 64 bytes.
+#[test]
+fn short_messages() {
+ let n = check_messages(
+ include_str!("../../vectors/nist-cavp/sha224/SHA224ShortMsg.rsp"),
+ Sha224::digest,
+ );
+ assert_eq!(n, 65);
+}
+
+#[test]
+fn long_messages() {
+ let n = check_messages(
+ include_str!("../../vectors/nist-cavp/sha224/SHA224LongMsg.rsp"),
+ Sha224::digest,
+ );
+ assert_eq!(n, 64);
+}
+
+/// The SHAVS Monte Carlo test.
+#[test]
+fn monte_carlo() {
+ check_monte_carlo(
+ include_str!("../../vectors/nist-cavp/sha224/SHA224Monte.rsp"),
+ Sha224::digest,
+ );
+}